From 1659638a8fc585734fe6893f2700f40f9488c2b4 Mon Sep 17 00:00:00 2001 From: Marius Gavrilescu Date: Fri, 1 Apr 2016 01:15:09 +0300 Subject: [PATCH 1/1] Initial commit --- Changes | 4 + MANIFEST | 9 ++ Makefile.PL | 26 ++++++ README | 35 ++++++++ app.psgi | 12 +++ db.sql | 18 ++++ lib/App/Web/Comstock.pm | 190 ++++++++++++++++++++++++++++++++++++++++ t/App-Web-Comstock.t | 5 ++ tmpl/index.html | 33 +++++++ 9 files changed, 332 insertions(+) create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 Makefile.PL create mode 100644 README create mode 100644 app.psgi create mode 100644 db.sql create mode 100644 lib/App/Web/Comstock.pm create mode 100644 t/App-Web-Comstock.t create mode 100644 tmpl/index.html diff --git a/Changes b/Changes new file mode 100644 index 0000000..3614f84 --- /dev/null +++ b/Changes @@ -0,0 +1,4 @@ +Revision history for Perl extension App::Web::Comstock. + +0.000_001 2016-04-01T01:15+03:00 + - Initial release diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..d79af47 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,9 @@ +app.psgi +Changes +db.sql +lib/App/Web/Comstock.pm +Makefile.PL +MANIFEST +README +t/App-Web-Comstock.t +tmpl/index.html diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..cb371fe --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,26 @@ +use 5.010000; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'App::Web::Comstock', + VERSION_FROM => 'lib/App/Web/Comstock.pm', + ABSTRACT_FROM => 'lib/App/Web/Comstock.pm', + AUTHOR => 'Marius Gavrilescu ', + MIN_PERL_VERSION => '5.10.0', + LICENSE => 'perl', + SIGN => 1, + PREREQ_PM => { + qw/DateTime 0 + DBIx::Simple 0 + HTML::TreeBuilder 0 + HTML::Element::Library 0 + Plack::Builder 0 + Plack::Request 0/, + }, + META_ADD => { + dynamic_config => 0, + resources => { + repository => 'https://git.ieval.ro/?p=app-web-comstock.git', + }, + } +); diff --git a/README b/README new file mode 100644 index 0000000..8632262 --- /dev/null +++ b/README @@ -0,0 +1,35 @@ +App-Web-Comstock version 0.000_001 +================================== + +Comstock is an unfinished website for managing bookings of generic +items. Users will be able to see the availability of existing items, +book them for some periods and view existing bookings. + +INSTALLATION + +To install this module type the following: + + perl Makefile.PL + make + make test + make install + +DEPENDENCIES + +This module requires these other modules and libraries: + +* DateTime +* DBIx::Simple +* HTML::Element +* HTML::Element::Library +* Plack + +COPYRIGHT AND LICENCE + +Copyright (C) 2016 by Marius Gavrilescu + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.22.1 or, +at your option, any later version of Perl 5 you may have available. + + diff --git a/app.psgi b/app.psgi new file mode 100644 index 0000000..ff22fb4 --- /dev/null +++ b/app.psgi @@ -0,0 +1,12 @@ +#!/usr/bin/perl +use 5.010000; +use warnings; + +use App::Web::Comstock; +use Plack::App::File; +use Plack::Builder; + +builder { + mount '/static/' => Plack::App::File->new(root => 'static')->to_app, + mount '/' => App::Web::Comstock::app, +} diff --git a/db.sql b/db.sql new file mode 100644 index 0000000..d94c518 --- /dev/null +++ b/db.sql @@ -0,0 +1,18 @@ +CREATE TABLE items ( + item SERIAL PRIMARY KEY, + title TEXT NOT NULL, + category TEXT NOT NULL, + description TEXT, + + begin_hour INT NOT NULL DEFAULT 5, + end_hour INT NOT NULL DEFAULT 23, + min_hours INT NOT NULL DEFAULT 1, + max_hours INT NOT NULL DEFAULT 24 +); + +CREATE TABLE bookings ( + item INT NOT NULL REFERENCES items, + name TEXT NOT NULL, + begin_time BIGINT NOT NULL, + end_time BIGINT NOT NULL +); diff --git a/lib/App/Web/Comstock.pm b/lib/App/Web/Comstock.pm new file mode 100644 index 0000000..db4cd32 --- /dev/null +++ b/lib/App/Web/Comstock.pm @@ -0,0 +1,190 @@ +package App::Web::Comstock; + +use 5.010000; +use strict; +use warnings; +our $VERSION = '0.000_001'; + +use DateTime; +use DBIx::Simple; +use HTML::TreeBuilder; +use HTML::Element::Library; +use Plack::Builder; +use Plack::Request; +use POSIX qw/strftime/; + +sub HTML::Element::iter3 { + my ($self, $data, $code) = @_; + my $orig = $self; + my $prev = $orig; + for my $el (@$data) { + my $current = $orig->clone; + $code->($el, $current); + $prev->postinsert($current); + $prev = $current; + } + $orig->detach; +} + +sub HTML::Element::fid { shift->look_down(id => shift) } +sub HTML::Element::fclass { shift->look_down(class => qr/\b$_[0]\b/) } + +################################################## + +my ($index); + +{ + sub parse_html { + my $builder = HTML::TreeBuilder->new; + $builder->ignore_unknown(0); + $builder->parse_file("tmpl/$_[0].html"); + $builder + } + + $index = parse_html 'index'; +} + +sub db : lvalue { + shift->{'comstock.db'} +} + +sub nav_li { + my ($data, $li) = @_; + $li->find('a')->replace_content($data->{title}); + $li->find('a')->attr(href => '?item='.$data->{item}); +} + +sub nav_ul { + my ($data, $ul) = @_; + $ul->find('li')->iter3($data, \&nav_li); +} + +sub display_app { + my ($env) = @_; + my $req = Plack::Request->new($env); + my $tree = $index->clone; + my @items = db($env)->select(items => '*')->hashes; + my %items; + + for my $item (@items) { + $items{$item->{category}} //= []; + push @{$items{$item->{category}}}, $item + } + + my @data = sort { $a->[0]{category} cmp $b->[0]{category} } values %items; #map { $items{$_} } sort keys %items; + $tree->fid('comstock_nav')->find('ul')->iter3(\@data, \&nav_ul); + my $item = $req->param('item'); + if ($item) { + $tree->look_down(name => 'item')->attr(value => $item); + my ($begin, $end) = db($env)->select(items => [qw/begin_hour end_hour/], {item => $item})->list; + for my $name (qw/begin_hour end_hour/) { + my $select = $tree->look_down(name => $name); + $select->iter($select->find('option') => $begin .. $end) + } + } else { + $tree->fid('book_div')->detach + } + $tree +} + +sub error { + 'Error: ' . $_[0] +} + +sub book_app { + my ($env) = @_; + my $req = Plack::Request->new($env); + my ($begin_year, $begin_month, $begin_day) = split '/', $req->param('begin'); + my ($end_year, $end_month, $end_day) = split '/', $req->param('end'); + my $begin_hour = $req->param('begin_hour'); + my $end_hour = $req->param('end_hour'); + my $begin = DateTime->new(year => $begin_year, month => $begin_month, day => $begin_day, hour => $begin_hour)->epoch; + my $end = DateTime->new(year => $end_year, month => $end_month, day => $end_day, hour => $end_hour)->epoch; + my $item = 0+$req->param('item'); + 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'; + + return error 'End time is not later than begin time' if $end <= $begin; + 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; + return error 'Bookings must last for at least $min_hours hours' if (($end - $begin) / 3600 < $min_hours); + return error 'Bookings must last for at most $max_hours hours' if (($end - $begin) / 3600 > $max_hours); + 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; + db($env)->insert(bookings => { + item => $item, + name => scalar $req->param('name'), + begin_time => $begin, + end_time => $end, + }); + return [200, ['Content-Type' => 'text/plain'], ['Booking was successful']]; +} + +sub view_app { + my ($env) = @_; + my $req = Plack::Request->new($env); + my $item = $req->param('item'); + my $time = time; + $time -= $time % 86400; + my @bookings = db($env)->select(bookings => '*', {item => $item, begin_time => {'>', $time}}, 'begin_time')->hashes; + my $ans; + for my $booking (@bookings) { + $booking->{name} =~ y/\n//d; + $ans .= sprintf "%s -> %s %s\n", strftime ('%c', gmtime $booking->{begin_time}), strftime ('%c', gmtime $booking->{end_time}), $booking->{name}; + } + [200, ['Content-type' => 'text/plain'], [$ans]] +} + +sub app { + builder { + enable 'ContentLength'; + enable sub { + my $app = shift; + my $db = DBIx::Simple->connect($ENV{COMSTOCK_DSN} // 'dbi:Pg:'); + sub { + my ($env) = @_; + db($env) = $db; + my $res = $app->($env); + return $res if ref $res eq 'ARRAY'; + return [200, ['Content-type' => 'text/html; charset=utf-8'], [$res->as_HTML]] + if ref $res; + return [500, ['Content-type' => 'text/plain'], ["$res"]] + } + }; + mount '/book' => \&book_app; + mount '/view' => \&view_app; + mount '/' => \&display_app; + } +} + +1; +__END__ + +=encoding utf-8 + +=head1 NAME + +App::Web::Comstock - Website for managing bookings of generic items + +=head1 SYNOPSIS + + use App::Web::Comstock; + App::Web::Comstock->app + +=head1 DESCRIPTION + +Comstock is an unfinished website for managing bookings of generic +items. Users will be able to see the availability of existing items, +book them for some periods and view existing bookings. + +=head1 AUTHOR + +Marius Gavrilescu, Emarius@ieval.roE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2016 by Marius Gavrilescu + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.22.1 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/t/App-Web-Comstock.t b/t/App-Web-Comstock.t new file mode 100644 index 0000000..3a88590 --- /dev/null +++ b/t/App-Web-Comstock.t @@ -0,0 +1,5 @@ +use strict; +use warnings; + +use Test::More tests => 1; +BEGIN { use_ok('App::Web::Comstock') }; diff --git a/tmpl/index.html b/tmpl/index.html new file mode 100644 index 0000000..d784084 --- /dev/null +++ b/tmpl/index.html @@ -0,0 +1,33 @@ + +Comstock + + + + +
+ +
+ +
+
+ +
+
+
+
+
+ +
-- 2.30.2