Permalink
Cannot retrieve contributors at this time
package MusicBrainz::Server::Data::Editor; | |
use Moose; | |
use namespace::autoclean; | |
use LWP; | |
use URI::Escape; | |
use Authen::Passphrase; | |
use Authen::Passphrase::BlowfishCrypt; | |
use Authen::Passphrase::RejectAll; | |
use DateTime; | |
use Digest::MD5 qw( md5_hex ); | |
use Encode; | |
use List::MoreUtils qw( uniq ); | |
use MusicBrainz::Server::Constants qw( :edit_status entities_with ); | |
use MusicBrainz::Server::Entity::Preferences; | |
use MusicBrainz::Server::Entity::Editor; | |
use MusicBrainz::Server::Data::Utils qw( | |
generate_token | |
get_area_containment_query | |
hash_to_row | |
load_subobjects | |
object_to_ids | |
placeholders | |
type_to_model | |
); | |
use MusicBrainz::Server::Constants qw( :edit_status :privileges ); | |
use MusicBrainz::Server::Constants qw( $PASSPHRASE_BCRYPT_COST ); | |
use MusicBrainz::Server::Constants qw( :create_entity $EDIT_HISTORIC_ADD_RELEASE ); | |
use MusicBrainz::Server::Constants qw( $EDIT_RELEASE_ADD_COVER_ART ); | |
use MusicBrainz::Server::Constants qw( :vote ); | |
extends 'MusicBrainz::Server::Data::Entity'; | |
with 'MusicBrainz::Server::Data::Role::Subscription' => { | |
table => 'editor_subscribe_editor', | |
column => 'subscribed_editor', | |
active_class => 'MusicBrainz::Server::Entity::EditorSubscription' | |
}; | |
sub _table | |
{ | |
return 'editor'; | |
} | |
sub _columns | |
{ | |
return 'editor.id, editor.name, password, privs, email, website, bio, | |
member_since, email_confirm_date, last_login_date, | |
EXISTS (SELECT 1 FROM edit WHERE edit.editor = editor.id AND edit.autoedit = 0 AND edit.status = ' . $STATUS_APPLIED . ' OFFSET 9) AS has_ten_accepted_edits, | |
gender, area, | |
birth_date, ha1, deleted'; | |
} | |
sub _column_mapping | |
{ | |
return { | |
id => 'id', | |
name => 'name', | |
email => 'email', | |
password => 'password', | |
privileges => 'privs', | |
website => 'website', | |
biography => 'bio', | |
has_ten_accepted_edits => 'has_ten_accepted_edits', | |
email_confirmation_date => 'email_confirm_date', | |
registration_date => 'member_since', | |
last_login_date => 'last_login_date', | |
gender_id => 'gender', | |
area_id => 'area', | |
birth_date => 'birth_date', | |
ha1 => 'ha1', | |
deleted => 'deleted', | |
}; | |
} | |
sub _entity_class | |
{ | |
return 'MusicBrainz::Server::Entity::Editor'; | |
} | |
sub get_by_name | |
{ | |
my ($self, $name) = @_; | |
my $query = 'SELECT ' . $self->_columns . | |
' FROM ' . $self->_table . | |
' WHERE lower(name) = lower(?) LIMIT 1'; | |
my $row = $self->sql->select_single_row_hash($query, $name); | |
my $editor = $self->_new_from_row($row); | |
$self->load_preferences($editor); | |
return $editor; | |
} | |
sub summarize_ratings | |
{ | |
my ($self, $user, $me) = @_; | |
return { | |
map { | |
my ($entities) = $self->c->model(type_to_model($_))->rating | |
->find_editor_ratings($user->id, $me, 10, 0); | |
($_ => $entities); | |
} entities_with('ratings') | |
}; | |
} | |
sub _get_tags_for_type | |
{ | |
my ($self, $id, $type) = @_; | |
my $query = "SELECT tag, count(tag) | |
FROM ${type}_tag_raw | |
WHERE editor = ? AND is_upvote | |
GROUP BY tag"; | |
my $results = $self->c->sql->select_list_of_hashes($query, $id); | |
return { map { $_->{tag} => $_ } @$results }; | |
} | |
sub get_tags | |
{ | |
my ($self, $user) = @_; | |
my $tags = {}; | |
my $max = 0; | |
foreach my $entity (entities_with('tags')) | |
{ | |
my $data = $self->_get_tags_for_type($user->id, $entity); | |
foreach (keys %$data) | |
{ | |
if ($tags->{$_}) | |
{ | |
$tags->{$_}->{count} += $data->{$_}->{count}; | |
} | |
else | |
{ | |
$tags->{$_} = $data->{$_}; | |
} | |
$max = $tags->{$_}->{count} if $tags->{$_}->{count} > $max; | |
} | |
} | |
my $entities = $self->c->model('Tag')->get_by_ids(keys %$tags); | |
foreach (keys %$entities) | |
{ | |
$tags->{$_}->{tag} = $entities->{$_}; | |
} | |
my @tags = sort { $a->{tag}->name cmp $b->{tag}->name } values %$tags; | |
return { max => $max, tags => \@tags }; | |
} | |
around '_get_by_keys' => sub { | |
my $orig = shift; | |
my $self = shift; | |
my @ret = $self->$orig(@_); | |
$self->load_preferences(@ret); | |
return @ret; | |
}; | |
sub find_by_email | |
{ | |
my ($self, $email) = @_; | |
return $self->_get_by_keys('email', $email); | |
} | |
sub find_by_ip { | |
my ($self, $ip) = @_; | |
my $query = 'SELECT ' . $self->_columns . | |
' FROM ' . $self->_table . ' WHERE id = any(?)' . | |
' ORDER BY member_since LIMIT 100'; | |
my @ids = $self->store->set_members("ipusers:$ip"); | |
$self->query_to_list($query, [\@ids]); | |
} | |
sub search_by_email { | |
my ($self, $email) = @_; | |
my $query = 'SELECT ' . $self->_columns . | |
' FROM ' . $self->_table . ' WHERE email ~ ?' . | |
' ORDER BY member_since LIMIT 100'; | |
$self->query_to_list($query, [$email]); | |
} | |
sub find_by_area { | |
my ($self, $area_id, $limit, $offset) = @_; | |
my ( | |
$containment_query, | |
@containment_query_args, | |
) = get_area_containment_query('$2', 'area', check_all_levels => 1); | |
my $query = "SELECT " . $self->_columns . " | |
FROM " . $self->_table . " | |
WHERE area = \$1 OR EXISTS ( | |
SELECT 1 FROM ($containment_query) ac | |
WHERE ac.descendant = area AND ac.parent = \$1 | |
) | |
ORDER BY name, id"; | |
$self->query_to_list_limited( | |
$query, [$area_id, @containment_query_args], $limit, $offset, undef, | |
dollar_placeholders => 1, | |
); | |
} | |
sub find_by_privileges | |
{ | |
my ($self, $privs) = @_; | |
my $query = "SELECT " . $self->_columns . " | |
FROM " . $self->_table . " | |
WHERE (privs & ?) > 0 | |
ORDER BY editor.name, editor.id"; | |
$self->query_to_list($query, [$privs]); | |
} | |
sub find_by_subscribed_editor | |
{ | |
my ($self, $editor_id, $limit, $offset) = @_; | |
my $query = "SELECT " . $self->_columns . " | |
FROM " . $self->_table . " | |
JOIN editor_subscribe_editor s ON editor.id = s.subscribed_editor | |
WHERE s.editor = ? | |
ORDER BY editor.name, editor.id"; | |
$self->query_to_list_limited($query, [$editor_id], $limit, $offset); | |
} | |
sub find_subscribers | |
{ | |
my ($self, $editor_id, $limit, $offset) = @_; | |
my $query = "SELECT " . $self->_columns . " | |
FROM " . $self->_table . " | |
JOIN editor_subscribe_editor s ON editor.id = s.editor | |
WHERE s.subscribed_editor = ? | |
ORDER BY editor.name, editor.id"; | |
$self->query_to_list_limited($query, [$editor_id], $limit, $offset); | |
} | |
sub insert | |
{ | |
my ($self, $data) = @_; | |
die "Invalid user name" if $data->{name} =~ qr{^deleted editor \#\d+$}i; | |
my $plaintext = $data->{password}; | |
$data->{password} = hash_password($plaintext); | |
$data->{ha1} = ha1_password($data->{name}, $plaintext); | |
return Sql::run_in_transaction(sub { | |
return $self->_entity_class->new( | |
id => $self->sql->insert_row('editor', $data, 'id'), | |
name => $data->{name}, | |
password => $data->{password}, | |
ha1 => $data->{ha1}, | |
registration_date => DateTime->now | |
); | |
}, $self->sql); | |
} | |
sub update_email | |
{ | |
my ($self, $editor, $email) = @_; | |
Sql::run_in_transaction(sub { | |
if ($email) { | |
my $email_confirmation_date = $self->sql->select_single_value( | |
'UPDATE editor SET email=?, email_confirm_date=NOW() | |
WHERE id=? RETURNING email_confirm_date', $email, $editor->id); | |
$editor->email($email); | |
$editor->email_confirmation_date($email_confirmation_date); | |
} | |
else { | |
$self->sql->do('UPDATE editor SET email=NULL, email_confirm_date=NULL | |
WHERE id=?', $editor->id); | |
delete $editor->{email}; | |
delete $editor->{email_confirmation_date}; | |
} | |
}, $self->sql); | |
} | |
sub update_password | |
{ | |
my ($self, $editor_name, $password) = @_; | |
Sql::run_in_transaction(sub { | |
$self->sql->do('UPDATE editor SET password = ?, ha1 = md5(name || \':musicbrainz.org:\' || ?), last_login_date = now() WHERE lower(name) = lower(?)', | |
hash_password($password), | |
$password, | |
$editor_name); | |
}, $self->sql); | |
} | |
sub update_profile | |
{ | |
my ($self, $editor, $update) = @_; | |
my $row = hash_to_row( | |
$update, | |
{ | |
name => 'username', | |
bio => 'biography', | |
area => 'area_id', | |
gender => 'gender_id', | |
website => 'website', | |
birth_date => 'birth_date', | |
} | |
); | |
if (my $date = delete $row->{birth_date}) { | |
if (%$date) { # if date is given but all NULL, it will be an empty hash. | |
$row->{birth_date} = sprintf '%d-%d-%d', map { $date->{$_} } qw( year month day ) | |
} | |
else { | |
$row->{birth_date} = undef; | |
} | |
} | |
Sql::run_in_transaction(sub { | |
$self->sql->update_row('editor', $row, { id => $editor->id }); | |
}, $self->sql); | |
} | |
sub update_privileges { | |
my ($self, $editor, $values) = @_; | |
my $privs = ($values->{auto_editor} // 0) * $AUTO_EDITOR_FLAG | |
+ ($values->{bot} // 0) * $BOT_FLAG | |
+ ($values->{untrusted} // 0) * $UNTRUSTED_FLAG | |
+ ($values->{link_editor} // 0) * $RELATIONSHIP_EDITOR_FLAG | |
+ ($values->{location_editor} // 0) * $LOCATION_EDITOR_FLAG | |
+ ($values->{no_nag} // 0) * $DONT_NAG_FLAG | |
+ ($values->{wiki_transcluder} // 0) * $WIKI_TRANSCLUSION_FLAG | |
+ ($values->{banner_editor} // 0) * $BANNER_EDITOR_FLAG | |
+ ($values->{mbid_submitter} // 0) * $MBID_SUBMITTER_FLAG | |
+ ($values->{account_admin} // 0) * $ACCOUNT_ADMIN_FLAG | |
+ ($values->{editing_disabled} // 0) * $EDITING_DISABLED_FLAG | |
+ ($values->{adding_notes_disabled} // 0) * $ADDING_NOTES_DISABLED_FLAG; | |
Sql::run_in_transaction(sub { | |
$self->sql->do('UPDATE editor SET privs = ? WHERE id = ?', $privs, $editor->id); | |
}, $self->sql); | |
} | |
sub make_autoeditor | |
{ | |
my ($self, $editor_id) = @_; | |
$self->sql->do('UPDATE editor SET privs = privs | ? WHERE id = ?', | |
$AUTO_EDITOR_FLAG, $editor_id); | |
} | |
sub load | |
{ | |
my ($self, @objs) = @_; | |
load_subobjects($self, 'editor', @objs); | |
$self->load_preferences(map { $_->editor } grep defined, @objs); | |
} | |
sub load_preferences | |
{ | |
my ($self, @editors) = @_; | |
return unless @editors; | |
my %editors = map { $_->id => $_ } grep { defined } @editors | |
or return; | |
my $query = sprintf "SELECT editor, name, value ". | |
"FROM editor_preference WHERE editor IN (%s)", | |
placeholders(keys %editors); | |
my $prefs = $self->sql->select_list_of_hashes($query, keys %editors); | |
for my $pref (@$prefs) { | |
my ($editor_id, $key, $value) = ($pref->{editor}, $pref->{name}, $pref->{value}); | |
next unless $editors{$editor_id}->preferences->can($key); | |
$editors{$editor_id}->preferences->$key($value); | |
} | |
} | |
sub save_preferences | |
{ | |
my ($self, $editor, $values) = @_; | |
Sql::run_in_transaction(sub { | |
$self->sql->do('DELETE FROM editor_preference WHERE editor = ?', $editor->id); | |
my $preferences_meta = $editor->preferences->meta; | |
foreach my $name (keys %$values) { | |
my $default = $preferences_meta->get_attribute($name)->default; | |
unless ($default eq $values->{$name}) { | |
$self->sql->insert_row('editor_preference', { | |
editor => $editor->id, | |
name => $name, | |
value => $values->{$name}, | |
}); | |
} | |
} | |
$editor->preferences(MusicBrainz::Server::Entity::Preferences->new(%$values)); | |
}, $self->sql); | |
} | |
# Must be run in a transaction to actually do anything. Acquires a row-level lock for a given editor ID. | |
sub lock_row | |
{ | |
my ($self, $editor_id) = @_; | |
my $query = "SELECT 1 FROM " . $self->_table . " WHERE id = ? FOR UPDATE"; | |
$self->sql->do($query, $editor_id); | |
} | |
sub donation_check | |
{ | |
my ($self, $obj) = @_; | |
my $nag = 1; | |
$nag = 0 if ($obj->is_nag_free || $obj->is_auto_editor || $obj->is_bot || | |
$obj->is_relationship_editor || $obj->is_wiki_transcluder || | |
$obj->is_location_editor); | |
my $days = 0.0; | |
if ($nag) { | |
my $response = $self->c->lwp->get( | |
'https://metabrainz.org/donations/nag-check?editor=' . uri_escape_utf8($obj->name) | |
); | |
if ($response->is_success && $response->content =~ /\s*([-01]+),([-0-9.]+)\s*/) { | |
# Possible values for nag will be -1, 0, 1 (only 0 means do not nag) | |
$nag = $1; | |
$days = $2; | |
} else { | |
return undef; | |
} | |
} | |
return { nag => $nag, days => $days }; | |
} | |
sub load_for_collection { | |
my ($self, $collection) = @_; | |
my $id = $collection->{id}; | |
return unless $id; # nothing to do | |
$self->load($collection); | |
my $query = "SELECT " . $self->_columns . ", ep.value AS show_gravatar | |
FROM " . $self->_table . " | |
JOIN editor_collection_collaborator ecc ON editor.id = ecc.editor | |
LEFT JOIN editor_preference ep ON ep.editor = editor.id AND ep.name = 'show_gravatar' | |
WHERE ecc.collection = $id | |
ORDER BY editor.name, editor.id"; | |
my @collaborators = $self->query_to_list($query, undef, sub { | |
my ($model, $row) = @_; | |
my $collaborator = $model->_new_from_row($row); | |
$collaborator->preferences->show_gravatar($row->{show_gravatar}) | |
if defined $row->{show_gravatar}; | |
$collaborator; | |
}); | |
$collection->collaborators(\@collaborators); | |
} | |
sub editors_with_subscriptions { | |
my ($self, $after, $limit) = @_; | |
my @tables = (entities_with('subscriptions', | |
take => sub { return "editor_subscribe_" . (shift) }), | |
entities_with(['subscriptions', 'deleted'], | |
take => sub { return "editor_subscribe_" . (shift) . "_deleted" })); | |
my $ids = join(' UNION ALL ', map { "SELECT editor FROM $_" } @tables); | |
my $query = "SELECT " . $self->_columns . ", ep.value AS prefs_value | |
FROM " . $self->_table . " | |
LEFT JOIN editor_preference ep | |
ON ep.editor = editor.id AND | |
ep.name = 'subscriptions_email_period' | |
WHERE editor.id > ? | |
AND editor.id IN ($ids) | |
ORDER BY editor.id ASC | |
LIMIT ?"; | |
$self->query_to_list($query, [$after, $limit], sub { | |
my ($model, $row) = @_; | |
my $editor = $model->_new_from_row($row); | |
$editor->preferences->subscriptions_email_period($row->{prefs_value}) | |
if defined $row->{prefs_value}; | |
$editor; | |
}); | |
} | |
sub delete { | |
my ($self, $editor_id, $allow_reuse) = @_; | |
die "Invalid editor_id: $editor_id" unless $editor_id > 0; | |
my $editor = $self->c->model('Editor')->get_by_id($editor_id); | |
$self->sql->begin; | |
$self->sql->do( | |
'INSERT INTO old_editor_name (name) | |
(SELECT name FROM editor WHERE id = ?)', | |
$editor_id, | |
) unless $allow_reuse; | |
$self->sql->do( | |
"UPDATE editor SET name = 'Deleted Editor #' || id, | |
password = ?, | |
ha1 = '', | |
privs = 0, | |
email = NULL, | |
email_confirm_date = NULL, | |
website = NULL, | |
bio = NULL, | |
area = NULL, | |
birth_date = NULL, | |
gender = NULL, | |
deleted = TRUE | |
WHERE id = ?", | |
Authen::Passphrase::RejectAll->new->as_rfc2307, | |
$editor_id | |
); | |
$self->sql->do("DELETE FROM editor_preference WHERE editor = ?", $editor_id); | |
$self->c->model('EditorLanguage')->delete_editor($editor_id); | |
$self->c->model('EditorOAuthToken')->delete_editor($editor_id); | |
$self->c->model('Application')->delete_editor($editor_id); | |
$self->c->model('EditorSubscriptions')->delete_editor($editor_id); | |
$self->c->model('Editor')->unsubscribe_to($editor_id); | |
$self->c->model('Collection')->delete_editor($editor_id); | |
$self->c->model('WatchArtist')->delete_editor($editor_id); | |
$self->c->model($_)->tags->clear($editor_id) | |
for (entities_with('tags', take => 'model')); | |
$self->c->model($_)->rating->clear($editor_id) | |
for (entities_with('ratings', take => 'model')); | |
# Cancel any open edits the editor still has | |
# We want to cancel the latest edits first, to make sure | |
# no conflicts happen that make some cancelling fail and all | |
# entities that should be autoremoved do get removed | |
my $own_edit_ids = $self->sql->select_single_column_array( | |
'SELECT id FROM edit WHERE editor = ? AND status = ? ORDER BY open_time DESC', | |
$editor_id, $STATUS_OPEN); | |
my $own_edits = $self->c->model('Edit')->get_by_ids(@$own_edit_ids); | |
for my $edit_id (@$own_edit_ids) { | |
$self->c->model('Edit')->cancel($own_edits->{$edit_id}); | |
} | |
# Override any Yes/No votes on open edits with Abstain | |
# to avoid pre-deletion vandalism | |
my $voted_open_edit_ids = $self->sql->select_single_column_array( | |
'SELECT edit.id | |
FROM edit | |
JOIN vote | |
ON edit.id = vote.edit | |
WHERE edit.status = ? | |
AND vote.editor = ? | |
AND vote.vote IN (?, ?) | |
AND vote.superseded = FALSE | |
ORDER BY open_time DESC', | |
$STATUS_OPEN, $editor_id, $VOTE_YES, $VOTE_NO); | |
for my $edit_id (@$voted_open_edit_ids) { | |
$self->c->model('Vote')->enter_votes( | |
$editor, | |
{ | |
vote => $VOTE_ABSTAIN, | |
edit_id => $edit_id | |
} | |
); | |
} | |
# Delete completely if they're not actually referred to by anything | |
# These AND NOT EXISTS clauses are ordered by likelihood of a row existing | |
# and whether or not they have an index to use, as postgresql will not execute | |
# the later clauses if an earlier one has already excluded the lone editor row. | |
my $should_delete = $self->sql->select_single_value( | |
"SELECT TRUE FROM editor WHERE id = ? " . | |
"AND NOT EXISTS (SELECT TRUE FROM edit WHERE editor = editor.id) " . | |
"AND NOT EXISTS (SELECT TRUE FROM edit_note WHERE editor = editor.id) " . | |
"AND NOT EXISTS (SELECT TRUE FROM vote WHERE editor = editor.id) " . | |
"AND NOT EXISTS (SELECT TRUE FROM annotation WHERE editor = editor.id) " . | |
"AND NOT EXISTS (SELECT TRUE FROM autoeditor_election_vote WHERE voter = editor.id) " . | |
"AND NOT EXISTS (SELECT TRUE FROM autoeditor_election WHERE candidate = editor.id OR proposer = editor.id OR seconder_1 = editor.id OR seconder_2 = editor.id)", | |
$editor_id); | |
if ($should_delete) { | |
$self->sql->do("DELETE FROM editor WHERE id = ?", $editor_id); | |
} | |
$self->sql->commit; | |
} | |
sub subscription_summary { | |
my ($self, $editor_id) = @_; | |
$self->sql->select_single_row_hash( | |
'SELECT ' . | |
join(', ', map { | |
"COALESCE( | |
(SELECT count(*) FROM editor_subscribe_$_ WHERE editor = ?), | |
0) AS $_" | |
} entities_with('subscriptions')), | |
($editor_id) x 5 | |
); | |
} | |
sub various_edit_counts { | |
my ($self, $editor_id) = @_; | |
my %result = map { $_ . '_count' => 0 } | |
qw( accepted accepted_auto rejected cancelled open failed ); | |
my $query = | |
q{SELECT | |
CASE | |
WHEN status = ? THEN | |
CASE | |
WHEN autoedit = 0 THEN 'accepted' | |
ELSE 'accepted_auto' | |
END | |
WHEN status = ? THEN 'rejected' | |
WHEN status = ? THEN 'cancelled' | |
WHEN status = ? THEN 'open' | |
ELSE 'failed' | |
END AS category, | |
COUNT(*) AS count | |
FROM edit | |
WHERE editor = ? | |
GROUP BY category}; | |
my @params = ($STATUS_APPLIED, $STATUS_FAILEDVOTE, $STATUS_DELETED, $STATUS_OPEN); | |
my $rows = $self->sql->select_list_of_lists($query, @params, $editor_id); | |
for my $row (@$rows) { | |
my ($category, $count) = @$row; | |
$result{$category . '_count'} = $count; | |
} | |
return \%result; | |
} | |
sub added_entities_counts { | |
my ($self, $editor_id) = @_; | |
my $cache_key = "editor:$editor_id:added_entities_counts"; | |
my $cached_result = $self->c->cache->get($cache_key); | |
return $cached_result if defined $cached_result; | |
my %result = map { $_ => 0 } | |
qw( artist release cover_art event label place series work other ); | |
my $query = | |
q{SELECT | |
CASE | |
WHEN type = ? THEN 'artist' | |
WHEN type IN (?, ?) THEN 'release' | |
WHEN type = ? THEN 'cover_art' | |
WHEN type = ? THEN 'event' | |
WHEN type = ? THEN 'label' | |
WHEN type = ? THEN 'place' | |
WHEN type = ? THEN 'series' | |
WHEN type = ? THEN 'work' | |
ELSE 'other' | |
END AS type, | |
COUNT(*) AS count | |
FROM edit | |
WHERE editor = ? | |
GROUP BY type}; | |
my @params = ($EDIT_ARTIST_CREATE, $EDIT_RELEASE_CREATE, $EDIT_HISTORIC_ADD_RELEASE, | |
$EDIT_RELEASE_ADD_COVER_ART, $EDIT_EVENT_CREATE, $EDIT_LABEL_CREATE, | |
$EDIT_PLACE_CREATE, $EDIT_SERIES_CREATE, $EDIT_WORK_CREATE); | |
my $rows = $self->sql->select_list_of_lists($query, @params, $editor_id); | |
for my $row (@$rows) { | |
my ($type, $count) = @$row; | |
$result{$type} = $count; | |
} | |
$self->c->cache->set($cache_key, \%result, 60 * 60 * 24); | |
return \%result; | |
} | |
sub last_24h_edit_count | |
{ | |
my ($self, $editor_id) = @_; | |
my $query = | |
"SELECT count(*) | |
FROM edit | |
WHERE editor = ? | |
AND open_time >= (now() - interval '1 day') | |
"; | |
return $self->sql->select_single_value($query, $editor_id); | |
} | |
sub unsubscribe_to { | |
my ($self, $editor_id) = @_; | |
$self->sql->do( | |
'DELETE FROM editor_subscribe_editor WHERE subscribed_editor = ?', | |
$editor_id); | |
} | |
sub update_last_login_date { | |
my ($self, $editor_id) = @_; | |
$self->sql->auto_commit(1); | |
$self->sql->do('UPDATE editor SET last_login_date = now() WHERE id = ?', $editor_id); | |
} | |
sub hash_password { | |
my $password = shift; | |
Authen::Passphrase::BlowfishCrypt->new( | |
salt_random => 1, | |
cost => $PASSPHRASE_BCRYPT_COST, | |
passphrase => encode('utf-8', $password), | |
)->as_rfc2307 | |
} | |
sub ha1_password { | |
my ($username, $password) = @_; | |
return md5_hex(join(':', encode('utf-8', $username), 'musicbrainz.org', encode('utf-8', $password))); | |
} | |
sub consume_remember_me_token { | |
my ($self, $user_name, $token) = @_; | |
my $token_key = "$user_name|$token"; | |
# Expire consumed tokens in 5 minutes. This allows the case where the user | |
# has no session, and opens multiple tabs using the same remember_me token. | |
$self->store->expire($token_key, 5 * 60); | |
$self->store->exists($token_key); | |
} | |
sub allocate_remember_me_token { | |
my ($self, $user_name) = @_; | |
if ( | |
my $normalized_name = $self->sql->select_single_value( | |
'SELECT name FROM editor WHERE lower(name) = lower(?)', | |
$user_name | |
) | |
) { | |
my $token = generate_token(); | |
my $key = "$normalized_name|$token"; | |
$self->store->set($key, 1); | |
# Expire tokens after 1 year. | |
$self->store->expire($key, 60 * 60 * 24 * 7 * 52); | |
return ($normalized_name, $token); | |
} | |
else { | |
return undef; | |
} | |
} | |
sub is_email_used_elsewhere { | |
my ($self, $email, $user_id) = @_; | |
return 1 if $self->sql->select_single_value( | |
'SELECT 1 FROM editor WHERE lower(email) = lower(?) AND id != ?', $email, $user_id); | |
return 0; | |
} | |
sub is_name_used { | |
my ($self, $name) = @_; | |
return 1 if $self->sql->select_single_value( | |
'SELECT 1 FROM editor WHERE lower(name) = lower(?)', $name); | |
return 1 if $self->sql->select_single_value( | |
'SELECT 1 FROM old_editor_name WHERE lower(name) = lower(?)', $name); | |
return 0; | |
} | |
sub are_names_equivalent { | |
my ($self, $name1, $name2) = @_; | |
return $self->sql->select_single_value( | |
'SELECT lower(?) = lower(?)', $name1, $name2); | |
} | |
no Moose; | |
__PACKAGE__->meta->make_immutable; | |
1; | |
=head1 NAME | |
MusicBrainz::Server::Data::Editor - database level loading support for | |
Editors | |
=head1 DESCRIPTION | |
Provides support for fetching editors from the database | |
=head1 COPYRIGHT AND LICENSE | |
Copyright (C) 2009 Oliver Charles | |
Copyright (C) 2010 MetaBrainz Foundation | |
This file is part of MusicBrainz, the open internet music database, | |
and is licensed under the GPL version 2, or (at your option) any | |
later version: http://www.gnu.org/licenses/gpl-2.0.txt | |
=cut |