Compare commits
2 Commits
4eec3f79c5
...
e12eeebb6e
Author | SHA1 | Date |
---|---|---|
ngoomie | e12eeebb6e | |
ngoomie | b7c57b277b |
|
@ -4,3 +4,6 @@ charmboard.conf
|
||||||
# SQLite
|
# SQLite
|
||||||
*.db
|
*.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": [
|
"cSpell.enableFiletypes": [
|
||||||
"mojolicious",
|
"mojolicious",
|
||||||
"perl"
|
"perl"
|
||||||
|
@ -8,28 +9,19 @@
|
||||||
"Authen",
|
"Authen",
|
||||||
"CharmBoard",
|
"CharmBoard",
|
||||||
"Facepunch",
|
"Facepunch",
|
||||||
|
"listsubf",
|
||||||
"passchk",
|
"passchk",
|
||||||
"passgen",
|
"passgen",
|
||||||
|
"pgsql",
|
||||||
"resultset",
|
"resultset",
|
||||||
"signup",
|
"signup",
|
||||||
"SMALLINT",
|
|
||||||
"subf",
|
"subf",
|
||||||
"subforum",
|
"subforum",
|
||||||
"subforums",
|
"subforums",
|
||||||
"TINYTEXT"
|
"subfs"
|
||||||
],
|
],
|
||||||
"better-comments.highlightPlainText": true,
|
"better-comments.highlightPlainText": true,
|
||||||
"better-comments.tags": [
|
"better-comments.tags": [
|
||||||
|
|
||||||
{
|
|
||||||
"tag": "!",
|
|
||||||
"color": "#FF2D00",
|
|
||||||
"strikethrough": false,
|
|
||||||
"underline": false,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"bold": false,
|
|
||||||
"italic": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"tag": "?",
|
"tag": "?",
|
||||||
"color": "#3498DB",
|
"color": "#3498DB",
|
||||||
|
@ -57,5 +49,10 @@
|
||||||
"bold": false,
|
"bold": false,
|
||||||
"italic": 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
|
## Requirements
|
||||||
|
|
||||||
- Perl5 v5.20.0 or higher
|
- Perl5
|
||||||
- `Mojolicious` ([website](https://www.mojolicious.org/), [metacpan](https://metacpan.org/pod/Mojolicious))
|
- `Mojolicious` ([website](https://www.mojolicious.org/), [metacpan](https://metacpan.org/pod/Mojolicious))
|
||||||
- `Mojolicious::Plugin::Renderer::WithoutCache` — only needed in dev environment
|
- `Mojolicious::Plugin::Renderer::WithoutCache` — only needed in dev environment
|
||||||
- `DBI`
|
- `DBI`
|
||||||
- `DBIx::Class`
|
- `DBIx::Class`
|
||||||
- one of two `DBD` database drivers — see `INSTALLING.md` for detailed information
|
- one of two `DBD` database drivers — see `INSTALLING.md` for detailed information
|
||||||
|
- `Tree::Simple`
|
||||||
- `Authen::Passphrase::Argon2`
|
- `Authen::Passphrase::Argon2`
|
||||||
- `Math::Random::Secure`
|
- `Math::Random::Secure`
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
board_name => '',
|
board_name => '',
|
||||||
|
|
||||||
database => {
|
database => {
|
||||||
type => '', # 'sqlite' or 'mysql'
|
type => '', # 'sqlite' or 'mariadb'
|
||||||
name => '',
|
name => '',
|
||||||
user => '',
|
user => '',
|
||||||
pass => ''
|
pass => ''
|
||||||
},
|
},
|
||||||
|
|
||||||
pass_crypt => {
|
pass_crypt => {
|
||||||
pepper => '' # generate this with `tools/pepper.pl` for now
|
pepper => ''
|
||||||
},
|
},
|
||||||
|
|
||||||
environment => '', # only use 'dev' for now
|
environment => '', # only use 'dev' for now
|
||||||
|
|
|
@ -1,32 +1,17 @@
|
||||||
package CharmBoard;
|
package CharmBoard;
|
||||||
|
|
||||||
use utf8;
|
use utf8;
|
||||||
use experimental 'try', 'smartmatch';
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
|
||||||
use Mojo::Base 'Mojolicious', -signatures;
|
use Mojo::Base 'Mojolicious', -signatures;
|
||||||
use CharmBoard::Schema;
|
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
|
# this method will run once at server start
|
||||||
sub startup ($self) {
|
sub startup {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
# load plugins that require no additional conf
|
# load plugins that require no additional conf
|
||||||
$self->plugin('TagHelpers');
|
$self->plugin('TagHelpers');
|
||||||
|
|
||||||
|
@ -55,7 +40,7 @@ sub startup ($self) {
|
||||||
$dsn = "dbi:SQLite:" . $config->{database}->{name};
|
$dsn = "dbi:SQLite:" . $config->{database}->{name};
|
||||||
$dbUnicode = "sqlite_unicode"}
|
$dbUnicode = "sqlite_unicode"}
|
||||||
|
|
||||||
elsif ($self->config->{database}->{type} ~~ 'mysql') {
|
elsif ($self->config->{database}->{type} ~~ 'mariadb') {
|
||||||
$dsn = "dbi:mysql:" . $config->{database}->{name};
|
$dsn = "dbi:mysql:" . $config->{database}->{name};
|
||||||
$dbUnicode = "mysql_enable_utf"}
|
$dbUnicode = "mysql_enable_utf"}
|
||||||
|
|
||||||
|
@ -63,7 +48,7 @@ sub startup ($self) {
|
||||||
in charmboard.conf. If you're sure you've set it to
|
in charmboard.conf. If you're sure you've set it to
|
||||||
something supported, maybe double check your spelling?
|
something supported, maybe double check your spelling?
|
||||||
\n\n\t
|
\n\n\t
|
||||||
Valid options: 'sqlite', 'mysql'"};
|
Valid options: 'sqlite', 'mariadb'"};
|
||||||
|
|
||||||
my $schema = CharmBoard::Schema->connect(
|
my $schema = CharmBoard::Schema->connect(
|
||||||
$dsn,
|
$dsn,
|
||||||
|
@ -104,3 +89,5 @@ sub startup ($self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
|
__END__
|
|
@ -1,11 +1,45 @@
|
||||||
package CharmBoard::Controller::Index;
|
package CharmBoard::Controller::Index;
|
||||||
|
|
||||||
use utf8;
|
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 Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
use Tree::Simple;
|
||||||
|
|
||||||
sub index ($self) {
|
sub index {
|
||||||
$self->render(template => 'index')
|
my $self = shift;
|
||||||
|
|
||||||
}
|
# 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;
|
1;
|
||||||
|
__END__
|
|
@ -1,23 +1,22 @@
|
||||||
package CharmBoard::Controller::Login;
|
package CharmBoard::Controller::Login;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
use utf8;
|
use utf8;
|
||||||
use experimental 'try', 'smartmatch';
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
use CharmBoard::Crypt::Password;
|
use CharmBoard::Crypt::Password;
|
||||||
use CharmBoard::Crypt::Seasoning;
|
use CharmBoard::Crypt::Seasoning;
|
||||||
|
|
||||||
=pod
|
sub login {
|
||||||
=encoding utf8
|
my $self = shift;
|
||||||
=head1 NAME
|
|
||||||
CharmBoard::Controller::Login
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub login ($self) {
|
|
||||||
$self->render(
|
$self->render(
|
||||||
template => 'login',
|
template => 'login',
|
||||||
error => $self->flash('error'),
|
error => $self->flash('error'),
|
||||||
message => $self->flash('message'))};
|
message => $self->flash('message'))};
|
||||||
|
|
||||||
sub login_do ($self) {
|
sub login_do {
|
||||||
|
my $self = shift;
|
||||||
my $username = $self->param('username');
|
my $username = $self->param('username');
|
||||||
my $password = $self->pepper . ':' . $self->param('password');
|
my $password = $self->pepper . ':' . $self->param('password');
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ sub login_do ($self) {
|
||||||
|
|
||||||
try { # check user credentials first
|
try { # check user credentials first
|
||||||
# check to see if user by entered username exists
|
# check to see if user by entered username exists
|
||||||
$userInfo = $self->schema->resultset('Users')->find(
|
$userInfo = $self->schema->resultset('Users')->search(
|
||||||
{username => $username});
|
{username => $username});
|
||||||
$userInfo or die;
|
$userInfo or die;
|
||||||
|
|
||||||
|
@ -75,3 +74,9 @@ sub login_do ($self) {
|
||||||
$self->redirect_to('login')}}
|
$self->redirect_to('login')}}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
|
__END__
|
||||||
|
=pod
|
||||||
|
=head1 NAME
|
||||||
|
CharmBoard::Controller::Login
|
||||||
|
=cut
|
|
@ -1,11 +1,15 @@
|
||||||
package CharmBoard::Controller::Logout;
|
package CharmBoard::Controller::Logout;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
use utf8;
|
use utf8;
|
||||||
use experimental 'try', 'smartmatch';
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
|
||||||
sub logout_do ($self) {
|
sub logout_do {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
# destroy entry for this session in the database
|
# 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;
|
session_key => $self->session('session_key')})->delete;
|
||||||
# now nuke the actual session cookie
|
# now nuke the actual session cookie
|
||||||
$self->session(expires => 1);
|
$self->session(expires => 1);
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
package CharmBoard::Controller::Register;
|
package CharmBoard::Controller::Register;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
use utf8;
|
use utf8;
|
||||||
use experimental 'try', 'smartmatch';
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
use CharmBoard::Crypt::Password;
|
use CharmBoard::Crypt::Password;
|
||||||
|
|
||||||
# initial registration page
|
# initial registration page
|
||||||
sub register ($self) {
|
sub register {
|
||||||
|
my $self = shift;
|
||||||
$self->render(
|
$self->render(
|
||||||
template => 'register',
|
template => 'register',
|
||||||
error => $self->flash('error'),
|
error => $self->flash('error'),
|
||||||
message => $self->flash('message'))};
|
message => $self->flash('message'))};
|
||||||
|
|
||||||
# process submitted registration form
|
# process submitted registration form
|
||||||
sub register_do ($self) {
|
sub register_do {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
my $username = $self->param('username');
|
my $username = $self->param('username');
|
||||||
my $email = $self->param('email');
|
my $email = $self->param('email');
|
||||||
my $password = $self->param('password');
|
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;
|
# check to make sure username and/or email isn't already in use;
|
||||||
# if not, continue with registration
|
# if not, continue with registration
|
||||||
## search for input username and email in database
|
## search for input username and email in database
|
||||||
$userCheck = $self->schema->resultset('Users')->find(
|
$userCheck = $self->schema->resultset('Users')->search(
|
||||||
{username => $username});
|
{username => $username})->single;
|
||||||
$emailCheck = $self->schema->resultset('Users')->find(
|
$emailCheck = $self->schema->resultset('Users')->search(
|
||||||
{email => $email});
|
{email => $email})->single;
|
||||||
|
|
||||||
($userCheck && $emailCheck) eq undef
|
($userCheck && $emailCheck) eq undef
|
||||||
or die "Username already in use.\nemail already in use.";
|
or die "Username already in use.\nemail already in use.";
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
package CharmBoard::Crypt::Password;
|
package CharmBoard::Crypt::Password;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
use utf8;
|
use utf8;
|
||||||
use Authen::Passphrase::Argon2;
|
use Authen::Passphrase::Argon2;
|
||||||
use CharmBoard::Crypt::Seasoning;
|
use CharmBoard::Crypt::Seasoning;
|
||||||
|
@ -6,40 +9,7 @@ use CharmBoard::Crypt::Seasoning;
|
||||||
use Exporter qw(import);
|
use Exporter qw(import);
|
||||||
our @EXPORT = qw(passgen passchk);
|
our @EXPORT = qw(passgen passchk);
|
||||||
|
|
||||||
=pod
|
sub passgen {
|
||||||
=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 ($) {
|
|
||||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||||
salt => seasoning(32),
|
salt => seasoning(32),
|
||||||
passphrase => $_[0],
|
passphrase => $_[0],
|
||||||
|
@ -50,21 +20,7 @@ sub passgen ($) {
|
||||||
|
|
||||||
return ($argon2->salt_hex, $argon2->hash_hex)};
|
return ($argon2->salt_hex, $argon2->hash_hex)};
|
||||||
|
|
||||||
=pod
|
sub passchk {
|
||||||
=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 ($$$) {
|
|
||||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||||
salt_hex => $_[0],
|
salt_hex => $_[0],
|
||||||
hash_hex => $_[1],
|
hash_hex => $_[1],
|
||||||
|
@ -76,3 +32,37 @@ sub passchk ($$$) {
|
||||||
return ($argon2->match($_[2]))}
|
return ($argon2->match($_[2]))}
|
||||||
|
|
||||||
1;
|
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
|
|
@ -1,15 +1,18 @@
|
||||||
package CharmBoard::Crypt::Seasoning;
|
package CharmBoard::Crypt::Seasoning;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
use utf8;
|
use utf8;
|
||||||
use Math::Random::Secure qw(irand);
|
use Math::Random::Secure qw(irand);
|
||||||
|
|
||||||
use Exporter qw(import);
|
use Exporter qw(import);
|
||||||
our @EXPORT = qw(seasoning);
|
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
|
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
|
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 ! @ $ % ^
|
G H I J K L M N O P Q R S T U V W X Y Z ! @ $ % ^
|
||||||
& * / ? . ; : \ [ ] - _ < > ` ~ + = £ ¥ ¢);
|
& * / ? . ; : \ [ ] - _ < > ` ~ + = £ ¥ ¢ §);
|
||||||
|
|
||||||
my $blend;
|
my $blend;
|
||||||
while (length($blend) < $_[0]) {
|
while (length($blend) < $_[0]) {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
package CharmBoard::Schema;
|
package CharmBoard::Schema;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Schema);
|
use base qw(DBIx::Class::Schema);
|
||||||
|
|
||||||
__PACKAGE__->load_namespaces(
|
__PACKAGE__->load_namespaces(
|
||||||
|
@ -6,3 +10,5 @@ __PACKAGE__->load_namespaces(
|
||||||
resultset_namespace => 'Set');
|
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,53 +1,24 @@
|
||||||
package CharmBoard::Schema::Source::Categories;
|
package CharmBoard::Schema::Source::Categories;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
=pod
|
|
||||||
=encoding utf8
|
|
||||||
=head1 NAME
|
|
||||||
CharmBoard::Schema::Source::Categories - DBIx::Class
|
|
||||||
ResultSource module for the database's C<categories> 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<INTEGER>; MariaDB C<INT>.
|
|
||||||
Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item cat_rank
|
|
||||||
The order in which categories should be displayed.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>; MariaDB C<SMALLINT>.
|
|
||||||
Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item cat_name
|
|
||||||
The name of the category to be displayed in the forum list.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>; MariaDB C<TINYTEXT>.
|
|
||||||
Cannot be C<NULL>.
|
|
||||||
=back
|
|
||||||
=cut
|
|
||||||
|
|
||||||
__PACKAGE__->table('categories');
|
__PACKAGE__->table('categories');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
|
|
||||||
cat_id => {
|
cat_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_numeric => 1,
|
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
cat_rank => {
|
cat_rank => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
cat_name => {
|
cat_name => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, });
|
is_nullable => 0, });
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('cat_id');
|
__PACKAGE__->set_primary_key('cat_id');
|
||||||
|
|
||||||
1
|
1;
|
||||||
|
__END__
|
|
@ -1,74 +1,26 @@
|
||||||
package CharmBoard::Schema::Source::Posts;
|
package CharmBoard::Schema::Source::Posts;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
=pod
|
|
||||||
=encoding utf8
|
|
||||||
=head1 NAME
|
|
||||||
CharmBoard::Schema::Source::Posts - DBIx::Class
|
|
||||||
ResultSource module for the database's C<posts> 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<INTEGER>; MariaDB C<INT>. Cannot
|
|
||||||
be C<NULL>.
|
|
||||||
|
|
||||||
=item user_id
|
|
||||||
Contains the user ID of the creator of a given post.
|
|
||||||
|
|
||||||
Is foreign key of C<users.user_id>, and as such, shares the
|
|
||||||
same datatype (SQLite C<INTEGER>; MariaDB C<INT>).
|
|
||||||
Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item thread_id
|
|
||||||
Contains the ID of the thread this post was posted in.
|
|
||||||
|
|
||||||
Is foreign key of C<threads.thread_id>, and as such, shares
|
|
||||||
the same datatype (C<INTEGER> in SQLite). Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item post_date
|
|
||||||
Contains the date the post was made, in the format provided
|
|
||||||
by Perl's C<time> function.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item post_body
|
|
||||||
Contains the actual text content of the post.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
=back
|
|
||||||
=cut
|
|
||||||
|
|
||||||
__PACKAGE__->table('posts');
|
__PACKAGE__->table('posts');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
post_id => {
|
post_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_foreign_key => 0,
|
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
user_id => {
|
user_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_foreign_key => 1,
|
is_foreign_key => 1,
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
thread_id => {
|
thread_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_foreign_key => 1,
|
is_foreign_key => 1,
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
post_date => {
|
post_date => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_foreign_key => 0,
|
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
|
||||||
post_body => {
|
|
||||||
data_type => 'text',
|
|
||||||
is_foreign_key => 0,
|
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, });
|
is_nullable => 0, });
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('post_id');
|
__PACKAGE__->set_primary_key('post_id');
|
||||||
|
@ -82,4 +34,5 @@ __PACKAGE__->belongs_to(
|
||||||
'CharmBoard::Schema::Source::Threads',
|
'CharmBoard::Schema::Source::Threads',
|
||||||
'thread_id' );
|
'thread_id' );
|
||||||
|
|
||||||
1
|
1;
|
||||||
|
__END__
|
|
@ -1,34 +1,34 @@
|
||||||
package CharmBoard::Schema::Source::Session;
|
package CharmBoard::Schema::Source::Session;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('sessions');
|
__PACKAGE__->table('sessions');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
session_key => {
|
session_key => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
user_id => {
|
user_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
session_expiry => {
|
session_expiry => {
|
||||||
data_type => 'numeric',
|
data_type => 'numeric',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
is_ip_bound => {
|
is_ip_bound => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
bound_ip => {
|
bound_ip => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 1, });
|
is_nullable => 1, });
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key(qw(session_key user_id));
|
__PACKAGE__->set_primary_key('session_key');
|
||||||
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
user_id =>
|
user_id =>
|
||||||
'CharmBoard::Schema::Source::Users',
|
'CharmBoard::Schema::Source::Users',
|
||||||
'user_id');
|
'user_id');
|
||||||
|
|
||||||
1
|
1;
|
||||||
|
__END__
|
|
@ -1,47 +1,10 @@
|
||||||
package CharmBoard::Schema::Source::Subforums;
|
package CharmBoard::Schema::Source::Subforums;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
=pod
|
|
||||||
=encoding utf8
|
|
||||||
=head1 NAME
|
|
||||||
CharmBoard::Schema::Source::Subforums - DBIx::Class
|
|
||||||
ResultSource module for the database's C<subforums> table
|
|
||||||
=head1 DESCRIPTION
|
|
||||||
This table contains information about subforums.
|
|
||||||
=head2 Columns
|
|
||||||
=over
|
|
||||||
=item subf_id
|
|
||||||
Contains unique IDs for individual subforums.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>.. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item subf_cat
|
|
||||||
Which category from the C<categories> table this subforum
|
|
||||||
belongs to and should show up as a child of.
|
|
||||||
|
|
||||||
Is foreign key of C<categories.cat_id> and as such, shares
|
|
||||||
the same datatype (C<INTEGER> in SQLite). Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item subf_rank
|
|
||||||
The order in which subforums from a given category should
|
|
||||||
be displayed, which should be unique per category group.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item subf_name
|
|
||||||
The name of the subforum to be displayed in the forum list,
|
|
||||||
or anywhere else where it is relevant.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item subf_desc
|
|
||||||
A blurb of text describing the use of a subforum to be
|
|
||||||
displayed in the forum list.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Can be C<NULL>.
|
|
||||||
=back
|
|
||||||
=cut
|
|
||||||
|
|
||||||
__PACKAGE__->table('subforums');
|
__PACKAGE__->table('subforums');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
subf_id => {
|
subf_id => {
|
||||||
|
@ -51,20 +14,16 @@ __PACKAGE__->add_columns(
|
||||||
subf_cat => {
|
subf_cat => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_foreign_key => 1,
|
is_foreign_key => 1,
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
subf_rank => {
|
subf_rank => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_foreign_key => 0,
|
is_numeric => 1,
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
subf_name => {
|
subf_name => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
subf_desc => {
|
subf_desc => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_auto_increment => 0,
|
|
||||||
is_nullable => 1, });
|
is_nullable => 1, });
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('subf_id');
|
__PACKAGE__->set_primary_key('subf_id');
|
||||||
|
@ -74,4 +33,5 @@ __PACKAGE__->belongs_to(
|
||||||
'CharmBoard::Schema::Source::Categories',
|
'CharmBoard::Schema::Source::Categories',
|
||||||
{'foreign.cat_id' => 'self.subf_cat'});
|
{'foreign.cat_id' => 'self.subf_cat'});
|
||||||
|
|
||||||
1
|
1;
|
||||||
|
__END__
|
|
@ -1,33 +1,10 @@
|
||||||
package CharmBoard::Schema::Source::Threads;
|
package CharmBoard::Schema::Source::Threads;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
=pod
|
|
||||||
=encoding utf8
|
|
||||||
=head1 NAME
|
|
||||||
CharmBoard::Schema::Source::Categories - DBIx::Class
|
|
||||||
ResultSource module for the database's C<threads> table
|
|
||||||
=head1 DESCRIPTION
|
|
||||||
This table contains information about threads.
|
|
||||||
=head2 COLUMNS
|
|
||||||
=over
|
|
||||||
=item thread_id
|
|
||||||
Contains unique IDs for threads.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item thread_title
|
|
||||||
Contains the title for a given thread.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item thread_subf
|
|
||||||
Contains the ID of the subforum a given thread belongs to.
|
|
||||||
|
|
||||||
Is foreign key of C<subforums.subf_id>, and as such, shares
|
|
||||||
the same datatype (SQLite C<INTEGER>). Cannot be C<NULL>.
|
|
||||||
=back
|
|
||||||
=cut
|
|
||||||
|
|
||||||
__PACKAGE__->table('threads');
|
__PACKAGE__->table('threads');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
thread_id => {
|
thread_id => {
|
||||||
|
@ -43,9 +20,11 @@ __PACKAGE__->add_columns(
|
||||||
is_nullable => 0, });
|
is_nullable => 0, });
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('thread_id');
|
__PACKAGE__->set_primary_key('thread_id');
|
||||||
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
thread_subf =>
|
thread_subf =>
|
||||||
'CharmBoard::Schema::Source::Subforums',
|
'CharmBoard::Schema::Source::Subforums',
|
||||||
{'foreign.subf_id' => 'self.thread_subf'});
|
{'foreign.subf_id' => 'self.thread_subf'});
|
||||||
|
|
||||||
1
|
1;
|
||||||
|
__END__
|
|
@ -1,53 +1,10 @@
|
||||||
package CharmBoard::Schema::Source::Users;
|
package CharmBoard::Schema::Source::Users;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw(try smartmatch);
|
||||||
use utf8;
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
=pod
|
|
||||||
=encoding utf8
|
|
||||||
=head1 NAME
|
|
||||||
CharmBoard::Schema::Source::Users - DBIx::Class
|
|
||||||
ResultSource module for the database's C<users> table
|
|
||||||
=head1 DESCRIPTION
|
|
||||||
This table contains information about users.
|
|
||||||
=head2 COLUMNS
|
|
||||||
=over
|
|
||||||
=item user_id
|
|
||||||
Contains unique IDs for users.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>; MariaDB C<INT>. Cannot
|
|
||||||
be C<NULL>.
|
|
||||||
|
|
||||||
=item username
|
|
||||||
Contains a given user's username. Please do not use this
|
|
||||||
field as an identifier for users, that's what C<user_id> is
|
|
||||||
intended for, instead.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item email
|
|
||||||
Contains a user's email address.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item password
|
|
||||||
Contains the user's password in the form of a
|
|
||||||
Crypt::Passphrase string in hexadecimal form.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item salt
|
|
||||||
Contains the user's salt in hexadecimal form.
|
|
||||||
|
|
||||||
Data type is SQLite C<TEXT>. Cannot be C<NULL>.
|
|
||||||
|
|
||||||
=item signup_date
|
|
||||||
Contains the date the user signed up, in the format
|
|
||||||
provided by Perl's C<time> function.
|
|
||||||
|
|
||||||
Data type is SQLite C<INTEGER>. Cannot be C<NULL>.
|
|
||||||
=back
|
|
||||||
=cut
|
|
||||||
|
|
||||||
__PACKAGE__->table('users');
|
__PACKAGE__->table('users');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
user_id => {
|
user_id => {
|
||||||
|
@ -57,19 +14,15 @@ __PACKAGE__->add_columns(
|
||||||
is_auto_increment => 1, },
|
is_auto_increment => 1, },
|
||||||
username => {
|
username => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_numeric => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
email => {
|
email => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_numeric => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
password => {
|
password => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_numeric => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
salt => {
|
salt => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_numeric => 0,
|
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
signup_date => {
|
signup_date => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
@ -78,4 +31,5 @@ __PACKAGE__->add_columns(
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('user_id');
|
__PACKAGE__->set_primary_key('user_id');
|
||||||
|
|
||||||
1
|
1;
|
||||||
|
__END__
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env perl
|
#!/usr/bin/env perl
|
||||||
use experimental 'try', 'smartmatch';
|
use experimental qw(try smartmatch);
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use utf8;
|
use utf8;
|
||||||
|
@ -10,3 +10,28 @@ use Mojolicious::Commands;
|
||||||
|
|
||||||
# Start command line interface for application
|
# Start command line interface for application
|
||||||
Mojolicious::Commands->start_app('CharmBoard');
|
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;
|
% 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