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