File Coverage

lib/Dancer2/Plugin/ViewCache.pm
Criterion Covered Total %
statement 50 53 94.3
branch 10 12 83.3
condition 7 14 50.0
subroutine 8 8 100.0
pod 1 2 50.0
total 76 89 85.3


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::ViewCache;
2 1     1   1263993 use Modern::Perl;
  1         9  
  1         11  
3              
4             our $VERSION = '1.0001'; # VERSION
5             our $AUTHORITY = 'cpan:CLEARBLT'; # AUTHORITY
6             # ABSTRACT: Create a code for a guest user to use to view a page
7 1     1   201 use Dancer2::Plugin;
  1         2  
  1         5  
8 1     1   3463 use Carp;
  1         2  
  1         1254  
9              
10             has base_url => (
11             is => 'ro',
12             default => sub {
13             my $conf = $_[0]->config->{base_url};
14              
15             return $conf;
16             }
17             );
18              
19             has delete_after_view => (
20             is => 'ro',
21             default => sub {
22             my $conf = $_[0]->config->{delete_after_view} || 0;
23              
24             return $conf;
25             }
26             );
27              
28             has randomize_code_length => (
29             is => 'ro',
30             default => sub {
31             my $conf = $_[0]->config->{randomize_code_length} || 0;
32              
33             return $conf;
34             }
35             );
36              
37             has minimum_random_length => (
38             is => 'ro',
39             default => sub {
40             my $conf = $_[0]->config->{minimum_random_length} || 0;
41              
42             return $conf;
43             }
44             );
45              
46             has maximum_random_length => (
47             is => 'ro',
48             default => sub {
49             my $conf = $_[0]->config->{maximum_random_length} || 128;
50              
51             return $conf;
52             }
53             );
54              
55             has code => (
56             is => 'ro',
57             default => sub {
58             my $conf = $_[0]->config->{code};
59              
60             return $conf;
61             }
62             );
63              
64             has d2pdb => ( is => 'rwp', );
65              
66             plugin_keywords qw/
67             generate_guest_url
68             /;
69              
70             sub BUILD {
71 1     1 0 73 my $self = shift;
72 1 50       7 $self->_set_d2pdb( $self->find_plugin('Dancer2::Plugin::DBIC') )
73             or croak
74             'Dancer2::Plugin::ViewCache is dependent on Dancer2::Plugin::DBIC!';
75              
76             # This will be the proper code once bug with route prefixing is fixed
77             # $self->app->add_route(
78             # method => 'get',
79             # regexp => '/view_by_code/:code',
80             # code => sub {
81             # my $app = shift;
82             # my $code = $app->request->route_parameters->get('code');
83              
84             # my $view_by_code = $self->d2pdb->resultset('ViewCache')->search(code => $code)->single;
85             # unless ( defined $view_by_code ) {
86             # return "Unable to display content";
87             # }
88              
89             # my $html = $self->template;
90             # if ( $view_by_code->delete_after_view ) {
91             # $view_by_code->delete();
92             # }
93              
94             # return $html;
95             # },
96             # );
97              
98             my $route = Dancer2::Core::Route->new(
99             type_library => $self->config->{type_library},
100             method => 'get',
101             regexp => '/view_by_code/:code',
102             prefix => undef,
103             code => sub {
104 4     4   29981 my $app = shift;
105 4         23 my $code = $app->request->route_parameters->get('code');
106 4         63 my $view_by_code = $self->d2pdb->resultset('ViewCache')
107             ->search( { code => $code } )->single;
108 4 100       11907 unless ( defined $view_by_code ) {
109 1         41 return 'Unable to find content to display';
110             }
111              
112 3 100       194 if ( $view_by_code->delete_after_view ) {
113 1         59 $view_by_code->delete();
114             }
115              
116 3         12916 return $view_by_code->html;
117             }
118 1         71 );
119 1         18782 my $method = $route->method;
120 1         3 push @{ $self->app->routes->{$method} }, $route;
  1         25  
121              
122             }
123              
124             sub generate_guest_url {
125 3     3 1 216828 my $self = shift;
126              
127 3         16 my $params = { @_, };
128 3         10 my $del = $params->{delete_after_view};
129 3 100       12 unless ( defined $del ) {
130 2         5 $del = 0;
131             }
132              
133 3         7 my $html = $params->{html};
134             croak 'You must pass "html" into generate_guest_url. No html found.'
135             unless ( exists $params->{html}
136             && defined $params->{html}
137 3 50 33     33 && $params->{html} ne '' );
      33        
138              
139 3   50     14 my $length = $self->_randominteger // 128;
140 3         5 my $code;
141 3 100 66     23 if ( exists $params->{code}
      66        
142             && defined $params->{code}
143             && $params->{code} ne '' ) {
144 1         4 $code = $params->{code};
145             }
146             else {
147 2         29 $code = $self->_randomstring( $length, 'A' .. 'Z', 'a' .. 'z', 0 .. 9 );
148             }
149              
150             my %code_args = (
151             code => $code,
152             delete_after_view => $del,
153             html => $params->{html},
154 3         68 );
155              
156 3         21 my $rset = $self->d2pdb->resultset('ViewCache');
157 3         1353 $rset->create( \%code_args );
158              
159 3         43539 my $url = $self->base_url . "/view_by_code/$code";
160              
161 3         100 return $url;
162             }
163              
164             sub _randominteger {
165 3     3   8 my ($self) = @_;
166 3         15 my $min = $self->minimum_random_length;
167 3         12 my $max = $self->maximum_random_length;
168              
169 3         14 return $min + int( rand( $max - $min + 1 ) );
170             }
171              
172             sub _randomstring {
173 2     2   3 my $self = shift;
174 2         4 my $length = shift;
175              
176 2         12 my $rand_string = join '', @_[ map { rand @_ } 1 .. $length ];
  162         211  
177              
178             # If the code happens to already be in the db, generate another one
179 2         22 while (
180             defined(
181             $self->d2pdb->resultset('ViewCache')
182             ->find( { code => $rand_string } )
183             )
184             ) {
185 0         0 warn
186             "Generated code was already in the database, generating another one\n";
187              
188 0         0 $rand_string
189             = $self->_randomstring( 128,, 'A' .. 'Z', 'a' .. 'z', 0 .. 9 );
190              
191 0         0 last;
192             }
193              
194 2         325916 return $rand_string;
195             }
196              
197             1;
198              
199             __END__
200              
201             =pod
202              
203             =encoding UTF-8
204              
205             =head1 NAME
206              
207             Dancer2::Plugin::ViewCache - Create a code for a guest user to use to view a page
208              
209             =head1 VERSION
210              
211             version 1.0001
212              
213             =head1 SYNOPSIS
214              
215             In your L<Dancer2> application configuration:
216              
217             plugins:
218             ViewCache:
219             base_url: 'https://my.server.com'
220             template: 'project/order_acknowledgement'
221              
222             Then in your application:
223              
224             package MyApp;
225             use Dancer2 appname => 'MyApp';
226             # This plugin has been tested with Provider::DBIC, but it should work for others.
227             use Dancer2::Plugin::DBIC;
228             use Dancer2::Plugin::ViewCache;
229              
230             =head1 DESCRIPTION
231              
232             This L<Dancer2> plugin lets you create a url with a unique code that can be given to a guest user to view
233             a web page without logging into the site.
234              
235             If delete_after_view is set, the generated link will be invalidated after being viewed.
236              
237             =head1 CONFIGURATION
238              
239             Example configuration
240              
241             plugins:
242             ViewCache:
243             base_url: 'https://my.server.com' # No default
244             delete_after_view: '1' # Default '0'
245             randomize_code_length: '1' # Default '0'
246             minimum_random_length: '5' # Default '1'
247             maximum_random_length: '5' # Default '128'
248             template: 'project/order_acknowledgement' # No default
249              
250             =head2 base_url
251              
252             The base URL that the code will be appended to. E.g.
253             https://www.servername.com/
254              
255             =head2 randomize_code_length
256              
257             Makes the code generated for the guest URL be of random length. Without a random value, the default code length is 128.
258              
259             =head2 minimum_random_length
260              
261             Minimum length for randomize_code_length, default of 1
262              
263             =head2 maximum_random_length
264              
265             Maximum length for randomize_code_length, default of 128
266              
267             =head1 SUGGESTED SCHEMA
268              
269             You'll need a table to store the generated URL data named view_cache. The following example is for Postgres:
270              
271             =head2 view_cache Table
272              
273             CREATE TABLE view_cache (
274             cache_id SERIAL NOT NULL PRIMARY KEY,
275             code TEXT NOT NULL UNIQUE,
276             html TEXT NOT NULL,
277             delete_after_view BOOLEAN NOT NULL DEFAULT FALSE,
278             created_dt TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
279             );
280              
281             =head1 KEYWORDS
282              
283             =head2 generate_guest_url([ \%options ])
284              
285             Stores provided HTML and generates a URL for a guest user to view it with.
286              
287             The "html" argument is mandatory. This is the HTML that will be displayed by the generated URL.
288              
289             If the optional $code argument is provided, this will be used in the generated URL. If this is not provided, a random code will be generated and used.
290              
291             Note: You should not make any calls to this that store values to the database inside a transaction, if you plan to consume them before the transaction ends.
292              
293             Examples:
294              
295             my $url = generate_guest_url({ html => $html});
296              
297             my $url = generate_guest_url(
298             code => '123abc',
299             html => $html
300             );
301              
302             my $url = generate_guest_url(
303             html => $html,
304             delete_after_view => '1',
305             randomize_code_length => '1'
306             );
307              
308             =head1 REQUIRES
309              
310             =over 4
311              
312             =item *
313             L<Dancer2::Plugin|Dancer2::Plugin>
314              
315             =item *
316             L<Dancer2::Plugin::DBIC|Dancer2::Plugin::DBIC>
317              
318             =back
319              
320             =head1 ROADMAP
321              
322             =over 4
323              
324             =item *
325             Generate a URL for a PDF or XML file stored on disk
326              
327             =item *
328             Specify a number of days for the link to be active before invalidating
329              
330             =back
331              
332             =head1 AUTHOR
333              
334             Tracey Clark <traceyc@clearbuilt.com>
335              
336             =head1 COPYRIGHT AND LICENSE
337              
338             This software is copyright (c) 2022 by Clearbuilt.
339              
340             This is free software; you can redistribute it and/or modify it under
341             the same terms as the Perl 5 programming language system itself.
342              
343             =cut