From b7c57b277b2097675c40ae71c80abf464b540668 Mon Sep 17 00:00:00 2001 From: ngoomie Date: Mon, 15 May 2023 17:50:44 -0600 Subject: [PATCH] Partial subforum listing on index, add some perlcritic stuff --- .gitignore | 5 +- .percriticrc | 5 ++ .vscode/settings.json | 21 +++-- README.md | 3 +- charmboard.example.conf | 4 +- lib/CharmBoard.pm | 41 ++++------ lib/CharmBoard/Controller/Index.pm | 46 +++++++++-- lib/CharmBoard/Controller/Login.pm | 27 ++++--- lib/CharmBoard/Controller/Logout.pm | 10 ++- lib/CharmBoard/Controller/Register.pm | 19 +++-- lib/CharmBoard/Crypt/Password.pm | 90 ++++++++++------------ lib/CharmBoard/Crypt/Seasoning.pm | 7 +- lib/CharmBoard/Schema.pm | 8 +- lib/CharmBoard/Schema/Set/Categories.pm | 29 +++++++ lib/CharmBoard/Schema/Set/Subforums.pm | 30 ++++++++ lib/CharmBoard/Schema/Source/Categories.pm | 41 ++-------- lib/CharmBoard/Schema/Source/Posts.pm | 59 ++------------ lib/CharmBoard/Schema/Source/Session.pm | 14 ++-- lib/CharmBoard/Schema/Source/Subforums.pm | 54 ++----------- lib/CharmBoard/Schema/Source/Threads.pm | 35 ++------- lib/CharmBoard/Schema/Source/Users.pm | 56 ++------------ script/CharmBoard | 27 ++++++- templates/index.html.ep | 32 +++++++- 23 files changed, 317 insertions(+), 346 deletions(-) create mode 100644 .percriticrc create mode 100644 lib/CharmBoard/Schema/Set/Categories.pm create mode 100644 lib/CharmBoard/Schema/Set/Subforums.pm diff --git a/.gitignore b/.gitignore index 05af134..8164840 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ charmboard.conf # SQLite *.db -*.db-* \ No newline at end of file +*.db-* + +# Perl::Critic +perlcritic.log \ No newline at end of file diff --git a/.percriticrc b/.percriticrc new file mode 100644 index 0000000..8862995 --- /dev/null +++ b/.percriticrc @@ -0,0 +1,5 @@ +include = CodeLayout::RequireUseUTF8 CompileTime Documentation::RequirePodAtEnd + +severity = 5 +verbose = 5 +criticism-fatal = 1 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b02443..518df7f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "editor.tabSize": 2, "cSpell.enableFiletypes": [ "mojolicious", "perl" @@ -8,28 +9,19 @@ "Authen", "CharmBoard", "Facepunch", + "listsubf", "passchk", "passgen", + "pgsql", "resultset", "signup", - "SMALLINT", "subf", "subforum", "subforums", - "TINYTEXT" + "subfs" ], "better-comments.highlightPlainText": true, "better-comments.tags": [ - - { - "tag": "!", - "color": "#FF2D00", - "strikethrough": false, - "underline": false, - "backgroundColor": "transparent", - "bold": false, - "italic": false - }, { "tag": "?", "color": "#3498DB", @@ -57,5 +49,10 @@ "bold": false, "italic": false } + ], + "perl-toolbox.lint.perlcriticProfile": "$workspaceRoot/.perlcriticrc", + "perl-toolbox.lint.useProfile": true, + "perl-toolbox.syntax.includePaths": [ + "$workspaceRoot/libs" ] } \ No newline at end of file diff --git a/README.md b/README.md index e79b4d4..70ba6fd 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ CharmBoard is forum software written in Perl, inspired by AcmlmBoard/its derivat ## Requirements -- Perl5 v5.20.0 or higher +- Perl5 - `Mojolicious` ([website](https://www.mojolicious.org/), [metacpan](https://metacpan.org/pod/Mojolicious)) - `Mojolicious::Plugin::Renderer::WithoutCache` — only needed in dev environment - `DBI` - `DBIx::Class` - one of two `DBD` database drivers — see `INSTALLING.md` for detailed information +- `Tree::Simple` - `Authen::Passphrase::Argon2` - `Math::Random::Secure` diff --git a/charmboard.example.conf b/charmboard.example.conf index 2a57439..1a206b3 100644 --- a/charmboard.example.conf +++ b/charmboard.example.conf @@ -2,14 +2,14 @@ board_name => '', database => { - type => '', # 'sqlite' or 'mysql' + type => '', # 'sqlite' or 'mariadb' name => '', user => '', pass => '' }, pass_crypt => { - pepper => '' # generate this with `tools/pepper.pl` for now + pepper => '' }, environment => '', # only use 'dev' for now diff --git a/lib/CharmBoard.pm b/lib/CharmBoard.pm index cc26438..0f271e2 100644 --- a/lib/CharmBoard.pm +++ b/lib/CharmBoard.pm @@ -1,35 +1,20 @@ package CharmBoard; + use utf8; -use experimental 'try', 'smartmatch'; +use strict; +use warnings; +use experimental qw(try smartmatch); + use Mojo::Base 'Mojolicious', -signatures; use CharmBoard::Schema; -=pod -=encoding utf8 -=head1 NAME -CharmBoard - revive the fun posting experience! -=head1 NOTES -This documentation is intended for prospective code -contributors. If you're looking to set CharmBoard up, -look for the Markdown format (.md) documentation instead. - -CharmBoard uses a max line length of 60 chars and a tab -size of two spaces. -=head1 DESCRIPTION -CharmBoard is forum software written in Perl with -Mojolicious, intended to be a more fun alternative to the -bigger forum suites available today, inspired by older -forum software like AcmlmBoard, while also being more -modernized in terms of security practices than they are. -Customization ability is another important goal next to -making software that feels fun for the end user to use. -=cut - # this method will run once at server start -sub startup ($self) { +sub startup { + my $self = shift; + # load plugins that require no additional conf $self->plugin('TagHelpers'); - + # load configuration from config file my $config = $self->plugin('Config' => {file => 'charmboard.conf'}); @@ -55,7 +40,7 @@ sub startup ($self) { $dsn = "dbi:SQLite:" . $config->{database}->{name}; $dbUnicode = "sqlite_unicode"} - elsif ($self->config->{database}->{type} ~~ 'mysql') { + elsif ($self->config->{database}->{type} ~~ 'mariadb') { $dsn = "dbi:mysql:" . $config->{database}->{name}; $dbUnicode = "mysql_enable_utf"} @@ -63,7 +48,7 @@ sub startup ($self) { in charmboard.conf. If you're sure you've set it to something supported, maybe double check your spelling? \n\n\t - Valid options: 'sqlite', 'mysql'"}; + Valid options: 'sqlite', 'mariadb'"}; my $schema = CharmBoard::Schema->connect( $dsn, @@ -96,7 +81,7 @@ sub startup ($self) { $r->post('/login')->to( controller => 'Controller::Login', action => 'login_do'); - + ## logout $r->get('/logout')->to( controller => 'Controller::Logout', @@ -104,3 +89,5 @@ sub startup ($self) { } 1; + +__END__ \ No newline at end of file diff --git a/lib/CharmBoard/Controller/Index.pm b/lib/CharmBoard/Controller/Index.pm index 8f1ba0a..f864d68 100644 --- a/lib/CharmBoard/Controller/Index.pm +++ b/lib/CharmBoard/Controller/Index.pm @@ -1,11 +1,45 @@ package CharmBoard::Controller::Index; + use utf8; -use experimental 'try', 'smartmatch'; +use strict; +use warnings; +use feature qw(say unicode_strings); +use experimental qw(try smartmatch); + use Mojo::Base 'Mojolicious::Controller', -signatures; +use Tree::Simple; -sub index ($self) { - $self->render(template => 'index') - - } +sub index { + my $self = shift; -1; \ No newline at end of file + # fetch a list of all categories + my @allCat = + $self->schema->resultset('Categories')->fetch_all; + + # create a Tree::Simple object that will contain the list + # of categories and the subforums that belong to them + my $tree = + Tree::Simple->new("subfList", Tree::Simple->ROOT); + + my ($fetchSubf, $catBranch); + foreach my $iterCat (@allCat) { + # create branch of subfList for the current category + $catBranch = + Tree::Simple->new($iterCat, $tree); + + # fetch all subforums that belong to this category + $fetchSubf = + $self->schema->resultset('Subforums') + ->fetch_by_cat($iterCat); + + # add each fetched subforum as children of the branch + # for the current category + foreach my $iterSubf ($fetchSubf) { + Tree::Simple->new($iterSubf, $catBranch)}} + + $self->render( + template => 'index', + categoryTree => $tree)} + +1; +__END__ \ No newline at end of file diff --git a/lib/CharmBoard/Controller/Login.pm b/lib/CharmBoard/Controller/Login.pm index 49434e8..3305cc5 100644 --- a/lib/CharmBoard/Controller/Login.pm +++ b/lib/CharmBoard/Controller/Login.pm @@ -1,23 +1,22 @@ package CharmBoard::Controller::Login; +use strict; +use warnings; +use experimental qw(try smartmatch); use utf8; -use experimental 'try', 'smartmatch'; use Mojo::Base 'Mojolicious::Controller', -signatures; use CharmBoard::Crypt::Password; use CharmBoard::Crypt::Seasoning; -=pod -=encoding utf8 -=head1 NAME -CharmBoard::Controller::Login -=cut - -sub login ($self) { +sub login { + my $self = shift; + $self->render( template => 'login', error => $self->flash('error'), message => $self->flash('message'))}; -sub login_do ($self) { +sub login_do { + my $self = shift; my $username = $self->param('username'); my $password = $self->pepper . ':' . $self->param('password'); @@ -29,7 +28,7 @@ sub login_do ($self) { try { # check user credentials first # check to see if user by entered username exists - $userInfo = $self->schema->resultset('Users')->find( + $userInfo = $self->schema->resultset('Users')->search( {username => $username}); $userInfo or die; @@ -74,4 +73,10 @@ sub login_do ($self) { logged so the administrator can fix it.'); $self->redirect_to('login')}} -1; \ No newline at end of file +1; + +__END__ +=pod +=head1 NAME +CharmBoard::Controller::Login +=cut \ No newline at end of file diff --git a/lib/CharmBoard/Controller/Logout.pm b/lib/CharmBoard/Controller/Logout.pm index a4f830c..9e20740 100644 --- a/lib/CharmBoard/Controller/Logout.pm +++ b/lib/CharmBoard/Controller/Logout.pm @@ -1,11 +1,15 @@ package CharmBoard::Controller::Logout; +use strict; +use warnings; +use experimental qw(try smartmatch); use utf8; -use experimental 'try', 'smartmatch'; use Mojo::Base 'Mojolicious::Controller', -signatures; -sub logout_do ($self) { +sub logout_do { + my $self = shift; + # destroy entry for this session in the database - $self->schema->resultset('Session')->find({ + $self->schema->resultset('Session')->search({ session_key => $self->session('session_key')})->delete; # now nuke the actual session cookie $self->session(expires => 1); diff --git a/lib/CharmBoard/Controller/Register.pm b/lib/CharmBoard/Controller/Register.pm index 0efe3a9..2329412 100644 --- a/lib/CharmBoard/Controller/Register.pm +++ b/lib/CharmBoard/Controller/Register.pm @@ -1,18 +1,23 @@ package CharmBoard::Controller::Register; +use strict; +use warnings; +use experimental qw(try smartmatch); use utf8; -use experimental 'try', 'smartmatch'; use Mojo::Base 'Mojolicious::Controller', -signatures; use CharmBoard::Crypt::Password; # initial registration page -sub register ($self) { +sub register { + my $self = shift; $self->render( template => 'register', error => $self->flash('error'), message => $self->flash('message'))}; # process submitted registration form -sub register_do ($self) { +sub register_do { + my $self = shift; + my $username = $self->param('username'); my $email = $self->param('email'); my $password = $self->param('password'); @@ -39,10 +44,10 @@ sub register_do ($self) { # check to make sure username and/or email isn't already in use; # if not, continue with registration ## search for input username and email in database - $userCheck = $self->schema->resultset('Users')->find( - {username => $username}); - $emailCheck = $self->schema->resultset('Users')->find( - {email => $email}); + $userCheck = $self->schema->resultset('Users')->search( + {username => $username})->single; + $emailCheck = $self->schema->resultset('Users')->search( + {email => $email})->single; ($userCheck && $emailCheck) eq undef or die "Username already in use.\nemail already in use."; diff --git a/lib/CharmBoard/Crypt/Password.pm b/lib/CharmBoard/Crypt/Password.pm index 31ccfb7..f70119c 100644 --- a/lib/CharmBoard/Crypt/Password.pm +++ b/lib/CharmBoard/Crypt/Password.pm @@ -1,4 +1,7 @@ package CharmBoard::Crypt::Password; +use strict; +use warnings; +use experimental qw(try smartmatch); use utf8; use Authen::Passphrase::Argon2; use CharmBoard::Crypt::Seasoning; @@ -6,40 +9,7 @@ use CharmBoard::Crypt::Seasoning; use Exporter qw(import); our @EXPORT = qw(passgen passchk); -=pod -=encoding utf8 -=head1 NAME -CharmBoard::Crypt::Password - password processing module -=head1 SYNOPSIS -=begin perl -use CharmBoard::Crypt::Password; - -my ($salt, $hash) = - passgen($plaintextPassword); - -$passwordVerification = - passchk($salt, $hash, $plaintextPassword) -=end perl -=head1 DESCRIPTION -CharmBoard::Crypt::Password processes passwords, either -processing new passwords for database storage, or checking -passwords entered when logging in to make sure they're -correct. - -Currently the only available password hashing scheme is -Argon2, but this might be changed later on. -=over -=cut - -=pod -=item passgen -passgen is the function for generating password salts and -hashes to be inserted into the database. It takes the -plaintext password you wish to hash as the only argument, -and outputs the salt and Argon2 hash string in hexadecimal -form. -=cut -sub passgen ($) { +sub passgen { my $argon2 = Authen::Passphrase::Argon2->new( salt => seasoning(32), passphrase => $_[0], @@ -50,21 +20,7 @@ sub passgen ($) { return ($argon2->salt_hex, $argon2->hash_hex)}; -=pod -=item passchk -passchk is the function for checking plaintext passwords -against the hashed password + salt already stored in the -database. It takes the salt and Argon2 hash string in hex -form plus the plaintext password as inputs, and outputs a -true/false value indicating whether or not the input -password matched. Intended for login authentication or -anywhere else where one may need to verify passwords (i.e. -before changing existing passwords, or for admins -confirming they wish to perform a risky or nonreversible -operation.) -=back -=cut -sub passchk ($$$) { +sub passchk { my $argon2 = Authen::Passphrase::Argon2->new( salt_hex => $_[0], hash_hex => $_[1], @@ -75,4 +31,38 @@ sub passchk ($$$) { return ($argon2->match($_[2]))} -1; \ No newline at end of file +1; + +__END__ +=pod +=head1 NAME +CharmBoard::Crypt::Password - password processing module +=head1 SYNOPSIS +=begin perl +use CharmBoard::Crypt::Password; + +($salt, $hash) = passgen($plaintextPassword); +$passwordVerification = passchk($salt, $hash, $plaintextPassword) +=end perl +=head1 DESCRIPTION +CharmBoard::Crypt::Password processes passwords, either processing +new passwords for database storage, or checking passwords entered +when logging in to make sure they're correct. + +Currently the only available password hashing scheme is Argon2, but +this might be changed later on. +=head2 passgen +passgen is the function for generating password salts and hashes to +be inserted into the database. It takes the plaintext password you +wish to hash as the only argument, and outputs the salt and +Argon2 hash string in hexadecimal form. +=head2 passchk +passchk is the function for checking plaintext passwords against the +hashed password + salt already stored in the database. It takes the +salt and Argon2 hash string in hex form plus the plaintext password +as inputs, and outputs a true/false value indicating whether or not +the input password matched. Intended for login authentication or +anywhere else where one may need to verify passwords (i.e. before +changing existing passwords, or for admins confirming they wish to +perform a risky or nonreversible operation.) +=cut \ No newline at end of file diff --git a/lib/CharmBoard/Crypt/Seasoning.pm b/lib/CharmBoard/Crypt/Seasoning.pm index 347dff2..fcf79df 100644 --- a/lib/CharmBoard/Crypt/Seasoning.pm +++ b/lib/CharmBoard/Crypt/Seasoning.pm @@ -1,15 +1,18 @@ package CharmBoard::Crypt::Seasoning; +use strict; +use warnings; +use experimental qw(try smartmatch); use utf8; use Math::Random::Secure qw(irand); use Exporter qw(import); our @EXPORT = qw(seasoning); -sub seasoning ($) { +sub seasoning { my @spices = qw(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ! @ $ % ^ - & * / ? . ; : \ [ ] - _ < > ` ~ + = £ ¥ ¢); + & * / ? . ; : \ [ ] - _ < > ` ~ + = £ ¥ ¢ §); my $blend; while (length($blend) < $_[0]) { diff --git a/lib/CharmBoard/Schema.pm b/lib/CharmBoard/Schema.pm index f39ba17..29868bf 100644 --- a/lib/CharmBoard/Schema.pm +++ b/lib/CharmBoard/Schema.pm @@ -1,8 +1,14 @@ package CharmBoard::Schema; +use strict; +use warnings; +use experimental qw(try smartmatch); +use utf8; use base qw(DBIx::Class::Schema); __PACKAGE__->load_namespaces( result_namespace => 'Source', resultset_namespace => 'Set'); -1; \ No newline at end of file +1; + +__END__ \ No newline at end of file diff --git a/lib/CharmBoard/Schema/Set/Categories.pm b/lib/CharmBoard/Schema/Set/Categories.pm new file mode 100644 index 0000000..e9f8911 --- /dev/null +++ b/lib/CharmBoard/Schema/Set/Categories.pm @@ -0,0 +1,29 @@ +package CharmBoard::Schema::Set::Categories; + +use utf8; +use strict; +use warnings; +use feature qw(say unicode_strings); +use experimental qw(try smartmatch); + +use base 'DBIx::Class::ResultSet'; + +sub fetch_all { + my $set = shift; + + my $_fetch = + $set->search({}, + {order_by => 'cat_rank'}); + + return($_fetch->get_column('cat_id')->all)} + +sub title_from_id { + my $set = shift; + + return( + $set->search({'cat_id' => $_[0]})-> + get_column('cat_name')->first)} + +1; + +__END__ \ No newline at end of file diff --git a/lib/CharmBoard/Schema/Set/Subforums.pm b/lib/CharmBoard/Schema/Set/Subforums.pm new file mode 100644 index 0000000..f6a895e --- /dev/null +++ b/lib/CharmBoard/Schema/Set/Subforums.pm @@ -0,0 +1,30 @@ +package CharmBoard::Schema::Set::Subforums; + +use utf8; +use strict; +use warnings; +use feature qw(say unicode_strings); +use experimental qw(try smartmatch); + +use base 'DBIx::Class::ResultSet'; + +sub fetch_by_cat { + my $set = shift; + + my $fetch = + $set->search( + {'subf_cat' => $_[0] }, + {order_by => 'subf_rank', + group_by => undef}); + + return($fetch->get_column('subf_id')->all)} + +sub title_from_id { + my $set = shift; + + return( + $set->search({'subf_id' => $_[0]})-> + get_column('subf_name')->first)} + +1; +__END__ \ No newline at end of file diff --git a/lib/CharmBoard/Schema/Source/Categories.pm b/lib/CharmBoard/Schema/Source/Categories.pm index cae0380..d57ffe5 100644 --- a/lib/CharmBoard/Schema/Source/Categories.pm +++ b/lib/CharmBoard/Schema/Source/Categories.pm @@ -1,53 +1,24 @@ package CharmBoard::Schema::Source::Categories; +use strict; +use warnings; +use experimental qw(try smartmatch); +use utf8; use base qw(DBIx::Class::Core); -=pod -=encoding utf8 -=head1 NAME -CharmBoard::Schema::Source::Categories - DBIx::Class -ResultSource module for the database's C table -=head1 DESCRIPTION -This table contains info about categories, which are used -solely to organize subforums on places like the index page. -=head2 Columns -=over -=item cat_id -Contains unique IDs for individual categories. - -Data type is SQLite C; MariaDB C. -Cannot be C. - -=item cat_rank -The order in which categories should be displayed. - -Data type is SQLite C; MariaDB C. -Cannot be C. - -=item cat_name -The name of the category to be displayed in the forum list. - -Data type is SQLite C; MariaDB C. -Cannot be C. -=back -=cut - __PACKAGE__->table('categories'); __PACKAGE__->add_columns( - cat_id => { data_type => 'integer', - is_numeric => 1, is_auto_increment => 1, is_nullable => 0, }, cat_rank => { data_type => 'integer', - is_auto_increment => 0, is_nullable => 0, }, cat_name => { data_type => 'text', - is_auto_increment => 0, is_nullable => 0, }); __PACKAGE__->set_primary_key('cat_id'); -1 \ No newline at end of file +1; +__END__ \ No newline at end of file diff --git a/lib/CharmBoard/Schema/Source/Posts.pm b/lib/CharmBoard/Schema/Source/Posts.pm index c6a7cc6..8e82b16 100644 --- a/lib/CharmBoard/Schema/Source/Posts.pm +++ b/lib/CharmBoard/Schema/Source/Posts.pm @@ -1,74 +1,26 @@ package CharmBoard::Schema::Source::Posts; +use strict; +use warnings; +use experimental qw(try smartmatch); +use utf8; use base qw(DBIx::Class::Core); -=pod -=encoding utf8 -=head1 NAME -CharmBoard::Schema::Source::Posts - DBIx::Class -ResultSource module for the database's C table -=head1 DESCRIPTION -This table contains post content and other important post -information (such as post date). -=head2 Columns -=over -=item post_id -Contains unique IDs for posts. - -Data type is SQLite C; MariaDB C. Cannot -be C. - -=item user_id -Contains the user ID of the creator of a given post. - -Is foreign key of C, and as such, shares the -same datatype (SQLite C; MariaDB C). -Cannot be C. - -=item thread_id -Contains the ID of the thread this post was posted in. - -Is foreign key of C, and as such, shares -the same datatype (C in SQLite). Cannot be C. - -=item post_date -Contains the date the post was made, in the format provided -by Perl's C