view release on metacpan or search on metacpan
- Fixed bug in Ado::Sessions. It was failing to set default session type.
0.86 2015-03-30
- Started work in Ado::Build to handle upgrades beside installations
so the sqlite database is not replaced with the newly installed version
and thus destroing content of unconscious users - Alpha.
- Writing Ado::Manual::Intro.
- Upgraded to Mojolicious 6.05!
0.85 2015-03-22
- Added hook 'after_login', emited by Ado::Plugin::Auth.
- Added possibilty to add items to the adobar menu
via session->{adobar_links}.
- $CODENAME changed to
"землÑ" - U+2C08 GLAGOLITIC CAPITAL LETTER ZEMLJA (â°)
- Showing current controller and action only when in development mode
(templates/layouts/default.html.ep).
- Upgraded to Mojolicious::Plugin::SemanticUI 0.15!
- Upgraded to DBIx::Simple::Class 1.009!
- Upgraded to Mojolicious::Plugin::DSC 1.005!
- Upgraded to Mojolicious 6.03!
- Implemented do_sql_file application helper in Ado::Plugin::AdoHelpers.
- Improved documentation.
Added the newly created google group Ado-dev to Ado::Manual page.
- Upgraded to Mojolicious 5.81.
- Upgraded to Mojolicious::Plugin::DSC 1.002.
0.79 2015-02-15
- $ENV{MOJO_HOME} is not set explicitly any more.
Detecting properly app->home and falling back to Mojo::home.
- Cleanups.
- Moved Ado::Control::Ado to Ado-Plugin-Admin where it logically belongs.
- Fixed broken tests with Mojolicious < 5.78 - Renee Baecker (RENEEB). Thanks!
- Works with Mojolicious 5.79 too.
0.78 2015-02-01
- Improved documentation.
- Cleanups.
- Moved Ado::Control::Ado::Users and
Ado::Control::Ado::Default to Ado::Plugin::Admin distribution.
- Moved generic routes to be always defined last.
$c->reply->exception() instead.
- Started cleanups:
Removed templates/добÑе/ок.html.ep just to reduce
http://cpants.cpanauthors.org/dist/Ado/errors
- Noted in etc/ado.conf that arbitrary Perl code can be executed
when connecting to the database.
- POD improvements and cleanups.
- Upgraded to Mojolicious::Plugin::SemanticUI 0.11.
0.76 2014-12-14
- Cleaned up Login form. It is used for local login only.
- Not using flags for languages. Language menu is now a dropdown menu.
- Upgraded to Mojolicious::Plugin::DSC 1.000 to be able to execute
perl code upon connecting to the database.
- Implemented unicode aware upper() and lower() for SQLite.
- The name of the authentication method is now shown beside its icon
in the "Sign in" dropdown menu.
0.75 2014-11-26
- $CODENAME changed to
"Ðзело" - U+2C07 GLAGOLITIC CAPITAL LETTER DZELO (â°)
0.74 2014-11-22
- Fixed path to reset.min.js from Semantic-UI.
- Fixed regression with I18n and Mojolicious 5.62.
- Code optimisations.
0.73 2014-11-17
- Upgraded to Mojolicious::Plugin::SemanticUI 0.08.
- Works with Mojolicious 5.62.
0.72 2014-11-15
- Emphasized the passwordless login feature in Ado::Plugin::Auth.
- overeall improvement of Ado::Plugin::Auth documentation.
- Added Mojolicious::Plugin::OAuth2 as a recommended dependency.
It will be required only if some of the supported providers is enabled.
- Implemented experimental authentication via Google.
- Added hook "after_user_add" emited by Ado::Plugin::Auth.
- Added German language (Joachim Astel). Thanks!
- Improved Ado::Manual::Plugins a lot.
0.71 2014-11-01
- Fixed bug in Ado::Plugin::home_dir(). Wrong assumption regarding %INC keys.
in which some fields are not available.
- Improved Ado::Control::require_formats(@formats).
- Improved Ado::Model::Users::by_group_name($group, $limit, $offset).
- TODO: Improve Test Coverage.
0.69 2014-10-26
- $CODENAME changed to
"ÐивеÑе" - U+2C06 GLAGOLITIC CAPITAL LETTER ZHIVETE (â°)
- Switched from favicon.ico to favicon.png for better quality.
- Fixed popup positioning and appearance of the logo in the main menu.
- Added the condition 'ingroup' to Ado::Plugin::Auth.
- Implemented Ado::Model::Users::by_group_name($group, $limit, $offset).
0.68 2014-10-25
- Upgraded to Mojolicious 5.54.
- Added templates/not_found.html.ep.
- Upgraded to DBD::SQLite 1.44.
- Dramatic performance improvement with 'PRAGMA synchronous = OFF' and
'PRAGMA journal_mode=WAL'.
- Improved Ado::Command::adduser documentation.
0.44 2014-05-29
- Fixed typos in documentation.
0.43 2014-05-25
- Default page now has a Bulgarian variant.
0.42 2014-05-18
- Fixed typos and improved POD.
- Added Ado::Plugin::I18n to the list of the end-user
features at the bottom of the home page.
- Created and used templates/partials/logo.html.ep.
- Created embedded template languages_menu.html.ep in Ado::Plugin::I18n
and used it on the home page. This is a usage example.
- Added jQuery Cookie Plugin v1.4.1.
- Added some flag icons from http://www.famfamfam.com.
- Upgraded to Mojolicious 4.99.
- Upgraded to Semantic UI 0.17.0
0.41 2014-05-11
- Translated intro.md. to Bulgarian.
- Menu items in adobar.html.ep now are only icons.
- ЧеÑÑиÑа Ðаба ÐаÑÑа!
- Improved documentation.
- Added default/index.html.ep template and
default index page content.
- Upgraded to Mojolicious 4.85.
- Fixed failing test in t/sessions/database.t
0.34 2014-02-23
- Optimised /help User Unterface for hand-held devices.
- Added text in Bulgarian to cover.md, intro.md. admin.md, domains.md,
groups.md, users.md, cms.md, pages.md, articles.md, blog.md - TODO:
Translate them to English.
- Optimized Ado::Control::generator().
- Added markdown() helper to MarkdownRenderer plugin.
0.33 2014-02-20
- Really fixed failing test under Windows.
- Really upgraded to Mojolicious 4.83.
0.32 2014-02-19
- Fixed failing test under Windows.
- This is a bugfix release.
- Have a testing copy of ado.conf and ado.sqlite in t/etc.
- Checking for Test::Output in t/command/version.t.
- Creating README only if $ENV{TEST_AUTHOR}.
0.27 2014-01-29
- Upgraded to Mojolicious 4.71.
- Experimental CORS-ready Ado::Sessions with file
and database storage.
- Fixed bug in Ado::Build::ACTION_dist.
It was not emptying log files.
0.26 2014-01-11
- Improved Ado::Command::version output.
- Improved Ado::Manual.
- Not depending on Mojo::Util in Ado::Build.
- Fixed small bug in create_readme() and optimized it.
- Installing to the more popular "siteprefix" by default.
- Fixed failing test because of missing Test::Output.
0.25 2014-01-01 20:27:50 CET
- Added experimental tests for Ado::Build.
- Fixed ACTION_perltidy - did not tidy Build.PL
- Dynamically generating META.json and META.yml
depending on if $ENV{TEST_AUTHOR} is set
thus requiring fewer dependencies.
- Updated Manual.pod
- Added uninstall and fakeuninstall actions to Ado::Build (Valcho).
- Fixed wrong install_path for lib directory
when "--install_base" is not passed as argument.
- Using Perl's $Config{siteprefixexp} for
other directories: qw(etc public log templates).
0.22 2013-12-29 03:30:38 CET
- Refactored creation of README.md, kept README.
- Wellcome Valcho to the core team :)!
- Removed $ADO_HOME (by popular demand)
and symplified installation process a lot.
- Improved Manual.pod/README.
0.21 2013-12-28 03:37:42 CET
- Added build action submit (draft).
- Skipping pod-coverage.t under Perl 5.014
0.19 2013-12-23 00:37:22 CET
- More strict checks regarding --install_base.
use 5.014 to enable say().
0.18 2013-12-22 17:34:32 CET
- Removed release_status=>'unstable' from Build.PL.
This does not mean much.
- Started developing an external plugin - Ado::Plugin::Mess.
- First attempt for a logo.
0.17 2013-12-21 00:11:06 CET
- Refactored list() in Ado::Control::Ado::Users
and implemented list_for_json in Ado::Control.
- Added test users test1 and test2.
- Added dummy missing actions to Ado::Control::Ado::Users.
- Added $c->require_formats(['json', 'xml', 'etc']) to Ado::Control.
- Enhanced _set_env in Ado::Build.
- Lower required Perl version to 5.014000.
- Enhanced POD.
- Set lib and arch install paths to "lib".
- Enhanced documentation.
- Implemented custom automatic README
generation from Ado::Nabual.
- Improved Build.PL
0.14 2013-12-14 14:11:45 CET
- Setting explicitly MOJO_MODE in bin/ado
- Better detection for MOJO_HOME and MOJO_CONFIG
- Enhanced the Build process,
added ACTION_build, ACTION_dist process_log_files
and _empty_log_files to Ado::Build.
- Enhanced Ado::Plugin::Routes, added tests.
- Implemented Ado::Plugin::Routes.
- Again changed the routes structure in config.
Every route description is simply a HASHREF.
- Enhanced documentation.
- Reenabled pod-coverage tests.
0.13 2013-12-10 01:44:30 CET
- Removed usage of PoweredBy.
- Lower required Perl version to 5.014002.
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
lib/Ado/Plugin.pm
lib/Ado/Plugin/AdoHelpers.pm
lib/Ado/Plugin/Auth.pm
lib/Ado/Plugin/I18n.pm
lib/Ado/Plugin/MarkdownRenderer.pm
lib/Ado/Plugin/Routes.pm
lib/Ado/Sessions.pm
lib/Ado/Sessions/Database.pm
lib/Ado/Sessions/File.pm
LICENSE
log/development.log
log/production.log
log/README
MANIFEST This list of files
META.json
META.yml
public/articles/ala/bala.md
public/articles/hello.md
public/css/ado.css
public/css/flags/bg.png
public/css/flags/de.png
public/css/flags/en.png
public/css/flags/es.png
public/css/flags/fr.png
public/css/flags/readme.txt
public/css/flags/ru.png
public/css/ui-bg_diagonal-maze_light_10x10.png
public/doc/bg/accounting.md
public/doc/bg/admin.md
public/doc/bg/apps.md
public/doc/bg/articles.md
public/doc/bg/blog.md
public/doc/bg/cms.md
public/doc/bg/cover.md
public/doc/bg/crm.md
public/doc/bg/domains.md
public/doc/bg/groups.md
public/doc/bg/img/default_admin_screen.epz
public/doc/bg/img/default_admin_screen.png
public/doc/bg/intro.md
public/doc/bg/messages.md
public/doc/bg/no_title.md
templates/articles/show.html.ep
templates/default/index.html.ep
templates/default/index_text.html+bg.ep
templates/default/index_text.html+de.ep
templates/default/index_text.html.ep
templates/doc/show.html.ep
templates/exception.html.ep
templates/layouts/articles.html.ep
templates/layouts/default.html.ep
templates/layouts/doc.html.ep
templates/login.html.ep
templates/not_found.html.ep
templates/partials/adobar.html.ep
templates/partials/apache2htaccess.ep
templates/partials/apache2vhost.ep
templates/partials/authbar.html.ep
templates/partials/head.html.ep
templates/partials/login_form.html.ep
templates/partials/logo.html.ep
templates/test/ado_helpers.html.ep
based on Mojolicious, written in the Perl programming language
<http://www.perl.org/>. Ado is a typical well structured, MVC
<http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>
Mojolicious application. It aims to allow teams to build on it a CMS, ERP,
CRM or all of them integrated together.
Please note that the project is still a work-in-progress. Parts of it
work, others are simply missing.
GETTING STARTED
* Start a personal blog with Ado::Manual::FiveMinutes if you are
impatient.
* Read the Ado::Manual::Intro for a general overview.
* To see different installation methods, go to
Ado::Manual::Installation.
* To learn about plugins and how to write your own plugin, see
Ado::Manual::Plugins.
language](http://www.perl.org/). Ado is a typical well structured,
[MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
[Mojolicious](https://metacpan.org/pod/Mojolicious) application. It aims to allow teams to build on it a CMS, ERP,
CRM or all of them integrated together.
Please note that the project is still a work-in-progress. Parts of it work,
others are simply missing.
# GETTING STARTED
- Start a personal blog with [Ado::Manual::FiveMinutes](https://metacpan.org/pod/Ado::Manual::FiveMinutes) if you are impatient.
- Read the [Ado::Manual::Intro](https://metacpan.org/pod/Ado::Manual::Intro) for a general overview.
- To see different installation methods, go to
[Ado::Manual::Installation](https://metacpan.org/pod/Ado::Manual::Installation).
- To learn about plugins and how to write your own plugin, see [Ado::Manual::Plugins](https://metacpan.org/pod/Ado::Manual::Plugins).
# SUPPORT AND DOCUMENTATION
After installing, you can find documentation with the perldoc command.
perldoc Ado
etc/ado-sqlite-schema.sql view on Meta::CPAN
changed_by INTEGER REFERENCES users(id),
disabled INT(1) NOT NULL DEFAULT 1
);
-- 'This table stores the users'
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- 'Primary group for this user'
group_id INTEGER REFERENCES groups(id),
login_name varchar(100) UNIQUE,
-- 'Mojo::Util::sha1_hex($login_name.$login_password)'
login_password varchar(40) NOT NULL,
first_name varchar(100) NOT NULL DEFAULT '',
last_name varchar(100) NOT NULL DEFAULT '',
email varchar(255) NOT NULL UNIQUE,
description varchar(255) DEFAULT NULL,
-- 'id of who created this user.'
created_by INTEGER REFERENCES users(id),
-- 'Who modified this user the last time?'
changed_by INTEGER REFERENCES users(id),
-- 'last modification time'
-- 'All dates are stored as seconds since the epoch(1970) in GMT. In Perl we use gmtime as object from Time::Piece'
etc/ado.conf view on Meta::CPAN
'SemanticUI',
#These are Ado plugins, loading their own config files.
'ado_helpers',
'markdown_renderer',
'auth',
'i18n',
#'admin',
#'vest',
#'blog',
#put your own routes for your private code in
# etc/plugins/routes.$mode.conf
'routes',
],
#Configuration for commands
#Every Ado::Command:: ISA Mojolicious::Command
commands => {},
#Routes order is important, so this configuration value is an ARRAYREF
etc/plugins/auth.conf view on Meta::CPAN
# key =>'123456654321abcd.apps.googleusercontent.com',
# secret =>'Y0uRS3cretHEre',
# scope=>'profile email',
# info_url => 'https://www.googleapis.com/userinfo/v2/me',
# },
},
#Add your routes definitions here.
routes => [
#the login form and authentication
{ route => '/login/:auth_method',
via => ['GET', 'POST'],
to => {
auth_method => 'ado',
cb => \&Ado::Plugin::Auth::login
},
description => 'GET: Renders a login form when :auth_method is "ado".'
. ' or authenticates a user when :auth_method is an OAuth2 provider.'
. $/
. 'POST: Authenticates a user.',
},
{ route => '/authorize/:auth_method',
via => ['GET', 'POST'],
to => {
auth_method => 'google',
cb => \&Ado::Plugin::Auth::authorize
},
},
{ route => '/logout',
to => {cb => \&Ado::Plugin::Auth::logout}
},
{ route => '/test/authenticateduser',
over => {authenticated => 1},
to => 'test#authenticateduser'
},
],
};
: $app->ado_home->rel_file('etc/' . lc($CLASS) . '.conf');
$app->plugin('Config');
return $app;
}
sub load_plugins {
my ($app) = @_;
my $plugins = $app->config('plugins') || [];
foreach my $plugin (@$plugins) {
$app->log->debug('Loading Plugin ' . (ref $plugin ? $plugin->{name} : $plugin));
if (ref $plugin eq 'HASH') {
$app->plugin($plugin->{name} => $plugin->{config});
}
elsif ($plugin) {
$app->plugin($plugin);
}
}
return $app;
}
lib/Ado/Build.pm view on Meta::CPAN
sub PERL_DIRS {
state $dirs = [map { catdir($_[0]->base_dir, $_) } qw(bin lib etc t)];
return @$dirs;
}
sub create_build_script {
my $self = shift;
#Deciding where to install
my $prefix = $self->install_base || $self->config('siteprefix');
for my $be (qw(etc public log templates)) {
#in case of installing a plugin, check if folder exists
next unless -d $be;
$self->add_build_element($be);
$self->install_path($be => catdir($prefix, $be));
}
return $self->SUPER::create_build_script();
}
sub process_public_files {
lib/Ado/Build.pm view on Meta::CPAN
for my $asset (@{$self->rscan_dir('etc')}) {
if (-d $asset) {
make_path(catdir('blib', $asset));
next;
}
# skip SQLite automatically created files
next if ($asset =~ m/ado\-(shm|wal)/);
my $asset_path = catfile($prefix, $asset);
if ($asset =~ m/ado\.sqlite/ && -s $asset_path) {
$self->log_info("Skipping $asset because it already exists at $asset_path! $/");
next;
}
copy($asset, catfile('blib', $asset));
}
return;
}
sub process_log_files {
my $self = shift;
for my $asset (@{$self->rscan_dir('log')}) {
if (-d $asset) {
make_path(catdir('blib', $asset));
next;
}
copy($asset, catfile('blib', $asset));
}
return;
}
sub process_templates_files {
lib/Ado/Build.pm view on Meta::CPAN
}
sub ACTION_fakeuninstall {
my $self = shift;
return $self->_uninstall('dry-run');
}
sub ACTION_build {
my $self = shift;
#Make sure *log files are empty before moving them to blib
_empty_log_files('log');
#Do other interventions before the real build...
$self->SUPER::ACTION_build;
return;
}
sub ACTION_test {
my $self = shift;
#Custom functionality before test
$self->_process_custom_files(catdir('blib', 'etc'), catdir('blib', 'log'))
if -d 'blib';
$self->_process_custom_files('etc', 'log');
$self->SUPER::ACTION_test;
return;
}
sub ACTION_dist {
my $self = shift;
#Make sure *log files are empty before including them into the distro
_empty_log_files('log');
#Do other interventions before the real dist..
$self->SUPER::ACTION_dist;
return;
}
sub ACTION_install {
my $self = shift;
#Custom functionality before installation
#here...
#TODO: Think about what to do with *.conf and *.sqlite files in case of upgrade!!!
#TODO: (upgrade)rotate logs - archive existing log files before emptying.
$self->SUPER::ACTION_install;
#Custom functionality after installation
$self->_process_custom_files($self->install_path('etc'), $self->install_path('log'));
return;
}
sub _process_custom_files {
my ($self, $etc_dir, $log_dir) = @_;
#make some files writable and/or readable only by the user that runs the application
my $ro = oct('0400');
my $rw = oct('0600');
for my $asset (
qw(ado.conf plugins/routes.conf plugins/auth.conf plugins/markdown_renderer.conf))
{
_chmod($ro, catfile($etc_dir, $asset));
}
_chmod($rw, catfile($etc_dir, 'ado.sqlite'));
#Make sure *log files are existing and empty
_empty_log_files($log_dir);
for my $asset (qw(development production)) {
_chmod($rw, catfile($log_dir, "$asset.log"));
}
return;
}
sub _chmod {
my ($mode, $file) = @_;
return chmod($mode, $file)
|| Carp::carp("Could not change mode for $file: $!");
}
sub ACTION_perltidy {
my $self = shift;
eval { require Perl::Tidy } || do {
$self->log_warn(
"Perl::Tidy is not installed$/" . "Please install it and rerun ./Build perltidy$/");
return;
};
my @files;
for my $dir ($self->PERL_DIRS) {
my $dir_files = $self->rscan_dir($dir);
for my $file (@$dir_files) {
push @files, $file
if -f $file && $file =~ m{(\.pl|/ado|\.pm|ado.*?\.conf|\.t)$}x;
}
lib/Ado/Build.pm view on Meta::CPAN
sub ACTION_submit {
my $self = shift;
#$self->depends_on("perltidy");
say "TODO: commit and push after tidying and testing and who knows what";
return;
}
#Empties log files in a given directory.
sub _empty_log_files {
(my ($log_dir) = @_) || Carp::croak('Please provide $log_dir');
open my $logd, ">", "$log_dir/development.log" || Carp::croak $!;
close $logd;
open my $logp, ">", "$log_dir/production.log" || Carp::croak $!;
close $logp;
return;
}
sub do_create_readme {
my $self = shift;
if ($self->dist_version_from =~ /Ado\.pm$/) {
#Create README from Ado::Manual.pod
require Pod::Text;
my $readme_from = catfile('lib', 'Ado', 'Manual.pod');
my $parser = Pod::Text->new(sentence => 0, indent => 2, width => 76);
$parser->parse_from_file($readme_from, 'README');
$self->log_info("Created README$/");
#add README.md just to be cool..
eval { require Pod::Markdown }
|| return $self->log_warn('Pod::Markdown required for creating README.md' . $/);
$parser = Pod::Markdown->new;
$parser->parse_from_file($readme_from);
my $readme_md = 'README.md';
if (open(my $out, '>', $readme_md)) {
my $markdown = $parser->as_markdown;
my $ci_badge =
'[](https://travis-ci.org/kberov/Ado)';
$markdown =~ s/(\n.+Travis-CI.+\n)/$1\n$ci_badge\n\n/xgm;
$out->say($markdown);
$out->close;
$self->log_info("Created $readme_md$/");
}
else { Carp::croak("Could not create $readme_md... $!"); }
}
else {
$self->SUPER::do_create_readme();
}
return;
}
1;
lib/Ado/Build.pm view on Meta::CPAN
=head1 METHODS
Ado::Build inherits all methods from L<Module::Build> and implements
the following ones.
=head2 create_build_script
This method is called in C<Build.PL>.
In this method we also call C<add_build_element> for C<etc> C<public>,
C<templates> and C<log> folders.
Finally we set all the C<install_path>s for the distro
and we call C<$self-E<gt>SUPER::create_build_script>.
=head2 process_etc_files
Moves files found in C<Ado/etc> to C<Ado/blib/etc>.
See L<Module::Build::API/METHODS>
Returns void.
=head2 process_log_files
Moves files found in C<Ado/log> to C<Ado/blib/log>.
Returns void.
=head2 process_public_files
Moves files found in C<Ado/public> to C<Ado/blib/public>.
Returns void.
=head2 process_templates_files
Moves files found in C<Ado/templates> to C<Ado/blib/templates>.
lib/Ado/Command.pm view on Meta::CPAN
#first try (global config) !!!autovivification
my $config = $app->config->{commands} && $app->config->{commands}->{$name};
$config && return $config;
#second try (command specific configuration file)
my $conf_file = $app->home->rel_file('/etc/commands/' . decamelize($name) . '.conf');
if ($config = eval { Mojolicious::Plugin::Config->new->load($conf_file, {}, $app) }) {
return $config;
}
else {
$app->log->warn(
"Could not load configuration from file $conf_file! " . decode('UTF-8', $@));
return {};
}
}
sub config {
my ($self, $key) = @_;
state $config = $self->_get_command_config();
return $key
? $config->{$key}
lib/Ado/Command/adduser.pm view on Meta::CPAN
has args => sub {
my $t = time;
{ changed_by => 1,
created_by => 1,
disabled => 1,
#TODO: add funcionality for notifying users on account expiration
#TODO: document this
stop_date => $t + ONE_YEAR, #account expires after one year
start_date => $t,
login_password => rand($t) . $$ . {} . $t,
};
};
sub init {
my ($self, @args) = @_;
$self->SUPER::init();
unless (@args) { Carp::croak($self->usage); }
my $args = $self->args;
my $ret = GetOptionsFromArray(
\@args,
'u|login_name=s' => \$args->{login_name},
'p|login_password=s' => \$args->{login_password},
'e|email=s' => \$args->{email},
'g|ingroup=s' => \$args->{ingroup},
'd|disabled:i' => \$args->{disabled},
'f|first_name=s' => \$args->{first_name},
'l|last_name=s' => \$args->{last_name},
'start_date=s' => sub {
$args->{start_date} =
$_[1] ? Time::Piece->strptime('%Y-%m-%d', $_[1])->epoch : time;
},
);
# Assume an UTF-8 terminal. TODO: make this more clever
utf8::decode($args->{login_name})
if ($args->{login_name} && !utf8::is_utf8($args->{login_name}));
utf8::decode($args->{first_name})
if ($args->{first_name} && !utf8::is_utf8($args->{first_name}));
utf8::decode($args->{last_name})
if ($args->{last_name} && !utf8::is_utf8($args->{last_name}));
$args->{login_password} = Mojo::Util::sha1_hex($args->{login_name} . $args->{login_password});
unless ($args->{ingroup}) {
say($self->usage)
unless ($args->{first_name}
and $args->{last_name}
and $args->{login_name}
and $args->{email});
}
$self->app->log->debug('$self->args: ' . $self->app->dumper($self->args));
return $ret;
}
#default action
sub adduser {
my $self = shift;
my $args = $self->args;
my ($group, $user, $ingroup);
if (($group = Ado::Model::Groups->by_name($args->{login_name}))->id) {
$self->app->log->debug('$group:', $self->app->dumper($group));
#if we have such group, we have the user or we do not want to give a user
#the privileges of a shared group
say "'$args->{login_name}' is already taken!";
}
else {
$user = Ado::Model::Users->add($args) unless $group->id;
return unless $user;
}
if ($user) {
say "User '$args->{login_name}' was created with primary group '$args->{login_name}'.";
}
else {
$user = Ado::Model::Users->by_login_name($args->{login_name});
}
return unless $args->{ingroup};
if (not $user->ingroup($args->{ingroup})) {
if ($ingroup = $user->add_to_group($args)) {
say "User '$args->{login_name}' was added to group '$args->{ingroup}'.";
}
}
else {
say "User '$args->{login_name}' is already in group '$args->{ingroup}'.";
}
return 1;
}
1;
=pod
=encoding utf8
=head1 NAME
Ado::Command::adduser - adduser command
=head1 SYNOPSIS
USAGE
# On the command line
# Minimal required options to add a user
ado adduser --login_name USERNAME --email user\@example.com \
--first_name John --last_name Smith
# Add a user to an additional group
ado adduser --login_name USERNAME --ingroup GROUPNAME
# Change password / disable a user
ado adduser --login_name USERNAME --login_password N3W$36RE7P1$5W
# Disable a user
ado adduser --login_name USERNAME --disabled
# Programatically
use Ado::Command::adduser;
Ado::Command::adduser->run('--login_name'=>'test1',...);
=head1 DESCRIPTION
L<Ado::Command::adduser> adds a user to an L<Ado> application.
It is a facade for L<Ado::Model::Users>.
This is a core L<Ado> command, that means it is always available and its code
a good example for learning to build new L<Ado> commands, you're welcome to fork it.
=head1 ATTRIBUTES
L<Ado::Command::adduser> inherits all attributes from
L<Ado::Command> and implements the following new ones.
=head2 args
$self->args(login_name=>'peter','ingroup'=>'facebook');
my $args = $self->args;
Default arguments for creating a user.
=head2 description
my $description = $a->description;
$a = $a->description('Foo!');
lib/Ado/Command/adduser.pm view on Meta::CPAN
my $usage = $a->usage;
$a = $a->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 OPTIONS
On the commandline C<ado adduser> accepts the following options:
'u|login_name=s' #username (mandatory)
'p|login_password=s' #the user password (optional, random is generated)
'e|email=s' #user email (mandatory)
'g|ingroup=s' #existing users can be added to other groups too
'd|disabled:i' #is user disabled? (1 by default)
'f|first_name=s' #user's first name (mandatory)
'l|last_name=s' #user's last name (mandatory)
'start_date=s' #format: %Y-%m-%d (optional, today by default)
=head1 METHODS
L<Ado::Command::adduser> inherits all methods from
lib/Ado/Command/adduser.pm view on Meta::CPAN
=head2 init
Calls the default parent L<Ado::Command/init> and parses the arguments
passed on the command-line. Returns true on success.
Croaks with L</usage> message on failure.
=head2 adduser
The default and only action this command implements.
Makes logical checks for existing user and group and calls
L<Ado::Model::Users/adduser> and L<Ado::Model::Users/add_to_group>
depending on parsed arguments.
See L<Ado::Command/run>.
=head1 SEE ALSO
L<Ado::Model::Users>,
L<Ado::Command> L<Ado::Manual>, L<Mojolicious::Command>,
L<Mojolicious>, L<Mojolicious::Guides>.
lib/Ado/Command/generate/adoplugin.pm view on Meta::CPAN
=head1 NAME
Ado::Command::generate::adoplugin - Generates an Ado::Plugin
=head1 SYNOPSIS
On the command-line:
$ cd ~/opt/public_dev
# Ado is "globally" installed for the current perlbrew Perl
$ ado generate adoplugin --name MyBlog
$ ado generate adoplugin --name MyBlog --crud -t 'articles,news'
Programmatically:
use Ado::Command::generate::adoplugin;
my $vhost = Ado::Command::generate::adoplugin->new;
$vhost->run(-n => 'MyBlog', -c => 1, -t => 'articles,news');
=head1 DESCRIPTION
L<Ado::Command::generate::adoplugin> generates directory structures for
fully functional L<Ado>-specific plugins with optional
L<MVC set of files|Ado::Command::generate::crud> in the newly created plugin directory.
The new plugin is generated in the current directory.
This is a core command, that means it is always enabled and its code a
more complex example for learning to build new commands. You're welcome to fork it.
lib/Ado/Command/generate/apache2vhost.pm view on Meta::CPAN
'v|verbose' => \$args->{verbose},
'u|user=s' => \$args->{user},
'g|group=s' => \$args->{group},
's|with_suexec' => \$args->{with_suexec};
Carp::croak $self->usage unless $args->{ServerName};
$args->{ServerAlias} //=
$$args{ServerName} =~ /^www\./ ? $$args{ServerName} : 'www.' . $$args{ServerName};
$args->{ServerAdmin} //= 'webmaster@' . $args->{ServerName};
$args->{user} //= ($ENV{USER} || getlogin || 'nobody');
$args->{group} //= $args->{user};
$args->{DocumentRoot} =~ s|\\|/|g if $IS_DOS;
say STDERR 'Using arguments:' . $app->dumper($args) if $args->{verbose};
state $rel_file = 'templates/partials/apache2vhost.ep';
state $template_file = (
-s $home->rel_file($rel_file)
? $home->rel_file($rel_file)
: $ado_home->rel_file($rel_file)
);
lib/Ado/Control.pm view on Meta::CPAN
state $app = $_[0]->app;
return $app->config(ref $_[0])->{$_[1]} if $_[1]; #if key
return $app->config(ref $_[0]);
}
sub debug;
if ($DEV_MODE) {
sub debug {
my ($package, $filename, $line, $subroutine) = caller(0);
state $log = $_[0]->app->log;
return $log->debug(
@_[1 .. $#_] #, " at $filename:$line"
);
}
}
#Require a list of formats or render "415 - Unsupported Media Type"
#and return false.
sub require_formats {
my ($c, @formats) = @_;
unless ($c->accepts(@formats)) {
lib/Ado/Control.pm view on Meta::CPAN
data => 'validate_input'
}
)
: (output => $v->output)
)
};
}
sub user {
my ($c, $user) = @_;
state $delete_fields = [qw(login_password created_by changed_by disabled start_date email)];
if ($user) {
# Remove as much as possible user data.
delete @{$user->data}{@$delete_fields};
$c->{user} = $user;
return $c;
}
elsif ($c->{user}) {
return $c->{user};
}
# Called for the first time without a $user object.
# Defaults to current user or Guest.
$c->{user} = Ado::Model::Users->by_login_name($c->session->{login_name} //= 'guest');
delete @{$c->{user}->data}{@$delete_fields};
return $c->{user};
}
1;
=pod
=encoding utf8
lib/Ado/Control.pm view on Meta::CPAN
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<ARRAY> references and one arbitrary C<HASH>
reference as parameters:
my $res = $c->list_for_json([$limit, $offset], \@list_of_AMobjects_or_hashes, $meta);
lib/Ado/Control.pm view on Meta::CPAN
#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
lib/Ado/Control/Articles.pm view on Meta::CPAN
#in your browser go to
http://your-host/articles
=head1 DESCRIPTION
Ado::Control::Articles is a controller that generates full static HTML
documents for markdown files found in the folder specified in
C<md_articles_root> in C<etc/plugins/markdown_renderer.$mode.conf>. It allows
you to have a simple static blog on Ado in no time, i.e. install Ado and you
have a personal blog.
=head1 METHODS/ACTIONS
L<Ado::Control::Articles> inherits all the methods from
L<Ado::Control> and defines the following.
=head2 show
Renders the file found in C<$c-E<gt>stash('html_file')> but with extension
C<.md>. If C<$config-E<gt>{md_reuse_produced_html}> is set, the produced html
lib/Ado/Control/Test.pm view on Meta::CPAN
$c->debug('$$stash{language_from}:' . $$stash{language_from});
$$stash{language_from} ||= $c->param('from');
$c->debug('$$stash{language_from}:' . $$stash{language_from});
return $c->render();
}
# Test Ado::Model::Users->by_group_name
sub ingroup {
my $c = shift;
#get users from group with the same name as the user login_name
my @users = Ado::Model::Users->by_group_name($c->user->login_name, $c->param('offset'),
$c->param('limit'));
return $c->render(json => [@users]);
}
sub guest {
my $c = shift;
#get users from group with the same name as the user login_name
state $guest = Ado::Model::Users->by_login_name('guest')->data;
return $c->render(json => $guest);
}
1;
=pod
=encoding utf8
=head1 NAME
lib/Ado/I18n/bg.pm view on Meta::CPAN
package Ado::I18n::bg;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = ( ##no critic(ProhibitPackageVars)
hello => 'ÐдÑаÑÑи, [_1]!',
Login => 'ÐпиÑване',
Logout => 'ÐзÑ
од',
Help => 'ÐомоÑ',
login_name => 'ÐоÑÑебиÑел',
login_password => 'ÐаÑола',
login_field_error =>
'ÐÐ¾Ð»Ñ Ð²ÑведеÑе валидна ÑÑойноÑÑ Ð·Ð° полеÑо "[_1]"!',
first_name => 'Ðме',
last_name => 'ФамилиÑ',
email => 'Ð-поÑа',
title => 'Ðаглавие/Ðме',
tags => 'ÐÑикеÑи',
time_created => 'Created on',
sorting => 'ÐодÑедба',
data_type => 'Тип',
data_format => 'ФоÑмаÑ',
lib/Ado/I18n/de.pm view on Meta::CPAN
package Ado::I18n::de;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = ( ##no critic(ProhibitPackageVars)
hello => 'Hallo [_1],',
Logout => 'Ausloggen',
login_name => 'Benutzer',
login_password => 'Passwort',
login_field_error => 'Bitte gültigen Wert in Feld "[_1]" eintragen!',
first_name => 'Vorname',
last_name => 'Nachname',
title => 'Titel/Name',
tags => 'Tags',
time_created => 'Angelegt am',
tstamp => 'Verändert am',
body => 'Inhalt (body)',
invisible => 'Versteckt',
language => 'Sprache',
group_id => 'Gruppe',
lib/Ado/I18n/en.pm view on Meta::CPAN
package Ado::I18n::en;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = ( ##no critic(ProhibitPackageVars)
hello => 'Hello [_1],',
Logout => 'Sign out',
login_name => 'User',
login_password => 'Password',
login_field_error => 'Please enter a valid value for the field "[_1]"!',
first_name => 'First Name',
last_name => 'Last Name',
title => 'Title/Name',
tags => 'Tags',
time_created => 'Created on',
tstamp => 'Changed on',
body => 'Content (body)',
invisible => 'Invisible',
language => 'Language',
group_id => 'Group',
lib/Ado/I18n/en.pm view on Meta::CPAN
DESC => 'Descending',
created_by => 'Created by',
created_by_help => 'Who created this user?',
changed_by_help => 'Who modified this user the last time?',
changed_by => 'Changed by',
reg_date => 'Date of registration',
disabled => 'Locked',
description => 'Description',
'Sign in' => 'Sign in',
Translate => 'Translate',
LoginThanks => 'Thanks for logging in! Welcome!',
);
1;
=pod
=encoding utf8
=head1 NAME
lib/Ado/Manual.pod view on Meta::CPAN
L<Mojolicious> application. It aims to allow teams to build on it a CMS, ERP,
CRM or all of them integrated together.
Please note that the project is still a work-in-progress. Parts of it work,
others are simply missing.
=head1 GETTING STARTED
=over
=item * Start a personal blog with L<Ado::Manual::FiveMinutes> if you are impatient.
=item * Read the L<Ado::Manual::Intro> for a general overview.
=item * To see different installation methods, go to
L<Ado::Manual::Installation>.
=item * To learn about plugins and how to write your own plugin, see L<Ado::Manual::Plugins>.
=back
lib/Ado/Manual/Contributing.pod view on Meta::CPAN
"RESTful Service Best Practices" at L<www.RestApiTutorial.com>.
You must have a reference implementation of a browser-based user-agent(yourpluginroute.html) residing in
C<Ado/public/>. It would be best if your code is CORS-ready this
should be easy if your code depends on Ado::Plugin::CORS(TODO).
See L<Ado::Manual::RESTAPI>(TODO). Feel free to propose additional best
practices. Be ready to explain well your proposal.
=head2 TEMPLATES
We have a minimal set of templates residing in the C<templates> directory.
Separation of concerns must be to its maximum. Strive for minimal logic in
your templates if you have such.
=head2 MODEL
Put your database related code under the L<Model> namespace.
Use L<DBIx::Simple::Class>.
Generate your classes from tables using C<dsc_dump_schema.pl> utility
that comes with DBIx::Simple::Class.
Feel free to contribute to L<DBIx::Simple::Class>.
Minimize SQL complexity. Write ANSI SQL if you need to write SQL.
lib/Ado/Manual/FiveMinutes.pod view on Meta::CPAN
=encoding utf8
=head1 NAME
Ado::Manual::FiveMinutes - Start a personal blog in five minutes.
=head1 FIVE MINUTES
Within five minutes we will use L<Ado::Plugin::MarkdownRenderer> and
L<Ado::Control::Articles> to create a personal blog.
Create a directory for L<Ado> and install it in it.
mkdir ~/ado
curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org -l ~/ado -n Ado
Start Ado in development mode.
cd ~/ado
morbo -w ./lib -w ./site_templates -w templates bin/ado
lib/Ado/Manual/Intro.pod view on Meta::CPAN
â  âââ Command # Commands namespace for commands such as Ado::Command::adduser
â  â  âââ generate # Generators such as Ado::Command::generate::apache2vhost are here
â  âââ Control # Controller namespace where controller classes
â  â # like Ado::Control::Users reside
â  âââ I18n # Namespace for lexicon packages such as Ado::I18n::bg
â  âââ Manual # Namespace for developer manuals like this very file
â  âââ Model # Controller namespace where controller classes
â  â # like Ado::Model::Users reside
â  âââ Plugin # Ado plugins namespace, e.g. Ado::Plugin::Auth
â  âââ Sessions # Server side sessions - e.g. Ado::Sessions::Database
âââ log # Log directory
âââ public # Static file directory (served automatically)
â  â # Good for generated static pages served by e.g. Apache or Nginx
â  âââ css
â  â  âââ flags
â  âââ doc
â  â  âââ bg
â  â  â  âââ img
â  â  âââ en
â  âââ fonts
â  âââ img
lib/Ado/Manual/Plugins.pod view on Meta::CPAN
C<app-E<gt>home-E<gt>rel_dir('etc/plugins/hello.conf')>. The file must return
a HASHREF. See the code of the listed plugins below for examples of how and
what can be done in a plugin.
To create your own plugin do the following (Example):
=over
=item * Create one or more tables to be used by your plugin in C<etc/ado.sqlite>
CREATE TABLE blog (
id INTEGER PRIMARY KEY,
title VARCHAR NOT NULL UNIQUE,
body TEXT NOT NULL,
published BOOL DEFAULT '0',
deleted BOOL NOT NULL DEFAULT '0',
user_id INTEGER REFERENCES users(id),
group_id INTEGER REFERENCES groups(id),
permissions VARCHAR(10) DEFAULT '-rwxr-xr-xr'
);
CREATE INDEX blog_published ON blog(published);
CREATE INDEX blog_deleted ON blog(deleted);
=item * Add some dummy records.
INSERT INTO blog(title,body,user_id,group_id)
VALUES('Hey','Hello world',3,3);
INSERT INTO blog(title,body,user_id,group_id)
VALUES('Hey You','Hello Universe',3,3);
=item * Generate the files for the plugin. These are the files which you will edit :).
$ cd ~/opt/public_dev
$ ado generate adoplugin -n Blog --crud -t blog
The above command will generate the needed files for an ado plugin which can
even be uploaded to and subsequently downloaded from
L<CPAN|http://www.cpan.org/>. L<CPAN> is the best open source dependency
management system. You can also use L<Stratopan|https://stratopan.com/> if you
wish.
=back
Ado uses L<Ado::Build> and L<Ado::BuildPlugin> which extend L<Module::Build>.
They were created to add some custom actions and handle the additional
C<templates>,C<log> and C<public> directories in Ado root folder.
The file tree looks like the following:
~/opt/public_dev/Ado-Plugin-Blog$ tree
.
âââ Build.PL
âââ etc
â  âââ plugins
â  âââ blog.conf
âââ lib
â  âââ Ado
â  âââ Control
â  â  âââ Blog.pm
â  âââ Plugin
â  âââ Blog.pm
âââ templates
â  âââ blog
â  âââ create.html.ep
â  âââ delete.html.ep
â  âââ list.html.ep
â  âââ read.html.ep
âââ t
âââ plugin
âââ blog-00.t
No worries, your plugin has everything needed to be installed from CPAN.
L<Ado::Plugin::Vest> was started using this command.
L<Ado> can be stripped down to a bare Mojolicious application by not loading
any plugins. And L<Ado> can be extended I<infinitely> just by adding helpers,
conditions, routes, templates and injecting code into hooks from plugins.
I<This is true for any Mojolicious application.>
lib/Ado/Model/Users.pm view on Meta::CPAN
use parent qw(Ado::Model);
use Carp;
use Email::Address;
sub is_base_class { return 0 }
my $CLASS = __PACKAGE__;
my $TABLE_NAME = 'users';
sub TABLE { return $TABLE_NAME }
sub PRIMARY_KEY { return 'id' }
my $COLUMNS = [
'id', 'group_id', 'login_name', 'login_password',
'first_name', 'last_name', 'email', 'description',
'created_by', 'changed_by', 'tstamp', 'reg_date',
'disabled', 'start_date', 'stop_date'
];
sub COLUMNS { return $COLUMNS }
my $ALIASES = {};
sub ALIASES { return $ALIASES }
my $CHECKS = {
lib/Ado/Model/Users.pm view on Meta::CPAN
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^\d{1,1}$)/,
'default' => '1'
},
'tstamp' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^\d{1,10}$)/
},
'login_password' => {
'required' => 1,
'defined' => 1,
#result of Mojo::Util::sha1_hex($login_name.$login_password)
'allow' => qr/^[A-Fa-f0-9]{40}$/x
},
'stop_date' => {'allow' => qr/(?^x:^-?\d{1,}$)/},
'description' => {
'allow' => qr/(?^x:^.{0,255}$)/,
'default' => ''
},
'last_name' => {'allow' => qr/(?^x:^.{1,100}$)/},
'email' => {
'required' => 1,
lib/Ado/Model/Users.pm view on Meta::CPAN
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^-?\d{1,}$)/
},
'start_date' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^-?\d{1,}$)/
},
'id' => {'allow' => qr/(?^x:\d{1,}$)/},
'login_name' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^.{1,100}$)/
},
'created_by' => {'allow' => qr/(?^x:^-?\d{1,}$)/},
'first_name' => {'allow' => qr/(?^x:^.{1,100}$)/}
};
sub CHECKS { return $CHECKS }
__PACKAGE__->QUOTE_IDENTIFIERS(0);
#__PACKAGE__->BUILD;#build accessors during load
#find and instantiate a user object by email
sub by_email {
state $sql = $_[0]->SQL('SELECT') . ' WHERE email=?';
return $_[0]->query($sql, $_[1]);
}
#find and instantiate a user object by login_name
sub by_login_name {
state $sql = $_[0]->SQL('SELECT') . ' WHERE login_name=?';
return $_[0]->query($sql, $_[1]);
}
sub name {
ref($_[0]) || Carp::croak("The method $_[0]::name must be called only on instances!");
return $_[0]->{name} ||= do {
Mojo::Util::trim(
($_[0]->{data}{first_name} || '') . ' ' . ($_[0]->{data}{last_name} || ''))
|| $_[0]->{data}{login_name};
};
}
sub add {
my $class = shift;
my $args = $class->_get_obj_args(@_);
state $dbix = $class->dbix;
state $GR = 'Ado::Model::Groups'; #shorten class name
my ($group, $user);
my $try = eval {
$dbix->begin_work;
#First we need a primary group for the user.
$group = $GR->create(
name => $args->{login_name},
disabled => 0,
description => 'Primary group for user ' . $args->{login_name},
created_by => $args->{created_by} || 1,
);
#Let us create the user now...
$user = $class->create(
first_name => $args->{first_name},
last_name => $args->{last_name},
login_name => $args->{login_name},
login_password => $args->{login_password},
email => $args->{email},
disabled => $args->{disabled},
tstamp => time,
reg_date => time,
created_by => $args->{created_by},
changed_by => $args->{changed_by},
stop_date => $args->{stop_date},
start_date => $args->{start_date},
description => $args->{description},
group_id => $group->id,
lib/Ado/Model/Users.pm view on Meta::CPAN
state $GR = 'Ado::Model::Groups'; #shorten class name
my $ingroup;
my $try = eval {
$dbix->begin_work;
#Create the group if it does not exist yet
if (!(($ingroup = $GR->by_name($args->{ingroup}))->id)) {
$ingroup = $GR->create(
name => $args->{ingroup},
disabled => 0,
description => 'Additional group initially created for user ' . $self->login_name,
created_by => $args->{created_by} || 1,
);
}
#Link them
Ado::Model::UserGroup->create(
user_id => $self->id,
group_id => $ingroup->id
);
$dbix->commit;
lib/Ado/Model/Users.pm view on Meta::CPAN
}
return @groups;
}
$CLASS->SQL('user_id_by_group_name' => <<"UG");
SELECT user_id FROM user_group WHERE group_id =
(SELECT id FROM groups WHERE name = ?)
UG
$CLASS->SQL('by_group_name' => <<"SQL");
SELECT id, login_name, first_name, last_name, email
FROM ${\ $CLASS->TABLE }
WHERE id IN(${\ $CLASS->SQL('user_id_by_group_name') })
AND (disabled=0 AND (stop_date>? OR stop_date=0) AND start_date<?)
ORDER BY first_name, last_name ASC
SQL
#Selects users belonging to a group only.
#returns a list of hashes
sub by_group_name {
lib/Ado/Model/Users.pm view on Meta::CPAN
=encoding utf8
=head1 NAME
A class for TABLE users in schema main
=head1 SYNOPSIS
#In a controller use the helper.
#Find a user by login_name and change the current user
my $user = Ado::Model::Users->by_login_name($login_name);
$c->user($user);
#in a template
<h1>Hello, <%=user->name%>!</h1>
#Create a new user.
my $user = Ado::Model::Users->add(login_name=>'petko'...);
#Add the user to a group
$user->add_to_group('cool');
=head1 DESCRIPTION
This class maps to rows in table C<users>.
=head1 ATTRIBUTES
Ado::Model::Users inherits all attributes from Ado::Model
lib/Ado/Model/Users.pm view on Meta::CPAN
<h1>Hello, <%=user->name%>!</h1>
=head1 COLUMNS
Each column from table C<users> has an accessor method in this class.
=head2 id
=head2 group_id
=head2 login_name
=head2 login_password
=head2 first_name
=head2 last_name
=head2 email
=head2 description
=head2 created_by
lib/Ado/Model/Users.pm view on Meta::CPAN
following additional methods:
=head2 add
Given enough parameters creates a new user object and inserts it into the
table C<users>. Creates a primary group for the user with the same group
C<name>. Throws an exception if any of the above fails. Returns (the
eventually newly created) user object.
my $user = Ado::Model::Users->add(
login_name => $login_name,
login_password => Mojo::Util::sha1_hex($login_name.$login_password)
);
=head2 add_to_group
Adds a user with C<login_name> to a group.
Creates the group if it does not already exists.
Returns the group.
$ingroup = $user->add_to_group(ingroup=>'admin');
=head2 by_email
Selects a user by email column.
my $user = Ado::Model::Users->by_email('user@example.com');
say $user->email if $user->id;
=head2 by_group_name
Selects active users (C<WHERE (disabled=0 AND (stop_date>$now OR stop_date=0)
AND start_date<$now )>) belonging to a given group only and within a given
range, ordered by C<first_name, last_name> alphabetically. C<$limit> defaults
to 500 and C<$offset> to 0. Only the following fields are retrieved: C<id,
login_name, first_name, last_name, email>.
Returns an array of hashes. The L</name> method is executed for each row in
the resultset and the evaluation is available via key 'name'.
#get contacts of the user 'berov'
my @users = Ado::Model::Users->by_group_name('vest_contacts_for_berov', $limit, $offset);
=head2 by_login_name
Selects a user by login_name column.
my $user = Ado::Model::Users->by_login_name('guest');
say $user->login_name if $user->id;
=head2 ingroup
Given a group name returns true if a user is member of the group.
Returns false otherwise.
Returns a list of all group names a user belongs to if no group name passed.
say $user->name . ' is admin!' if $user->ingroup('admin');
say $user->name .' is member of the following groups:'
. join(', ', $user->ingroup);
lib/Ado/Plugin/AdoHelpers.pm view on Meta::CPAN
use Mojo::Base 'Ado::Plugin';
use Mojo::Util qw(decode);
use List::Util qw(first);
use Mojo::File 'path';
# allow plugins to process SQL scripts while loading
sub do_sql_file {
my ($app, $sql_file) = @_;
my $dbh = $app->dbix->dbh;
#$app->log->debug('do_sql_file:' . $sql_file) if $Ado::Control::DEV_MODE;
my $SQL = decode('UTF-8', path($sql_file)->slurp());
#Remove multi-line comments
$SQL =~ s|/\*+.+?\*/\s+?||gsmx;
#$app->log->debug('$SQL:' . $SQL) if $Ado::Control::DEV_MODE;
local $dbh->{RaiseError} = 1;
my $last_statement = '';
return eval {
$dbh->begin_work;
for my $st (split /;/smx, $SQL) {
$last_statement = $st;
$dbh->do($st) if ($st =~ /\S+/smx);
}
$dbh->commit;
} || do {
my $e = "\nError in statement:$last_statement\n$@";
my $e2;
eval { $dbh->rollback } || ($e2 = $/ . 'Additionally we have a rollback error:' . $@);
$app->log->error($e . ($e2 ? $e2 : ''));
Carp::croak($e . ($e2 ? $e2 : ''));
};
}
sub register {
my ($self, $app, $conf) = shift->initialise(@_);
# Add helpers
$app->helper(user => sub { shift->user(@_) });
# http://irclog.perlgeek.de/mojo/2014-10-03#i_9453021
$app->helper(to_json => sub { Mojo::JSON::to_json($_[1]) });
Mojo::Util::monkey_patch(ref($app), do_sql_file => \&Ado::Plugin::AdoHelpers::do_sql_file);
$app->helper('head_css' => \&_head_css);
$app->helper('head_javascript' => \&_head_javascript);
return $self;
} #end of register
my $file_re = qr/\w+\.\w+(\?.*)?$/;
lib/Ado/Plugin/AdoHelpers.pm view on Meta::CPAN
$c->stash(user_as_js => $chars);
# in a javascript chunk of a template
var user = <%== $user_as_js %>;
var user_group_names = <%== to_json([user->ingroup]) %>;
=head2 user
Returns the current user. This is the user C<guest> for not authenticated users.
This helper is a wrapper for L<Ado::Control/user>.
$c->user(Ado::Model::Users->query("SELECT * from users WHERE login_name='guest'"));
#in a controller action:
my $current_user = $c->user;
#in a template:
<h1>Hello, <%=user->name%>!</h1>
=head1 METHODS
L<Ado::Plugin::AdoHelpers> inherits all methods from
L<Ado::Plugin> and implements the following new ones.
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
package Ado::Plugin::Auth;
use Mojo::Base 'Ado::Plugin';
use Mojo::Util qw(class_to_path);
sub register {
my ($self, $app, $conf) = shift->initialise(@_);
# Make sure we have all we need from config files.
$conf->{auth_methods} ||= ['ado'];
$app->helper(login_ado => \&_login_ado);
$app->config(auth_methods => $conf->{auth_methods});
$app->config(ref($self) => $conf);
#OAuth2 providers
my @auth_methods = @{$conf->{auth_methods}};
if (@auth_methods > 1) {
for my $m (@auth_methods) {
next if $m eq 'ado';
Carp::croak("Configuration options for authentication method \"$m\" "
. "are not enough!. Please add them.")
if (keys %{$conf->{providers}{$m}} < 2);
}
$app->plugin('OAuth2', {%{$conf->{providers}}, fix_get_token => 1});
}
# Add helpers
#oauth2 links - helpers after 'ado'
$app->helper(login_google => \&_login_google)
if (List::Util::first { $_ eq 'google' } @auth_methods);
$app->helper(login_facebook => \&_login_facebook)
if (List::Util::first { $_ eq 'google' } @auth_methods);
# Add conditions
$app->routes->add_condition(authenticated => \&authenticated);
$app->routes->add_condition(
ingroup => sub {
$_[1]->debug("is user " . $_[1]->user->name . " in group $_[-1]?")
if $Ado::Control::DEV_MODE;
return $_[1]->user->ingroup($_[-1]);
}
);
$app->hook(
after_user_add => sub {
my ($c, $user, $raw_data) = @_;
$app->log->info($user->description . ' $user->id ' . $user->id . ' added!');
$c->debug('new user created with arguments:' . $c->dumper($user->data, $raw_data))
if $Ado::Control::DEV_MODE;
}
);
$app->hook(
after_login => sub {
my ($c) = @_;
$c->session(adobar_links => []);
# Store a friendly message for the next page in flash
$c->flash(login_message => $c->l('LoginThanks'));
}
);
return $self;
}
# general condition for authenticating users - redirects to /login
sub authenticated {
my ($route, $c) = @_;
$c->debug('in condition "authenticated"') if $Ado::Control::DEV_MODE;
if ($c->user->login_name eq 'guest') {
$c->session(over_route => $c->req->url);
$c->debug('session(over_route => $c->req->url):' . $c->session('over_route'))
if $Ado::Control::DEV_MODE;
$c->redirect_to('/login');
return;
}
return 1;
}
#expires the session.
sub logout {
my ($c) = @_;
$c->session(expires => 1);
$c->redirect_to('/');
return;
}
#authenticate a user /login route implementation is here
sub login {
my ($c) = @_;
#TODO: add json format
#prepare redirect url for after login
unless ($c->session('over_route')) {
my $base_url = $c->url_for('/')->base;
my $referrer = $c->req->headers->referrer // $base_url;
$referrer = $base_url unless $referrer =~ m|^$base_url|;
$c->session('over_route' => $referrer);
$c->debug('over_route is ' . $referrer) if $Ado::Control::DEV_MODE;
}
my $auth_method = Mojo::Util::trim($c->param('auth_method'));
return $c->render(status => 200, template => 'login')
if $c->req->method ne 'POST' && $auth_method eq 'ado';
#derive a helper name for login the user
my $login_helper = 'login_' . $auth_method;
$c->debug('Chosen $login_helper: ' . $login_helper) if $Ado::Control::DEV_MODE;
my $authnticated = 0;
if (eval { $authnticated = $c->$login_helper(); 1 }) {
if ($authnticated) {
$c->app->plugins->emit_hook(after_login => $c);
# Redirect to referrer page with a 302 response
$c->debug('redirecting to ' . $c->session('over_route'))
if $Ado::Control::DEV_MODE;
$c->redirect_to($c->session('over_route'));
return;
}
else {
unless ($c->res->code // '' eq '403') {
$c->stash(error_login => 'Wrong credentials! Please try again!');
$c->render(status => 401, template => 'login');
return;
}
}
}
else {
$c->app->log->error("Error calling \$login_helper:[$login_helper][$@]");
$c->stash(error_login => 'Please choose one of the supported login methods.');
$c->render(status => 401, template => 'login');
return;
}
return;
}
#used as helper 'login_ado' returns 1 on success, '' otherwise
sub _login_ado {
my ($c) = @_;
#1. do basic validation first
my $val = $c->validation;
return '' unless $val->has_data;
if ($val->csrf_protect->has_error('csrf_token')) {
delete $c->session->{csrf_token};
$c->render(error_login => 'Bad CSRF token!', status => 403, template => 'login');
return '';
}
my $_checks = Ado::Model::Users->CHECKS;
$val->required('login_name')->like($_checks->{login_name}{allow});
$val->required('digest')->like(qr/^[0-9a-f]{40}$/);
if ($val->has_error) {
delete $c->session->{csrf_token};
return '';
}
#2. find the user and do logical checks
my $login_name = $val->param('login_name');
my $user = Ado::Model::Users->by_login_name($login_name);
if ((not $user->id) or $user->disabled) {
delete $c->session->{csrf_token};
$c->stash(error_login_name => "No such user '$login_name'!");
return '';
}
#3. really authnticate the user
my $checksum = Mojo::Util::sha1_hex($c->session->{csrf_token} . $user->login_password);
if ($checksum eq $val->param('digest')) {
$c->session(login_name => $user->login_name);
$c->user($user);
$c->app->log->info('$user ' . $user->login_name . ' logged in!');
delete $c->session->{csrf_token};
return 1;
}
$c->debug('We should not be here! - wrong password') if $Ado::Control::DEV_MODE;
delete $c->session->{csrf_token};
return '';
}
#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_google {
my ($c) = @_;
state $app = $c->app;
my $provider = $c->param('auth_method');
my $providers = $app->config('Ado::Plugin::Auth')->{providers};
#second call should get the token it self
my $response = $c->oauth2->get_token($provider, $providers->{$provider});
do {
$c->debug("in _login_google \$response: " . $c->dumper($response));
$c->debug("in _login_google error from provider: " . ($c->param('error') || 'no error'));
} if $Ado::Control::DEV_MODE;
if ($response->{access_token}) { #Athenticate, create and login the user.
return _create_or_authenticate_google_user(
$c,
$response->{access_token},
$providers->{$provider}
);
}
else {
#Redirect to front-page and say sorry
# We are very sorry but we need to know you are a reasonable human being.
$c->flash(error_login => $c->l('oauth2_sorry[_1]', ucfirst($provider))
. ($c->param('error') || ''));
$c->app->log->error('error_response:' . $c->dumper($response));
$c->res->code(307); #307 Temporary Redirect
$c->redirect_to('/');
}
return;
}
#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_facebook {
my ($c) = @_;
state $app = $c->app;
my $provider = $c->param('auth_method');
my $providers = $app->config('Ado::Plugin::Auth')->{providers};
#second call should get the token it self
my $response = $c->oauth2->get_token($provider, $providers->{$provider});
do {
$c->debug("in _login_facebook \$response: " . $c->dumper($response));
$c->debug(
"in _login_facebook error from provider: " . ($c->param('error') || 'no error'));
} if $Ado::Control::DEV_MODE;
if ($response->{access_token}) { #Athenticate, create and login the user.
return _create_or_authenticate_facebook_user(
$c,
$response->{access_token},
$providers->{$provider}
);
}
else {
#Redirect to front-page and say sorry
# We are very sorry but we need to know you are a reasonable human being.
$c->flash(error_login => $c->l('oauth2_sorry[_1]', ucfirst($provider))
. ($c->param('error') || ''));
$c->app->log->error('error_response:' . $c->dumper($response));
$c->res->code(307); #307 Temporary Redirect
$c->redirect_to('/');
}
return;
}
sub _authenticate_oauth2_user {
my ($c, $user, $time) = @_;
if ( $user->disabled
|| ($user->stop_date != 0 && $user->stop_date < $time)
|| $user->start_date > $time)
{
$c->flash(login_message => $c->l('oauth2_disabled'));
$c->redirect_to('/');
return;
}
$c->session(login_name => $user->login_name);
$c->user($user);
$c->app->log->info('$user ' . $user->login_name . ' logged in!');
return 1;
}
#Creates a user using given info from provider
sub _create_oauth2_user {
my ($c, $user_info, $provider) = @_;
state $app = $c->app;
if (my $user = Ado::Model::Users->add(_user_info_to_args($user_info, $provider))) {
$app->plugins->emit_hook(after_user_add => $c, $user, $user_info);
$c->user($user);
$c->session(login_name => $user->login_name);
$app->log->info($user->description . ' New $user ' . $user->login_name . ' logged in!');
$c->flash(login_message => $c->l('oauth2_wellcome[_1]', $user->name));
$c->redirect_to('/');
return 1;
}
$app->log->error($@);
return;
}
#next two methods
#(_create_or_authenticate_facebook_user and _create_or_authenticate_google_user)
# exist only because we pass different parameters in the form
# which are specific to the provider.
# TODO: think of a way to map the generation of the form arguments to the
# specific provider so we can dramatically reduce the number of provider
# specific subroutines
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
#else create the user
return _create_oauth2_user($c, $user_info, $provider);
}
# Redirects to Consent screen
sub authorize {
my ($c) = @_;
my $m = $c->param('auth_method');
my $params = $c->app->config('Ado::Plugin::Auth')->{providers}{$m};
$params->{redirect_uri} = '' . $c->url_for("/login/$m")->to_abs;
#This call will redirect the user to the provider Consent screen.
$c->redirect_to($c->oauth2->auth_url($m, %$params));
return;
}
# Maps user info given from provider to arguments for
# Ado::Model::Users->new
sub _user_info_to_args {
my ($ui, $provider) = @_;
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
elsif (index($provider->{info_url}, 'facebook') > -1) {
$args{first_name} = $ui->{first_name};
$args{last_name} = $ui->{last_name};
}
#Add another elsif to map different %args to $ui from a new provider
else {
Carp::croak('Unknown provider info_url:' . $provider->{info_url});
}
$args{email} = $ui->{email};
$args{login_name} = $ui->{email};
$args{login_name} =~ s/[\@\.]+//g;
$args{login_password} =
Mojo::Util::sha1_hex($args{login_name} . Ado::Sessions->generate_id());
$args{description} = "Registered via $provider->{info_url}!";
$args{created_by} = $args{changed_by} = 1;
$args{start_date} = $args{disabled} = $args{stop_date} = 0;
return %args;
}
1;
=pod
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
To find more about conditions read L<Mojolicious::Guides::Routing/Conditions>.
=head2 authenticated
Condition for routes used to check if a user is authenticated.
=cut
#TODO:?
#Additional parameters can be passed to specify the preferred
#authentication method to be preselected in the login form
#if condition redirects to C</login/:auth_method>.
=pod
# add the condition programatically
$app->routes->route('/ado-users/:action', over => {authenticated=>1});
$app->routes->route('/ado-users/:action',
over => [authenticated => 1, ingroup => 'admin']
);
#in etc/ado.$mode.conf or etc/plugins/foo.$mode.conf
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
over => [authenticated => 1, ingroup => 'foo'],
}
# programatically
$app->routes->route('/ado-users/:action', over => {ingroup => 'foo'});
=head1 HELPERS
L<Ado::Plugin::Auth> provides the following helpers for use in
L<Ado::Control> methods and templates.
=head2 login_ado
Finds and logs in a user locally. Returns true on success, false otherwise.
=head2 login_google
Called via C</login/google>. Finds an existing user and logs it in via Google.
Creates a new user if it does not exist and logs it in via Google. The new
user can login via any supported OAuth2 provider as long as it has the same
email. The user can not login using Ado local authentication because he does
not know his password, which is randomly generated. Returns true on success,
false otherwise.
=head2 login_facebook
Called via C</login/facebook>. Finds an existing user and logs it in via
Facebook. Creates a new user if it does not exist and logs it in via Facebook.
The new user can login via any supported Oauth2 provider as long as it has the
same email. The user can not login using Ado local authentication because he
does not know his password, which is randomly generated. Returns true on
success, false otherwise.
=head1 HOOKS
Ado::Plugin::Auth emits the following hooks.
=head2 after_login
In your plugin you can define some functionality to be executed right after a
user has logged in. For example add some links to the adobar template,
available only to logged-in users. Only the controller C<$c> is passed to this
hook.
#example from Ado::Plugin::Admin
$app->hook(
after_login => sub {
push @{shift->session->{adobar_links} //= []},
{icon => 'dashboard', href => '/ado', text => 'Dashboard'};
}
);
=head2 after_user_add
$app->hook(after_user_add => sub {
my ($c, $user, $raw_data) = @_;
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
=head1 ROUTES
L<Ado::Plugin::Auth> provides the following routes (actions):
=head2 /authorize/:auth_method
Redirects to an OAuth2 provider consent screen where the user can authorize
L<Ado> to use his information or not. Currently L<Ado> supports Facebook and
Google.
=head2 /login
/login/ado
If accessed using a C<GET> request displays a login form. If accessed via
C<POST> performs authentication using C<ado> system database, and emits the
hook L</after_login>.
/login/facebook
Facebook consent screen redirects to this action. This action is handled by
L</login_facebook>.
/login/google
Google consent screen redirects to this action. This action is handled by
L</login_google>.
=head2 /logout
Expires the session and redirects to the base URL.
$c->logout();
=head1 TEMPLATES
L<Ado::Plugin::Auth> uses the following templates. The paths are in the
C</templates> folder. Feel free to move them to the site_templates folder and
modify them for your needs.
=head2 partials/authbar.html.ep
Renders a menu dropdown for choosing methods for signing in.
=head2 partials/login_form.html.ep
Renders a Login form to authenticate locally.
=head2 login.html.ep
Renders a page containing the login form above.
=head1 METHODS
L<Ado::Plugin::Auth> inherits all methods from L<Ado::Plugin> and implements
the following new ones.
=head2 register
This method is called by C<$app-E<gt>plugin>. Registers the plugin in L<Ado>
lib/Ado/Plugin/I18n.pm view on Meta::CPAN
$config->{language_from_host} //= 1;
$config->{language_from_param} //= 1;
$config->{language_from_cookie} //= 1;
$config->{language_from_headers} //= 1;
$config->{language_param} //= 'language';
#Allow other namespaces too
$config->{namespace} ||= 'Ado::I18n';
my $e = Mojo::Loader::load_class($config->{namespace});
$app->log->error(qq{Loading "$config->{namespace}" failed: $e}) if $e;
# Defaults for $c->stash so templates without controllers can be used
$app->defaults(language => '', language_from => '');
#Add helpers
$app->helper(language => \&language);
$app->helper(l => \&_maketext);
#default routes including language placeholder.
$app->load_routes($self->routes);