From 41801722ed3d06e70369b9f56672c8437a67aad9 Mon Sep 17 00:00:00 2001 From: Darin McBride Date: Tue, 2 Dec 2014 13:20:42 -0700 Subject: [PATCH] Inbox's trash_messages_where API call now uses named params, defaults to not returning deleted message IDs. --- docs/Inbox.pod | 48 +++++++++++++++---- lib/Lacuna/RPC/Inbox.pm | 95 ++++++++++++++++++++++++++++++-------- var/www/public/changes.txt | 3 ++ 3 files changed, 119 insertions(+), 27 deletions(-) diff --git a/docs/Inbox.pod b/docs/Inbox.pod index 69af1ace..1d6f7d62 100644 --- a/docs/Inbox.pod +++ b/docs/Inbox.pod @@ -243,7 +243,7 @@ An array reference of message ids. -=head2 trash_messages_where ( session_id, options, [ options, ... ] ) +=head2 trash_messages_where ( session_id, { spec => [ options, options, ... ] } ) Trashed messages are marked as read. @@ -251,6 +251,7 @@ Trashes all messages matching the options. Returns: { "deleted" : ["id-goes-here","id-goes-here"], + "deleted_count" : 17340, "status" : { ... } } @@ -258,33 +259,62 @@ Trashes all messages matching the options. Returns: A session id. -=head3 options +=head3 named parameters + +The named parameter list takes the following keys: + +=head4 spec -A hash reference of extra options. A message must match all options -to be deleted ("AND"). It is an error to specify no options. To delete -all non-archived messages, simply use subject of C<%>. +A list of specifications for deletion. Each specification is another +object (hash). A message must match all keys provided to be deleted ("AND"). +It is an error to specify no options. To delete all non-archived messages, +simply use C of C<%>. Compounding multiple delete requests can be done by passing in additional hash references. -=head4 tags +Example: + + { tags => [ 'Attack', 'Alert' ], subject => 'BFG%' } + +This will delete any message with either the C or C tags, +if the subject starts with I. Attack and alert messages that do not +start with "BFG" will not be deleted. + +Keys are: + +=head5 tags An array reference of tags. Messages with any of these tags will be eligible for deletion. Default is any and all tags (no tag evaluation). +Tags are given as a set, so are automatically "or"ed together. -=head4 subject +=head5 subject A subject line to match. Use C<%> to represent zero or more characters in that position, such as "Pass:%" to select all messages whose subject starts with "Pass:". Default is essentially any subject (no subject evaluation). -=head4 from +The subject may also be passed in as an array object, in which case it +is treated as tags above: a set to exactly match as a set of "or"ed values. + +=head5 from + +An array reference of from empire names. Full names are required. -An array reference of from names. Full names are required. +=head4 return_ids +If this is present and set to a true value, all deleted message IDs will +be saved and returned. Gathering the IDs can, for large deletions, result +in extra server work, and also increase the size of the returned JSON +dramatically. This should only be set if you need the IDs for deleting from +an off-site message store, such as a local database, or an active message +view (UI). +If this is not set, the C key will not be returned at all. +The C key will still be present either way. =head2 send_message ( session_id, recipients, subject, body, [ options ] ) diff --git a/lib/Lacuna/RPC/Inbox.pm b/lib/Lacuna/RPC/Inbox.pm index e546ad16..084eaf01 100644 --- a/lib/Lacuna/RPC/Inbox.pm +++ b/lib/Lacuna/RPC/Inbox.pm @@ -7,6 +7,7 @@ extends 'Lacuna::RPC'; use DateTime; use Lacuna::Verify; use Lacuna::Util qw(format_date); +use List::Util qw(none); sub read_message { @@ -98,21 +99,63 @@ sub trash_messages { } sub trash_messages_where { - my $self = shift; - my $session_id = shift; + my ($self, $session_id, $opts) = @_; my $empire = $self->get_empire_by_session($session_id); - - my @deleted; - while (@_) + if (!$opts->{spec}) { - my $opts = shift; + $opts = { spec => [ @_[2..$#_] ] }; + } + + # initialise deleted_count to ensure it gets set on return + my %return = (deleted_count => 0); + + # if we're saving returns, same thing, ensure there's an empty + # list even if nothing is deleted. + $return{deleted} = [] if $opts->{save_ids}; + + my $count = -1; + for my $spec (@{$opts->{spec}}) + { + ++$count; my %where; - $where{tag} = [ 'in', $opts->{tags} ] if $opts->{tags}; - $where{subject} = { like => $opts->{subject} } if $opts->{subject}; - $where{from_name} = [ 'in', $opts->{from} ] if $opts->{from}; + $where{tag} = $spec->{tags} if $spec->{tags} && ref $spec->{tags} eq 'ARRAY'; + $where{tag} ||= $spec->{tag} if $spec->{tag}; + $where{from_name} = [ $spec->{from} ] if $spec->{from}; + + if ($spec->{subject}) + { + # some variation allowed, but need to ensure sanity. + + # only allow lists of subjects as explicit items, and each one + # must be a string only - no nested objects, because DBIx::Class + # will do more stuff down lower, and we really don't want to ensure + # its security. + if (ref $spec->{subject} && + ref $spec->{subject} eq 'ARRAY' && + none { ref $_ } @{$spec->{subject}}) + { + $where{subject} = $spec->{subject}; + } + # single string, with % or _, use like + elsif ($spec->{subject} =~ /[%_]/) + { + $where{subject} = { like => $spec->{subject} }; + } + # otherwise, just match directly. + elsif (not ref $spec->{subject}) + { + $where{subject} = $spec->{subject}; + } + # if we got some other sort of ref, craok instead of trying + # to ensure security. + else + { + confess [ 1009, 'Invalid subject specified for mass delete' ]; + } + } - confess [ 1009, 'No options specified for mass delete' ] + confess [ 1009, 'No options specified for mass delete spec #' . $count ] unless keys %where; # the parts the caller can't override: @@ -120,26 +163,42 @@ sub trash_messages_where { $where{to_id} = $empire->id; $where{has_trashed} = 0; # only look at ones not already trashed + use Data::Dump; ddx \%where; + my $messages = Lacuna->db->resultset('Lacuna::DB::Result::Message')->search(\%where); - my @deleting = map { $_->id } $messages->search(undef, { columns => [ 'id' ] })->all; - if (@deleting) + # check if we have anything to delete + my $count; + if ($opts->{save_ids}) + { + my @deleting = map { $_->id } $messages->search(undef, { columns => [ 'id' ] })->all; + if (@deleting) + { + $count = @deleting; + push @{$return{deleted}}, @deleting; + } + } + else { + $count = $messages->count; + } + + # delete it + if ($count) + { + $return{deleted_count} += $count; $messages->update( { has_read => 1, has_trashed => 1, }); - push @deleted, @deleting; } } - $empire->recalc_messages if @deleted; + $empire->recalc_messages if $return{deleted_count}; - return { - deleted => \@deleted, - status => $self->format_status($empire), - }; + $return{status} = $self->format_status($empire); + return \%return; } sub send_message { diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index 81cb9ac9..3e4e4420 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,3 +1,6 @@ +3.0908: + - Mod: trash_messages_where now defaults to not returning deleted message IDs + 3.0907: - Add: Timer added to colony object preventing passage into Neutral Zone. - Add: colonies and stations added to empire stat. planets kept to maintain backwards compatibility -- 2.51.2