]> git.ktnx.net Git - lsl.git/blob - lib/App/LazyShoppingList/API/v1.pm
185c0947ba85960a13db52b2de72e60ede8a7d21
[lsl.git] / lib / App / LazyShoppingList / API / v1.pm
1 use v5.26;
2 use warnings;
3 use utf8;
4
5 package App::LazyShoppingList::API::v1;
6 use App::LazyShoppingList::API;
7 use Dancer2;
8
9 our $VERSION = '0.1';
10
11 use JSON();
12
13 use experimental 'signatures';
14
15 set charset      => 'UTF-8';
16 set serializer => 'JSON';
17
18 hook before_request => sub {
19     get_database->txn_begin;
20 };
21
22 hook after_request => sub {
23     my $dbh = get_database;
24
25     if (response->status =~ /^5/) {
26         $dbh->txn_rollback;
27     }
28     else {
29         $dbh->txn_commit;
30     }
31 };
32
33 # get the URI for the list of shopping lists
34 get '/' => sub {
35     return { lists => uri_for('/list') };
36 };
37
38 # get the list of shopping lists
39 get '/list' => sub {
40     my $dbh = get_database;
41
42     my %r = ( lists_version => get_lists_version($dbh), lists => [] );
43     for my $list (
44         $dbh->resultset('ShoppingList')
45         ->search( undef,
46             { order_by => { -asc => 'name' } } )->all
47         )
48     {
49         push @{ $r{lists} },
50             { uri => uri_for( "/list/" . $list->id ), name => $list->name };
51     }
52
53     return \%r;
54 };
55
56 # create shopping list
57 post '/list' => sub {
58     my $req = request_data;
59
60     my $name = $req->{name};
61     unless ($name) {
62         return exception 400, "Missing list name";
63     }
64
65     my $dbh = get_database;
66     my $list = $dbh->resultset('ShoppingList')->create({
67             name => $name});
68     $list->discard_changes;
69
70     my  $lists_ver = increment_lists_version($dbh);
71
72     return {
73         uri           => uri_for( '/list/' . $list->id ),
74         version       => $list->version,
75         lists_version => $lists_ver
76     };
77 };
78
79 # get shopping list items
80 get '/list/:list_id' => sub {
81     my $list_id = route_parameters->get('list_id');
82     length($list_id) and $list_id =~ /^\d{1,18}$/ or return invalid_input;
83
84     my $dbh = get_database;
85
86     warn $dbh;
87     my %r = ( items => [] );
88
89     $r{lists_version} = get_lists_version($dbh);
90
91     my $list = $dbh->resultset('ShoppingList')->find($list_id);
92
93     unless ($list) {
94         return exception 404, "No list with that ID found";
95     }
96
97     $r{version} = $list->version;
98
99     my @items = $dbh->resultset('ShoppingListItem')->search(
100         { shopping_list => $list->id },
101         { order_by      => { -asc => 'id' } }
102     )->all;
103     for my $item (@items) {
104         push @{ $r{items} },
105             {   uri =>
106                 uri_for( sprintf( "/list/%s/%s", $list->id, $item->id ) ),
107                 description => $item->description,
108                 done        => JSON->boolean( $item->done ),
109                 version     => $item->version,
110             };
111     }
112
113     return \%r;
114 };
115
116 # create shopping list item
117 post '/list/:id' => sub {
118     my $list_id = route_parameters->get('id');
119     length($list_id) and $list_id =~ /^\d{1,18}$/
120         or return invalid_input('bad list ID');
121
122     my $req = request_data;
123
124     my $descr = $req->{description};
125     my $done  = JSON->boolean( $req->{done} // 0 );
126
127     my $dbh = get_database;
128
129     my %r = (
130         lists_version => get_lists_version($dbh),
131     );
132
133     my $list = $dbh->resultset('ShoppingList')->find($list_id);
134
135     unless ($list) {
136         exception 404, "No such list";
137     }
138
139     my $item = $dbh->resultset('ShoppingListItem')->create(
140         {   shopping_list => $list->id,
141             description   => $descr,
142             done          => $done,
143         }
144     );
145     $item->discard_changes;
146
147     $list->update( { version => $list->version + 1 } );
148
149     $r{uri} = uri_for( sprintf( "list/%s/%s", $list->id, $item->id ) );
150     $r{version} = $item->version;
151     $r{list_version} = $list->version;
152
153     return \%r;
154 };
155
156 # modify shopping list item
157 put '/list/:list_id/:item_id' => sub {
158     my $list_id = route_parameters->get('list_id');
159     length($list_id) and $list_id =~ /^\d{1,18}$/
160         or return invalid_input('bad list ID');
161
162     my $item_id = route_parameters->get('item_id');
163     length($item_id) and $item_id =~ /^\d{1,18}$/
164         or return invalid_input('bad item ID');
165
166     my $req = request_data;
167
168     my $descr = $req->{description};
169     my $done =
170         exists $req->{done} ? JSON->boolean( $req->{done} // 0 ) : undef;
171     my $version = $req->{version};
172
173     length($version) and $version =~ /^\d{1,18}$/
174         or return invalid_input('bad version');
175
176     my $dbh = get_database;
177
178     my %r = (
179         lists_version => get_lists_version($dbh),
180     );
181
182     my $list = $dbh->resultset('ShoppingList')->find($list_id);
183     unless ($list) {
184         return exception 404, "No such list";
185     }
186
187     my $item = $dbh->resultset('ShoppingListItem')->find({shopping_list => $list->id, id => $item_id});
188     unless ($item) {
189         return exception 404, "No such item";
190     }
191
192     # in case no real changes are needed will return the current state
193     if (   defined($descr) and $descr ne ( $item->description // '' )
194         or defined($done) and $done != $item->done )
195     {
196         unless ($version == $item->version) {
197             return exception 409,
198                 sprintf( 'Outdated version (current is %d)', $item->version );
199         }
200
201         $item->update({
202             defined($descr) ? (description  => $descr) : (),
203             defined($done) ? (done =>  $done) : (),
204             version => $version + 1,
205         });
206
207         $list->update( { version => $list->version + 1 } );
208     }
209
210     $r{uri} = uri_for( sprintf( "list/%s/%s", $list->id, $item->id ) );
211     $r{version} = $item->version;
212     $r{list_version} = $list->version;
213
214     return \%r;
215 };
216
217 # modify shopping list
218 put '/list/:list_id' => sub {
219     my $list_id = route_parameters->get('list_id');
220     length($list_id) and $list_id =~ /^\d{1,18}$/
221         or return invalid_input('bad list ID');
222
223     my $req = request_data;
224
225     my $name    = $req->{name}    // '';
226     my $version = $req->{version} // -1;
227
228     length($version) and $version =~ /^\d{1,18}$/
229         or return invalid_input('bad version');
230
231     my $dbh = get_database;
232
233     my $list = $dbh->resultset('ShoppingList')->find($list_id);
234     unless ($list) {
235         return exception 404, "No such list";
236     }
237
238     # in case no real changes are needed will return the current state
239     if ( $name ne ( $list->name // '') ) {
240         unless ($version == $list->version) {
241             return exception 409,
242                 sprintf( 'Outdated version (current is %d)', $list->version );
243         }
244
245         $list->update({
246             name =>  $name // '',
247             version => $version + 1,
248         });
249     }
250
251     return {
252         version       => $list->version,
253         lists_version => increment_lists_version($dbh),
254     };
255 };
256
257 # delete shopping list
258 del '/list/:list_id' => sub {
259     my $list_id = route_parameters->get('list_id');
260     length($list_id) and $list_id =~ /^\d{1,18}$/
261         or return invalid_input('bad list ID');
262
263     my $dbh = get_database;
264
265     my %r = (
266         lists_version => get_lists_version($dbh),
267     );
268
269     my $list = $dbh->resultset('ShoppingList')->find($list_id);
270     if ($list) {
271         $list->delete;
272         $r{lists_version} = increment_lists_version($dbh)
273     }
274
275     return \%r;
276 };
277
278 # delete shopping list item
279 del '/list/:list_id/:item_id' => sub {
280     my $list_id = route_parameters->get('list_id');
281     length($list_id) and $list_id =~ /^\d{1,18}$/
282         or return invalid_input('bad list ID');
283
284     my $item_id = route_parameters->get('item_id');
285     length($item_id) and $item_id =~ /^\d{1,18}$/
286         or return invalid_input('bad item ID');
287
288     my $dbh = get_database;
289
290     my %r = (
291         lists_version => get_lists_version($dbh),
292     );
293
294     my $list = $dbh->resultset('ShoppingList')->find($list_id)
295         or return exception 404, 'No such list';
296
297     my $item = $dbh->resultset('ShoppingListItem')
298         ->find( { shopping_list => $list->id, id => $item_id } );
299
300     if ($item) {
301         $item->delete;
302         $list->update({version => $list->version + 1});
303     }
304
305     $r{list_version} = $list->version;
306
307     return \%r;
308 };
309
310 true;