The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

package Ado::Control;
our $DEV_MODE = ($ENV{MOJO_MODE} || '' =~ /dev/);
has description => 'Ado is a framework for web projects based on Mojolicious,'
. ' written in the Perl programming language.';
has keywords => 'SSOT, CRM, ERP, CMS, Perl, SQL';
sub generator { return 'Ado ' . $Ado::VERSION . ' - ' . $Ado::CODENAME }
sub config {
my ($c, $key) = @_;
state $app = $c->app;
return $app->config(ref $c)->{$key} if $key;
return $app->config(ref $c);
}
sub debug;
if ($DEV_MODE) {
sub debug {
my ($package, $filename, $line, $subroutine) = caller(1);
return shift->app->log->debug(@_, "$package:$filename:$line in $subroutine");
}
}
#Require a list of formats or render "415 - Unsupported Media Type"
#and return false.
sub require_formats {
my ($c, @formats) = @_;
unless ($c->accepts(@formats)) {
#propose urls with the accepted formats
my @locations = map { $c->url_for(format => $_)->to_abs } @formats;
$c->res->headers->add('Content-Location' => $locations[0]);
my $message =
"415 - Unsupported Media Type \""
. ($c->req->headers->accept // '')
. "\". Please try ${\ join(', ', @locations)}!";
$c->debug($c->url_for . " requires " . join(',', @formats) . ". Rendering: $message")
if $DEV_MODE;
$c->render(
text => $message,
status => 415
);
return;
}
return 1;
}
sub list_for_json {
my ($c, $range, $dsc_objects) = @_;
my $url = $c->url_with(format => $c->stash->{format})->query('limit' => $$range[0]);
my $prev = $$range[1] - $$range[0];
$prev = $prev > 0 ? $prev : 0;
#arrayref of hashes or DSC objects?
my $data =
ref($dsc_objects->[0]) eq 'HASH'
? $dsc_objects
: [map { $_->data } @$dsc_objects];
return {
json => {
#TODO: Strive to implement linking using this reference:
links => [
{ rel => 'self',
href => "" . $url->query([offset => $$range[1]])
},
( @$data == $$range[0]
? { rel => 'next',
href => "" . $url->query([offset => $$range[0] + $$range[1]])
}
: ()
),
( $$range[1]
? { rel => 'prev',
href => "" . $url->query([offset => $prev])
}
: ()
),
],
data => $data
},
};
} # end sub list_for_json
#validates input parameters given a rules template
sub validate_input {
my ($c, $template) = @_;
my $v = $c->validation;
my $errors = {};
foreach my $param (keys %$template) {
my $checks = $template->{$param};
$checks || next; #false or undefined?!?
#field
my $f =
$checks->{required}
? $v->required($param)
: $v->optional($param);
foreach my $check (keys %$checks) {
next if $check eq 'required';
if (ref $$checks{$check} eq 'HASH') {
$f->$check(%{$checks->{$check}});
}
elsif (ref $$checks{$check} eq 'ARRAY') {
$f->$check(@{$checks->{$check}});
}
else { $f->$check($checks->{$check}) }
} #end foreach my $check
$errors->{$param} = $f->error($param)
if $f->error($param);
} #end foreach my $param
return {
( !!keys %{$errors}
? ( errors => $errors,
json => {
status => 'error',
code => 400,
message => $errors,
data => 'validate_input'
}
)
: (output => $v->output)
)
};
}
sub user {
my ($c, $user) = @_;
if ($user) {
delete @{$user->data}{qw(login_password created_by changed_by disabled start_date)};
$c->{user} = $user;
return $c;
}
elsif ($c->{user}) {
return $c->{user};
}
$c->{user} = Ado::Model::Users->by_login_name($c->session->{login_name} //= 'guest');
delete @{$c->{user}->data}{qw(login_password created_by changed_by disabled start_date)};
return $c->{user};
}
1;
=pod
=encoding utf8
=head1 NAME
Ado::Control - The base class for all controllers!
=head1 SYNOPSIS
It must be inherited by all controllers.
Put code here only to be shared by it's subclasses or used in hooks.
package Ado::Control::Hello;
use Mojo::Base 'Ado::Control';
=head1 ATTRIBUTES
Ado::Control inherits all attributes from L<Mojolicious::Controller>
and implements the following new ones.
=head2 description
Returns a default description used in C<head> element of HTML pages.
=head2 generator
Returns the concatenated moniker, VERSION and L<CODENAME>.
=head2 keywords
Returns default keywords used in C<head> element of HTML pages.
=head1 SUBROUTINES/METHODS
Methods shared among subclasses and in hooks
=head2 config
Overwrites the default helper L<Mojolicious::Plugin::DefaultHelpers/config>
which is actually an alias for L<Mojo/config>.
Returns configuration specific to the I<current controller> package only.
#in Ado::Control::List or Ado::Control::Foo or...
my $myvalue = $c->config('mykey');
#a shortcut to
my $myvalue = $app->config(__PACKAGE__)->{mykey}
...
To access the application-wide configuration use C<$c-E<gt>app-E<gt>config('key')>.
=head2 debug
A shortcut to:
$c->app->log->debug(@_);
=head2 list_for_json
Prepares a structure suitable for rendering as JSON for
listing an ARRAYref of HASHES or L<Ado::Model>* objects,
returned by L<Ado::Model/select_range> and returns it.
Accepts two C<ARRAYREF>s as parameters:
my $res = $c->list_for_json([$limit, $offset], \@list_of_AMobjects_or_hashes);
Use this method to ensure uniform and predictable representation
across all listing resources.
and L<Ado::Control::Ado::Users/list> for the example source.
my @range = ($c->param('limit') || 10, $c->param('offset') || 0);
return $c->respond_to(
json => $c->list_for_json(\@range, [Ado::Model::Users->select_range(@range)])
);
return $c->respond_to(
json => $c->list_for_json(\@range, [$dbix->query($SQL,@range)->hashes])
);
=head2 require_formats
Checks for a list of accepted formats or renders "415 - Unsupported Media Type"
with a text/html type and links to the preferred formats, and returns false.
If the URL is in the required format, returns true.
Adds a header C<Content-Location> pointing to the first URL of the required formats.
#in an action serving only json
sub list {
my $c = shift;
$c->require_formats('json') || return;
$c->debug('rendering json only');
#your stuff here...
return;
}
This method exists only to show more descriptive message with available formats
to the end user and to give a chance to user agents to go to the preferred resource URL.
=head2 validate_input
Uses L<Mojolicious::Controller/validation> to validate all input parameters at once
given a validation template.
The template consists of keys matching the input parameters to be validated.
The values are HASH references describing the rules. Each rule name corresponds
to a method/check in L<Mojolicious::Validator/CHECKS>. You can use your own
checks if you add them using L<Mojolicious::Validator/add_check>.
Returns a HASH reference.
In case of errors it contains C<errors> and C<json> HASH references.
In case of success contains only C<output> HASH reference from
L<Mojolicious::Validator::Validation/output>.
my $rules = {
to_uid => {
'required' => 1, like => qr/^\d{1,20}$/
},
subject => {
'required' => 1, like => qr/^.{1,255}$/
},
#...
}
my $result = $c->validate_input($rules);
#400 Bad Request
return $c->render(
status => 400,
json => $result->{json}
) if $result->{errors};
=head2 user
Returns the current user. This is the user C<guest> for not authenticated users.
Note that this instance is not meant for manipulation and some fields are not available
for security reasons. The fields are:
C<login_password created_by changed_by disabled start_date>.
TODO: move as much as possible checks and fields retrieval in SQL, not in Perl.
$c->user(Ado::Model::Users->by_login_name($login_name));
my $names = $c->user->name;
=head1 SEE ALSO
L<Mojolicious::Controller>, L<Ado::Manual::Controllers>,
L<Ado::Manual::RESTAPI>, L<DBIx::Simple>
=head1 AUTHOR
Красимир Беров (Krasimir Berov)
=head1 COPYRIGHT AND LICENSE
Copyright 2013-2014 Красимир Беров (Krasimir Berov).
This program is free software, you can redistribute it and/or
modify it under the terms of the
GNU Lesser General Public License v3 (LGPL-3.0).
You may copy, distribute and modify the software provided that
modifications are open source. However, software that includes
the license may release under a different license.
=cut