File Coverage

blib/lib/Pepper.pm
Criterion Covered Total %
statement 18 57 31.5
branch 0 20 0.0
condition 0 15 0.0
subroutine 6 11 54.5
pod 0 3 0.0
total 24 106 22.6


line stmt bran cond sub pod time code
1             package Pepper;
2              
3             $Pepper::VERSION = '1.4';
4              
5 1     1   1354 use Pepper::DB;
  1         4  
  1         34  
6 1     1   710 use Pepper::PlackHandler;
  1         2  
  1         30  
7 1     1   6 use Pepper::Utilities;
  1         2  
  1         30  
8 1     1   473 use lib $ENV{HOME}.'/pepper/lib';
  1         562  
  1         9  
9              
10             # try to be a good person
11 1     1   138 use strict;
  1         1  
  1         19  
12 1     1   4 use warnings;
  1         2  
  1         572  
13              
14             # constructor instantiates DB and CGI classes
15             sub new {
16 0     0 0   my ($class,%args) = @_;
17             # %args can have:
18             # 'skip_db' => 1 if we don't want a DB handle
19             # 'skip_config' => 1, # only for 'pepper' command in setup mode
20             # 'request' => $plack_request_object, # if coming from pepper.psgi
21             # 'response' => $plack_response_object, # if coming from pepper.psgi
22              
23             # start the object
24 0           my $self = bless {
25             'utils' => Pepper::Utilities->new( \%args ),
26             }, $class;
27             # pass in request, response so that Utilities->send_response() works
28            
29             # bring in the configuration file from Utilities->read_system_configuration()
30 0           $self->{config} = $self->{utils}->{config};
31            
32             # unless they indicate not to connect to the database, go ahead
33             # and set up the database object / connection
34 0 0 0       if (!$args{skip_db} && $self->{config}{use_database} eq 'Y') {
35             $self->{db} = Pepper::DB->new({
36             'config' => $self->{config},
37 0           });
38            
39             # let the Utilities have this db object for use in send_response()
40 0           $self->{utils}->{db} = $self->{db};
41             # yes, i did some really gross stuff for send_response()
42             }
43              
44             # if we are in a Plack environment, instantiate PlackHandler
45             # this will gather up the parameters
46 0 0         if ($args{request}) {
47             $self->{plack_handler} = Pepper::PlackHandler->new({
48             'request' => $args{request},
49             'response' => $args{response},
50             'utils' => $self->{utils},
51 0           });
52            
53             # and read all the plack attributes into $self
54 0           foreach my $key ('hostname','uri','cookies','params','auth_token','uploaded_files') {
55 0           $self->{$key} = $self->{plack_handler}{$key};
56             }
57            
58             }
59              
60             # ready to go
61 0           return $self;
62            
63             }
64              
65             # here is where we import and execute the custom code for this endpoint
66             sub execute_handler {
67 0     0 0   my ($self,$endpoint) = @_;
68             # the endpoint will likely be the plack request URI, but accepting
69             # via an arg allows for script mode
70            
71             # resolve the endpoint / uri to a module under $ENV{HOME}/pepper/lib
72 0           my $endpoint_handler_module = $self->determine_endpoint_module($endpoint);
73            
74             # import that module
75 0 0         unless (eval "require $endpoint_handler_module") { # Error out if this won't import
76 0           $self->send_response("Could not import $endpoint_handler_module: ".$@,1);
77             }
78             # execute the request endpoint handler; this is not OO for the sake of simplicity
79 0           my $response_content = $endpoint_handler_module->endpoint_handler( $self );
80              
81             # always commit to the database
82 0 0         if ($self->{config}{use_database} eq 'Y') {
83 0           $self->commit();
84             }
85            
86             # ship the content to the client
87 0           $self->send_response($response_content);
88              
89             }
90              
91             # method to determine the handler for this URI / endpoint from the configuration
92             sub determine_endpoint_module {
93 0     0 0   my ($self,$endpoint) = @_;
94            
95             # probably in plack mode
96 0           my $endpoint_handler_module = '';
97 0   0       $endpoint ||= $self->{uri};
98              
99             # did they choose to store in a database table?
100 0 0         if ($self->{config}{url_mappings_table}) {
    0          
101            
102 0           ($endpoint_handler_module) = $self->quick_select(qq{
103             select handler_module from $self->{config}{url_mappings_table}
104             where endpoint_uri = ?
105             }, [ $endpoint ] );
106            
107             # or maybe a JSON file
108             } elsif ($self->{config}{url_mappings_file}) {
109            
110 0           my $url_mappings = $self->read_json_file( $self->{config}{url_mappings_file} );
111            
112 0           $endpoint_handler_module = $$url_mappings{$endpoint};
113            
114             }
115              
116             # hopefully, they provided a default
117 0   0       $endpoint_handler_module ||= $self->{config}{default_endpoint_module};
118            
119             # if a module was found, send it back
120 0 0         if ($endpoint_handler_module) {
121 0           return $endpoint_handler_module;
122            
123             # otherwise, we have an error
124             } else {
125 0           $self->send_response('Error: No handler defined for this endpoint.',1);
126             }
127             }
128              
129             # autoload module to support passing calls to our subordinate modules.
130             our $AUTOLOAD;
131             sub AUTOLOAD {
132 0     0     my $self = shift;
133             # figure out the method they tried to call
134 0           my $called_method = $AUTOLOAD =~ s/.*:://r;
135            
136             # utilities function?
137 0 0 0       if ($self->{utils}->can($called_method)) {
    0 0        
    0          
138              
139 0           return $self->{utils}->$called_method(@_);
140              
141             # database function?
142             } elsif ($self->{config}{use_database} eq 'Y' && $self->{db}->can($called_method)) {
143 0           return $self->{db}->$called_method(@_);
144            
145             # plack handler function?
146             } elsif ($self->{plack_handler} && $self->{plack_handler}->can($called_method)) {
147 0           return $self->{plack_handler}->$called_method(@_);
148            
149             } else { # hard fail with an error message
150 0           my $message = "ERROR: No '$called_method' method defined for Pepper.";
151 0           $self->{utils}->send_response( $message, 1 );
152              
153             }
154            
155             }
156              
157             # empty destroy for now
158             sub DESTROY {
159 0     0     my $self = shift;
160             }
161              
162             1;
163              
164             __END__
165              
166             =head1 NAME
167              
168             Pepper - Quick-start kit for learning and creating microservices in Perl.
169              
170             =head1 DESCRIPTION / PURPOSE
171              
172             This quick-start kit is designed for new users to easily experiment and learn
173             about Perl and for seasoned users to quickly stand up simple web services.
174              
175             This is not a framework. This is a quick-start kit meant to simplify learning and
176             small projects. The goal is for you to fall in love with Perl and continue your
177             journey on to Mojo, Dancer2, AnyEvent, Rex, PDL, POE, and all other the many terrific
178             Perl libraries (more than I can list here). This is a great community of builders,
179             and there is so much to discover at L<https://metacpan.org>
180             and L<https://perldoc.perl.org/>
181              
182             This kit supports database connections to MySQL 5.7/8 or MariaDB 10.3+.
183             There are many other great options out there, but one database driver
184             was chosen for the sake of simplicity. If your heart is set on Postgres,
185             answer 'N' to the 'Connect to a MySQL/MariaDB database server?' prompt
186             and use L<DBD:Pg> instead of Pepper::DB.
187              
188             =head1 SYNOPSIS
189              
190             To configure Pepper:
191              
192             # pepper setup
193              
194             To set up a new web service:
195              
196             # pepper set-endpoint /dogs/daisy PepperApps::Dogs::Daisy
197              
198             A new Perl module is created at $ENV{HOME}/pepper/lib/PepperApps/Dogs/Daisy.pm.
199             Edit that module to have it perform your actions and return any content you prefer.
200             You will be able to execute the service via http://you.hostname.ext:5000/dogs/daisy
201             If you change your code, restart the Plack service via 'pepper restart'
202              
203             For a simple Perl script, just add this at the top:
204              
205             use Pepper;
206             my $pepper = Pepper->new();
207              
208             The $pepper object provides several conveniences for MySQL/MariaDB databases, JSON
209             parsing, Template Toolkit, file handling and logging. In Web/Plack mode,
210             $pepper will also include the entire PSGI (CGI) environment.
211              
212             =head1 BREAKING CHANGE IN VERSION 1.1
213              
214             From version 1.1, Pepper looks for its workspace directory in your home directory.
215             If you installed Pepper prior to 1.1 (9/12/2020), that workspace will be under
216             /opt/pepper. You can work around this via this command 'ln -s /opt/pepper ~/peper',
217             or you can re-run 'pepper setup' and copy/move your custom files from /opt/pepper
218             as needed.
219              
220             =head1 INSTALLATION / GETTTING STARTED
221              
222             This kit has been tested with Ubuntu 18.04 & 20.04, CentOS 8, and FeeBSD 12.
223             B<Installation on Windows is not supported at this time.> Pepper should work
224             on any modern system capable of running Perl 5.22+ and Plack. I will happily add
225             any install notes you can provide for other systems (especially WSL or MacOS).
226              
227             Ubuntu 18/20 users have a quick-start option:
228              
229             # curl https://raw.githubusercontent.com/ericschernoff/Pepper/ginger/ubuntu20_quickstart.sh | sh
230              
231             =head2 Installing the required system packages
232              
233             These package-installation commands will need to be run as root or via 'sudo'.
234              
235             =over 12
236              
237             =item C<Ubuntu 18 or 20>
238              
239             # apt -y install build-essential cpanminus libmysqlclient-dev perl-doc zlib1g-dev apache2 apache2-utils
240              
241             =item C<CentOS 8>
242              
243             # yum install perl perl-utils perl-devel httpd gcc mysql mariadb-connector-c mariadb-devel
244              
245             =item C<FreeBSD 12>
246              
247             # pkg update -f && pkg install perl5-5.30.3 git p5-DBD-mysql p5-App-cpanminus p5-Parallel-Prefork
248              
249             =back
250              
251             =head2 Recommended: Install and configure your MySQL/MariaDB database.
252              
253             Create a designated user and database for Pepper. See the Mysql / MariaDB docs for guidance on this task.
254             B<Note:> Use the 'mysql_native_password' plugin when creating your database users, as in:
255              
256             create user pepper@'127.0.0.1' identified with mysql_native_password by 'MySecurePassword!';
257            
258             =head2 Install Pepper:
259              
260             # cpanm Pepper
261             - or -
262             # cpan Pepper
263              
264             It may take several minutes to build and install the dependencies.
265            
266             =head2 Set up / configure Pepper:
267              
268             # pepper setup
269              
270             This will prompt you for the configuration options. Choose carefully, but you can
271             safely re-run this command if needed to make changes. This command will create the
272             directory under $ENV{HOME}/pepper with the needed sub-directories and templates.
273             Do not provide a 'Default endpoint-handler Perl module' for now. (You can update later.)
274              
275             =head2 Check out the examples:
276            
277             Open up PepperExample.pm and HTMLExample.pm under $ENV{HOME}/pepper/lib/PepperApps
278             and read the comments to see how easy it is to code up web endpoints.
279            
280             =head2 Start the Plack service:
281              
282             # pepper start
283              
284             Check out the results of PepperExample.pm here: https://127.0.0.1:5000 (or in your
285             browser, using your server's hostname:5000). You should receive basic JSON results.
286             Modify PepperExample.pm to tweak those results and then restart the Plack service
287             to test your changes:
288              
289             # pepper restart
290              
291             Any errors will be logged to $ENV{HOME}/pepper/log/fatals-YYYY-MM-DD.log (replacing YYYY-MM-DD).
292             In a dev environment, you can auto-restart, please see 'pepper help'
293            
294             =head2 Write a small script:
295              
296             If you would like to write command-line Perl scripts, you can skip the Plack steps
297             and just start your script like this:
298            
299             use Pepper;
300             my $pepper = Pepper->new();
301              
302             The setup command places a simple example script at $ENV{HOME}/pepper/template/system/example_perl_script.pl
303              
304             The $pepper object will have all the methods and variables described below.
305              
306             If you are new to Perl, please have L<https://perldoc.perl.org/> handy, especially
307             the 'Functions' menu and 'Tutorials' under the 'Manuals' menu.
308              
309             =head1 ADDING / MANAGING ENDPOINTS
310              
311             Adding a new endpoint is as easy as:
312              
313             # pepper set-endpoint /Some/URI PerlModuleDirectory::PerlModule
314            
315             For example:
316              
317             # pepper set-endpoint /Carrboro/WeaverStreet PepperApps::Carrboro::WeaverStreet
318            
319             That will map any request to your /Carrboro/WeaverStreet URI to the 'endpoint_handler'
320             subroutine within $ENV{HOME}/pepper/lib/PepperApps/Carrboro/WeaverStreet.pm and a very basic version
321             of that file will be created for you. Simply edit and test the file to power the endpoint.
322              
323             If you wish to change the endpoint to another module, just re-issue the command:
324              
325             # pepper set-endpoint /Carrboro/WeaverStreet PepperApps::Carrboro::AnotherWeaverStreet
326              
327             You can see your current endpoints via list-endpoints
328              
329             # pepper list-endpoints
330            
331             To deactivate an endpoint, you might want to set it to the default:
332              
333             # pepper set-endpoint /Carrboro/WeaverStreet default
334            
335             Or you can just delete it
336              
337             # pepper delete-endpoint /Carrboro/WeaverStreet
338              
339             =head1 BASICS OF AN ENDPOINT HANDLER
340              
341             You can have any code you require within the endpoint_handler subroutine. You must
342             leave the 'my ($pepper) = @_;' right below 'sub endpoint_handler', and your endpoint_handler
343             subroutine must return some text or data that can be sent to the browser.
344              
345             If you wish to send JSON to the client, just return a reference to the data structure
346             that should be converted to JSON. Otherwise, you can return HTML or text. For your convenience,
347             an interface to the excellent Template-Toolkit library is a part of this kit (see below).
348              
349             For example:
350              
351             my $data_to_send = {
352             'colors' => ['Red','Green','Blue'],
353             'favorite_city' => 'Boston',
354             };
355              
356             return $data_to_send; # client gets JSON of the above
357            
358             return qq{
359             <html>
360             <body>
361             <h1>This is a bad web page</h1>
362             </body>
363             </html>
364             }; # client gets some HTML
365            
366             # you can return plain text as well, i.e. generated config files
367            
368             The $pepper object has lots of goodies, described in the following sections. There
369             is also a wealth of libraries in L<https://metacpan.org> and you add include your
370             own re-usable packages under $ENV{HOME}/pepper/lib . For instance, if many of your endpoints
371             share some data-crunching routines, you could create $ENV{HOME}/pepper/lib/MyUtils/DataCrunch.pm
372             and import it as: use MyUtils::DataCrunch; . You can also add subroutines below
373             the main endpoint_handler() subroutine. Pepper is just plain Perl, and the only "rule"
374             is that endpoint_handler() needs to return what will be sent to the client.
375              
376             =head1 WEB / PSGI ENVIRONMENT
377              
378             When you are building an endpoint handler for a web URI, the $pepper object will
379             contain the full PSGI environment (which is the web request), including the
380             parameters sent by the client. This can be accessed as follows:
381              
382             =head2 $pepper->{params}
383              
384             This is a hash of all the parameters sent via a GET or POST request or
385             via a JSON request body. For example, if a web form includes a 'book_title'
386             field, the submitted value would be found in $pepper->{params}{book_title} .
387              
388             For multi-value fields, such as checkboxes or multi-select menus, those values
389             can be found as arrays under $pepper->{params}{multi} or comma-separated lists
390             under $pepper->{params}. For example, if are two values, 'Red' and 'White',
391             for the 'colors' param, you could access:
392              
393             $pepper->{params}{colors} # Would be 'Red,White'
394              
395             $pepper->{params}{multi}{colors}[0] # would be 'Red'
396              
397             $pepper->{params}{multi}{colors}[1] # would be 'White'
398              
399             =head2 $pepper->{cookies}
400              
401             This is a name/value hash of the cookies sent by the client. If there is
402             a cookie named 'Oreo' with a value of 'Delicious but unhealthy', that
403             text value would be accessible at $pepper->{cookies}{Oreo} .
404              
405             Setting cookies can be done like so:
406              
407             $pepper->set_cookie({
408             'name' => 'Oreo', # could be any name
409             'value' => 'Delicious but unhealthy', # any text you wish
410             'days_to_live' => integer over 0, # optional, default is 10
411             });
412            
413             These cookies are tied to your web service's hostname.
414              
415             =head2 $pepper->{uploaded_files}
416              
417             If there are any uploaded files, this will contain a name/value hash,
418             were the name (key) is the filename and the value is the path to access
419             the file contents on your server. For example, to save all the
420             uploaded files to a permmanet space:
421              
422             use File::Copy;
423              
424             foreach my $filename (keys %{ $pepper->{uploaded_files} }) {
425             my ($clean_file_name = $filename) =~ s/[^a-z0-9\.]//gi;
426             copy($pepper->{uploaded_files}{$filename}, '/some/directory/'.$clean_file_name);
427             }
428              
429             =head2 $pepper->{auth_token}
430              
431             If the client sends an 'Authorization' header, that value will be stored in $pepper->{auth_token}.
432             Useful for a minimally-secure API, provided you have some code to validate this token.
433              
434             =head2 $pepper->{hostname}
435              
436             This will contain the HTTP_HOST for the request. If the URL being accessed is
437             https://pepper.weaverstreet.net/All/Hail/Ginger, the value of $pepper->{hostname}
438             will be 'pepper.weaverstreet.net'; for http://pepper.weaverstreet.net:5000/All/Hail/Ginger,
439             $pepper->{hostname} will be 'pepper.weaverstreet.net:5000'.
440              
441             =head2 $pepper->{uri}
442              
443             This will contain the endpoint URI. If the URL being accessed is
444             https://pepper.weaverstreet.net/All/Hail/Ginger, the value of $pepper->{uri}
445             will be '/All/Hail/Ginger'.
446              
447             =head2 Accessing the Plack Request / Response objects.
448              
449             The plain request and response Plack objects will be available at $pepper->{plack_handler}->{request}
450             and $pepper->{plack_handler}->{response} respectively. Please only use these if you absolutely must,
451             and please see L<Plack::Request> and L<Plack::Response> before working with these.
452              
453             =head1 RESPONSE / LOGGING / TEMPLATE METHODS
454              
455             =head2 template_process
456              
457             This is an simple interface to the excellent Template Toolkit, which is great for generating
458             HTML and really any kind of text files. Create your Template Toolkit templates
459             under $ENV{HOME}/pepper/template and please see L<Template> and L<http://www.template-toolkit.org>
460             The basic idea is to process a template with the values in a data structure to create the
461             appropriate text output.
462              
463             To process a template and have your endpoint handler return the results:
464              
465             return $pepper->template_process({
466             'template_file' => 'some_template.tt',
467             'template_vars' => $some_data_structure,
468             });
469              
470             That expects to find some_template.tt under $ENV{HOME}/pepper/template. You can add
471             subdirectories under $ENV{HOME}/pepper/template and refer to the files as
472             'subdirectory_name/template_filename.tt'.
473              
474             To save the generated text as a file:
475              
476             $pepper->template_process({
477             'template_file' => 'some_template.tt',
478             'template_vars' => $some_data_structure,
479             'save_file' => '/some/file/path/new_filename.ext',
480             });
481              
482             To have the template immediate sent out, such as for a fancy error page:
483              
484             $pepper->template_process({
485             'template_file' => 'some_template.tt',
486             'template_vars' => $some_data_structure,
487             'send_out' => 1,
488             'stop_here' => 1, # recommended to stop execution
489             });
490            
491             =head2 logger
492              
493             This adds entries to the files under $ENV{HOME}/pepper/log and is useful to log actions or
494             debugging messages. You can send a plain text string or a reference to a data structure.
495              
496             $pepper->logger('A nice log message','example-log');
497              
498             That will add a timestamped entry to a file named for example-log-YYYY-MM-DD.log. If you
499             leave off the second argument, the message is appended to today's errors-YYYY-MM-DD.log.
500              
501             $pepper->logger($hash_reference,'example-log');
502              
503             This will save the output of Data::Dumper's Dumper($hash_reference) to today's
504             example-log-YYYY-MM-DD.log.
505              
506             =head2 send_response
507              
508             This method will send data to the client. It is usually unnecessary, as you will simply
509             return data structures or text at the end of endpoint_handler().
510             However, send_response() may be useful in two situations:
511              
512             # To bail-out in case of an error:
513             $pepper->send_response('Error, everything just blew up.',1);
514              
515             # To send out a binary file:
516             $pepper->send_response($file_contents,'the_filename.ext',2,'mime/type');
517             $pepper->send_response($png_file_contents,'lovely_ginger.png',2,'image/png');
518            
519             =head2 set_cookie
520              
521             From a web endpoint handler, you may set a cookie like this:
522              
523             $pepper->set_cookie({
524             'name' => 'Cookie_name', # could be any name
525             'value' => 'Cookie_value', # any text you wish
526             'days_to_live' => integer over 0, # optional, default is 10
527             });
528              
529             =head1 DATABASE METHODS
530              
531             =head2 Random hints
532              
533             These method will work if you configured a MySQL or MariaDB connection
534             via 'pepper setup' command.
535              
536             The L<DBI> database handle object is stored in $pepper->{db}->{dbh}.
537              
538             The 'pepper' command can test your database connection config:
539              
540             # pepper test-db
541              
542             =head2 quick_select
543              
544             Use to get results for SQL SELECT's that will return one row of results.
545             The required first argument is the SELECT statement, and the optional
546             second argument is an array reference of values for the placeholders.
547              
548             my ($birth_date, $adopt_date) = $pepper->quick_select(qq{
549             select birth_date, adoption_date from family.dogs
550             where name=? and breed=? limit 1
551             }, [ 'Daisy', 'Shih Tzu' ] );
552            
553             ($todays_date) = $pepper->quick_select(' select curdate() ');
554              
555             =head2 sql_hash
556              
557             Very useful for SELECT's with multi-row results. Creates a two-level
558             data structure, where the top key is the values of the first column, and
559             the second-level keys are either the other column names or the keys you
560             provide. Returns references to the results hash and the array of
561             the first level keys.
562              
563             my ($results, $result_keys) = $pepper->sql_hash(qq{
564             select id, name, birth_date from my_family.family_members
565             where member_type=? order by name
566             }, [ 'Dog'] );
567            
568             You now have:
569            
570             $results = {
571             '1' => {
572             'name' => 'Ginger',
573             'birth_date' => 1999-08-01',
574             },
575             '2' => {
576             'name' => 'Pepper',
577             'birth_date' => 2002-04-12',
578             },
579             '3' => {
580             'name' => 'Polly',
581             'birth_date' => 2016-03-31',
582             },
583             '4' => {
584             'name' => 'Daisy',
585             'birth_date' => 2019-08-01',
586             },
587             };
588            
589             $result_keys = [
590             '4','1','2','3'
591             ];
592              
593             Using alternative keys:
594              
595             my ($results, $result_keys) = $pepper->sql_hash(qq{
596             select id, name, date_format(birth_date,'%Y') from my_family.family_members
597             where member_type=? order by name
598             },[ 'Dog' ], [ 'name','birth_year' ] );
599              
600             Now, results would look like
601              
602             $results = {
603             '1' => {
604             'name' => 'Ginger',
605             'birth_year' => 1999',
606             },
607             ...and so forth...
608             };
609              
610             B<Note:> sql_hash() does not work with 'select *' queries. The column names must be
611             a part of your SELECT statement or provided as the third argument.
612              
613             =head2 list_select
614              
615             Useful for SELECT statements which return multiple results with one column each.
616             The required first argument is the SELECT statement to run. The optional second
617             argument is a reference to an array of values for the placeholders (recommended).
618              
619             my $list = $pepper->list_select(
620             'select name from my_family.family_members where member_type=?,
621             ['Dog']
622             );
623            
624             # $list will look like:
625             $list = ['Ginger','Pepper','Polly','Daisy'];
626              
627             =head2 comma_list_select
628              
629             Provides the same functionality as list_select() but returns a scalar containing
630             a comma-separated list of the values found by the SELECT statement.
631              
632             my $text_list = $pepper->comma_list_select(
633             "select name from my_family.family_members where member_type=?",
634             ['Dog']
635             );
636            
637             # $text_list will look like:
638             $text_list = 'Ginger,Pepper,Polly,Daisy';
639              
640             =head2 do_sql
641              
642             Flexible method to execute a SQL statement of any kind. It may be worth noting
643             that do_sql() is the only provided method that will perform non-SELECT statements.
644              
645             Args are the SQL statement itself and optionally (highly-encouraged), an arrayref
646             of values for placeholders.
647              
648             $pepper->do_sql(qq{
649             insert into my_family.family_members
650             (name, birth_date, member_type)
651             values (?,?,?)
652             }, \@values );
653              
654             $pepper->do_sql(qq{
655             insert into my_family.family_members
656             (name, birth_date, member_type)
657             values (?,?,?)
658             }, [ 'Daisy', '2019-08-01', 'Dog'] );
659            
660             $pepper->do_sql(qq{
661             update my_family.family_members.
662             set name=? where id=?
663             }, ['Sandy', 6] );
664              
665             For a SELECT statement, do_sql() returns a reference to an array of arrays of results.
666              
667             my $results = $pepper->do_sql(
668             'select code,name from finances.properties where name=?',
669             ['123 Any Street']
670             );
671             my ($code, $name);
672             while (($code,$name) = @{shift(@$results)}) {
673             print "$code == $name\n";
674             }
675              
676             For most uses, quick_select() and sql_hash() are much simpler for running SELECT's.
677              
678             =head2 change_database
679              
680             Changes the current working database. This allows you to query tables without prepending their
681             DB name (i.e no 'db_name.table_name').
682              
683             $pepper->change_database('new_db_name');
684              
685             =head2 commit
686              
687             Pepper does not turn on auto-commit, so. if you are using database support, each web request
688             will be a database transaction. This commit() method will be called automatically at the end
689             of the web request, but if you wish to manually commit changes, just call $pepper->commit(); .
690              
691             If a request fails before completely, commit() is not called and the changes should be rolled-back.
692             (Unless you already called 'commit()' prior to the error.)
693              
694             =head1 JSON METHODS
695              
696             These methods provide default/basic functions of the excellent L<Cpanel::JSON::XS> library.
697              
698             =head2 json_from_perl
699              
700             Accepts a reference to a data structure and converts it to JSON text:
701              
702             my $json_string = $pepper->json_from_perl($hash_reference);
703              
704             my $json_string = $pepper->json_from_perl(\%some_hash);
705              
706             # in either case, $json_string now contains a JSON representation of the data structure
707              
708             =head2 json_to_perl
709              
710             Accepts a scalar with a JSON string and converts it into a reference to a Perl data structure.
711              
712             my $data = $pepper->json_to_perl($json_text);
713            
714             # now you have $$data{name} or other keys / layers to access like
715             # any other Perl hashref
716            
717             =head2 read_json_file
718              
719             Similar to json_to_perl() but added convenience of retrieving the JSON string from a file:
720              
721             my $data = $pepper->read_json_file('path/to/data_file.json');
722              
723             =head2 write_json_file
724              
725             Converts Perl data structure to JSON and saves it to a file.
726              
727             $pepper->write_json_file('path/to/data_file.json', $data_structure);
728              
729             $pepper->write_json_file('path/to/data_file.json', \%data_structure);
730              
731             =head1 GENERAL / DATE UTILITY METHODS
732              
733             =head2 filer
734              
735             This is a basic interface for reading, writing, and appending files using the Path::Tiny library.
736              
737             To load the contents of a file into a scalar (aka 'slurp'):
738              
739             my $file_contents = $pepper->filer('/path/to/file.ext');
740            
741             To save the contents of a scalar into a file:
742              
743             $pepper->filer('/path/to/new_file.ext','write',$scalar_of_content);
744              
745             # or maybe you have an array
746             $pepper->filer('/path/to/new_file.ext','write', join("\n",@array_of_lines) );
747              
748             To append a file with additional content
749              
750             $pepper->filer('/path/to/new_file.ext','append',$scalar_of_content);
751              
752             =head2 random_string
753              
754             Handy method to generate a random string of numbers and uppercase letters.
755              
756             To create a 10-character random string:
757              
758             my $random_string = $pepper->random_string();
759            
760             To specify that it be 25 characters long;
761              
762             my $longer_random_string = $pepper->random_string(25);
763              
764             =head2 time_to_date
765              
766             Useful method for converting UNIX epochs or YYYY-MM-DD dates to more human-friendly dates.
767             This takes three arguments:
768              
769             1. A timestamp, preferably an epoch like 1018569600, but can be a date like 2002-04-12 or 'April 12, 2002'.
770             The epochs are best for functions that will include the time.
771              
772             2. An action / command, such as 'to_year' or 'to_date_human_time'. See below for full list.
773              
774             3. Optionally, an Olson DB time zone name, such as 'America/New_York'. The default is UTC / GMT.
775             You can set your own default via the PERL_DATETIME_DEFAULT_TZ environmental variable or placing
776             in $pepper->{utils}->{time_zone_name}. Most of examples below take the default time zone, which is
777             UTC. B<Be sure to set the time zone if you need local times.>
778              
779             To get the epoch of 00:00:00 on a particular date:
780              
781             my $epoch_value = $pepper->time_to_date('2002-04-12', 'to_unix_start');
782             # $epoch_value is now something like 1018569600
783              
784             To convert an epoch into a YYYY-MM-DD date:
785              
786             my $date = $pepper->time_to_date(1018569600, 'to_date_db');
787             # $date is now something like '2002-04-12'
788            
789             To convert a date or epoch to a more friendly format, such as April 12, 2002:
790              
791             my $peppers_birthday = $pepper->time_to_date('2002-04-12', 'to_date_human');
792             my $peppers_birthday = $pepper->time_to_date(1018569600, 'to_date_human');
793             # for either case, $peppers_birthday is now 'April 12, 2002'
794            
795             You can always use time() to get values for the current moment:
796              
797             my $todays_date_human = $pepper->time_to_date(time(), 'to_date_human');
798             # $todays_date_human is 'September 1' at the time of this writing
799              
800             'to_date_human' leaves off the year if the moment is within the last six months.
801             This can be useful for displaying a history log.
802              
803             Use 'to_date_human_full' to force the year to be included:
804              
805             my $todays_date_human = $pepper->time_to_date(time(), 'to_date_human_full');
806             # $todays_date_human is now 'September 1, 2020'
807            
808             Use 'to_date_human_abbrev' to abbreviate the month name:
809              
810             my $nice_date_string = $pepper->time_to_date('2020-09-01', 'to_date_human_abbrev');
811             # $nice_date_string is now 'Sept. 1, 2020'
812              
813             To include the weekday with 'to_date_human_abbrev' output:
814              
815             my $nicer_date_string = $pepper->time_to_date('2002-04-12', 'to_date_human_dayname');
816             # $nicer_date_string is now 'Friday, Apr 12, 2002'
817              
818             To find just the year from a epoch:
819              
820             my $year = $pepper->time_to_date(time(), 'to_year');
821             # $year is now '2020' (as of this writing)
822            
823             my $year = $pepper->time_to_date(1018569600, 'to_year');
824             # $year is now '2002'
825              
826             To convert an epoch to its Month/Year value:
827              
828             my $month_year = $pepper->time_to_date(1018569600, 'to_month');
829             # $month_year is now 'April 2002'
830              
831             To convert an epoch to an abbreviated Month/Year value (useful for ID's):
832              
833             my $month_year = $pepper->time_to_date(1018569600, 'to_month_abbrev');
834             # $month_year is now 'Apr02'
835              
836             To retrieve a human-friendly date with the time:
837              
838             my $date_with_time = $pepper->time_to_date(time(), 'to_date_human_time');
839             # $date_with_time is now 'Sep 1 at 2:59pm' as of this writing
840            
841             my $a_time_in_the_past = $pepper->time_to_date(1543605300,'to_date_human_time','America/Chicago');
842             # $a_time_in_the_past is now 'Nov 30, 2018 at 1:15pm'
843              
844             Use 'to_just_human_time' to retrieve just the human-friendly time part;
845              
846             my $a_time_in_the_past = $pepper->time_to_date(1543605300,'to_just_human_time','America/Chicago');
847             # $a_time_in_the_past is now '1:15pm'
848              
849             To get the military time:
850              
851             my $past_military_time = $pepper->time_to_date(1543605300,'to_just_military_time');
852             # $past_military_time is now '19:15'
853             # I left off the time zone, so that's UTC time
854              
855             To extract the weekday name
856              
857             my $weekday_name = $pepper->time_to_date(1018569600, 'to_day_of_week');
858             my $weekday_name = $pepper->time_to_date('2002-04-12', 'to_day_of_week');
859             # in both cases, $weekday_name is now 'Friday'
860              
861             To get the numeric day of the week (0..6):
862              
863             my $weekday_value = $pepper->time_to_date(1543605300,'to_day_of_week_numeric');
864             # weekday_value is now '5'
865              
866             To retrieve an ISO-formatted timestamp, i.e. 2004-10-04T16:12:00+00:00
867              
868             my $iso_timestamp = $pepper->time_to_date(1096906320,'to_datetime_iso');
869             # $iso_timestamp is now '2004-10-04T16:12:00+0000'
870              
871             my $iso_timestamp = $pepper->time_to_date(1096906320,'to_datetime_iso','America/Los_Angeles');
872             # $iso_timestamp is now '2004-10-04T09:12:00+0000' (it displays the UTC value)
873              
874             =head1 THE pepper DIRECTORY
875              
876             After running 'pepper setup', a 'pepper' directory will be created in your home directory,
877             aka $ENV{HOME}/pepper. This should contain the following subdirectories:
878              
879             =over 12
880              
881             =item C<lib>
882              
883             This is where your endpoint handler modules go. This will be added to the library path
884             in the Plack service, so you can place any other custom modules/packages that you create
885             to use in your endpoints. You may choose to store scripts in here.
886              
887             =item C<config>
888              
889             This will contain your main pepper.cfg file, which should only be updated via 'pepper setup'.
890             If you do not opt to specify an option for 'url_mappings_database', the pepper_endpoints.json file
891             will be stored here as well. Please store any other custom configurations.
892              
893             =item C<psgi>
894              
895             This contains the pepper.psgi script used to run your services via Plack/Gazelle.
896             Please only modify if you are 100% sure of the changes you are making.
897              
898             =item C<log>
899              
900             All logs generated by Pepper are kept here. This includes access and error logs, as well
901             as any messages you save via $pepper->logger(). The primary process ID file is also
902             kept here. Will not contain the logs created by Apache/Nginx or the database server.
903              
904             =item C<template>
905              
906             This is where your Template Toolkit templates are kept. These can be used to create text
907             files of any type, including HTML to return via the web. Be sure to not remove the
908             'system' subdirectory or any of its files.
909              
910             =back
911              
912             =head1 USING APACHE AND SYSTEMD
913              
914             Plack services, like Pepper, should not be exposed directly to the internet.
915             Instead, you should always have a full-featured web server like Apache and
916             Nginx as a front-end for Plack, and be sure to use HTTPS / TLS. The good news is
917             that you only need to configure Apache / Nginx once (in a while).
918              
919             A sample pepper_apache.conf file will be saved under $ENV{HOME}/pepper/template/system
920             after you run 'pepper setup'. Use this file as a basis for adding a virtual
921             host configuration under /etc/apache2/conf-enabled . Several comments have been
922             added with friendly suggestions. You will want to enable several Apahce modules:
923              
924             # a2enmod proxy ssl headers proxy_http rewrite
925              
926             Nginx is a fine web server, but I recommend Apache as it can be integrated with
927             ModSecurity with much less effort.
928              
929             Use Systemd keep Pepper online as a server (like Apache or MySQL). You will
930             find an example SystemD service/config file at $ENV{HOME}/pepper/template/system/pepper.service .
931             Customize this to your needs, such as changing the '30' on the 'ExecStart' line
932             to have more/less workers, and follow your OS guides to install as a SystemD service.
933              
934             =head1 REGARDING AUTHENTICATION & SECURITY
935              
936             Pepper does not provide user authentication beyond looking for the 'Authorization'
937             header -- but you will need to validate that in your custom code.
938              
939             For basic projects, Auth0 has a generous free tier and can be easily integrated with
940             Apache L<https://auth0.com/docs/quickstart/webapp/apache> so your Perl code will
941             be able to see the user's confirmed identify in %ENV.
942              
943             You can also configure Apache/OpenID to authenticate against Google's social login
944             without modifying your Perl code: L<https://spin.atomicobject.com/2020/05/09/google-sso-apache/>
945              
946             It is easy to configure htaccess/htpasswd authentication in Apache, which places
947             the username in $ENV{REMOTE_USER} for your Perl. This may not be the most secure solution,
948             but it may suit your needs fine. L<https://httpd.apache.org/docs/2.4/howto/auth.html>
949              
950             Please do set up HTTPS with TLS 1.2+, and please look into ModSecurity with the OWASP ruleset.
951              
952             =head1 ABOUT THE NAME
953              
954             Our first two Shih Tzu's were Ginger and Pepper. Ginger was the most excellent, amazing
955             creature to ever grace the world. Pepper was a sickly ragamuffin. Ginger chased pit bulls
956             like mice and commanded the wind itself, but Pepper was your friend. Pepper was easy to love
957             and hard to disappoint, just like Perl.
958              
959             =head1 SEE ALSO
960              
961             L<https://perlmaven.com/>
962              
963             L<https://perldoc.perl.org/>
964              
965             L<http://www.template-toolkit.org/>
966              
967             L<https://metacpan.org>
968              
969             L<DBI>
970              
971             L<DateTime>
972              
973             L<Cpanel::JSON::XS>
974              
975             L<Mojolicious>
976              
977             L<Mojolicious::Lite>
978              
979             L<Dancer2>
980              
981             =head1 AUTHOR
982              
983             Eric Chernoff <eric@weaverstreet.net>
984              
985             Please send me a note with any bugs or suggestions.
986              
987             =head1 LICENSE
988              
989             MIT License
990              
991             Copyright (c) 2020 Eric Chernoff
992              
993             Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
994              
995             The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
996              
997             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.