diff --git a/Makefile.PL b/Makefile.PL index b9806f1..3029ffd 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -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 " diff --git a/cpanfile b/cpanfile index 16f4f9b..b14eb1b 100644 --- a/cpanfile +++ b/cpanfile @@ -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'; diff --git a/lib/BeastBB.pm b/lib/BeastBB.pm index d625467..ec951d1 100644 --- a/lib/BeastBB.pm +++ b/lib/BeastBB.pm @@ -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 { diff --git a/lib/BeastBB/Controller/Install.pm b/lib/BeastBB/Controller/Install.pm index 36eefc6..8591ebc 100644 --- a/lib/BeastBB/Controller/Install.pm +++ b/lib/BeastBB/Controller/Install.pm @@ -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, diff --git a/lib/BeastBB/Controller/Main.pm b/lib/BeastBB/Controller/Main.pm new file mode 100644 index 0000000..5318add --- /dev/null +++ b/lib/BeastBB/Controller/Main.pm @@ -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; diff --git a/lib/BeastBB/DAO/GroupManager.pm b/lib/BeastBB/DAO/GroupManager.pm new file mode 100644 index 0000000..52b7c32 --- /dev/null +++ b/lib/BeastBB/DAO/GroupManager.pm @@ -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; diff --git a/lib/BeastBB/DAO/UserManager.pm b/lib/BeastBB/DAO/UserManager.pm index 51811ac..4012055 100644 --- a/lib/BeastBB/DAO/UserManager.pm +++ b/lib/BeastBB/DAO/UserManager.pm @@ -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 ) ? ( diff --git a/lib/BeastBB/Model/Group.pm b/lib/BeastBB/Model/Group.pm new file mode 100644 index 0000000..720937b --- /dev/null +++ b/lib/BeastBB/Model/Group.pm @@ -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; diff --git a/lib/BeastBB/Model/User.pm b/lib/BeastBB/Model/User.pm new file mode 100644 index 0000000..d7d1210 --- /dev/null +++ b/lib/BeastBB/Model/User.pm @@ -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; diff --git a/lib/BeastBB/DAO/Response.pm b/lib/BeastBB/Response.pm similarity index 98% rename from lib/BeastBB/DAO/Response.pm rename to lib/BeastBB/Response.pm index bdcdea0..a1b25d3 100644 --- a/lib/BeastBB/DAO/Response.pm +++ b/lib/BeastBB/Response.pm @@ -1,4 +1,4 @@ -package BeastBB::DAO::Response; +package BeastBB::Response; use 5.32.1; diff --git a/migrations/1/up.sql b/migrations/1/up.sql index 55d5520..0e36519 100644 --- a/migrations/1/up.sql +++ b/migrations/1/up.sql @@ -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) ); diff --git a/t/Model/00-user.t b/t/Model/00-user.t new file mode 100644 index 0000000..080600f --- /dev/null +++ b/t/Model/00-user.t @@ -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.'; +} diff --git a/t/Model/01-group.t b/t/Model/01-group.t new file mode 100644 index 0000000..ee3c0ba --- /dev/null +++ b/t/Model/01-group.t @@ -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.'; +} diff --git a/templates/main/Index.html.ep b/templates/main/Index.html.ep new file mode 100644 index 0000000..159202e --- /dev/null +++ b/templates/main/Index.html.ep @@ -0,0 +1 @@ +

Hello world