File Coverage

blib/lib/App/Dochazka/REST/Model/Privhistory.pm
Criterion Covered Total %
statement 34 60 56.6
branch 0 4 0.0
condition n/a
subroutine 12 19 63.1
pod 7 7 100.0
total 53 90 58.8


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2017, 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 41     41   94515 use strict;
  41         141  
36 41     41   203 use warnings;
  41         99  
  41         723  
37 41     41   188 use App::CELL qw( $CELL $log $meta $site );
  41         82  
  41         1163  
38 41     41   263 use App::Dochazka::REST::Model::Shared qw( cud get_history load );
  41         97  
  41         4513  
39 41     41   1135 use Data::Dumper;
  41         101  
  41         2058  
40 41     41   284 use Params::Validate qw( :all );
  41         108  
  41         1812  
41 41     41   244 use Try::Tiny;
  41         106  
  41         6067  
42 41     41   271  
  41         108  
  41         2271  
43             # we get 'spawn', 'reset', and accessors from parent
44             use parent 'App::Dochazka::Common::Model::Privhistory';
45 41     41   240  
  41         91  
  41         257  
46              
47              
48              
49             =head1 NAME
50              
51             App::Dochazka::REST::Model::Privhistory - privilege history functions
52              
53              
54              
55              
56             =head1 SYNOPSIS
57              
58             use App::Dochazka::REST::Model::Privhistory;
59              
60             ...
61              
62              
63              
64             =head1 DESCRIPTION
65              
66             A description of the privhistory data model follows.
67              
68              
69             =head2 Privilege levels in the database
70              
71             =head3 Type
72              
73             The privilege levels themselves are defined in the C<privilege> enumerated
74             type:
75              
76             CREATE TYPE privilege AS ENUM ('passerby', 'inactive', 'active',
77             'admin')
78              
79              
80             =head3 Table
81              
82             Employees are associated with privilege levels using a C<privhistory>
83             table:
84              
85             CREATE TABLE IF NOT EXISTS privhistory (
86             phid serial PRIMARY KEY,
87             eid integer REFERENCES employees (eid) NOT NULL,
88             priv privilege NOT NULL;
89             effective timestamp NOT NULL,
90             remark text,
91             stamp json
92             );
93              
94              
95              
96             =head3 Stored procedures
97              
98             There are also two stored procedures for determining privilege levels:
99              
100             =over
101              
102             =item * C<priv_at_timestamp>
103             Takes an EID and a timestamp; returns privilege level of that employee as
104             of the timestamp. If the privilege level cannot be determined for the given
105             timestamp, defaults to the lowest privilege level ('passerby').
106              
107             =item * C<current_priv>
108             Wrapper for C<priv_at_timestamp>. Takes an EID and returns the current
109             privilege level for that employee.
110              
111             =back
112              
113              
114             =head2 Privhistory in the Perl API
115              
116             When an employee object is loaded (assuming the employee exists), the
117             employee's current privilege level and schedule are included in the employee
118             object. No additional object need be created for this. Privhistory objects
119             are created only when an employee's privilege level changes or when an
120             employee's privilege history is to be viewed.
121              
122             In the data model, individual privhistory records are represented by
123             "privhistory objects". All methods and functions for manipulating these objects
124             are contained in L<App::Dochazka::REST::Model::Privhistory>. The most important
125             methods are:
126              
127             =over
128              
129             =item * constructor (L<spawn>)
130              
131             =item * basic accessors (L<phid>, L<eid>, L<priv>, L<effective>, L<remark>)
132              
133             =item * L<reset> (recycles an existing object by setting it to desired state)
134              
135             =item * L<load> (loads a single privhistory record)
136              
137             =item * L<load_by_phid> (wrapper for load_by_id)
138              
139             =item * L<load_by_id> (load a single privhistory record by its PHID)
140              
141             =item * L<insert> (inserts object into database)
142              
143             =item * L<delete> (deletes object from database)
144              
145             =back
146              
147             For basic C<privhistory> workflow, see C<t/model/privhistory.t>.
148              
149              
150              
151              
152             =head1 EXPORTS
153              
154             This module provides the following exports:
155              
156             =over
157              
158             =item L<phid_exists> (boolean)
159              
160             =item L<get_privhistory>
161              
162             =back
163              
164             =cut
165              
166             use Exporter qw( import );
167 41     41   63884 our @EXPORT_OK = qw( phid_exists get_privhistory );
  41         102  
  41         21917  
168              
169              
170              
171              
172             =head1 METHODS
173              
174              
175             =head2 load_by_eid
176              
177             Supposed to be a class method, but in reality we just don't care what the first
178             argument is.
179              
180             =cut
181              
182             shift; # discard the first argument
183             my ( $conn, $eid, $ts ) = validate_pos( @_,
184 0     0 1   { isa => 'DBIx::Connector' },
185 0           { type => SCALAR }, # EID
186             { type => SCALAR|UNDEF, optional => 1 }, # timestamp
187             );
188            
189             if ( $ts ) {
190             return load(
191 0 0         conn => $conn,
192 0           class => __PACKAGE__,
193             sql => $site->SQL_PRIVHISTORY_SELECT_ARBITRARY,
194             keys => [ $eid, $ts ],
195             );
196             }
197              
198             return load(
199             conn => $conn,
200 0           class => __PACKAGE__,
201             sql => $site->SQL_PRIVHISTORY_SELECT_CURRENT,
202             keys => [ $eid ],
203             );
204             }
205              
206              
207             =head2 load_by_id
208              
209             Class method.
210              
211             =cut
212              
213             my $self = shift;
214             my ( $conn, $phid ) = validate_pos( @_,
215             { isa => 'DBIx::Connector' },
216 0     0 1   { type => SCALAR },
217 0           );
218              
219             return load(
220             conn => $conn,
221             class => __PACKAGE__,
222 0           sql => $site->SQL_PRIVHISTORY_SELECT_BY_PHID,
223             keys => [ $phid ],
224             );
225             }
226              
227              
228             =head2 load_by_phid
229              
230             Wrapper for load_by_id
231              
232             =cut
233              
234             my $self = shift;
235             my ( $conn, $phid ) = validate_pos( @_,
236             { isa => 'DBIx::Connector' },
237             { type => SCALAR },
238 0     0 1   );
239 0           return $self->load_by_id( $conn, $phid );
240             }
241              
242              
243 0           =head2 insert
244              
245             Instance method. Attempts to INSERT a record into the 'privhistory' table.
246             Field values are taken from the object. Returns a status object.
247              
248             =cut
249              
250             my $self = shift;
251             my ( $context ) = validate_pos( @_, { type => HASHREF } );
252              
253             my $status = cud(
254             conn => $context->{'dbix_conn'},
255 0     0 1   eid => $context->{'current'}->{'eid'},
256 0           object => $self,
257             sql => $site->SQL_PRIVHISTORY_INSERT,
258             attrs => [ 'eid', 'priv', 'effective', 'remark' ],
259             );
260 0            
261             return $status;
262             }
263              
264              
265             =head2 update
266 0            
267             Instance method. Updates the record. Returns status object.
268              
269             =cut
270              
271             my $self = shift;
272             my ( $context ) = validate_pos( @_, { type => HASHREF } );
273              
274             my $status = cud(
275             conn => $context->{'dbix_conn'},
276             eid => $context->{'current'}->{'eid'},
277 0     0 1   object => $self,
278 0           sql => $site->SQL_PRIVHISTORY_UPDATE,
279             attrs => [ 'priv', 'effective', 'remark', 'phid' ],
280             );
281              
282 0           return $status;
283             }
284              
285              
286             =head2 delete
287              
288 0           Instance method. Deletes the record. Returns status object.
289              
290             =cut
291              
292             my $self = shift;
293             my ( $context ) = validate_pos( @_, { type => HASHREF } );
294              
295             my $status = cud(
296             conn => $context->{'dbix_conn'},
297             eid => $context->{'current'}->{'eid'},
298             object => $self,
299 0     0 1   sql => $site->SQL_PRIVHISTORY_DELETE,
300 0           attrs => [ 'phid' ],
301             );
302             $self->reset( 'phid' => $self->{phid} ) if $status->ok;
303              
304 0           return $status;
305             }
306              
307              
308              
309 0 0         =head1 FUNCTIONS
310              
311 0            
312             =head2 phid_exists
313              
314             Boolean function
315              
316             =cut
317              
318             BEGIN {
319             no strict 'refs';
320             *{'phid_exists'} = App::Dochazka::REST::Model::Shared::make_test_exists( 'phid' );
321             }
322              
323              
324             =head2 get_privhistory
325              
326 41     41   305 Takes a PARAMHASH which can have one or more of the properties 'eid', 'nick',
  41         113  
  41         1582  
327 41     41   237 and 'tsrange'.
  41         3502  
328              
329             At least one of { 'eid', 'nick' } must be specified. If both are specified,
330             the employee is determined according to 'eid'.
331              
332             The function returns the history of privilege level changes for that employee
333             over the given tsrange, or the entire history if no tsrange is supplied.
334              
335             The return value will always be an L<App::CELL::Status|status> object.
336              
337             Upon success, the payload will contain a 'history' key, the value of which will
338             be a reference to an array of C<privhistory> objects. If nothing is found, the
339             array will be empty. If there is a DBI error, the payload will be undefined.
340              
341             =cut
342              
343             my $context = shift;
344             return get_history( 'priv', $context->{'dbix_conn'}, @_ );
345             }
346              
347              
348              
349              
350             =head1 EXAMPLES
351 0     0 1    
352 0           In this section, some examples are presented to help understand how this
353             module is used.
354              
355             =head2 Mr. Moujersky joins the firm
356              
357             Mr. Moujersky was hired and his first day on the job was 2012-06-04. The
358             C<privhistory> entry for that might be:
359              
360             phid 1037 (automatically assigned by PostgreSQL)
361             eid 135 (Mr. Moujersky's Dochazka EID)
362             priv 'active'
363             effective '2012-06-04 00:00'
364              
365              
366             =head2 Mr. Moujersky becomes an administrator
367              
368             Effective 2013-01-01, Mr. Moujersky was given the additional responsibility
369             of being a Dochazka administrator for his site.
370              
371             phid 1512 (automatically assigned by PostgreSQL)
372             eid 135 (Mr. Moujersky's Dochazka EID)
373             priv 'admin'
374             effective '2013-01-01 00:00'
375              
376              
377             =head2 Mr. Moujersky goes on parental leave
378              
379             In February 2014, Mrs. Moujersky gave birth to a baby boy and effective
380             2014-07-01 Mr. Moujersky went on parental leave to take care of the
381             Moujersky's older child over the summer while his wife takes care of the
382             baby.
383              
384             phid 1692 (automatically assigned by PostgreSQL)
385             eid 135 (Mr. Moujersky's Dochazka EID)
386             priv 'inactive'
387             effective '2014-07-01 00:00'
388              
389             Note that Dochazka will begin enforcing the new privilege level as of
390             C<effective>, and not before. However, if Dochazka's session management
391             is set up to use LDAP authentication, Mr. Moujersky's access to Dochazka may be
392             revoked at any time at the LDAP level, effectively shutting him out.
393              
394              
395              
396              
397             =head1 AUTHOR
398              
399             Nathan Cutler, C<< <presnypreklad@gmail.com> >>
400              
401             =cut
402              
403             1;
404