Search code examples
perlmojolicious

How to fetch full route with `find` in Mojolicous?


I have shortcut to register REST routes like Mojolicious::Plugin::RESTRoutes

my $r = $self->routes;
$r->add_shortcut( resource =>  \&resource );

Also I have some under routes to check user access level:

my $guest =  $r->under->to( "auth#check_level" );
my $user  =  $r->under->to( "auth#check_level", { required_level =>  100 } );
my $admin =  $r->under->to( "auth#check_level", { required_level =>  200 } );

When I create resource:

my $uResource =  $user->resource( 'users' );

I get next routes:

$ myapp.pl routes
/               *                 # under for guest
/               *                 # under for user
  +/users       *       users
    +/          GET     "list_users"
    +/create    GET     "create_user"
    +/          POST    "store_user"
    +/:id       GET     "show_user"
    +/:id/edit  GET     "edit_user"
    +/:id       PUT     "update_user"
    +/:id       DELETE  "delete_user"
/               *                       # under for admin

No I want to adjust some of them to restrict access:

$guest->add_child( $user->find( 'create_user' ) );
$admin->add_child( $user->find( 'list_users'  ) );
$admin->add_child( $user->find( 'delete_user' ) );

Now I get this routes:

$ myapp.pl routes
/               *                        # under for guest
  +/create      GET     "create_user"
/               *                        # under for user
  +/users       *       users
    +/          POST    "store_user"
    +/:id       GET     "show_user"
    +/:id/edit  GET     "edit_user"
    +/:id       PUT     "update_user"
/               *                        # under for admin
  +/            GET     "list_users"    
  +/:id         DELETE  "delete_user"

As you can see only last part of the route is moved from user into guest/admin.
Because of I use here $user instead of $uResource
I expect the route +/users is also matched
:

$ myapp.pl routes
/               *                        # under for guest
  +/users       *       users
    +/create    GET     "create_user"
/               *                        # under for user
  +/users       *       users
    +/          POST    "store_user"
    +/:id       GET     "show_user"
    +/:id/edit  GET     "edit_user"
    +/:id       PUT     "update_user"
/               *                        # under for admin
  +/users       *       users
    +/          GET     "list_users"    
    +/:id       DELETE  "delete_user"

This is exact as:

$guest->any( "/users" )->add_child( $user->find( 'create_user' ) );
$admin->any( "/users" )->add_child( $user->find( 'list_users'  ) );
$admin->any( "/users" )->add_child( $user->find( 'delete_user' ) );

Is there a way to prepend /users automatically?


Solution

  • I just create different shortcut. The sources:

    my $r = $self->routes;
    $r->add_shortcut( resource   =>  \&resource   );
    $r->add_shortcut( privileged =>  \&privileged );
    
    
    # Each time someone make query we setup cookie and check his access level
    my $guest =  $r->under->to( "auth#check_level" );
    my $user  =  $r->under->to( "auth#check_level", { required_level =>  100 } );
    my $admin =  $r->under->to( "auth#check_level", { required_level =>  200 } );
    
    $user->privileged( 'users',  CS => $guest,  LD => $admin );
    
    
    sub privileged {
        my( $r, $name, %restriction ) =  @_;
        my $singular =  Lingua::EN::Inflect::PL( $name );
    
        # Prefix for resource
        $r =  $r->any( "/$name" )->to( "$singular#" );
    
        my %api =  (()
            # Order definition has not matter each part of resource has unique path
            # type       #method       #path        #action    #route name
            ,L =>  [ $r, [ "GET"    ], "/",         '#index',  "list_$name"       ]
            ,C =>  [ $r, [ "GET"    ], '/create',   '#create', "create_$singular" ]
            ,S =>  [ $r, [ "POST"   ], '/',         '#store',  "store_$singular"  ]
            ,R =>  [ $r, [ "GET"    ], '/:id',      '#show',   "show_$singular"   ]
            ,E =>  [ $r, [ "GET"    ], '/:id/edit', '#edit',   "edit_$singular"   ]
            ,U =>  [ $r, [ "PUT"    ], '/:id',      '#update', "update_$singular" ]
            ,D =>  [ $r, [ "DELETE" ], '/:id',      '#delete', "delete_$singular" ]
        );
    
        # CS => $guest,  REU => $user,  LD => $admin  translated into:
        # $api{ C => [ $guest ...],  S => [ $guest ... ], ..., D => [ $admin ... ] }
        for my $level ( keys %restriction ) {
            # Prefix for resource
            my $r =  $restriction{ $level }->any( "/$name" )->to( "$singular#" );
    
            for my $resource ( split //, $level ) {
                $api{ $resource }[0] =  $r;
            }
        }
    
    
        # Create route for all resouces: LCSREUD
        for my $resource ( keys %api ) {
            my( $guard, $method, $path, $action, $rname ) =  @{ $api{ $resource } };
            $guard->any( $method, $path, $rname )->to( $action );
        }
    
    
        return;
    }
    

    Any suggestions?