Adding a few dao and model to use the data defined in the database migration

Also some tests are added.
This commit is contained in:
sergiotarxz 2021-06-03 23:44:00 +02:00
parent 39c8bcbc89
commit 1bcb9bf973
Signed by: sergiotarxz
GPG Key ID: E5903508B6510AC2
14 changed files with 767 additions and 23 deletions

View File

@ -15,8 +15,8 @@ package MY {
my $return = $self->SUPER::top_targets(@_);
$return = [ split /\n/, $return ];
for my $i ( keys @$return ) {
$return->[$i] .= ' install_frontend'
if $return->[$i] =~ /^all :/;
$return->[$i] .= ' install_frontend_and_migrations'
if $return->[$i] =~ /^install :/;
}
return join "\n", @$return;
}
@ -24,7 +24,7 @@ package MY {
sub postamble {
return
"\n"
. "install_frontend:\n"
. "install_frontend_and_migrations:\n"
. "\tif [ ! -e lib/BeastBB/public ]; then "
. "mkdir -pv lib/BeastBB/public; " . "fi;"
. "if [ ! -e lib/BeastBB/templates ]; then "

View File

@ -8,3 +8,6 @@ requires 'Params::ValidationCompiler';
requires 'Types::Standard';
requires 'Crypt::Bcrypt::Easy';
requires 'DateTime';
requires 'DateTime::Format::ISO8601';
requires 'Test::Most';
requires 'Test::MockModule';

View File

@ -19,6 +19,7 @@ use BeastBB::Constants (
);
use BeastBB::ConfigWriter;
use BeastBB::Database;
use BeastBB::Response;
sub new {
my $class = shift;
@ -48,6 +49,14 @@ sub PrepareHelpers {
return $database;
}
);
$self->helper(
logged_user => sub {
my $self = shift;
my $session = $self->session;
return BeastBB::Response->new( is_error => 1, error_message => 'User is not logged in.');
my $username = $session->{username};
}
);
}
@ -60,6 +69,7 @@ sub PrepareSecrets {
sub PrepareRoutes {
my $self = shift;
my $routes = $self->routes;
@{ $self->renderer->paths() } =
( Mojo::File::curfile->dirname->child('BeastBB')->child('templates')
->to_string );
@ -68,11 +78,31 @@ sub PrepareRoutes {
->to_string );
print Data::Dumper::Dumper $self->renderer->paths;
if ( !exists $self->config->{finished_install} ) {
$routes->get('/')->to('install#welcome');
$routes->post('/install/database')->to('install#install_database');
$routes->post('/install/admin_user_create')
->to('install#admin_user_create');
$self->PrepareInstallationRoutes;
return;
}
$self->PrepareCommonRoutes;
}
sub PrepareCommonRoutes {
my $self = shift;
my $routes = $self->routes;
$routes->get('/')->to('Main#Index');
$routes->get('/login')->to('Main#GetLogin');
$routes->post('/login')->to('Main#Login');
$routes->post('/logout')->to('Main#Logout');
}
sub PrepareInstallationRoutes {
my $self = shift;
my $routes = $self->routes;
$routes->get('/')->to('install#welcome');
$routes->post('/install/database')->to('install#install_database');
$routes->post('/install/admin_user_create')
->to('install#admin_user_create');
}
sub PrepareConfig {

View File

@ -85,7 +85,7 @@ sub admin_user_create {
repeat_password => $repeat_password
);
my $user_manager = BeastBB::DAO::UserManager->new( app => $self );
my $response_create_user = $user_manager->CreateUser(
my $response_create_user = $user_manager->Create(
username => $username,
matrix_address => $matrix_address,
password => $password,

View File

@ -0,0 +1,15 @@
package BeastBB::Controller::Main;
use 5.30.3;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';
sub Index {
my $self = shift;
$self->stash( session => $self->session );
$self->render;
}
1;

View File

@ -0,0 +1,160 @@
package BeastBB::DAO::GroupManager;
use 5.32.1;
use strict;
use warnings;
use Carp;
use Params::ValidationCompiler 'validation_for';
use Types::Standard qw/Bool Str Int/;
use BeastBB::Response;
{
my $validator = validation_for(
params => {
app => { type => IsClassTypeGenerator('Mojolicious::Controller') },
}
);
sub new {
my $class = shift;
my %params = $validator->(@_);
return bless \%params, $class;
}
}
{
my $validator = validation_for(
params => {
id_group => { type => Int, optional => 1 },
groupname => { type => Str, optional => 1 },
recover_privileges => { type => Bool, default => 0 },
}
);
sub Get {
my $self = shift;
my %params = $validator->(@_);
my ( $id_group, $groupname, $recover_privileges ) =
@params{ 'id_group', 'groupname', 'recover_privileges' };
$self->_CheckGetRequirements(
( ( defined $id_group ) ? ( id_group => $id_group ) : () ),
( ( defined $groupname ) ? ( groupname => $groupname ) : () )
);
my $maybe_group_hash = $self->_RecoverGroupFromDatabase(
( ( defined $id_group ) ? ( id_group => $id_group ) : () ),
( ( defined $groupname ) ? ( groupname => $groupname ) : () )
);
return $maybe_group_hash if $maybe_group_hash->IsError;
$group_hash = $maybe_group_hash;
my $group = BeastBB::Model::Group->new(%$group_hash);
my $privileges;
if ($recover_privileges) {
$self->RecoverPrivilegesGroup( group => $group );
}
return BeastBB::Response->new( content => $group );
}
}
{
my $validator = validation_for(
id_group => { type => Int, optional => 1 },
groupname => { type => Str, optional => 1 },
);
sub _CheckGetRequirements {
my $self = shift;
my %params = $validator->(@_);
my ( $id_group, $groupname, ) = @params{ 'id_group', 'groupname', };
confess "${class}::Get should be used passing id_group xor groupname."
unless ( defined $id_group xor defined $groupname );
}
}
{
my $validator = validation_for(
params => {
id_group => { type => Int, optional => 1 },
groupname => { type => Str, optional => 1 },
}
);
sub _RecoverGroupFromDatabase {
my $self = shift;
my %params = $validator->(@_);
my $id_group = $params{id_group};
my $groupname = $params{groupname};
my $app = $self->_App;
my $database = $app->db;
my $pg = $database->Pg->db;
my $selected_group = $pg->select(
'group',
[ 'id_group', 'groupname' ],
{
( ( defined $id_group ) ? ( id_group => $id_group ) : () ),
( ( defined $groupname ) ? ( groupname => $groupname ) : () )
}
);
if ( !$selected_group->rows ) {
return BeastBB::Response->new(
is_error => 1,
error_message => 'No such group.'
);
}
return BeastBB::Response->new( content => $selected_group->hash );
}
}
{
my $validator = validation_for(
params => {
group => { type => IsClassTypeGenerator('BeastBB::Model::Group') },
}
);
sub RecoverPrivilegesGroup {
my $self = shift;
my %params = $validator->(@_);
my $group = $params{group};
my $id_group = $group->IdGroup;
my $app = $self->_App;
my $database = $app->db;
my $pg = $database->Pg->db;
my $selected_privileges = $pg->query(
'SELECT privilege.name'
. ' FROM group_privilege'
. ' INNER JOIN privilege USING (id_privilege)'
. ' WHERE id_group=?',
$id_group
);
if ( $selected_privileges->rows ) {
my $hashes_privileges = $selected_privileges->hashes;
$privileges =
{ map { $_->{name} => 1 } @$hashes_privileges };
}
$privileges //= {};
$group->Privileges($privileges);
return $group;
}
}
sub _App {
return $self->{app};
}
1;

View File

@ -1,10 +1,11 @@
package BeastBB::DAO::UserManager;
use 5.32.1;
use strict;
use warnings;
use Data::Dumper;
use Carp;
use Params::ValidationCompiler 'validation_for';
use Types::Standard qw/Bool Str/;
@ -13,8 +14,11 @@ use Crypt::Bcrypt::Easy;
use Const::Fast;
use DateTime;
use BeastBB::Types ( '$MATRIX_ADDRESS_REGEX', 'IsClassTypeGenerator' );
use BeastBB::DAO::Response;
use BeastBB::Types (
'$MATRIX_ADDRESS_REGEX', 'IsClassTypeGenerator',
'$MATRIX_ADDRESS_TYPE'
);
use BeastBB::Response;
const my $MINIMUM_PASSWORD_LENGHT => 8;
@ -32,6 +36,135 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
}
}
{
my $validator = validation_for(
params => {
id_user => { type => Int, optional => 1 },
username => { type => Str, optional => 1 },
matrix_address => { type => $MATRIX_ADDRESS_TYPE, optional => 1 },
recover_group => { type => Bool, default => 0 },
}
);
sub Get {
my $self = shift;
my %params = $validator->(@_);
my $id_user = $params{id_user};
my $username = $params{username};
my $matrix_address = $params{matrix_address};
my $recover_group = $params{recover_group};
confess
'You should pass id_user xor matrix_address xor username to Get.'
unless ( defined $id_user xor defined $username
xor defined $matrix_address );
my $maybe_user_hash = $self->_RecoverUserFromDatabase(
( ( defined $id_user ) ? ( id_user => $id_user ) : () ),
( ( defined $username ) ? ( username => $username ) : () ),
(
( defined $matrix_address )
? ( matrix_address => $matrix_address )
: ()
)
);
return $maybe_user_hash if $maybe_user_hash->IsError;
$user_hash = $maybe_user_hash->Content;
my $id_group = delete $user_hash->{id_group};
my $user = BeastBB::Model::User->new(%$user_hash);
if ($recover_group) {
my $maybe_recovered_group =
$self->_RecoverUserGroup( id_group => $id_group, user => $user );
return $maybe_recovered_group if $maybe_recovered_group->IsError;
}
return BeastBB::Response->new( content => $user );
}
}
{
my $validator = validation_for(
params => {
id_group => { type => Int },
user => { type => IsClassTypeGenerator('BeastBB::Model::User') },
}
);
sub _RecoverUserGroup {
my $self = shift;
my %params = $validator->(@_);
my $id_group = $params{id_group};
my $user = $params{user};
my $app = $self->_App;
my $group_manager = BeastBB::DAO::GroupManager->new( app => $app );
my $maybe_group = $group_manager->Get( id_group => $id_group );
if ( $maybe_group->IsError ) {
return $maybe_group;
}
my $group = $maybe_group->Content;
$user->Group($group);
return BeastBB::Response->new;
}
}
{
my $validator = validation_for(
params => {
id_user => { type => Int, optional => 1 },
username => { type => Str, optional => 1 },
matrix_address => { type => $MATRIX_ADDRESS_TYPE, optional => 1 },
}
);
sub _RecoverUserFromDatabase {
my $self = shift;
my %params = $validator->(@_);
my $app = $self->_App;
my $database = $app->db;
my $pg = $database->Pg->db;
my ( $id_user, $username, $matrix_address ) =
@params{ 'id_user', 'username', 'matrix_address' };
my $results = $pg->select(
'user',
[
'id_user', 'username', 'matrix_address', 'password_bcrypt',
'is_confirmed', 'creation_date', 'id_group', 'last_connection',
],
{
( ( defined $id_user ) ? ( id_user => $id_user ) : () ),
( ( defined $username ) ? ( username => $username ) : () ),
(
( defined $matrix_address )
? ( matrix_address => $matrix_address )
: ()
)
}
);
if ( !$results->rows ) {
return BeastBB::Response->new(
is_error => 1,
error_message => 'No such user found.',
);
}
my $user_hash = $results->hash;
$user_hash->{creation_date} = DateTime::Format::ISO8601->parse_datetime(
$user_hash->{creation_date} );
$user_hash->{last_connection} =
DateTime::Format::ISO8601->parse_datetime(
$user_hash->{last_connection} );
return BeastBB::Response( content => $user_hash );
}
}
{
my $validator = validation_for(
params => {
@ -44,7 +177,7 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
}
);
sub CreateUser {
sub Create {
my $self = shift;
my %params = $validator->(@_);
my (
@ -72,7 +205,7 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
my $result =
$pg->select( 'group', 'id_group', { groupname => $groupname } );
if ( !$result->rows ) {
return BeastBB::DAO::Response->new(
return BeastBB::Response->new(
is_error => 1,
error_message => "Unable to find the group $groupname."
);
@ -96,14 +229,13 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
}
);
if ( !$result_create_user->rows ) {
return BeastBB::DAO::Response->new(
return BeastBB::Response->new(
is_error => 1,
error_message => "Unable to create user $username.",
);
}
return BeastBB::DAO::Response->new(
content => $result_create_user->hash->{id_user}
);
return BeastBB::Response->new(
content => $result_create_user->hash->{id_user} );
}
}
@ -155,7 +287,7 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
if ( $password ne $repeat_password ) {
$error_message = 'Password and repeat password not matching.';
}
return BeastBB::DAO::Response->new(
return BeastBB::Response->new(
(
( defined $error_message )
? (
@ -183,7 +315,7 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
if ( $password =~ /^\d+$/ ) {
$error_message = "Password is numeric, it is not allowed.";
}
return BeastBB::DAO::Response->new(
return BeastBB::Response->new(
(
( defined $error_message )
? (
@ -212,7 +344,7 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
$error_message =
"Password has less than $MINIMUM_PASSWORD_LENGHT characters.";
}
return BeastBB::DAO::Response->new(
return BeastBB::Response->new(
(
( defined $error_message )
? (
@ -239,7 +371,7 @@ const my $MINIMUM_PASSWORD_LENGHT => 8;
if ( $matrix_address !~ /$MATRIX_ADDRESS_REGEX/ ) {
$error_message = "This does not look like a Matrix address.";
}
return BeastBB::DAO::Response->new(
return BeastBB::Response->new(
(
( defined $error_message )
? (

102
lib/BeastBB/Model/Group.pm Normal file
View File

@ -0,0 +1,102 @@
package BeastBB::Model::Group;
use 5.32.1;
use strict;
use warnings;
use Params::ValidationCompiler 'validation_for';
use Types::Standard qw/Int Str HashRef Bool/;
use BeastBB::Response;
{
my $validator = validation_for(
params => {
id_group => { type => Int, optional => 1 },
groupname => { type => Str },
privileges => { type => HashRef [Bool], optional => 1 },
}
);
sub new {
my $class = shift;
my %params = $validator->(@_);
return bless \%params, $class;
}
}
sub Hash {
my $self = shift;
my $maybe_id_group = $self->IdGroup;
my $maybe_privileges = $self->Privileges;
return {
(
( !$maybe_id_group->IsError )
? ( id_group => $maybe_id_group->Content )
: ()
),
(
( !$maybe_privileges->IsError )
? ( privileges => $maybe_privileges->Content )
: ()
),
groupname => $self->{groupname},
};
}
sub IdGroup {
my $self = shift;
my $id_group = $self->{id_group};
return BeastBB::Response->new(
is_error => 1,
error_message => 'No id group set to this model.'
) if !defined $id_group;
return BeastBB::Response->new( content => $id_group );
}
{
my $validator = validation_for(
params => [
{ type => Int },
]
);
sub SetIdGroup {
my $self = shift;
my %params = $validator->(@_);
my $id_group = shift;
$self->{id_group} = $id_group;
}
}
sub Groupname {
my $self = shift;
return $self->{groupname};
}
{
my $validator = validation_for(
params => [
{ type => HashRef [Bool], optional => 1 },
],
);
sub Privileges {
my $self = shift;
@_ = $validator->(@_);
if (@_) {
$self->{privileges} = shift;
}
if ( !defined $self->{privileges} ) {
return BeastBB::Response->new(
is_error => 1,
error_message => 'Privileges not recovered yet for this group.'
);
}
return BeastBB::Response->new( content => $self->{privileges} );
}
}
1;

217
lib/BeastBB/Model/User.pm Normal file
View File

@ -0,0 +1,217 @@
package BeastBB::Model::User;
use 5.32.1;
use strict;
use warnings;
use Params::ValidationCompiler 'validation_for';
use Types::Standard qw/Str Bool Int/;
use BeastBB::Types qw/$MATRIX_ADDRESS_TYPE IsClassTypeGenerator/;
use BeastBB::Response;
{
my $validator = validation_for(
params => {
id_user => { type => Int, optional => 1 },
group => {
type => IsClassTypeGenerator('BeastBB::Model::Group'),
optional => 1
},
username => { type => Str },
matrix_address => { type => Str },
password_bcrypt => { type => Str },
is_confirmed => { type => Bool, default => 0 },
creation_date => {
type => IsClassTypeGenerator('DateTime'),
default => sub { return DateTime->now }
},
last_connection => {
type => IsClassTypeGenerator('DateTime'),
default => sub { return DateTime->now }
},
}
);
sub new {
my $class = shift;
my %params = $validator->(@_);
return bless \%params, $class;
}
}
sub Hash {
my $self = shift;
return {
(
( !$self->IdUser->IsError )
? ( id_user => $self->IdUser->Content )
: ()
),
(
( !$self->Group->IsError )
? ( group => $self->Group->Content->hash )
: ()
),
username => $self->Username,
matrix_address => $self->MatrixAddress,
password_bcrypt => $self->PasswordBcrypt,
is_confirmed => $self->IsConfirmed,
creation_date => $self->CreationDate,
last_connection => $self->LastConnection,
};
}
{
my $validator = validation_for(
params => [
{
type => IsClassTypeGenerator('BeastBB::Model::Group'),
optional => 1
},
]
);
sub Group {
my $self = shift;
@_ = $validator->(@_);
if (@_) {
$self->{group} = shift;
}
if ( !defined $self->{group} ) {
return BeastBB::Response->new(
is_error => 1,
error_message => 'No group recovered for this user.',
);
}
return BeastBB::Model::Group->new(
content => $self->{group},
);
}
}
sub IdUser {
my $self = shift;
my $id_user = $self->{id_user};
return BeastBB::Response->new(
is_error => 1,
error_message => 'Id user not set for this user model.'
) if !defined $id_user;
return BeastBB::Response->new( content => $self->{id_user} );
}
{
my $validator = validation_for(
params => [
{ type => Int },
]
);
sub SetIdUser {
my $self = shift;
@_ = $validator->(@_);
$self->{id_user} = shift;
}
}
sub Username {
my $self = shift;
return $self->{username};
}
{
my $validator =
validation_for(
params => [ { type => $MATRIX_ADDRESS_TYPE, optional => 1 } ] );
sub MatrixAddress {
my $self = shift;
@_ = $validator->(@_);
if (@_) {
$self->{matrix_address} = shift;
}
return $self->{matrix_address};
}
}
{
my $validator = validation_for(
params => [
{
type => Str,
optional => 1
}
]
);
sub PasswordBcrypt {
my $self = shift;
@_ = $validator->(@_);
if (@_) {
$self->{password_bcrypt} = shift;
}
return $self->{password_bcrypt};
}
}
{
my $validator = validation_for(
params => [
{
type => Bool,
optional => 1
},
]
);
sub IsConfirmed {
my $self = shift;
@_ = $validator->(@_);
if (@_) {
$self->{is_confirmed} = shift;
}
return $self->{is_confirmed};
}
}
{
my $validator = validation_for(
params => [
{
type => IsClassTypeGenerator('DateTime'),
optional => 1
}
],
);
sub CreationDate {
my $self = shift;
if (@_) {
$self->{creation_date} = shift;
}
return $self->{creation_date};
}
}
{
my $validator = validation_for(
params => [
{
type => IsClassTypeGenerator('DateTime'),
optional => 1
}
],
);
sub LastConnection {
my $self = shift;
if (@_) {
$self->{last_connection} = shift;
}
return $self->{last_connection};
}
}
1;

View File

@ -1,4 +1,4 @@
package BeastBB::DAO::Response;
package BeastBB::Response;
use 5.32.1;

View File

@ -24,7 +24,7 @@ create table "user" (
is_confirmed BOOLEAN DEFAULT false,
creation_date TIMESTAMP NOT NULL,
id_group BIGINT NOT NULL,
last_connection TIMESTAMP,
last_connection TIMESTAMP NOT NULL,
FOREIGN KEY (id_group) REFERENCES "group" (id_group)
);

62
t/Model/00-user.t Normal file
View File

@ -0,0 +1,62 @@
use 5.32.1;
use Test::Most tests => 7;
use strict;
use warnings;
use Const::Fast;
use Crypt::Bcrypt::Easy;
use DateTime;
use Scalar::Util 'blessed';
const my %REQUIRED_FIELDS_USER => (
username => 'example_username',
matrix_address => '@example_username:example_host.com',
password_bcrypt => bcrypt->crypt('example_password'),
is_confirmed => 1,
creation_date => DateTime->new( year => 2021, month => 06, day => 3 ),
last_connection => DateTime->now,
);
{
use_ok 'BeastBB::Model::User';
}
{
my $user = BeastBB::Model::User->new(%REQUIRED_FIELDS_USER);
ok defined $user && $user->isa('BeastBB::Model::User'),
'This user is made of BeastBB::Model::User.';
my $user_hash = $user->Hash;
%$user_hash = map {
my $value = $user_hash->{$_};
(
$_ => (
( blessed $value && $value->isa('DateTime') )
? "$value"
: $value
)
)
} keys %$user_hash;
is_deeply $user_hash, \%REQUIRED_FIELDS_USER, 'User has the expected hash.';
}
{
my $user = BeastBB::Model::User->new(%REQUIRED_FIELDS_USER);
ok $user->IdUser->IsError, 'Id user can not be retrieved before set.';
$user->SetIdUser(1);
ok !$user->IdUser->IsError, 'Id user can be retrieved after set.';
is $user->IdUser->Content, 1, 'Id user can be set on runtime.';
my $user_hash = $user->Hash;
%$user_hash = map {
my $value = $user_hash->{$_};
(
$_ => (
( blessed $value && $value->isa('DateTime') )
? "$value"
: $value
)
)
} keys %$user_hash;
is_deeply $user_hash, { %REQUIRED_FIELDS_USER, id_user => 1 },
'User has the expected hash with id set after creation.';
}

22
t/Model/01-group.t Normal file
View File

@ -0,0 +1,22 @@
use 5.32.1;
use Test::Most tests => 3;
use strict;
use warnings;
use Const::Fast;
const my %REQUIRED_FIELDS_GROUP => ( groupname => 'example_group' );
{
use_ok 'BeastBB::Model::Group';
}
{
my $group = BeastBB::Model::Group->new(%REQUIRED_FIELDS_GROUP);
ok $group->isa('BeastBB::Model::Group'),
'Group is made of BeastBB::Model::Group.';
is_deeply $group->Hash, \%REQUIRED_FIELDS_GROUP,
'The generated hash of group is correct.';
}

View File

@ -0,0 +1 @@
<h1>Hello world</h1>