File Coverage

blib/lib/Yancy/Controller/Yancy/MultiTenant.pm
Criterion Covered Total %
statement 41 42 97.6
branch 11 14 78.5
condition 18 25 72.0
subroutine 7 7 100.0
pod 4 4 100.0
total 81 92 88.0


line stmt bran cond sub pod time code
1             package Yancy::Controller::Yancy::MultiTenant;
2             our $VERSION = '1.088';
3             # ABSTRACT: A controller to show a user only their content
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod use Mojolicious::Lite;
8             #pod plugin Yancy => {
9             #pod schema => {
10             #pod blog => {
11             #pod properties => {
12             #pod id => { type => 'integer' },
13             #pod user_id => { type => 'integer' },
14             #pod title => { type => 'string' },
15             #pod html => { type => 'string' },
16             #pod },
17             #pod },
18             #pod },
19             #pod };
20             #pod
21             #pod app->routes->get( '/user/:user_id' )->to(
22             #pod 'yancy-multi_tenant#list',
23             #pod schema => 'blog',
24             #pod template => 'index'
25             #pod );
26             #pod
27             #pod __DATA__
28             #pod @@ index.html.ep
29             #pod % for my $item ( @{ stash 'items' } ) {
30             #pod

<%= $item->{title} %>

31             #pod <%== $item->{html} %>
32             #pod % }
33             #pod
34             #pod =head1 DESCRIPTION
35             #pod
36             #pod This module contains routes to manage content owned by users. When paired
37             #pod with an authentication plugin like L, each user
38             #pod is allowed to manage their own content.
39             #pod
40             #pod This controller extends L to add filtering content
41             #pod by a user's ID.
42             #pod
43             #pod =head1 EXAMPLES
44             #pod
45             #pod To use this controller when the URL displays a username and the content
46             #pod uses an internal ID, you can use an C route to map the username
47             #pod in the path to the ID:
48             #pod
49             #pod my $user_route = app->routes->under( '/:username', sub {
50             #pod my ( $c ) = @_;
51             #pod my $username = $c->stash( 'username' );
52             #pod my @users = $c->yancy->list( user => { username => $username } );
53             #pod if ( my $user = $users[0] ) {
54             #pod $c->stash( user_id => $user->{id} );
55             #pod return 1;
56             #pod }
57             #pod return $c->reply->not_found;
58             #pod } );
59             #pod
60             #pod # /:username - List blog posts
61             #pod $user_route->get( '' )->to(
62             #pod 'yancy-multi_tenant#list',
63             #pod schema => 'blog',
64             #pod template => 'blog_list',
65             #pod );
66             #pod # /:username/:id/:slug - Get a single blog post
67             #pod $user_route->get( '/:id/:slug' )->to(
68             #pod 'yancy-multi_tenant#get',
69             #pod schema => 'blog',
70             #pod template => 'blog_view',
71             #pod );
72             #pod
73             #pod To build a website where content is only for the current logged-in user,
74             #pod combine this controller with an auth plugin like
75             #pod L. Use an C route to set the
76             #pod C from the current user.
77             #pod
78             #pod app->yancy->plugin( 'Auth::Basic', {
79             #pod route => any( '' ), # All routes require login
80             #pod schema => 'user',
81             #pod username_field => 'username',
82             #pod password_digest => { type => 'SHA-1' },
83             #pod } );
84             #pod
85             #pod my $user_route = app->yancy->auth->route->under( '/', sub {
86             #pod my ( $c ) = @_;
87             #pod my $user = $c->yancy->auth->current_user;
88             #pod $c->stash( user_id => $user->{id} );
89             #pod return 1;
90             #pod } );
91             #pod
92             #pod # / - List todo items
93             #pod $user_route->get( '' )->to(
94             #pod 'yancy-multi_tenant#list',
95             #pod schema => 'todo_item',
96             #pod template => 'todo_list',
97             #pod );
98             #pod
99             #pod =head1 SEE ALSO
100             #pod
101             #pod L, L, L
102             #pod
103             #pod =cut
104              
105 2     2   10168 use Mojo::Base 'Yancy::Controller::Yancy';
  2         5  
  2         14  
106 2     2   284 use Yancy::Util qw( derp );
  2         5  
  2         1462  
107              
108             #pod =method list
109             #pod
110             #pod $routes->get( '/:user_id' )->to(
111             #pod 'yancy-multi_tenant#list',
112             #pod schema => $schema_name,
113             #pod template => $template_name,
114             #pod );
115             #pod
116             #pod This method is used to list content owned by the given user (specified
117             #pod in the C stash value).
118             #pod
119             #pod =head4 Input Stash
120             #pod
121             #pod This method extends L and adds the
122             #pod following configuration and stash values:
123             #pod
124             #pod =over
125             #pod
126             #pod =item user_id
127             #pod
128             #pod The ID of the user whose content should be listed. Required. Should
129             #pod match a value in the C.
130             #pod
131             #pod =item user_id_field
132             #pod
133             #pod The field in the item that holds the user ID. Defaults to C.
134             #pod
135             #pod =back
136             #pod
137             #pod =cut
138              
139             sub list {
140 4     4 1 241917 my ( $c ) = @_;
141 4   100     20 my $user_id = $c->stash( 'user_id' ) || die "User ID not defined in stash";
142             $c->stash( filter => {
143 3   100     42 %{ $c->_resolve_filter },
  3         18  
144             $c->stash( 'user_id_field' ) // 'user_id' => $user_id,
145             } );
146 3         115 return $c->SUPER::list;
147             }
148              
149             #pod =method get
150             #pod
151             #pod $routes->get( '/:user_id/:id' )->to(
152             #pod 'yancy-multi_tenant#get',
153             #pod schema => $schema_name,
154             #pod template => $template_name,
155             #pod );
156             #pod
157             #pod This method is used to show a single item owned by a user (given by the
158             #pod C stash value).
159             #pod
160             #pod =head4 Input Stash
161             #pod
162             #pod This method extends L and adds the
163             #pod following configuration and stash values:
164             #pod
165             #pod =over
166             #pod
167             #pod =item user_id
168             #pod
169             #pod The ID of the user whose content should be listed. Required. Should
170             #pod match a value in the C.
171             #pod
172             #pod =item user_id_field
173             #pod
174             #pod The field in the item that holds the user ID. Defaults to C.
175             #pod
176             #pod =back
177             #pod
178             #pod =cut
179              
180             sub get {
181 7     7 1 680467 my ( $c ) = @_;
182 7 100       35 return if !$c->_is_owned_by;
183 2         17 return $c->SUPER::get;
184             }
185              
186             #pod =method set
187             #pod
188             #pod $routes->any( [ 'GET', 'POST' ] => '/:id/edit' )->to(
189             #pod 'yancy#set',
190             #pod schema => $schema_name,
191             #pod template => $template_name,
192             #pod );
193             #pod
194             #pod $routes->any( [ 'GET', 'POST' ] => '/create' )->to(
195             #pod 'yancy#set',
196             #pod schema => $schema_name,
197             #pod template => $template_name,
198             #pod forward_to => $route_name,
199             #pod );
200             #pod
201             #pod This route creates a new item or updates an existing item in
202             #pod a schema. If the user is making a C request, they will simply
203             #pod be shown the template. If the user is making a C or C
204             #pod request, the form parameters will be read, the data will be validated
205             #pod against L,
206             #pod and the user will either be shown the form again with the
207             #pod result of the form submission (success or failure) or the user will be
208             #pod forwarded to another place.
209             #pod
210             #pod This method does not authenticate users. User authentication and
211             #pod authorization should be performed by an auth plugin like
212             #pod L.
213             #pod
214             #pod =head4 Input Stash
215             #pod
216             #pod This method extends L and adds the
217             #pod following configuration and stash values:
218             #pod
219             #pod =over
220             #pod
221             #pod =item user_id
222             #pod
223             #pod The ID of the user whose content is being edited. Required. Will be
224             #pod set in the C.
225             #pod
226             #pod =item user_id_field
227             #pod
228             #pod The field in the item that holds the user ID. Defaults to C.
229             #pod This field will be filled in with the C stash value.
230             #pod
231             #pod =back
232             #pod
233             #pod =cut
234              
235             sub set {
236 14     14 1 473175 my ( $c ) = @_;
237              
238             # Users are not allowed to edit content from other users
239 14         48 my $id = $c->stash( 'id' );
240 14 50 66     190 return if $id && !$c->_is_owned_by;
241              
242 13 100       49 if ( $c->req->method ne 'GET' ) {
243 8   50     137 my $user_id_field = $c->stash( 'user_id_field' ) // 'user_id';
244 8         97 my $user_id = $c->stash( 'user_id' );
245 8 100       75 if ( eval { $c->req->json } ) {
  8         442  
246 2         595 $c->req->json->{ $user_id_field } = $user_id;
247             }
248             else {
249 6         6737 $c->req->param( $user_id_field => $c->stash( 'user_id' ) );
250             }
251             }
252              
253 13         3915 return $c->SUPER::set;
254             }
255              
256             #pod =method delete
257             #pod
258             #pod $routes->any( [ 'GET', 'POST' ], '/delete/:id' )->to(
259             #pod 'yancy#delete',
260             #pod schema => $schema_name,
261             #pod template => $template_name,
262             #pod forward_to => $route_name,
263             #pod );
264             #pod
265             #pod This route deletes an item from a schema. If the user is making
266             #pod a C request, they will simply be shown the template (which can be
267             #pod used to confirm the delete). If the user is making a C or C
268             #pod request, the item will be deleted and the user will either be shown the
269             #pod form again with the result of the form submission (success or failure)
270             #pod or the user will be forwarded to another place.
271             #pod
272             #pod This method does not authenticate users. User authentication and
273             #pod authorization should be performed by an auth plugin like
274             #pod L.
275             #pod
276             #pod =head4 Input Stash
277             #pod
278             #pod This method extends L and adds the
279             #pod following configuration and stash values:
280             #pod
281             #pod =over
282             #pod
283             #pod =item user_id
284             #pod
285             #pod The ID of the user whose content is being edited. Required. Will be
286             #pod set in the C.
287             #pod
288             #pod =item user_id_field
289             #pod
290             #pod The field in the item that holds the user ID. Defaults to C.
291             #pod This field will be filled in with the C stash value.
292             #pod
293             #pod =back
294             #pod
295             #pod =cut
296              
297             sub delete {
298 9     9 1 351203 my ( $c ) = @_;
299 9 50       35 return if !$c->_is_owned_by;
300 6         38 return $c->SUPER::delete;
301             }
302              
303             # =sub _is_owned_by
304             #
305             # return if !_is_owned_by();
306             #
307             # Check that the currently-requested item is owned by the user_id in the
308             # stash. This uses the schema, id, user_id, and user_id_field stash
309             # values. user_id_field defaults to 'user_id'. All other fields are
310             # required and will throw an exception if missing.
311             sub _is_owned_by {
312 23     23   57 my ( $c ) = @_;
313 23 50       106 if ( $c->stash( 'collection' ) ) {
314 0         0 derp '"collection" stash key is now "schema" in controller configuration';
315             }
316 23   100     308 my $schema_name = $c->stash( 'schema' ) || $c->stash( 'collection' )
317             || die "Schema name not defined in stash";
318 21   100     256 my $user_id = $c->stash( 'user_id' ) || die "User ID not defined in stash";
319 18         208 my $schema = $c->yancy->schema( $schema_name );
320             # XXX: The id_field stash is not documented and is only used by the
321             # editor plugin API. We should make it so the editor API does not
322             # need to use this anymore, and instead uses the x-id-field directly.
323 18   33     79 my $id_field = $c->stash( 'id_field' ) // $schema->{'x-id-field'} // 'id';
      50        
324 18   100     326 my $id = $c->stash( $id_field ) // die sprintf 'ID field "%s" not defined in stash', $id_field;
325 16   50     192 my $user_id_field = $c->stash( 'user_id_field' ) // 'user_id';
326 16         184 my $item = $c->yancy->backend->get( $schema_name => $id );
327 16 100 66     118 if ( !$item || $item->{ $user_id_field } ne $user_id ) {
328 2         23 $c->reply->not_found;
329 2         218924 return 0;
330             }
331 14         188 return 1;
332             }
333              
334             1;
335              
336             __END__