Initial commit
[app-web-comstock.git] / lib / App / Web / Comstock.pm
CommitLineData
1659638a
MG
1package App::Web::Comstock;
2
3use 5.010000;
4use strict;
5use warnings;
6our $VERSION = '0.000_001';
7
8use DateTime;
9use DBIx::Simple;
10use HTML::TreeBuilder;
11use HTML::Element::Library;
12use Plack::Builder;
13use Plack::Request;
14use POSIX qw/strftime/;
15
16sub HTML::Element::iter3 {
17 my ($self, $data, $code) = @_;
18 my $orig = $self;
19 my $prev = $orig;
20 for my $el (@$data) {
21 my $current = $orig->clone;
22 $code->($el, $current);
23 $prev->postinsert($current);
24 $prev = $current;
25 }
26 $orig->detach;
27}
28
29sub HTML::Element::fid { shift->look_down(id => shift) }
30sub HTML::Element::fclass { shift->look_down(class => qr/\b$_[0]\b/) }
31
32##################################################
33
34my ($index);
35
36{
37 sub parse_html {
38 my $builder = HTML::TreeBuilder->new;
39 $builder->ignore_unknown(0);
40 $builder->parse_file("tmpl/$_[0].html");
41 $builder
42 }
43
44 $index = parse_html 'index';
45}
46
47sub db : lvalue {
48 shift->{'comstock.db'}
49}
50
51sub nav_li {
52 my ($data, $li) = @_;
53 $li->find('a')->replace_content($data->{title});
54 $li->find('a')->attr(href => '?item='.$data->{item});
55}
56
57sub nav_ul {
58 my ($data, $ul) = @_;
59 $ul->find('li')->iter3($data, \&nav_li);
60}
61
62sub display_app {
63 my ($env) = @_;
64 my $req = Plack::Request->new($env);
65 my $tree = $index->clone;
66 my @items = db($env)->select(items => '*')->hashes;
67 my %items;
68
69 for my $item (@items) {
70 $items{$item->{category}} //= [];
71 push @{$items{$item->{category}}}, $item
72 }
73
74 my @data = sort { $a->[0]{category} cmp $b->[0]{category} } values %items; #map { $items{$_} } sort keys %items;
75 $tree->fid('comstock_nav')->find('ul')->iter3(\@data, \&nav_ul);
76 my $item = $req->param('item');
77 if ($item) {
78 $tree->look_down(name => 'item')->attr(value => $item);
79 my ($begin, $end) = db($env)->select(items => [qw/begin_hour end_hour/], {item => $item})->list;
80 for my $name (qw/begin_hour end_hour/) {
81 my $select = $tree->look_down(name => $name);
82 $select->iter($select->find('option') => $begin .. $end)
83 }
84 } else {
85 $tree->fid('book_div')->detach
86 }
87 $tree
88}
89
90sub error {
91 'Error: ' . $_[0]
92}
93
94sub book_app {
95 my ($env) = @_;
96 my $req = Plack::Request->new($env);
97 my ($begin_year, $begin_month, $begin_day) = split '/', $req->param('begin');
98 my ($end_year, $end_month, $end_day) = split '/', $req->param('end');
99 my $begin_hour = $req->param('begin_hour');
100 my $end_hour = $req->param('end_hour');
101 my $begin = DateTime->new(year => $begin_year, month => $begin_month, day => $begin_day, hour => $begin_hour)->epoch;
102 my $end = DateTime->new(year => $end_year, month => $end_month, day => $end_day, hour => $end_hour)->epoch;
103 my $item = 0+$req->param('item');
104 my ($begin_range, $end_range, $min_hours, $max_hours) = db($env)->select(items => [qw/begin_hour end_hour min_hours max_hours/], {item => $item})->list or return error 'No such item';
105
106 return error 'End time is not later than begin time' if $end <= $begin;
107 return error 'Begin/end hour not in allowed range' if $begin_hour < $begin_range || $begin_hour > $end_range || $end_hour < $begin_range || $end_hour > $end_range;
108 return error 'Bookings must last for at least $min_hours hours' if (($end - $begin) / 3600 < $min_hours);
109 return error 'Bookings must last for at most $max_hours hours' if (($end - $begin) / 3600 > $max_hours);
110 return error 'Item is not available for the selected period' if db($env)->query('SELECT item FROM bookings WHERE item = ? AND (end_time - begin_time + ? - ?) > GREATEST(end_time, ?) - LEAST(begin_time, ?)', $item, $end, $begin, $end, $begin)->list;
111 db($env)->insert(bookings => {
112 item => $item,
113 name => scalar $req->param('name'),
114 begin_time => $begin,
115 end_time => $end,
116 });
117 return [200, ['Content-Type' => 'text/plain'], ['Booking was successful']];
118}
119
120sub view_app {
121 my ($env) = @_;
122 my $req = Plack::Request->new($env);
123 my $item = $req->param('item');
124 my $time = time;
125 $time -= $time % 86400;
126 my @bookings = db($env)->select(bookings => '*', {item => $item, begin_time => {'>', $time}}, 'begin_time')->hashes;
127 my $ans;
128 for my $booking (@bookings) {
129 $booking->{name} =~ y/\n//d;
130 $ans .= sprintf "%s -> %s %s\n", strftime ('%c', gmtime $booking->{begin_time}), strftime ('%c', gmtime $booking->{end_time}), $booking->{name};
131 }
132 [200, ['Content-type' => 'text/plain'], [$ans]]
133}
134
135sub app {
136 builder {
137 enable 'ContentLength';
138 enable sub {
139 my $app = shift;
140 my $db = DBIx::Simple->connect($ENV{COMSTOCK_DSN} // 'dbi:Pg:');
141 sub {
142 my ($env) = @_;
143 db($env) = $db;
144 my $res = $app->($env);
145 return $res if ref $res eq 'ARRAY';
146 return [200, ['Content-type' => 'text/html; charset=utf-8'], [$res->as_HTML]]
147 if ref $res;
148 return [500, ['Content-type' => 'text/plain'], ["$res"]]
149 }
150 };
151 mount '/book' => \&book_app;
152 mount '/view' => \&view_app;
153 mount '/' => \&display_app;
154 }
155}
156
1571;
158__END__
159
160=encoding utf-8
161
162=head1 NAME
163
164App::Web::Comstock - Website for managing bookings of generic items
165
166=head1 SYNOPSIS
167
168 use App::Web::Comstock;
169 App::Web::Comstock->app
170
171=head1 DESCRIPTION
172
173Comstock is an unfinished website for managing bookings of generic
174items. Users will be able to see the availability of existing items,
175book them for some periods and view existing bookings.
176
177=head1 AUTHOR
178
179Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
180
181=head1 COPYRIGHT AND LICENSE
182
183Copyright (C) 2016 by Marius Gavrilescu
184
185This library is free software; you can redistribute it and/or modify
186it under the same terms as Perl itself, either Perl version 5.22.1 or,
187at your option, any later version of Perl 5 you may have available.
188
189
190=cut
This page took 0.020765 seconds and 4 git commands to generate.