View | Details | Raw Unified | Return to bug 16330
Collapse All | Expand All

(-)a/Koha/REST/V1/Patron.pm (+145 lines)
Lines 20-26 use Modern::Perl; Link Here
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use C4::Auth qw( haspermission );
22
use C4::Auth qw( haspermission );
23
use Koha::AuthUtils qw(hash_password);
23
use Koha::Patrons;
24
use Koha::Patrons;
25
use Koha::Patron::Categories;
26
use Koha::Libraries;
24
27
25
sub list {
28
sub list {
26
    my ($c, $args, $cb) = @_;
29
    my ($c, $args, $cb) = @_;
Lines 55-58 sub get { Link Here
55
    return $c->$cb($patron->unblessed, 200);
58
    return $c->$cb($patron->unblessed, 200);
56
}
59
}
57
60
61
sub add {
62
    my ($c, $args, $cb) = @_;
63
64
    my $user = $c->stash('koha.user');
65
66
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
67
        return $c->$cb({error => "You don't have the required permission"}, 403);
68
    }
69
70
    my $body = $c->req->json;
71
72
    # patron cardnumber and/or userid unique?
73
    if ($body->{cardnumber} || $body->{userid}) {
74
        my $patron = Koha::Patrons->find({cardnumber => $body->{cardnumber}, userid => $body->{userid} });
75
        if ($patron) {
76
            return $c->$cb({
77
                error => "Patron cardnumber and userid must be unique",
78
                conflict => { cardnumber => $patron->cardnumber, userid => $patron->userid }
79
            }, 409);
80
        }
81
    }
82
83
    my $branch = Koha::Libraries->find({branchcode => $body->{branchcode} });
84
    unless ($branch) {
85
        return $c->$cb({error => "Library with branchcode \"" . $body->{branchcode} . "\" does not exist"}, 404);
86
    }
87
    my $category = Koha::Patron::Categories->find({ categorycode => $body->{categorycode} });
88
    unless ($category) {
89
        return $c->$cb({error => "Patron category \"" . $body->{categorycode} . "\" does not exist"}, 404);
90
    }
91
    # All OK - save new patron
92
93
    if ($body->{password}) { $body->{password} = hash_password($body->{password}) }; # bcrypt password if given
94
95
    my $patron = eval {
96
        Koha::Patron->new($body)->store;
97
    };
98
99
    unless ($patron) {
100
        return $c->$cb({error => "Something went wrong, check Koha logs for details"}, 500);
101
    }
102
103
    return $c->$cb($patron->unblessed, 201);
104
}
105
106
sub edit {
107
    my ($c, $args, $cb) = @_;
108
109
    my $user = $c->stash('koha.user');
110
111
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
112
        return $c->$cb({error => "You don't have the required permission"}, 403);
113
    }
114
115
    my $patron = Koha::Patrons->find($args->{borrowernumber});
116
117
    unless ($patron) {
118
        return $c->$cb({error => "Patron not found"}, 404);
119
    }
120
121
    my $body = $c->req->json;
122
123
    # Can we change userid and/or cardnumber? in that case check that they are altered first
124
    if ($body->{cardnumber} || $body->{userid}) {
125
        if ( ($body->{cardnumber} && $body->{cardnumber} ne $patron->cardnumber) || ($body->{userid} && $body->{userid} ne $patron->userid) ) {
126
            my $conflictingPatron = Koha::Patrons->find({cardnumber => $body->{cardnumber}, userid => $body->{userid} });
127
            if ($conflictingPatron) {
128
                return $c->$cb({
129
                    error => "Patron cardnumber and userid must be unique",
130
                    conflict => { cardnumber => $conflictingPatron->cardnumber, userid => $conflictingPatron->userid }
131
                }, 409);
132
            }
133
        }
134
    }
135
136
    if ($body->{branchcode}) {
137
        my $branch = Koha::Libraries->find({branchcode => $body->{branchcode} });
138
        unless ($branch) {
139
            return $c->$cb({error => "Library with branchcode \"" . $body->{branchcode} . "\" does not exist"}, 404);
140
        }
141
    }
142
143
    if ($body->{categorycode}) {
144
        my $category = Koha::Patron::Categories->find({ categorycode => $body->{categorycode} });
145
        unless ($category) {
146
            return $c->$cb({error => "Patron category \"" . $body->{categorycode} . "\" does not exist"}, 404);
147
        }
148
    }
149
    # ALL OK - Update patron
150
    # Perhaps limit/validate what should be updated here? flags, et.al.
151
    if ($body->{password}) { $body->{password} = hash_password($body->{password}) }; # bcrypt password if given
152
153
    my $updatedpatron = eval {
154
        $patron->set($body);
155
    };
156
157
    if ($updatedpatron) {
158
        if ($updatedpatron->is_changed) {
159
160
            my $res = eval {
161
                $updatedpatron->store;
162
            };
163
164
            unless ($res) {
165
                return $c->$cb({error => "Something went wrong, check Koha logs for details"}, 500);
166
            }
167
            return $c->$cb($res->unblessed, 200);
168
169
        } else {
170
            return $c->$cb({}, 204); # No Content = No changes made
171
        }
172
    } else {
173
        return $c->$cb({error => "Something went wrong, check Koha logs for details"}, 500);
174
    }
175
}
176
177
sub delete {
178
    my ($c, $args, $cb) = @_;
179
    my $user = $c->stash('koha.user');
180
181
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
182
        return $c->$cb({error => "You don't have the required permission"}, 403);
183
    }
184
185
    my $patron = Koha::Patrons->find($args->{borrowernumber});
186
187
    unless ($patron) {
188
        return $c->$cb({error => "Patron not found"}, 404);
189
    }
190
191
    # check if loans, reservations, debarrment, etc. before deletion!
192
    my $res = $patron->delete;
193
194
    if ($res eq '1') {
195
        return $c->$cb({}, 200);
196
    } elsif ($res eq '-1') {
197
        return $c->$cb({}, 404);
198
    } else {
199
        return $c->$cb({}, 400);
200
    }
201
}
202
58
1;
203
1;
(-)a/api/v1/swagger.json (+137 lines)
Lines 38-43 Link Here
38
            }
38
            }
39
          }
39
          }
40
        }
40
        }
41
      },
42
      "post": {
43
        "operationId": "addPatron",
44
        "tags": ["patrons"],
45
        "parameters": [{
46
          "name": "body",
47
          "in": "body",
48
          "description": "A JSON object containing information about the new patron",
49
          "required": true,
50
          "schema": {
51
            "$ref": "#/definitions/patron"
52
          }
53
        }],
54
        "consumes": ["application/json"],
55
        "produces": ["application/json"],
56
        "responses": {
57
          "201": {
58
            "description": "A successfully created patron",
59
            "schema": {
60
              "items": {
61
                "$ref": "#/definitions/patron"
62
              }
63
            }
64
          },
65
          "403": {
66
            "description": "Access forbidden",
67
            "schema": {
68
              "$ref": "#/definitions/error"
69
            }
70
          },
71
          "404": {
72
            "description": "Resource not found",
73
            "schema": {
74
              "$ref": "#/definitions/error"
75
            }
76
          },
77
          "409": {
78
            "description": "Conflict in creating resource",
79
            "schema": {
80
              "$ref": "#/definitions/error"
81
            }
82
          },
83
          "500": {
84
            "description": "Internal error",
85
            "schema": {
86
              "$ref": "#/definitions/error"
87
            }
88
          }
89
        }
41
      }
90
      }
42
    },
91
    },
43
    "/patrons/{borrowernumber}": {
92
    "/patrons/{borrowernumber}": {
Lines 72-77 Link Here
72
            }
121
            }
73
          }
122
          }
74
        }
123
        }
124
      },
125
      "put": {
126
        "operationId": "editPatron",
127
        "tags": ["patrons"],
128
        "parameters": [
129
          { "$ref": "#/parameters/borrowernumberPathParam" },
130
          {
131
            "name": "body",
132
            "in": "body",
133
            "description": "A JSON object containing new information about existing patron",
134
            "required": true,
135
            "schema": {
136
              "$ref": "#/definitions/patron"
137
            }
138
          }
139
        ],
140
        "consumes": ["application/json"],
141
        "produces": ["application/json"],
142
        "responses": {
143
          "200": {
144
            "description": "A successfully updated patron",
145
            "schema": {
146
              "items": {
147
                "$ref": "#/definitions/patron"
148
              }
149
            }
150
          },
151
          "204": {
152
            "description": "No Content",
153
            "schema": {
154
              "type": "object"
155
            }
156
          },
157
          "403": {
158
            "description": "Access forbidden",
159
            "schema": {
160
              "$ref": "#/definitions/error"
161
            }
162
          },
163
          "404": {
164
            "description": "Resource not found",
165
            "schema": {
166
              "$ref": "#/definitions/error"
167
            }
168
          },
169
          "409": {
170
            "description": "Conflict in updating resource",
171
            "schema": {
172
              "$ref": "#/definitions/error"
173
            }
174
          },
175
          "500": {
176
            "description": "Internal error",
177
            "schema": {
178
              "$ref": "#/definitions/error"
179
            }
180
          }
181
        }
182
      },
183
      "delete": {
184
        "operationId": "deletePatron",
185
        "tags": ["patrons"],
186
        "parameters": [
187
          { "$ref": "#/parameters/borrowernumberPathParam" }
188
        ],
189
        "produces": ["application/json"],
190
        "responses": {
191
          "200": {
192
            "description": "Patron deleted successfully",
193
            "schema": {
194
              "type": "object"
195
            }
196
          },
197
          "400": {
198
            "description": "Patron deletion failed",
199
            "schema": { "$ref": "#/definitions/error" }
200
          },
201
          "403": {
202
            "description": "Access forbidden",
203
            "schema": {
204
              "$ref": "#/definitions/error"
205
            }
206
          },
207
          "404": {
208
            "description": "Patron not found",
209
            "schema": { "$ref": "#/definitions/error" }
210
          }
211
        }
75
      }
212
      }
76
    }
213
    }
77
  },
214
  },
(-)a/t/db_dependent/api/v1/patrons.t (-15 / +135 lines)
Lines 17-44 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 10;
21
use Test::Mojo;
22
use t::lib::TestBuilder;
20
use t::lib::TestBuilder;
23
21
22
use Test::More tests => 56;
23
use Test::Mojo;
24
24
use C4::Auth;
25
use C4::Auth;
25
use C4::Context;
26
use C4::Context;
26
27
use Koha::Database;
27
use Koha::Database;
28
use Koha::Patron;
29
28
30
my $builder = t::lib::TestBuilder->new();
29
BEGIN {
30
    use_ok('Koha::Object');
31
    use_ok('Koha::Patron');
32
}
31
33
32
my $dbh = C4::Context->dbh;
34
my $schema  = Koha::Database->schema;
33
$dbh->{AutoCommit} = 0;
35
my $dbh     = C4::Context->dbh;
34
$dbh->{RaiseError} = 1;
36
my $builder = t::lib::TestBuilder->new;
35
37
36
$ENV{REMOTE_ADDR} = '127.0.0.1';
38
$ENV{REMOTE_ADDR} = '127.0.0.1';
37
my $t = Test::Mojo->new('Koha::REST::V1');
39
my $t = Test::Mojo->new('Koha::REST::V1');
38
40
41
$schema->storage->txn_begin;
42
39
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
43
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
40
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
44
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
41
my $borrower = $builder->build({
45
my $patron = $builder->build({
42
    source => 'Borrower',
46
    source => 'Borrower',
43
    value => {
47
    value => {
44
        branchcode   => $branchcode,
48
        branchcode   => $branchcode,
Lines 49-55 my $borrower = $builder->build({ Link Here
49
$t->get_ok('/api/v1/patrons')
53
$t->get_ok('/api/v1/patrons')
50
  ->status_is(403);
54
  ->status_is(403);
51
55
52
$t->get_ok("/api/v1/patrons/" . $borrower->{ borrowernumber })
56
$t->get_ok("/api/v1/patrons/" . $patron->{ borrowernumber })
53
  ->status_is(403);
57
  ->status_is(403);
54
58
55
my $loggedinuser = $builder->build({
59
my $loggedinuser = $builder->build({
Lines 74-84 $tx->req->env({REMOTE_ADDR => '127.0.0.1'}); Link Here
74
$t->request_ok($tx)
78
$t->request_ok($tx)
75
  ->status_is(200);
79
  ->status_is(200);
76
80
77
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . $borrower->{ borrowernumber });
81
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . $patron->{ borrowernumber });
78
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
82
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
79
$t->request_ok($tx)
83
$t->request_ok($tx)
80
  ->status_is(200)
84
  ->status_is(200)
81
  ->json_is('/borrowernumber' => $borrower->{ borrowernumber })
85
  ->json_is('/borrowernumber' => $patron->{ borrowernumber })
82
  ->json_is('/surname' => $borrower->{ surname });
86
  ->json_is('/surname' => $patron->{ surname });
87
88
### POST /api/v1/patrons
89
90
my $newpatron = {
91
  branchcode   => $branchcode,
92
  categorycode => $categorycode,
93
  surname      => "TestUser",
94
  cardnumber => "123456",
95
  userid => "testuser"
96
};
97
98
$newpatron->{ branchcode } = "nonexistent"; # Test invalid branchcode
99
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
100
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
101
$t->request_ok($tx)
102
  ->status_is(404)
103
  ->json_is('/error' => "Library with branchcode \"nonexistent\" does not exist");
104
105
$newpatron->{ branchcode } = $branchcode;
106
$newpatron->{ categorycode } = "nonexistent"; # Test invalid patron category
107
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
108
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
109
$t->request_ok($tx)
110
  ->status_is(404)
111
  ->json_is('/error' => "Patron category \"nonexistent\" does not exist");
112
$newpatron->{ categorycode } = $categorycode;
113
114
$newpatron->{ falseproperty } = "Non existent property";
115
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
116
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
117
$t->request_ok($tx)
118
  ->status_is(500)
119
  ->json_is('/error' => "Something went wrong, check Koha logs for details");
120
121
delete $newpatron->{ falseproperty };
122
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
123
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
124
$t->request_ok($tx)
125
  ->status_is(201, 'Patron created successfully')
126
  ->json_has('/borrowernumber', 'got a borrowernumber')
127
  ->json_is('/cardnumber', $newpatron->{ cardnumber })
128
  ->json_is('/surname' => $newpatron->{ surname })
129
  ->json_is('/firstname' => $newpatron->{ firstname });
130
$newpatron->{borrowernumber} = $tx->res->json->{borrowernumber};
131
132
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
133
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
134
$t->request_ok($tx)
135
  ->status_is(409)
136
  ->json_has('/error', 'Fails when trying to POST duplicate cardnumber or userid')
137
  ->json_has('/conflict', { userid => $newpatron->{ userid }, cardnumber => $newpatron->{ cardnumber } });
138
139
### PUT /api/v1/patrons
140
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/0" => json => {});
141
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
142
$t->request_ok($tx)
143
  ->status_is(404)
144
  ->json_has('/error', 'Fails when trying to PUT nonexistent patron');
145
146
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => {categorycode => "nonexistent"});
147
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
148
$t->request_ok($tx)
149
  ->status_is(404)
150
  ->json_is('/error' => "Patron category \"nonexistent\" does not exist");
151
152
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => {branchcode => "nonexistent"});
153
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
154
$t->request_ok($tx)
155
  ->status_is(404)
156
  ->json_is('/error' => "Library with branchcode \"nonexistent\" does not exist");
157
158
$newpatron->{ falseproperty } = "Non existent property";
159
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
160
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
161
$t->request_ok($tx)
162
  ->status_is(500)
163
  ->json_is('/error' => "Something went wrong, check Koha logs for details");
164
delete $newpatron->{ falseproperty };
165
166
$newpatron->{ cardnumber } = $patron-> { cardnumber };
167
$newpatron->{ userid } = $patron-> { userid };
168
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
169
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
170
$t->request_ok($tx)
171
  ->status_is(409)
172
  ->json_has('/error' => "Fails when trying to update to an existing cardnumber or userid")
173
  ->json_has('/conflict', { cardnumber => $patron->{ cardnumber }, userid => $patron->{ userid } });
174
175
$newpatron->{ cardnumber } = "123456";
176
$newpatron->{ userid } = "testuser";
177
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
178
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
179
$t->request_ok($tx)
180
  ->status_is(204, 'No changes - patron NOT updated');
181
182
$newpatron->{ cardnumber } = "234567";
183
$newpatron->{ userid } = "updatedtestuser";
184
$newpatron->{ surname } = "UpdatedTestUser";
185
186
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
187
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
188
$t->request_ok($tx)
189
  ->status_is(200, 'Patron updated successfully')
190
  ->json_has($newpatron);
191
192
### DELETE /api/v1/patrons
193
194
$tx = $t->ua->build_tx(DELETE => "/api/v1/patrons/0");
195
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
196
$t->request_ok($tx)
197
  ->status_is(404, 'Patron not found');
198
199
$tx = $t->ua->build_tx(DELETE => "/api/v1/patrons/" . $newpatron->{ borrowernumber });
200
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
201
$t->request_ok($tx)
202
  ->status_is(200, 'Patron deleted successfully');
203
204
$schema->storage->txn_rollback;
83
205
84
$dbh->rollback;
85
- 

Return to bug 16330