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