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