Bug 34587: Remove counter files from API embedding to improve performance
[koha.git] / Koha / REST / V1 / ERM / EUsage / UsageDataProviders.pm
1 package Koha::REST::V1::ERM::EUsage::UsageDataProviders;
2
3 # Copyright 2023 PTFS Europe
4
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use MIME::Base64 qw( decode_base64 );
23 use Mojo::Base 'Mojolicious::Controller';
24
25 use Koha::ERM::EUsage::UsageDataProviders;
26 use Koha::ERM::EUsage::MonthlyUsages;
27 use Koha::ERM::EUsage::CounterFiles;
28
29 use Scalar::Util qw( blessed );
30 use Try::Tiny    qw( catch try );
31
32 =head1 API
33
34 =head2 Methods
35
36 =head3 list
37
38 =cut
39
40 sub list {
41     my $c = shift->openapi->valid_input or return;
42
43     return try {
44         my $usage_data_providers_set = Koha::ERM::EUsage::UsageDataProviders->new;
45         my $usage_data_providers     = $c->objects->search($usage_data_providers_set);
46         # if (   $c->validation->output->{"x-koha-embed"}[0]
47         #     && $c->validation->output->{"x-koha-embed"}[0] eq 'counter_files' )
48         # {
49             foreach my $provider (@$usage_data_providers) {
50                 my $title_dates = _get_earliest_and_latest_dates(
51                     'TR',
52                     $provider->{erm_usage_data_provider_id}
53                 );
54                 $provider->{earliest_title} =
55                       $title_dates->{earliest_date}
56                     ? $title_dates->{earliest_date}
57                     : '';
58                 $provider->{latest_title} =
59                       $title_dates->{latest_date}
60                     ? $title_dates->{latest_date}
61                     : '';
62
63                 my $platform_dates = _get_earliest_and_latest_dates(
64                     'PR',
65                     $provider->{erm_usage_data_provider_id}
66                 );
67                 $provider->{earliest_platform} =
68                       $platform_dates->{earliest_date}
69                     ? $platform_dates->{earliest_date}
70                     : '';
71                 $provider->{latest_platform} =
72                       $platform_dates->{latest_date}
73                     ? $platform_dates->{latest_date}
74                     : '';
75
76                 my $item_dates = _get_earliest_and_latest_dates(
77                     'IR',
78                     $provider->{erm_usage_data_provider_id}
79                 );
80                 $provider->{earliest_item} =
81                       $item_dates->{earliest_date}
82                     ? $item_dates->{earliest_date}
83                     : '';
84                 $provider->{latest_item} =
85                     $item_dates->{latest_date} ? $item_dates->{latest_date} : '';
86
87                 my $database_dates = _get_earliest_and_latest_dates(
88                     'DR',
89                     $provider->{erm_usage_data_provider_id}
90                 );
91                 $provider->{earliest_database} =
92                       $database_dates->{earliest_date}
93                     ? $database_dates->{earliest_date}
94                     : '';
95                 $provider->{latest_database} =
96                       $database_dates->{latest_date}
97                     ? $database_dates->{latest_date}
98                     : '';
99
100                 my @last_run = Koha::ERM::EUsage::CounterFiles->search(
101                     {
102                         usage_data_provider_id => $provider->{erm_usage_data_provider_id},
103                     },
104                     { columns => [ { date_uploaded => { max => "date_uploaded" } }, ] }
105                 )->unblessed;
106                 $provider->{last_run} = $last_run[0][0]->{date_uploaded} ? $last_run[0][0]->{date_uploaded} : '';
107
108             }
109         # }
110
111         return $c->render( status => 200, openapi => $usage_data_providers );
112     } catch {
113         $c->unhandled_exception($_);
114     };
115
116 }
117
118 =head3 get
119
120 Controller function that handles retrieving a single Koha::ERM::EUsage::UsageDataProvider object
121
122 =cut
123
124 sub get {
125     my $c = shift->openapi->valid_input or return;
126
127     return try {
128         my $usage_data_provider_id = $c->validation->param('erm_usage_data_provider_id');
129         my $usage_data_provider    = $c->objects->find(
130             Koha::ERM::EUsage::UsageDataProviders->search,
131             $usage_data_provider_id
132         );
133
134         unless ($usage_data_provider) {
135             return $c->render(
136                 status  => 404,
137                 openapi => { error => "Usage data provider not found" }
138             );
139         }
140
141         return $c->render(
142             status  => 200,
143             openapi => $usage_data_provider
144         );
145     } catch {
146         $c->unhandled_exception($_);
147     };
148 }
149
150 =head3 add
151
152 Controller function that handles adding a new Koha::ERM::EUsage::UsageDataProvider object
153
154 =cut
155
156 sub add {
157     my $c = shift->openapi->valid_input or return;
158
159     return try {
160         Koha::Database->new->schema->txn_do(
161             sub {
162
163                 my $body = $c->validation->param('body');
164
165                 my $usage_data_provider = Koha::ERM::EUsage::UsageDataProvider->new_from_api($body)->store;
166
167                 $c->res->headers->location(
168                     $c->req->url->to_string . '/' . $usage_data_provider->erm_usage_data_provider_id );
169                 return $c->render(
170                     status  => 201,
171                     openapi => $usage_data_provider->to_api
172                 );
173             }
174         );
175     } catch {
176
177         my $to_api_mapping = Koha::ERM::EUsage::UsageDataProvider->new->to_api_mapping;
178
179         if ( blessed $_ ) {
180             if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
181                 return $c->render(
182                     status  => 409,
183                     openapi => { error => $_->error, conflict => $_->duplicate_id }
184                 );
185             } elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
186                 return $c->render(
187                     status  => 400,
188                     openapi => { error => "Given " . $to_api_mapping->{ $_->broken_fk } . " does not exist" }
189                 );
190             } elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
191                 return $c->render(
192                     status  => 400,
193                     openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
194                 );
195             } elsif ( $_->isa('Koha::Exceptions::PayloadTooLarge') ) {
196                 return $c->render(
197                     status  => 413,
198                     openapi => { error => $_->error }
199                 );
200             }
201         }
202
203         $c->unhandled_exception($_);
204     };
205 }
206
207 =head3 update
208
209 Controller function that handles updating a Koha::ERM::EUsage::UsageDataProvider object
210
211 =cut
212
213 sub update {
214     my $c = shift->openapi->valid_input or return;
215
216     my $usage_data_provider_id = $c->validation->param('erm_usage_data_provider_id');
217     my $usage_data_provider    = Koha::ERM::EUsage::UsageDataProviders->find($usage_data_provider_id);
218
219     unless ($usage_data_provider) {
220         return $c->render(
221             status  => 404,
222             openapi => { error => "Usage data provider not found" }
223         );
224     }
225
226     return try {
227         Koha::Database->new->schema->txn_do(
228             sub {
229
230                 my $body = $c->validation->param('body');
231
232                 $usage_data_provider->set_from_api($body)->store;
233
234                 $c->res->headers->location(
235                     $c->req->url->to_string . '/' . $usage_data_provider->erm_usage_data_provider_id );
236                 return $c->render(
237                     status  => 200,
238                     openapi => $usage_data_provider->to_api
239                 );
240             }
241         );
242     } catch {
243         my $to_api_mapping = Koha::ERM::EUsage::UsageDataProvider->new->to_api_mapping;
244
245         if ( blessed $_ ) {
246             if ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
247                 return $c->render(
248                     status  => 400,
249                     openapi => { error => "Given " . $to_api_mapping->{ $_->broken_fk } . " does not exist" }
250                 );
251             } elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
252                 return $c->render(
253                     status  => 400,
254                     openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
255                 );
256             } elsif ( $_->isa('Koha::Exceptions::PayloadTooLarge') ) {
257                 return $c->render(
258                     status  => 413,
259                     openapi => { error => $_->error }
260                 );
261             }
262         }
263
264         $c->unhandled_exception($_);
265     };
266 }
267
268 =head3 delete
269
270 =cut
271
272 sub delete {
273     my $c = shift->openapi->valid_input or return;
274
275     my $usage_data_provider_id = $c->validation->param('erm_usage_data_provider_id');
276     my $usage_data_provider    = Koha::ERM::EUsage::UsageDataProviders->find($usage_data_provider_id);
277     unless ($usage_data_provider) {
278         return $c->render(
279             status  => 404,
280             openapi => { error => "Usage data provider not found" }
281         );
282     }
283
284     return try {
285         $usage_data_provider->delete;
286         return $c->render(
287             status  => 204,
288             openapi => q{}
289         );
290     } catch {
291         $c->unhandled_exception($_);
292     };
293 }
294
295 =head3 process_COUNTER_file
296
297 Controller function that handles processing of the COUNTER file
298 It will->enqueue_counter_file_processing_job for its respective usage data provider
299
300 =cut
301
302 sub process_COUNTER_file {
303     my $c = shift->openapi->valid_input or return;
304
305     return try {
306         Koha::Database->new->schema->txn_do(
307             sub {
308
309                 my $body = $c->validation->param('body');
310
311                 my $file_content =
312                     defined( $body->{file_content} )
313                     ? decode_base64( $body->{file_content} )
314                     : "";
315
316                 # Validate the file_content without storing, it'll throw an exception if fail
317                 my $counter_file_validation = Koha::ERM::EUsage::CounterFile->new( { file_content => $file_content } );
318                 $counter_file_validation->validate;
319
320                 # Validation was successful, enqueue the job
321                 my $udprovider =
322                     Koha::ERM::EUsage::UsageDataProviders->find( $c->validation->param('erm_usage_data_provider_id') );
323
324                 my $jobs = $udprovider->enqueue_counter_file_processing_job(
325                     {
326                         file_content => $file_content,
327                     }
328                 );
329
330                 return $c->render(
331                     status  => 200,
332                     openapi => { jobs => [ @{$jobs} ] }
333                 );
334             }
335         );
336     } catch {
337
338         my $to_api_mapping = Koha::ERM::EUsage::CounterFile->new->to_api_mapping;
339
340         if ( blessed $_ ) {
341             if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
342                 return $c->render(
343                     status  => 409,
344                     openapi => { error => $_->error, conflict => $_->duplicate_id }
345                 );
346             } elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
347                 return $c->render(
348                     status  => 400,
349                     openapi => { error => "Given " . $to_api_mapping->{ $_->broken_fk } . " does not exist" }
350                 );
351             } elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
352                 return $c->render(
353                     status  => 400,
354                     openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
355                 );
356             } elsif ( $_->isa('Koha::Exceptions::PayloadTooLarge') ) {
357                 return $c->render(
358                     status  => 413,
359                     openapi => { error => $_->error }
360                 );
361             } elsif ( $_->isa('Koha::Exceptions::ERM::EUsage::CounterFile::UnsupportedRelease') ) {
362                 return $c->render(
363                     status  => 400,
364                     openapi => { error => $_->description }
365                 );
366             }
367         }
368
369         $c->unhandled_exception($_);
370     };
371 }
372
373 =head3 process_SUSHI_response
374
375 Controller function that handles processing of the SUSHI response
376 It will ->enqueue_sushi_harvest_jobs for this usage data provider
377
378 =cut
379
380 sub process_SUSHI_response {
381     my $c = shift->openapi->valid_input or return;
382
383     my $body       = $c->validation->param('body');
384     my $begin_date = $body->{begin_date};
385     my $end_date   = $body->{end_date};
386
387     unless ( $begin_date lt $end_date ) {
388         return $c->render(
389             status  => 400,
390             openapi => { error => "Begin date must be before end date" }
391         );
392     }
393
394     my $udprovider = Koha::ERM::EUsage::UsageDataProviders->find( $c->validation->param('erm_usage_data_provider_id') );
395
396     unless ($udprovider) {
397         return $c->render(
398             status  => 404,
399             openapi => { error => "Usage data provider not found" }
400         );
401     }
402
403     return try {
404         my $jobs = $udprovider->enqueue_sushi_harvest_jobs(
405             {
406                 begin_date => $begin_date,
407                 end_date   => $end_date
408             }
409         );
410
411         return $c->render(
412             status  => 200,
413             openapi => { jobs => [ @{$jobs} ] }
414         );
415     } catch {
416         $c->unhandled_exception($_);
417     };
418 }
419
420 =head3 test_connection
421
422 =cut
423
424 sub test_connection {
425     my $c = shift->openapi->valid_input or return;
426
427     my $udprovider = Koha::ERM::EUsage::UsageDataProviders->find( $c->validation->param('erm_usage_data_provider_id') );
428
429     unless ($udprovider) {
430         return $c->render(
431             status  => 404,
432             openapi => { error => "Usage data provider not found" }
433         );
434     }
435     try {
436         my $service_active = $udprovider->test_connection;
437         return $c->render(
438             status  => 200,
439             openapi => $service_active
440         );
441     } catch {
442         $c->unhandled_exception($_);
443     };
444 }
445
446 =head3 _get_earliest_and_latest_dates
447
448 =cut
449
450 sub _get_earliest_and_latest_dates {
451     my ( $report_type, $id ) = @_;
452
453     my @years = Koha::ERM::EUsage::MonthlyUsages->search(
454         {
455             usage_data_provider_id => $id,
456             report_type            => { -like => "%$report_type%" }
457         },
458         {
459             columns => [
460                 { earliestYear => { min => "year" } },
461                 { latestYear   => { max => "year" } },
462             ]
463         }
464     )->unblessed;
465     if ( $years[0][0]->{earliestYear} ) {
466         my @earliest_month = Koha::ERM::EUsage::MonthlyUsages->search(
467             {
468                 usage_data_provider_id => $id,
469                 report_type            => { -like => "%$report_type%" },
470                 year                   => $years[0][0]->{earliestYear},
471             },
472             { columns => [ { month => { min => "month" } }, ] }
473         )->unblessed;
474         my @latest_month = Koha::ERM::EUsage::MonthlyUsages->search(
475             {
476                 usage_data_provider_id => $id,
477                 report_type            => { -like => "%$report_type%" },
478                 year                   => $years[0][0]->{latestYear},
479             },
480             { columns => [ { month => { max => "month" } }, ] }
481         )->unblessed;
482
483         $earliest_month[0][0]->{month} =
484             _format_month("0$earliest_month[0][0]->{month}");
485         $latest_month[0][0]->{month} =
486             _format_month("0$latest_month[0][0]->{month}");
487
488         my $earliest_date = "$years[0][0]->{earliestYear}-$earliest_month[0][0]->{month}";
489         my $latest_date   = "$years[0][0]->{latestYear}-$latest_month[0][0]->{month}";
490
491         return {
492             earliest_date => $earliest_date,
493             latest_date   => $latest_date,
494         };
495     } else {
496         return {
497             earliest_date => 0,
498             latest_date   => 0,
499         };
500     }
501 }
502
503 =head3 _format_month
504
505 =cut
506
507 sub _format_month {
508     my ($month) = @_;
509
510     $month = length($month) eq 2 ? $month : "0$month";
511
512     return $month;
513 }
514
515 1;