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