From 469ef2e07db451157800aff151353386a59079b1 Mon Sep 17 00:00:00 2001
From: Tomas Cohen Arazi <tomascohen@theke.io>
Date: Thu, 18 Mar 2021 15:15:13 -0300
Subject: [PATCH] Bug 23666: Add extended attributes routes

This patch adds routes to handle patron's extended attributes:

GET    /patrons/:patron_id/extended_attributes
POST   /patrons/:patron_id/extended_attributes
PUT    /patrons/:patron_id/extended_attributes <- to overwrite them all
DELETE /patrons/:patron_id/extended_attributes/:extended_attribute_id

Controllers rely on the Koha::Patron methods for retrieving/adding
extended attributes. Exceptions are correctly catch and handled in the
responses. This is all covered by tests as well.

Note: I decidedto override the default sorting on the PUT response,
    because the overloaded search sorting it felt a bit wrong on the API.

To test:
1. Apply this patches
2. Run:
   $ kshell
  k$ prove t/db_dependent/api/v1/patrons_extended_attributes.t
=> SUCCESS: Tests pass!
3. Play with your favourite tool and this routes
=> SUCCESS: They work well!
4. Sign off :-D

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
---
 Koha/REST/V1/Patrons/Attributes.pm | 270 +++++++++++++++++++++++++++++
 1 file changed, 270 insertions(+)
 create mode 100644 Koha/REST/V1/Patrons/Attributes.pm

diff --git a/Koha/REST/V1/Patrons/Attributes.pm b/Koha/REST/V1/Patrons/Attributes.pm
new file mode 100644
index 0000000000..2132ec0a04
--- /dev/null
+++ b/Koha/REST/V1/Patrons/Attributes.pm
@@ -0,0 +1,270 @@
+package Koha::REST::V1::Patrons::Attributes;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use Mojo::Base 'Mojolicious::Controller';
+
+use Koha::Patron::Attributes;
+use Koha::Patrons;
+
+use Scalar::Util qw(blessed);
+use Try::Tiny;
+
+=head1 NAME
+
+Koha::REST::V1::Patrons::Attributes
+
+=head1 API
+
+=head2 Methods
+
+=head3 list_patron_attributes
+
+Controller method that handles listing the Koha::Patron::Attribute objects that belong
+to a given patron.
+
+=cut
+
+sub list_patron_attributes {
+    my $c = shift->openapi->valid_input or return;
+
+    my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
+
+    unless ($patron) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error => 'Patron not found'
+            }
+        );
+    }
+
+    return try {
+
+        my $attributes_rs = $patron->extended_attributes;
+        my $attributes    = $c->objects->search($attributes_rs);
+
+        return $c->render(
+            status  => 200,
+            openapi => $attributes
+        );
+    }
+    catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 add
+
+Controller method that handles adding a Koha::Patron::Attribute to a given patron.
+
+=cut
+
+sub add {
+    my $c = shift->openapi->valid_input or return;
+
+    my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
+
+    unless ($patron) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error => 'Patron not found'
+            }
+        );
+    }
+
+    return try {
+
+        my $attribute = $patron->add_extended_attribute(
+            Koha::Patron::Attribute->new_from_api( # new_from_api takes care of mapping attributes
+                $c->validation->param('body')
+            )->unblessed
+        );
+
+        $c->res->headers->location( $c->req->url->to_string . '/' . $attribute->id );
+        return $c->render(
+            status  => 201,
+            openapi => $attribute->to_api
+        );
+    }
+    catch {
+        if ( blessed $_ ) {
+            if (
+                $_->isa(
+                    'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint')
+              )
+            {
+                return $c->render(
+                    status  => 409,
+                    openapi => {
+                        error => "$_"
+                    }
+                );
+            }
+            elsif (
+                $_->isa('Koha::Exceptions::Patron::Attribute::NonRepeatable') )
+            {
+                return $c->render(
+                    status  => 409,
+                    openapi => {
+                        error => "$_"
+                    }
+                );
+            }
+            elsif (
+                $_->isa('Koha::Exceptions::Patron::Attribute::InvalidType') )
+            {
+                return $c->render(
+                    status  => 400,
+                    openapi => { error => "$_" }
+                );
+            }
+        }
+
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 overwrite
+
+Controller method that handles overwriting extended attributes for a given patron.
+
+=cut
+
+sub overwrite {
+    my $c = shift->openapi->valid_input or return;
+
+    my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
+
+    unless ($patron) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error => 'Patron not found'
+            }
+        );
+    }
+
+    return try {
+
+        my $body = $c->validation->every_param('body');
+
+        my @attrs;
+
+        foreach my $attr ( @{$body} ) {
+            push @attrs, { code => $attr->{type}, attribute => $attr->{value} };
+        }
+
+        # Fetch the attributes, sorted by id
+        my $attributes = $patron->extended_attributes( \@attrs )->search( undef, { order_by => 'id' });
+
+        return $c->render(
+            status  => 200,
+            openapi => $attributes->to_api
+        );
+    }
+    catch {
+        if ( blessed $_ ) {
+            if ( $_->isa('Koha::Exceptions::Patron::Attribute::InvalidType') ) {
+                return $c->render(
+                    status  => 400,
+                    openapi => { error => "$_" }
+                );
+
+            }
+            elsif (
+                $_->isa(
+                    'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint')
+              )
+            {
+                return $c->render(
+                    status  => 409,
+                    openapi => { error => "$_" }
+                );
+            }
+            elsif (
+                $_->isa('Koha::Exceptions::Patron::Attribute::NonRepeatable') )
+            {
+                return $c->render(
+                    status  => 409,
+                    openapi => { error => "$_" }
+                );
+            }
+            elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
+                return $c->render(
+                    status  => 400,
+                    openapi => {
+                        error => 'Missing mandatory attribute of type "'
+                          . $_->value . '".'
+                    }
+                );
+
+            }
+        }
+
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 delete
+
+Controller method that handling removing an extended patron attribute
+
+=cut
+
+sub delete {
+    my $c = shift->openapi->valid_input or return;
+
+    my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
+
+    unless ($patron) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error => 'Patron not found'
+            }
+        );
+    }
+
+    return try {
+
+        my $attribute = $patron->extended_attributes->find(
+            $c->validation->param('extended_attribute_id') );
+
+        unless ($attribute) {
+            return $c->render(
+                status  => 404,
+                openapi => {
+                    error => 'Attribute not found'
+                }
+            );
+        }
+
+        $attribute->delete;
+        return $c->render(
+            status  => 204,
+            openapi => q{}
+        );
+    }
+    catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+1;
-- 
2.24.3 (Apple Git-128)