Merge commit 'b7c57b277b2097675c40ae71c80abf464b540668' into HEAD
This commit is contained in:
commit
e12eeebb6e
|
@ -3,4 +3,7 @@ charmboard.conf
|
|||
|
||||
# SQLite
|
||||
*.db
|
||||
*.db-*
|
||||
*.db-*
|
||||
|
||||
# Perl::Critic
|
||||
perlcritic.log
|
|
@ -0,0 +1,5 @@
|
|||
include = CodeLayout::RequireUseUTF8 CompileTime Documentation::RequirePodAtEnd
|
||||
|
||||
severity = 5
|
||||
verbose = 5
|
||||
criticism-fatal = 1
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"editor.tabSize": 2,
|
||||
"cSpell.enableFiletypes": [
|
||||
"mojolicious",
|
||||
"perl"
|
||||
|
@ -8,26 +9,19 @@
|
|||
"Authen",
|
||||
"CharmBoard",
|
||||
"Facepunch",
|
||||
"listsubf",
|
||||
"passchk",
|
||||
"passgen",
|
||||
"pgsql",
|
||||
"resultset",
|
||||
"signup",
|
||||
"subf",
|
||||
"subforum",
|
||||
"subforums"
|
||||
"subforums",
|
||||
"subfs"
|
||||
],
|
||||
"better-comments.highlightPlainText": true,
|
||||
"better-comments.tags": [
|
||||
|
||||
{
|
||||
"tag": "!",
|
||||
"color": "#FF2D00",
|
||||
"strikethrough": false,
|
||||
"underline": false,
|
||||
"backgroundColor": "transparent",
|
||||
"bold": false,
|
||||
"italic": false
|
||||
},
|
||||
{
|
||||
"tag": "?",
|
||||
"color": "#3498DB",
|
||||
|
@ -55,5 +49,10 @@
|
|||
"bold": false,
|
||||
"italic": false
|
||||
}
|
||||
],
|
||||
"perl-toolbox.lint.perlcriticProfile": "$workspaceRoot/.perlcriticrc",
|
||||
"perl-toolbox.lint.useProfile": true,
|
||||
"perl-toolbox.syntax.includePaths": [
|
||||
"$workspaceRoot/libs"
|
||||
]
|
||||
}
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,34 +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
|
||||
=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'});
|
||||
|
@ -54,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"}
|
||||
|
||||
|
@ -62,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,
|
||||
|
@ -95,7 +81,7 @@ sub startup ($self) {
|
|||
$r->post('/login')->to(
|
||||
controller => 'Controller::Login',
|
||||
action => 'login_do');
|
||||
|
||||
|
||||
## logout
|
||||
$r->get('/logout')->to(
|
||||
controller => 'Controller::Logout',
|
||||
|
@ -103,3 +89,5 @@ sub startup ($self) {
|
|||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
|
@ -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;
|
||||
# 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__
|
|
@ -1,22 +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
|
||||
=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');
|
||||
|
||||
|
@ -73,4 +73,10 @@ sub login_do ($self) {
|
|||
logged so the administrator can fix it.');
|
||||
$self->redirect_to('login')}}
|
||||
|
||||
1;
|
||||
1;
|
||||
|
||||
__END__
|
||||
=pod
|
||||
=head1 NAME
|
||||
CharmBoard::Controller::Login
|
||||
=cut
|
|
@ -1,9 +1,13 @@
|
|||
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')->search({
|
||||
session_key => $self->session('session_key')})->delete;
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,6 +9,31 @@ use CharmBoard::Crypt::Seasoning;
|
|||
use Exporter qw(import);
|
||||
our @EXPORT = qw(passgen passchk);
|
||||
|
||||
sub passgen {
|
||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||
salt => seasoning(32),
|
||||
passphrase => $_[0],
|
||||
cost => 17,
|
||||
factor => '32M',
|
||||
parallelism => 1,
|
||||
size => 32 );
|
||||
|
||||
return ($argon2->salt_hex, $argon2->hash_hex)};
|
||||
|
||||
sub passchk {
|
||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||
salt_hex => $_[0],
|
||||
hash_hex => $_[1],
|
||||
cost => 17,
|
||||
factor => '32M',
|
||||
parallelism => 1,
|
||||
size => 32 );
|
||||
|
||||
return ($argon2->match($_[2]))}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
=pod
|
||||
=head1 NAME
|
||||
CharmBoard::Crypt::Password - password processing module
|
||||
|
@ -23,27 +51,11 @@ 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.
|
||||
=cut
|
||||
|
||||
=pod
|
||||
=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.
|
||||
=cut
|
||||
sub passgen ($) {
|
||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||
salt => seasoning(32),
|
||||
passphrase => $_[0],
|
||||
cost => 17,
|
||||
factor => '32M',
|
||||
parallelism => 1,
|
||||
size => 32 );
|
||||
|
||||
return ($argon2->salt_hex, $argon2->hash_hex)};
|
||||
|
||||
=pod
|
||||
=head2 passchk
|
||||
passchk is the function for checking plaintext passwords against the
|
||||
hashed password + salt already stored in the database. It takes the
|
||||
|
@ -53,16 +65,4 @@ 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
|
||||
sub passchk ($$$) {
|
||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||
salt_hex => $_[0],
|
||||
hash_hex => $_[1],
|
||||
cost => 17,
|
||||
factor => '32M',
|
||||
parallelism => 1,
|
||||
size => 32 );
|
||||
|
||||
return ($argon2->match($_[2]))}
|
||||
|
||||
1;
|
||||
=cut
|
|
@ -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]) {
|
||||
|
|
|
@ -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;
|
||||
1;
|
||||
|
||||
__END__
|
|
@ -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__
|
|
@ -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__
|
|
@ -1,4 +1,8 @@
|
|||
package CharmBoard::Schema::Source::Categories;
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw(try smartmatch);
|
||||
use utf8;
|
||||
use base qw(DBIx::Class::Core);
|
||||
|
||||
__PACKAGE__->table('categories');
|
||||
|
@ -7,10 +11,14 @@ __PACKAGE__->add_columns(
|
|||
data_type => 'integer',
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0, },
|
||||
cat_rank => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0, },
|
||||
cat_name => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0, });
|
||||
|
||||
__PACKAGE__->set_primary_key('cat_id');
|
||||
|
||||
1
|
||||
1;
|
||||
__END__
|
|
@ -1,27 +1,26 @@
|
|||
package CharmBoard::Schema::Source::Posts;
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw(try smartmatch);
|
||||
use utf8;
|
||||
use base qw(DBIx::Class::Core);
|
||||
|
||||
__PACKAGE__->table('posts');
|
||||
__PACKAGE__->add_columns(
|
||||
post_id => {
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 0,
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0, },
|
||||
user_id => {
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 1,
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
thread_id => {
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 1,
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
post_date => {
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 0,
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, });
|
||||
|
||||
__PACKAGE__->set_primary_key('post_id');
|
||||
|
@ -35,4 +34,5 @@ __PACKAGE__->belongs_to(
|
|||
'CharmBoard::Schema::Source::Threads',
|
||||
'thread_id' );
|
||||
|
||||
1
|
||||
1;
|
||||
__END__
|
|
@ -1,27 +1,26 @@
|
|||
package CharmBoard::Schema::Source::Session;
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw(try smartmatch);
|
||||
use utf8;
|
||||
use base qw(DBIx::Class::Core);
|
||||
|
||||
__PACKAGE__->table('sessions');
|
||||
__PACKAGE__->add_columns(
|
||||
session_key => {
|
||||
data_type => 'text',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
user_id => {
|
||||
data_type => 'integer',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
session_expiry => {
|
||||
data_type => 'numeric',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
is_ip_bound => {
|
||||
data_type => 'integer',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
bound_ip => {
|
||||
data_type => 'text',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 1, });
|
||||
|
||||
__PACKAGE__->set_primary_key('session_key');
|
||||
|
@ -31,4 +30,5 @@ __PACKAGE__->belongs_to(
|
|||
'CharmBoard::Schema::Source::Users',
|
||||
'user_id');
|
||||
|
||||
1
|
||||
1;
|
||||
__END__
|
|
@ -1,4 +1,8 @@
|
|||
package CharmBoard::Schema::Source::Subforums;
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw(try smartmatch);
|
||||
use utf8;
|
||||
use base qw(DBIx::Class::Core);
|
||||
|
||||
__PACKAGE__->table('subforums');
|
||||
|
@ -10,15 +14,16 @@ __PACKAGE__->add_columns(
|
|||
subf_cat => {
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 1,
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
subf_rank => {
|
||||
data_type => 'integer',
|
||||
is_numeric => 1,
|
||||
is_nullable => 0, },
|
||||
subf_name => {
|
||||
data_type => 'text',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 0, },
|
||||
subf_desc => {
|
||||
data_type => 'text',
|
||||
is_auto_increment => 0,
|
||||
is_nullable => 1, });
|
||||
|
||||
__PACKAGE__->set_primary_key('subf_id');
|
||||
|
@ -28,4 +33,5 @@ __PACKAGE__->belongs_to(
|
|||
'CharmBoard::Schema::Source::Categories',
|
||||
{'foreign.cat_id' => 'self.subf_cat'});
|
||||
|
||||
1
|
||||
1;
|
||||
__END__
|
|
@ -1,4 +1,8 @@
|
|||
package CharmBoard::Schema::Source::Threads;
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw(try smartmatch);
|
||||
use utf8;
|
||||
use base qw(DBIx::Class::Core);
|
||||
|
||||
__PACKAGE__->table('threads');
|
||||
|
@ -13,9 +17,7 @@ __PACKAGE__->add_columns(
|
|||
thread_subf => {
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 1, });
|
||||
# ! thread_subf should NOT be nullable once subforums
|
||||
# ! are properly implemented
|
||||
is_nullable => 0, });
|
||||
|
||||
__PACKAGE__->set_primary_key('thread_id');
|
||||
|
||||
|
@ -24,4 +26,5 @@ __PACKAGE__->belongs_to(
|
|||
'CharmBoard::Schema::Source::Subforums',
|
||||
{'foreign.subf_id' => 'self.thread_subf'});
|
||||
|
||||
1
|
||||
1;
|
||||
__END__
|
|
@ -1,4 +1,7 @@
|
|||
package CharmBoard::Schema::Source::Users;
|
||||
use strict;
|
||||
use warnings;
|
||||
use experimental qw(try smartmatch);
|
||||
use utf8;
|
||||
use base qw(DBIx::Class::Core);
|
||||
|
||||
|
@ -11,19 +14,15 @@ __PACKAGE__->add_columns(
|
|||
is_auto_increment => 1, },
|
||||
username => {
|
||||
data_type => 'text',
|
||||
is_numeric => 0,
|
||||
is_nullable => 0, },
|
||||
email => {
|
||||
data_type => 'text',
|
||||
is_numeric => 0,
|
||||
is_nullable => 0, },
|
||||
password => {
|
||||
data_type => 'text',
|
||||
is_numeric => 0,
|
||||
is_nullable => 0, },
|
||||
salt => {
|
||||
data_type => 'text',
|
||||
is_numeric => 0,
|
||||
is_nullable => 0, },
|
||||
signup_date => {
|
||||
data_type => 'integer',
|
||||
|
@ -32,4 +31,5 @@ __PACKAGE__->add_columns(
|
|||
|
||||
__PACKAGE__->set_primary_key('user_id');
|
||||
|
||||
1
|
||||
1;
|
||||
__END__
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env perl
|
||||
use experimental 'try', 'smartmatch';
|
||||
use experimental qw(try smartmatch);
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
@ -10,3 +10,28 @@ use Mojolicious::Commands;
|
|||
|
||||
# Start command line interface for application
|
||||
Mojolicious::Commands->start_app('CharmBoard');
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
=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
|
||||
|
||||
|
|
|
@ -1,2 +1,32 @@
|
|||
% layout 'default', title => $self->boardName;
|
||||
this is the index page
|
||||
|
||||
<% my $catHeader = begin %>
|
||||
% my $_catID = shift; my $_name = shift;
|
||||
<div class="category-header category-<%= $_catID %>">
|
||||
<b><%= $_name %></b>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% my $subfItem = begin %>
|
||||
% my $_subfID = shift; my $_catID = shift;
|
||||
% my $_name = shift;
|
||||
<div class="
|
||||
subforum-item subforum-<%= $_subfID %>
|
||||
category-<%= $_catID %>
|
||||
"><%= $_name %></div>
|
||||
<% end %>
|
||||
|
||||
<%
|
||||
foreach my $category ($categoryTree->getAllChildren) { %>
|
||||
<%= $catHeader->(
|
||||
$category->getNodeValue,
|
||||
$self->schema->resultset('Categories')->
|
||||
title_from_id($category->getNodeValue)) %>
|
||||
<%
|
||||
foreach my $subforum ($category->getAllChildren) { %>
|
||||
<%= $subfItem->(
|
||||
$subforum->getNodeValue,
|
||||
$category->getNodeValue,
|
||||
$self->schema->resultset('Subforums')->
|
||||
title_from_id($subforum->getNodeValue)) %>
|
||||
<% }} %>
|
Loading…
Reference in New Issue