line
stmt
bran
cond
sub
pod
time
code
1
package Mojolicious::Plugin::RoutesAuthDBI;
2
1
1
69161
use Mojo::Base 'Mojolicious::Plugin::Authentication';
1
201910
1
9
3
1
1
3592
use Mojolicious::Plugin::RoutesAuthDBI::Util qw(load_class);
1
5
1
71
4
1
1
9
use Mojo::Util qw(hmac_sha1_sum);
1
3
1
46
5
1
1
654
use Hash::Merge qw( merge );
1
10984
1
69
6
1
1
16
use Scalar::Util 'weaken';
1
2
1
49
7
8
1
1
9
use constant PKG => __PACKAGE__;
1
2
1
3644
9
10
has [qw(app dbh conf)];
11
12
has default => sub {
13
my $self = shift;
14
require Mojolicious::Plugin::RoutesAuthDBI::Schema;
15
{
16
auth => {
17
stash_key => PKG."__user__",
18
current_user_fn => 'auth_user',# helper
19
load_user => \&load_user,
20
validate_user => \&validate_user,
21
},
22
23
access => {
24
namespace => PKG,
25
module => 'Access',
26
fail_auth_cb => sub {
27
shift->render(status => 401, format=>'txt', text=>"Please sign in.\n");
28
},
29
fail_access_cb => sub {
30
shift->render(status => 403, format=>'txt', text=>"You don`t have access on this route (url, action).\n");
31
},
32
import => [qw(load_user validate_user)],
33
},
34
35
admin => {
36
namespace => PKG,
37
controller => 'Admin',
38
prefix => lc($self->conf->{admin}{controller} || 'admin'),
39
trust => hmac_sha1_sum('admin', $self->app->secrets->[0]),
40
role_admin => 'administrators',
41
},
42
43
oauth => {
44
namespace => PKG,
45
controller => 'OAuth',
46
fail_auth_cb => sub {shift->render(format=>'txt', text=>"@_")},
47
},
48
49
template => $Mojolicious::Plugin::RoutesAuthDBI::Schema::defaults,
50
51
model_namespace => PKG.'::Model',
52
53
guest => {# from Mojolicious::Plugin::Authentication
54
#~ autoload_user => 1,
55
session_key => 'guest_data',
56
stash_key => PKG."__guest__",
57
#~ current_user_fn => 'current_guest',# helper
58
#~ load_user => \&load_guest,
59
#~ validate_user => not need
60
#~ fail_render => not need
61
#######end Mojolicious::Plugin::Authentication conf#######
62
namespace => PKG,
63
module => 'Guest',
64
#~ import => [qw(load_guest)],
65
},
66
log=>{
67
namespace => PKG,
68
module => 'Log',
69
disabled=>0,
70
},
71
};
72
};# end defaults
73
74
has merge_conf => sub {#hashref
75
my $self = shift;
76
merge($self->conf, $self->default);
77
};
78
79
has access => sub {# object
80
my $self = shift;
81
weaken $self;
82
my $conf = $self->merge_conf->{'access'};
83
@{$self->merge_conf->{template}{tables}}{keys %{$conf->{tables}}} = values %{$conf->{tables}}
84
if $conf->{tables};
85
my $class = load_class($conf);
86
$class->import( @{ $conf->{import} });
87
$class->new(app=>$self->app, plugin=>$self,);
88
}, weak => 1;
89
90
has admin => sub {# object
91
my $self = shift;
92
my $conf = $self->merge_conf->{'admin'};
93
@{$self->merge_conf->{template}{tables}}{keys %{$conf->{tables}}} = values %{$conf->{tables}}
94
if $conf->{tables};
95
load_class($conf)->init(%$conf, app=>$self->app, plugin=>$self,);
96
}, weak => 1;
97
98
has oauth => sub {
99
my $self = shift;
100
my $conf = $self->merge_conf->{'oauth'};
101
@{$self->merge_conf->{template}{tables}}{keys %{$conf->{tables}}} = values %{$conf->{tables}}
102
if $conf->{tables};
103
load_class($conf)->init(%$conf, app=>$self->app, plugin=>$self, model=>$self->model($conf->{controller}),);
104
}, weak => 1;
105
106
has guest => sub {# object
107
my $self = shift;
108
my $conf = $self->merge_conf->{'guest'};
109
@{$self->merge_conf->{template}{tables}}{keys %{$conf->{tables}}} = values %{$conf->{tables}}
110
if $conf->{tables};
111
112
$self->merge_conf->{template}{tables}{guests} = $conf->{table}
113
if $conf->{table};
114
115
my $class = load_class($conf);
116
$class->new( %$conf, app=>$self->app, plugin=>$self, model=>$self->model($conf->{module}), );
117
}, weak => 1;
118
119
has log => sub {# object
120
my $self = shift;
121
my $conf = $self->merge_conf->{'log'};
122
123
@{$self->merge_conf->{template}{tables}}{keys %{$conf->{tables}}} = values %{$conf->{tables}}
124
if $conf->{tables};
125
126
$self->merge_conf->{template}{tables}{logs} = $conf->{table}
127
if $conf->{table};
128
129
my $class = load_class($conf);
130
$class->new( %$conf, app=>$self->app, plugin=>$self, model=>$self->model($conf->{module}), )
131
unless $conf->{disabled};
132
}, weak => 1;
133
134
135
sub register {
136
0
0
1
my $self = shift;
137
0
$self->app(shift);
138
0
$self->conf(shift); # global
139
140
0
0
$self->dbh($self->conf->{dbh} || $self->app->dbh);
141
0
0
$self->dbh($self->dbh->($self->app))
142
if ref($self->dbh) eq 'CODE';
143
0
0
die "Plugin must work with dbh, see SYNOPSIS" unless $self->dbh;
144
145
# init base model
146
0
load_class($self->merge_conf->{model_namespace}."::Base")->singleton(dbh=>$self->dbh, template_vars=>$self->merge_conf->{template}, mt=>{tag_start=>'{%', tag_end=>'%}'});
147
148
0
my $access = $self->access;
149
150
die "Plugin [Authentication] already loaded"
151
0
0
if $self->app->renderer->helpers->{'authenticate'};
152
153
0
$self->SUPER::register($self->app, $self->merge_conf->{auth});
154
0
$self->app->plugin('HeaderCondition');# routes host_re
155
156
0
weaken $self;
157
0
0
$self->app->routes->add_condition(access => sub {$self->cond_access(@_)});
0
158
0
$access->apply_ns();
159
0
$access->apply_route($_) for @{ $access->routes };
0
160
161
0
0
if ($self->conf->{oauth}) {
162
0
my $oauth = $self->oauth;
163
0
$access->apply_route($_) for $oauth->_routes;
164
}
165
166
0
0
0
if ($self->conf->{admin} && ref $self->conf->{admin} eq 'HASH' && keys %{$self->conf->{admin}}) {
0
0
167
0
my $admin = $self->admin;
168
0
$access->apply_route($_) for $admin->self_routes;
169
}
170
171
$self->guest
172
0
0
if $self->conf->{guest};
173
174
$self->log
175
0
0
if $self->conf->{log};
176
177
0
weaken $access;
178
0
0
$self->app->helper('access', sub {$access});
0
179
180
0
return $self, $access;
181
182
}
183
184
sub cond_access {# add_condition
185
0
0
0
my $self= shift;
186
0
my ($route, $c, $captures, $args) = @_;# $args - это маршрут-хэш из запроса БД или хэш-конфиг из кода
187
0
$route->{(PKG)}{route} = $args;# может пригодиться: $c->match->endpoint->{'Mojolicious::Plugin::RoutesAuthDBI'}...
188
0
my $conf = $self->merge_conf;
189
0
my $app = $c->app;
190
0
my $access = $self->access;
191
#~ $app->log->debug($c->dumper($route));#$route->pattern->defaults
192
193
0
my $auth_helper = $conf->{auth}{current_user_fn};
194
0
my $u = $c->$auth_helper;
195
0
my $fail_auth_cb = $conf->{access}{fail_auth_cb};
196
197
0
0
if (ref $args eq 'CODE') {
198
0
0
0
$args->($u, @_)
0
199
or $self->deny_log($route, $args, $u, $c)
200
and $c->$fail_auth_cb()
201
and return undef;
202
0
$app->log->debug(sprintf(qq[Access allow [%s] by callback condition],
203
$route->pattern->unparsed,
204
));
205
0
return 0x01;
206
}
207
208
$app->log->debug(
209
sprintf(qq[Access allow [%s] for none {auth} and none {role} and none {guest}], $route->pattern->unparsed)
210
)
211
and return 1 # не проверяем доступ
212
0
0
0
unless $args->{auth} || $args->{role} || $args->{guest};
0
0
213
214
215
0
0
if ($args->{guest}) {# && $args->{auth} =~ m'\bguest\b'i
216
0
0
0
$app->log->debug(sprintf(qq[Access allow [%s] for {guest}],
217
$route->pattern->unparsed,
218
))
219
and return 1
220
if $self->guest->is_guest($c);
221
}
222
223
# не авторизовался
224
$self->deny_log($route, $args, $u, $c)
225
and $c->$fail_auth_cb()
226
and return undef
227
0
0
0
unless $u && $u->{id};
0
0
228
229
# допустить если {auth=>'only'}
230
$app->log->debug(sprintf(qq[Access allow [%s] for {auth}=~'only'],
231
$route->pattern->unparsed,
232
))
233
and return 1
234
0
0
0
if $args->{auth} && $args->{auth} =~ m'\bonly\b'i;
0
235
236
0
my $id2 = [$u->{id}, map($_->{id}, grep !$_->{disable},@{$u->roles})];
0
237
0
my $id1 = [grep $_, @$args{qw(id route_id action_id controller_id namespace_id)}];
238
239
# explicit acces to route
240
0
0
0
scalar @$id1
0
241
&& $access->access_explicit($id1, $id2)
242
&& $app->log->debug(sprintf "Access allow [%s] for roles=[%s] joined id1=%s; args=[%s]; defaults=%s",
243
$route->pattern->unparsed,
244
$c->dumper($id2) =~ s/\s+//gr,
245
$c->dumper($id1) =~ s/\s+//gr,
246
$c->dumper($args) =~ s/\s+//gr,
247
$c->dumper($route->pattern->defaults) =~ s/\s+//gr,
248
)
249
&& return 1;
250
251
# Access to non db route by role
252
$args->{role}
253
&& $access->access_role($args->{role}, $id2)
254
&& $app->log->debug(sprintf "Access allow [%s] by role [%s]",
255
$route->pattern->unparsed,
256
$args->{role},
257
)
258
0
0
0
&& return 1;
0
259
260
# implicit access to non db routes
261
0
0
my $controller = $args->{controller} || $route->pattern->defaults->{controller} && ucfirst(lc($route->pattern->defaults->{controller}));
262
0
0
my $namespace = $args->{namespace} || $route->pattern->defaults->{namespace};
263
0
0
0
if ($controller && !$namespace) {
264
0
0
(load_class(namespace=>$_, controller=>$controller) and ($namespace = $_) and last) for @{ $app->routes->namespaces };
0
0
265
#~ warn "FOUND CONTROLLER[$controller] in NAMESPACE: $namespace";
266
}
267
268
0
my $fail_access_cb = $conf->{access}{fail_access_cb};
269
270
0
0
0
$self->deny_log($route, $args, $u, $c)
0
0
271
and $c->$fail_access_cb()
272
and return undef
273
unless $controller && $namespace;# failed load class
274
275
0
0
0
$access->access_namespace($namespace, $id2)
276
&& $app->log->debug(sprintf "Access allow [%s] for roles=[%s] by namespace=[%s]; args=[%s]; defaults=[%s]",
277
$route->pattern->unparsed,
278
$c->dumper($id2) =~ s/\s+//gr,
279
$namespace,
280
$c->dumper($args) =~ s/\s+//gr,
281
$c->dumper($route->pattern->defaults) =~ s/\s+//gr,
282
)
283
&& return 1;
284
285
0
0
0
$access->access_controller($namespace, $controller, $id2)
286
&& $app->log->debug(sprintf "Access allow [%s] for roles=[%s] by namespace=[%s] and controller=[%s]; args=[%s]; defaults=[%s]",
287
$route->pattern->unparsed,
288
$c->dumper($id2) =~ s/\s+//gr,
289
$namespace, $controller,
290
$c->dumper($args) =~ s/\s+//gr,
291
$c->dumper($route->pattern->defaults) =~ s/\s+//gr,
292
)
293
&& return 1;
294
295
# еще раз контроллер, который тут без namespace и в базе без namespace ------> доступ из любого места
296
$args->{namespace} || $route->pattern->defaults->{namespace}
297
0
0
0
|| $access->access_controller(undef, $controller, $id2)
0
0
298
&& $app->log->debug(sprintf "Access allow [%s] for roles=[%s] by controller=[%s] without namespace on db; args=[%s]; defaults=[%s]",
299
$route->pattern->unparsed,
300
$c->dumper($id2) =~ s/\s+//gr,
301
$controller,
302
$c->dumper($args) =~ s/\s+//gr,
303
$c->dumper($route->pattern->defaults) =~ s/\s+//gr,
304
)
305
&& return 1;
306
307
my $action = $args->{action} || $route->pattern->defaults->{action}
308
0
0
0
or $self->deny_log($route, $args, $u, $c)
0
0
309
and $c->$fail_access_cb()
310
and return undef;
311
312
0
0
0
$access->access_action($namespace, $controller, $action, $id2)
313
&& $app->log->debug(sprintf "Access allow [%s] for roles=[%s] by namespace=[%s] and controller=[%s] and action=[%s]; args=[%s]; defaults=[%s]",
314
$route->pattern->unparsed,
315
$c->dumper($id2) =~ s/\s+//gr,
316
$namespace , $controller, $action,
317
$c->dumper($args) =~ s/\s+//gr,
318
$c->dumper($route->pattern->defaults) =~ s/\s+//gr,
319
)
320
&& return 1;
321
322
# еще раз контроллер, который тут без namespace и в базе без namespace ------> доступ из любого места
323
$args->{namespace} || $route->pattern->defaults->{namespace}
324
0
0
0
&& $access->access_action(undef, $controller, $action, $id2)
0
0
325
&& $app->log->debug(sprintf "Access allow [%s] for roles=[%s] by (namespace=[any]) controller=[%s] and action=[%s]; args=[%s]; defaults=[%s]",
326
$route->pattern->unparsed,
327
$c->dumper($id2) =~ s/\s+//gr,
328
$controller, $action,
329
$c->dumper($args) =~ s/\s+//gr,
330
$c->dumper($route->pattern->defaults) =~ s/\s+//gr,
331
)
332
&& return 1;
333
334
0
$self->deny_log($route, $args, $u, $c);
335
0
$c->$fail_access_cb();
336
0
return undef;
337
}
338
339
sub deny_log {
340
0
0
0
my $self = shift;
341
0
my ($route, $args, $u, $c) = @_;
342
0
my $app = $self->app;
343
$app->log->debug(sprintf "Access deny [%s] for profile id=[%s]; args=[%s]; defaults=[%s]",
344
$route->pattern->unparsed,
345
0
0
$u ? $u->{id} : 'non auth',
346
$app->dumper($args) =~ s/\s+//gr,
347
$app->dumper($route->pattern->defaults) =~ s/\s+//gr,
348
);
349
}
350
351
sub model {
352
0
0
0
my ($self, $name) = @_;
353
0
my $ns = $self->merge_conf->{'model_namespace'};
354
0
0
my $class = load_class(namespace => $ns, module=> $name)
355
or die "Model module [$name] not found at namespace [$ns] or has errors";
356
357
0
weaken $self;
358
#~ weaken $self->{app};
359
0
$class->new(app=>$self->app, plugin=>$self); # синглетоны в общем
360
361
};
362
363
our $VERSION = '0.872';
364
365
=pod
366
367
=encoding utf8
368
369
=head1 Доброго всем
370
371
¡ ¡ ¡ ALL GLORY TO GLORIA ! ! !
372
373
=head1 Mojolicious::Plugin::RoutesAuthDBI
374
375
Plugin makes an auth operations throught the plugin L and OAuth2 by L.
376
377
=head1 VERSION
378
379
0.872
380
381
=head1 NAME
382
383
Mojolicious::Plugin::RoutesAuthDBI - from DBI tables does generate app routes, make authentication and make restrict access (authorization).
384
385
=head1 DB DESIGN DIAGRAM
386
387
First of all you will see L or L
388
389
=head1 SYNOPSIS
390
391
$app->plugin('RoutesAuthDBI',
392
dbh => $app->dbh,
393
auth => {...},
394
access => {...},
395
admin => {...},
396
oauth => {...},
397
guest => {...},
398
template => {...},
399
model_namespace=>...,
400
);
401
402
403
=head2 PLUGIN OPTIONS
404
405
One option C is mandatory, all other - optional.
406
407
=head3 dbh
408
409
Handler DBI connection where are tables: controllers, actions, routes, logins, profiles, roles, refs and oauth.
410
411
dbh => $app->dbh,
412
# or
413
dbh => sub { shift->dbh },
414
415
=head3 auth
416
417
Hashref options pass to base plugin L.
418
By default the option:
419
420
current_user_fn => 'auth_user',
421
stash_key => "Mojolicious::Plugin::RoutesAuthDBI__user__",
422
423
The options:
424
425
load_user => \&load_user,
426
validate_user => \&validate_user,
427
428
are imported from package access module. See below.
429
430
=head3 access
431
432
Hashref options for special access module. This module has subs/methods for manage auth and access operations, has appling routes from DBI table. By default plugin will load the builtin module:
433
434
access => {
435
module => 'Access',
436
namespace => 'Mojolicious::Plugin::RoutesAuthDBI',
437
...,
438
},
439
440
441
You might define your own module by passing options:
442
443
access => {
444
module => 'Foo',
445
namespace => 'Bar::Baz',
446
...,
447
},
448
449
See L for detail options list.
450
451
=head3 admin
452
453
Hashref options for admin controller for actions on SQL tables routes, roles, profiles, logins. By default the builtin module:
454
455
admin => {
456
controller => 'Admin',
457
namespace => 'Mojolicious::Plugin::RoutesAuthDBI',
458
...,
459
},
460
461
462
You might define your own controller by passing options:
463
464
admin => {
465
controller => 'Foo',
466
namespace => 'Bar::Baz',
467
...,
468
},
469
470
See L for detail options list.
471
472
=head3 oauth
473
474
Hashref options for oauth controller. By default the builtin module:
475
476
oauth => {
477
controller => 'OAuth',
478
namespace => 'Mojolicious::Plugin::RoutesAuthDBI',
479
...,
480
},
481
482
483
You might define your own controller by passing options:
484
485
oauth => {
486
controller => 'Foo::Bar::Baz',
487
...,
488
},
489
490
See L for detail options list.
491
492
=head3 guest
493
494
Hashref options for guest module. Defaults are:
495
496
guest => {
497
namespace => 'Mojolicious::Plugin::RoutesAuthDBI',
498
module => 'Guest',
499
session_key => 'guest_data',
500
stash_key => "Mojolicious::Plugin::RoutesAuthDBI__guest__",
501
502
},
503
504
Disable guest module usage:
505
506
guest => undef, # or none in config
507
508
See L
509
510
=head3 model_namespace
511
512
Where are your models place. Default to "Mojolicious::Plugin::RoutesAuthDBI::Model".
513
514
=head3 template
515
516
Hashref variables for SQL templates of models dictionaries. Defaults is C<$Mojolicious::Plugin::RoutesAuthDBI::Schema::defaults>. See L.
517
518
=head1 INSTALL
519
520
See L.
521
522
=head1 OVER CONDITIONS
523
524
=head2 access
525
526
Heart of this plugin! This condition apply for all db routes even if column auth set to 0. It is possible to apply this condition to non db routes also:
527
528
=over 4
529
530
=item * No access check to route, but authorization by session will ready:
531
532
$r->route('/foo')->...->over(access=>{auth=>0})->...;
533
534
=item * Allow if has authentication only:
535
536
$r->route('/foo')->...->over(access=>{auth=>'only'})->...;
537
# same as
538
# $r->route('/foo')->...->over(authenticated => 1)->...; # see Mojolicious::Plugin::Authentication
539
540
=item * Allow for guest
541
542
$r->route('/foo')->...->over(access=>{guest=>1})->...;
543
544
To makes guest session:
545
546
$c->access->plugin->guest->store($c, {<...some data...>});
547
548
See L
549
550
=item * Route accessible if profile roles assigned to either B namespace or controller 'Bar.pm' (which assigned neither namespece on db or assigned to that loadable namespace) or action 'bar' on controller Bar.pm (action record in db table actions):
551
552
$r->route('/bar-bar-any-namespace')->to('bar#bar',)->over(access=>{auth=>1})->...;
553
554
=item * Explicit defined namespace route accessible either namespace 'Bar' or 'Bar::Bar.pm' controller or action 'bar' in controller 'Bar::Bar.pm' (which assigned to namespace 'Bar' in table refs):
555
556
$r->route('/bar-bar-bar')->to('bar#bar', namespace=>'Bar')->over(access=>{auth=>1})->...;
557
558
=item * Check access by overriden namespace 'BarX': controller and action also with that namespace in db table refs:
559
560
$r->route('/bar-nsX')->to('bar#bar', namespace=>'Bar')->over(access=>{auth=>1, namespace=>'BarX'})->...;
561
562
=item * Check access by overriden namespace 'BarX' and controller 'BarX.pm', action record also with that ns & c in db table refs:
563
564
$r->route('/bar-nsX-cX')->to('bar#bar', namespace=>'Bar')->over(access=>{auth=>1, namespace=>'BarX', controller=>'BarX'})->...;
565
566
=item * Full override names access:
567
568
$r->route('/bar-nsX-cX-aX')->to('bar#bar', namespace=>'Bar')->over(access=>{auth=>1, namespace=>'BarX', controller=>'BarX', action=>'barX'})->...;
569
570
=item *
571
572
$r->route('/bar-cX-aX')->to('bar#bar',)->over(access=>{auth=>1, controller=>'BarX', action=>'barX'})->...;
573
574
=item * Route accessible if profile roles list has defined role (admin):
575
576
$r->route('/bar-role-admin')->to('bar#bar',)->over(access=>{auth=>1, role=> 'admin'})->...;
577
578
=item * Pass callback to access condition
579
580
The callback will get parameters: $profile, $route, $c, $captures, $args (this callback ref). Callback must returns true or false for restrict access. Example simple auth access:
581
582
$r->route('/check-auth')->over(access=>sub {my ($profile, $route, $c, $captures, $args) = @_; return $profile;})->to(cb=>sub {my $c =shift; $c->render(format=>'txt', text=>"Hi @{[$c->auth_user->{names}]}!\n\nYou have access!");});
583
584
=back
585
586
=head1 HELPERS
587
588
=head2 access
589
590
Returns access instance obiect. See L methods.
591
592
if ($c->access->access_explicit([1,2,3], [1,2,3])) {
593
# yes, accessible
594
}
595
596
=head1 METHODS and SUBS
597
598
Registration() & access() & .
599
600
=head2 Example routing table records
601
602
Request
603
HTTP method(s) (optional)
604
and the URL (space delim)
605
Contoller Method Route Name Auth
606
------------------------- ----------- -------------- ----------------- -----
607
GET /city/new City new_form city_new_form 1
608
GET /city/:id City show city_show 1
609
GET /city/edit/:id City edit_form city_edit_form 1
610
GET /cities City index city_index 1
611
POST /city City save city_save 1
612
GET /city/delete/:id City delete_form city_delete_form 1
613
DELETE /city/:id City delete city_delete 1
614
/ Home index home_index 0
615
get post /foo/baz Foo baz foo_baz 1
616
617
It table will generate the L:
618
619
# GET /city/new
620
$r->route('/city/new')->via('get')->over()->to(controller => 'city', action => 'new_form')->name('city_new_form');
621
622
# GET /city/123 - show item with id 123
623
$r->route('/city/:id')->via('get')->over()->to(controller => 'city', action => 'show')->name('city_show');
624
625
# GET /city/edit/123 - form to edit an item
626
$r->route('/city/edit/:id')->via('get')->over()->to(controller => 'city', action => 'edit_form')->name('city_edit_form');
627
628
# GET /cities - list of all items
629
$r->route('/cities')->via('get')->over()->to(controller => 'city', action => 'index')->name('cities_index');
630
631
# POST /city - create new item or update the item
632
$r->route('/city')->via('post')->to(controller => 'city', action => 'save')->name('city_save');
633
634
# GET /city/delete/123 - form to confirm delete an item id=123
635
$r->route('/city/delete/:id')->via('get')->over()->to(controller => 'city', action => 'delete_form')->name('city_delete_form');
636
637
# DELETE /city/123 - delete an item id=123
638
$r->route('/city/:id')->via('delete')->over()->to(controller => 'city', action => 'delete')->name('city_delete');
639
640
# without HTTP method and no auth restrict
641
$r->route('/')->to(controller => 'Home', action => 'index')->name('home_index');
642
643
# GET or POST /foo/baz
644
$r->route('/foo/baz')->via('GET', 'post')->over()->to(controller => 'Foo', action => 'baz')->name('foo_baz');
645
646
=head2 Warning
647
648
If you changed the routes table then kill -HUP or reload app to regenerate routes. Changing assess not require reloading the service.
649
650
=head1 SEE ALSO
651
652
L
653
654
L
655
656
=head1 AUTHOR
657
658
Михаил Че (Mikhail Che), C<< >>
659
660
=head1 BUGS / CONTRIBUTING
661
662
Please report any bugs or feature requests at L. Pull requests also welcome.
663
664
=head1 COPYRIGHT
665
666
Copyright 2016+ Mikhail Che.
667
668
This library is free software; you can redistribute it and/or modify
669
it under the same terms as Perl itself.
670
671
=cut
672