File Coverage

blib/lib/App/Dochazka/REST.pm
Criterion Covered Total %
statement 52 201 25.8
branch 2 50 4.0
condition 0 5 0.0
subroutine 17 34 50.0
pod 9 9 100.0
total 80 299 26.7


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2015, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33             package App::Dochazka::REST;
34              
35 42     42   125130 use 5.012;
  42         157  
36 42     42   193 use strict;
  42         81  
  42         752  
37 42     42   180 use warnings;
  42         87  
  42         1085  
38              
39 42     42   188 use App::CELL qw( $CELL $log $meta $core $site );
  42         83  
  42         3967  
40 42     42   3634 use App::Dochazka::REST::ConnBank qw( $dbix_conn conn_status );
  42         85  
  42         3397  
41 42     42   256 use Data::Dumper;
  42         89  
  42         1566  
42 42     42   228 use File::Path;
  42         81  
  42         1834  
43 42     42   235 use File::ShareDir;
  42         71  
  42         1234  
44 42     42   240 use File::Spec;
  42         74  
  42         791  
45 42     42   11846 use Log::Any::Adapter;
  42         11022  
  42         432  
46 42     42   1130 use Params::Validate qw( :all );
  42         80  
  42         5568  
47 42     42   255 use Try::Tiny;
  42         81  
  42         1703  
48 42     42   10858 use Web::Machine;
  42         3336462  
  42         1237  
49 42     42   17613 use Web::MREST;
  42         42714  
  42         2918  
50 42     42   10680 use Web::MREST::CLI qw( normalize_filespec );
  42         4010556  
  42         3762  
51              
52              
53              
54             =head1 NAME
55              
56             App::Dochazka::REST - Dochazka REST server
57              
58              
59              
60             =head1 VERSION
61              
62             Version 0.557
63              
64             =cut
65              
66             our $VERSION = '0.557';
67              
68              
69             =head2 Development status
70              
71             Alpha.
72              
73              
74              
75             =head1 SYNOPSIS
76              
77             Start the server with default settings:
78              
79             $ dochazka-rest
80              
81             Point browser to:
82              
83             http://localhost:5000/
84              
85             Use L<App::Dochazka::CLI> command-line interface to access full functionality:
86              
87             $ dochazka-cli
88              
89              
90              
91             =head1 DESCRIPTION
92              
93             This distribution, L<App::Dochazka::REST>, including all the modules in C<lib/>,
94             the scripts in C<bin/>, and the configuration files in C<config/>,
95             constitutes the REST server (API) component of Dochazka, the open-source
96             Attendance/Time Tracking (ATT) system.
97              
98             Dochazka as a whole aims to be a convenient, open-source ATT solution.
99              
100              
101              
102             =head1 ARCHITECTURE
103              
104             Dochazka consists of four main components:
105              
106             =over
107              
108             =item * Dochazka clients
109              
110             =item * REST server (this module)
111              
112             =item * PostgreSQL database
113              
114             =item * Data model
115              
116             =back
117              
118             In a nutshell, clients attempt to translate user intent into REST API
119             calls, which are transmitted over a network (using the HTTP protocol) to
120             the server. The server processes incoming HTTP requests. Requests for
121             valid REST resources are passed to the API for processing and errors are
122             generated for invalid requests. The result is returned to the client in
123             an HTTP response. The REST API uses the PostgreSQL server to save state.
124             The clients and the REST API use the data model to represent and manipulate
125             objects.
126              
127              
128              
129             =head1 DOCUMENTATION
130              
131             =over
132              
133             =item * L<App::Dochazka::REST::Guide>
134              
135             A detailed guide to the REST server.
136              
137             =item * L<App::Dochazka::REST::Docs::Resources>
138              
139             Dochazka REST API documentation.
140              
141             =item * L<App::Dochazka::Common>
142              
143             Dochazka data model and other bits used by all Dochazka components.
144              
145             =item * L<App::Dochazka::CLI> and L<App::Dochazka::CLI::Guide>
146              
147             Reference Dochazka command-line client.
148              
149             =item * L<App::Dochazka::WWW>
150              
151             Reference Dochazka WWW client.
152              
153             =over
154              
155              
156              
157              
158             =head1 EXPORTS
159              
160             =cut
161              
162 42     42   413 use Exporter qw( import );
  42         136  
  42         62115  
163             our @EXPORT_OK = qw( init_arbitrary_script $faux_context );
164             our $faux_context;
165              
166              
167              
168             =head1 FUNCTIONS
169              
170              
171             =head2 run_sql
172              
173             Takes a L<DBIx::Connector> object and an array of SQL statements. Runs them
174             one by one until an exception is thrown or the last statement completes
175             successfully. Returns a status object which will be either OK or ERR.
176             If NOT_OK, the error text will be in C<< $status->text >>.
177              
178             =cut
179              
180             sub run_sql {
181 0     0 1 0 my ( $conn, @stmts ) = @_;
182 0         0 my $status;
183             try {
184 0     0   0 foreach my $stmt ( @stmts ) {
185 0         0 $log->debug( "Running SQL statement $stmt" );
186 0         0 $conn->run( fixup => sub { $_->do( $stmt ); } );
  0         0  
187             }
188             } catch {
189 0     0   0 $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] );
190 0         0 };
191 0 0       0 return $status if $status;
192 0         0 return $CELL->status_ok;
193             }
194              
195              
196             sub _do_audit_triggers {
197 0     0   0 my ( $mode, $conn ) = @_;
198              
199 0         0 my $sql;
200 0 0       0 if ( $mode eq 'create' ) {
    0          
201 0         0 $sql = $site->DBINIT_CREATE_AUDIT_TRIGGERS;
202             } elsif ( $mode eq 'delete' ) {
203 0         0 $sql = $site->DBINIT_DELETE_AUDIT_TRIGGERS;
204             } else {
205 0         0 die "AAADFDGGGGGGAAAAAAAHHH! " . __PACKAGE__ . "::_do_audit_triggers";
206             }
207              
208 0         0 my @prepped_sql;
209 0         0 foreach my $table ( @{ $site->DOCHAZKA_AUDIT_TABLES } ) {
  0         0  
210 0         0 my $sql_copy = $sql;
211 0         0 my $question_mark = quotemeta('?');
212 0         0 $log->debug( "Replacing question mark with $table" );
213 0         0 $sql_copy =~ s{$question_mark}{$table};
214 0         0 push( @prepped_sql, $sql_copy );
215             }
216 0         0 my $status = run_sql(
217             $conn,
218             @prepped_sql,
219             );
220 0         0 return $status;
221             }
222              
223              
224             =head2 create_audit_triggers
225              
226             Create the audit triggers. Wrapper for _do_audit_triggers
227              
228             =cut
229              
230             sub create_audit_triggers {
231 0     0 1 0 my $conn = shift;
232 0         0 return _do_audit_triggers( 'create', $conn );
233             }
234            
235              
236             =head2 delete_audit_triggers
237              
238             Delete the audit triggers. Wrapper for _do_audit_triggers
239              
240             =cut
241              
242             sub delete_audit_triggers {
243 0     0 1 0 my $conn = shift;
244 0         0 return _do_audit_triggers( 'delete', $conn );
245             }
246            
247              
248             =head2 reset_mason_dir
249              
250             Wipe out and re-create the Mason state directory. Returns status object.
251             Upon success, level will be 'OK' and payload will contain the full path
252             to the Mason component root.
253              
254             =cut
255              
256             sub reset_mason_dir {
257 39     39 1 77 my $status;
258              
259 39         226 $log->info( "Checking permissions of Mason directory (DOCHAZKA_STATE_DIR)" );
260 39         3188 my $statedir = $site->DOCHAZKA_STATE_DIR;
261 39 50       940 die "OUCH!!! DOCHAZKA_STATE_DIR site parameter not defined!" unless $statedir;
262 39 50       912 die "OUCH!!! DOCHAZKA_STATE_DIR $statedir is not readable by me!" unless -r $statedir;
263 0 0         die "OUCH!!! DOCHAZKA_STATE_DIR $statedir is not writable by me!" unless -w $statedir;
264 0 0         die "OUCH!!! DOCHAZKA_STATE_DIR $statedir is not executable by me!" unless -x $statedir;
265 0           my $masondir = File::Spec->catfile( $statedir, 'Mason' );
266 0           $log->debug( "Mason directory is $masondir" );
267 0           rmtree( $masondir );
268 0           mkpath( $masondir, 0, 0750 );
269              
270             # re-create
271 0           my $comp_root = File::Spec->catfile( $masondir, 'comp_root' );
272 0           mkpath( $comp_root, 0, 0750 );
273 0           my $data_dir = File::Spec->catfile( $masondir, 'data_dir' );
274 0           mkpath( $data_dir, 0, 0750 );
275 0           $status = App::Dochazka::REST::Mason::init_singleton(
276             comp_root => $comp_root,
277             data_dir => $data_dir
278             );
279 0 0         return $status unless $status->ok;
280 0           $status->payload( $comp_root );
281 0           return $status;
282             }
283              
284              
285             =head2 initialize_activities_table
286              
287             Create the activities defined in the site parameter
288             DOCHAZKA_ACTIVITY_DEFINITIONS
289              
290             =cut
291              
292             sub initialize_activities_table {
293 0     0 1   my $conn = shift;
294 0           my $status = $CELL->status_ok;
295             try {
296             $conn->txn( fixup => sub {
297 0           my $sth = $_->prepare( $site->SQL_ACTIVITY_INSERT );
298 0           foreach my $actdef ( @{ $site->DOCHAZKA_ACTIVITY_DEFINITIONS } ) {
  0            
299 0           $sth->bind_param( 1, $actdef->{code} );
300 0           $sth->bind_param( 2, $actdef->{long_desc} );
301 0           $sth->bind_param( 3, 'dbinit' );
302 0           $sth->execute;
303             }
304 0     0     } );
305             } catch {
306 0     0     $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] );
307 0           };
308 0           return $status;
309             }
310              
311              
312             =head2 reset_db
313              
314             Drop and re-create a Dochazka database. Takes superuser credentials as
315             arguments.
316              
317             Be very, _very_, _VERY_ careful with this function.
318              
319             =cut
320              
321             sub reset_db {
322              
323 0     0 1   my $status;
324 0           my $dbname = $site->DOCHAZKA_DBNAME;
325 0           my $dbuser = $site->DOCHAZKA_DBUSER;
326 0           my $dbpass = $site->DOCHAZKA_DBPASS;
327 0           $log->debug( "Entering " . __PACKAGE__ . "::reset_db to initialize database $dbname with credentials $dbuser / $dbpass" );
328              
329             # PGTZ *must* be set
330 0           $ENV{'PGTZ'} = $site->DOCHAZKA_TIMEZONE;
331              
332             # create:
333             # - audit schema (see config/sql/audit_Config.pm)
334             # - public schema (all application-specific tables, functions, triggers, etc.)
335             # - the 'root' and 'demo' employees
336             # - privhistory record for root
337 0           print "Getting database connection...";
338 0           my $conn = App::Dochazka::REST::ConnBank::get_arbitrary_dbix_conn(
339             $dbname, $dbuser, $dbpass
340             );
341 0           print "done\n";
342              
343 0           print "Initializing audit schema...";
344             $status = run_sql(
345             $conn,
346 0           @{ $site->DBINIT_AUDIT },
  0            
347             );
348 0 0         if ( $status->not_ok ) {
349 0           print Dumper( $status ), "\n";
350 0           return $status;
351             }
352 0           print "done\n";
353              
354 0           print "Initializing public schema...";
355             $status = run_sql(
356             $conn,
357 0           @{ $site->DBINIT_CREATE },
  0            
358             );
359 0 0         if ( $status->not_ok ) {
360 0           print Dumper( $status ), "\n";
361 0           return $status;
362             }
363 0           print "done\n";
364              
365             # get EID of root employee that was just created, since
366             # we will need it in the second round of SQL statements
367 0           my $eids = get_eid_of( $conn, "root", "demo" );
368 0           $site->set( 'DOCHAZKA_EID_OF_ROOT', $eids->{'root'} );
369 0           $site->set( 'DOCHAZKA_EID_OF_DEMO', $eids->{'demo'} );
370              
371             # the second round of SQL statements to make root employee immutable
372             # is taken from DBINIT_MAKE_ROOT_IMMUTABLE site param
373              
374             # prep DBINIT_MAKE_ROOT_IMMUTABLE
375             # (replace ? with EID of root employee in all the statements
376             # N.B.: we avoid the /r modifier here because we might be using Perl # 5.012)
377             my @root_immutable_statements = map {
378 0           local $_ = $_; s/\?/$eids->{'root'}/g; $_;
  0            
  0            
379 0           } @{ $site->DBINIT_MAKE_ROOT_IMMUTABLE };
  0            
380              
381             # run the modified statements
382 0           $status = run_sql(
383             $conn,
384             @root_immutable_statements,
385             );
386 0 0         return $status unless $status->ok;
387              
388             # insert initial set of activities
389 0           $status = initialize_activities_table( $conn );
390            
391             # insert initial set of components
392             try {
393             $conn->txn( fixup => sub {
394 0           my $sth = $_->prepare( $site->SQL_COMPONENT_INSERT );
395 0           foreach my $actdef ( @{ $site->DOCHAZKA_COMPONENT_DEFINITIONS } ) {
  0            
396 0 0         $actdef->{'validations'} = undef unless exists( $actdef->{'validations'} );
397 0           $sth->bind_param( 1, $actdef->{path} );
398 0           $sth->bind_param( 2, $actdef->{source} );
399 0           $sth->bind_param( 3, $actdef->{acl} );
400 0           $sth->bind_param( 4, $actdef->{validations} );
401 0           $sth->execute;
402             }
403 0     0     } );
404             } catch {
405 0     0     $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] );
406 0           };
407 0 0         return $status unless $status->ok;
408            
409             # if auditing is enabled, create the audit triggers
410 0 0         if ( $site->DOCHAZKA_AUDITING ) {
411 0           $status = create_audit_triggers( $conn );
412 0 0         return $status unless $status->ok;
413             }
414            
415 0           $log->notice( "Database $dbname successfully (re-)initialized" );
416 0           return $status;
417             }
418              
419              
420             =head2 get_eid_of
421              
422             Obtain the EIDs of a list of employee nicks. Returns a reference to a hash
423             where the keys are the nicks and the values are the corresponding EIDs.
424              
425             NOTE 1: This routine expects to receive a L<DBIx::Connector> object as its
426             first argument. It does not use the C<$dbix_conn> singleton.
427              
428             NOTE 2: The nicks are expected to exist and no provision (other than logging a
429             DOCHAZKA_DBI_ERR) is made for their non-existence.
430              
431             =cut
432              
433             sub get_eid_of {
434 0     0 1   my ( $conn, @nicks ) = @_;
435 0           $log->debug( "Entering " . __PACKAGE__ . "::get_eid_of" );
436 0           my ( %eids, $status );
437 0           $status = $CELL->status_ok;
438             try {
439             $conn->run( fixup => sub {
440 0           my $sth = $_->prepare( $site->DBINIT_SELECT_EID_OF );
441 0           foreach my $nick ( @nicks ) {
442 0           $sth->bind_param( 1, $nick );
443 0           $sth->execute;
444 0           ( $eids{$nick} ) = $sth->fetchrow_array();
445 0           $log->debug( "EID of $nick is $eids{$nick}" );
446             }
447 0     0     } );
448             } catch {
449 0     0     $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] );
450 0           };
451 0 0         die $status->text unless $status->ok;
452 0           return \%eids;
453             }
454              
455              
456             =head2 version
457              
458             Accessor method (to be called like a constructor) providing access to C<$VERSION> variable
459              
460             =cut
461              
462 0     0 1   sub version { $VERSION; }
463              
464              
465              
466             =head2 init_arbitrary_script
467              
468             For scripts that need to manipulate the database directly (i.e. via the data
469             model).
470              
471             =cut
472              
473             sub init_arbitrary_script {
474 0     0 1   my ( $ARGS ) = @_;
475 0           my $quiet = 0;
476 0 0 0       if ( ref( $ARGS ) eq 'HASH' and exists( $ARGS->{quiet} ) ) {
477 0           $quiet = $ARGS->{quiet};
478             }
479 0           my $app_distro = 'App-Dochazka-REST';
480 0           my $sitedir = '/etc/dochazka-rest';
481 0 0         print "Loading configuration parameters from $sitedir\n" unless $quiet;
482 0           my $status = Web::MREST::init(
483             distro => $app_distro,
484             sitedir => $sitedir,
485             );
486 0 0         die $status->text unless $status->ok;
487              
488 0 0         print "Setting up logging\n" unless $quiet;
489 0           my $log_file = normalize_filespec( $site->MREST_LOG_FILE );
490 0           my $should_reset = $site->MREST_LOG_FILE_RESET;
491 0 0         unlink $log_file if $should_reset;
492 0           Log::Any::Adapter->set( 'File', $log_file );
493 0           my $message = "Logging to $log_file";
494 0 0         print "$message\n" unless $quiet;
495 0           $log->info( $message );
496 0 0         if ( ! $site->MREST_APPNAME ) {
497 0           die "Site parameter MREST_APPNAME is undefined - please investigate!";
498             }
499             $log->init(
500 0   0       ident => $site->MREST_APPNAME,
501             debug_mode => ( $site->MREST_DEBUG_MODE || 0 ),
502             );
503              
504 0 0         print "Connecting to database\n" unless $quiet;
505 0           App::Dochazka::REST::ConnBank::init_singleton();
506 0 0         print "Database is " . conn_status() . "\n" unless $quiet;
507              
508 0           $faux_context = { 'dbix_conn' => $dbix_conn, 'current' => { 'eid' => 1 } };
509             }
510              
511              
512              
513             =head1 GLOSSARY OF TERMS
514              
515             In Dochazka, some commonly-used terms have special meanings:
516              
517             =over
518              
519             =item * B<employee> --
520             Regardless of whether they are employees in reality, for the
521             purposes of Dochazka employees are the folks whose attendance/time is being
522             tracked. Employees are expected to interact with Dochazka using the
523             following functions and commands.
524              
525             =item * B<administrator> --
526             In Dochazka, administrators are employees with special powers. Certain
527             REST/CLI functions are available only to administrators.
528              
529             =item * B<CLI client> --
530             CLI stands for Command-Line Interface. The CLI client is the Perl script
531             that is run when an employee types C<dochazka> at the bash prompt.
532              
533             =item * B<REST server> --
534             REST stands for ... . The REST server is a collection of Perl modules
535             running on a server at the site.
536              
537             =item * B<site> --
538             In a general sense, the "site" is the company, organization, or place that
539             has implemented (installed, configured) Dochazka for attendance/time
540             tracking. In a technical sense, a site is a specific instance of the
541             Dochazka REST server that CLI clients connect to.
542              
543             =back
544              
545              
546              
547             =head1 AUTHOR
548              
549             Nathan Cutler, C<< <ncutler@suse.cz> >>
550              
551              
552              
553              
554             =head1 BUGS
555              
556             To report bugs or request features, use the GitHub issue tracker at
557             L<https://github.com/smithfarm/dochazka-rest/issues>.
558              
559              
560              
561              
562             =head1 SUPPORT
563              
564             The full documentation comes with the distro, and can be comfortable
565             perused at metacpan.org:
566              
567             https://metacpan.org/pod/App::Dochazka::REST
568              
569             You can also read the documentation for individual modules using the
570             perldoc command, e.g.:
571              
572             perldoc App::Dochazka::REST
573             perldoc App::Dochazka::REST::Model::Activity
574              
575             Other resources:
576              
577             =over 4
578              
579             =item * GitHub issue tracker (report bugs here)
580              
581             L<https://github.com/smithfarm/dochazka-rest>
582              
583             =item * AnnoCPAN: Annotated CPAN documentation
584              
585             L<http://annocpan.org/dist/App-Dochazka-REST>
586              
587             =back
588              
589              
590              
591              
592             =head1 LICENSE AND COPYRIGHT
593              
594             Copyright (c) 2014-2015, SUSE LLC
595             All rights reserved.
596              
597             Redistribution and use in source and binary forms, with or without
598             modification, are permitted provided that the following conditions are met:
599              
600             1. Redistributions of source code must retain the above copyright notice, this
601             list of conditions and the following disclaimer.
602              
603             2. Redistributions in binary form must reproduce the above copyright notice,
604             this list of conditions and the following disclaimer in the documentation
605             and/or other materials provided with the distribution.
606              
607             3. Neither the name of SUSE LLC nor the names of its contributors
608             may be used to endorse or promote products derived from this software without
609             specific prior written permission.
610              
611             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
612             ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
613             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
614             DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
615             FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
616             DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
617             SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
618             CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
619             OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
620             OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
621              
622              
623             =cut
624              
625             1; # End of App::Dochazka::REST