package Newslash::Web;
use Mojo::Base 'Mojolicious';
use Mojo::Util qw(dumper);
-
-use Newslash::Model;
+use List::Util qw(any);
use constant CONFIG_FILE => '/etc/newslash/newslash.conf';
+has subcommand => "";
# This method will run once at server start
sub startup {
my $app = shift;
+ if ($ARGV[0]) {
+ $app->subcommand($ARGV[0]);
+ }
+
+ # add commands in Newslash::Command
+ push @{$app->commands->namespaces}, 'Newslash::Command';
+
# load config file
# first, check existence of /etc/newslash.conf
if ($app->mode eq 'production' && -e CONFIG_FILE) {
$app->plugin('Newslash::Plugin::YAMLConfig', file => CONFIG_FILE);
}
else {
- #$app->plugin('JSONConfig');
- $app->plugin('Newslash::Plugin::YAMLConfig');
+ if ($app->subcommand eq "configdump") {
+ $app->plugin('Newslash::Plugin::YAMLConfig', default => {});
+ }
+ else {
+ $app->plugin('Newslash::Plugin::YAMLConfig');
+ }
}
+
+ # load default configuration values
+ $app->plugin('Newslash::Plugin::DefaultConfig');
+
# TODO: load/save configs with database
- # set log level
- if ($app->config->{Log} && $app->config->{Log}->{level}) {
- my $loglv = $app->config->{Log}->{level};
- if (grep { $loglv eq $_ } qw(debug info warn error fatal)) {
- $app->log->level($loglv);
+ if ($app->config->{Log} && $app->config->{Log}->{backtrace}) {
+ require Carp::Always;
+ Carp::Always->import;
+ }
+
+ # system log config
+ if ($app->config->{Log} && $app->config->{Log}->{system_log}) {
+ my $cnf = $app->config->{Log}->{system_log};
+
+ # set log level
+ if ($cnf->{level}) {
+ my $loglv = $cnf->{level};
+ if (grep { $loglv eq $_ } qw(debug info warn error fatal)) {
+ $app->log->level($loglv);
+ }
+ else {
+ $app->log->warn('invalid log level given in config file');
+ }
+ }
+
+ # file output settings
+ if ($cnf->{mode} eq "local_file") {
+ my $pathname = $cnf->{local_file};
+ # check log is writable
+ if (!$pathname) {
+ $app->log->error("cannot write system log to file: filename not given");
+ }
+ elsif (-e $pathname) {
+ if (-w $pathname) {
+ $app->log->debug("logs will be outputed to $pathname ...");
+ $app->log(Mojo::Log->new);
+ $app->log->path($pathname);
+ }
+ else {
+ $app->log->error("cannot write system log to file: $pathname");
+ }
+ }
+ else {
+ if (open(my $fh, ">", $pathname)) {
+ close($fh);
+ $app->log->debug("logs will be outputed to $pathname ...");
+ $app->log(Mojo::Log->new);
+ $app->log->path($pathname);
+ }
+ else {
+ $app->log->error("cannot create system log file: $pathname");
+ }
+ }
}
- else {
- $app->log->warn('invalid log level given in config file');
+ }
+
+ # add static contents directories
+ if ($app->config->{System} && $app->config->{System}->{static_dir}) {
+ for my $dir (@{$app->config->{System}->{static_dir}}) {
+ push @{$app->static->paths}, $dir;
}
}
+ # secret key for hasing
+ $app->secrets([$app->config->{System}->{secret_key},]);
+
+ # when "test" mode, output debug logs.
+ $app->log->level('debug') if $app->mode eq 'test';
+
+ # check if 'maintenance' mode
+ my $maintenance_mode = 0;
+ if ($app->subcommand
+ && any { $app->subcommand eq $_ } qw[configdump configimport databaseinit
+ testdatainsert useradd usermod
+ ]) {
+ $maintenance_mode = 1;
+ }
+
############################################################
#
# Plugin Settings
#
############################################################
- # when "test" mode, output debug logs.
- $app->log->level('debug') if $app->mode eq 'test';
+ # profiler settings
+ if ($app->mode eq 'development'
+ && $app->config->{Profiler}
+ && $app->config->{Profiler}->{enable}) {
+ if ($app->config->{Profiler}->{profiler} eq 'nytprof') {
+ $app->plugin('Mojolicious::Plugin::NYTProf', { nytprof => {} });
+ }
+ }
- # enable logging
- $app->plugin('Newslash::Plugin::AccessLog::Debug', $app->config->{Log} || {});
- $app->plugin('Newslash::Plugin::AccessLog::LocalFile', $app->config->{Log} || {});
+ # enable access logging
+ $app->plugin('Newslash::Plugin::AccessLog');
- # secret key for hasing
- $app->secrets([$app->config->{System}->{secret_key},]);
+ # Helpers for Newslash
+ $app->plugin('Newslash::Plugin::NewslashHelpers');
- # stash for plugins
- #$app->config->{_Plugins} = {};
+ # use Epoch
+ $app->plugin('Newslash::Plugin::Epoch');
- # use BasicAuth?
- if ($app->config->{BasicAuth} && $app->config->{BasicAuth}->{enable}) {
- $app->plugin('Newslash::Plugin::BasicAuth');
- }
+ # support BasicAuth
+ $app->plugin('Newslash::Plugin::BasicAuth');
# use TimeLimitedCache ($app->cache)
$app->plugin('Newslash::Plugin::TimeLimitedCache');
$app->plugin('Newslash::Plugin::KeyValueStore');
# add Model Loader
- my $model_opts = $app->config;
- $model_opts->{Logger} = $app->log;
- $app->helper(model => Newslash::Model::loader($model_opts));
- Newslash::Model::startup($model_opts, $app);
+ $app->plugin('Newslash::Plugin::Model', { bypass_startup => $maintenance_mode });
- # use Model Cache ($app->model_cache)
- $app->plugin('Newslash::Plugin::ModelCache');
+ # use Easy Cache ($app->ezcache)
+ $app->plugin('Newslash::Plugin::EasyCache');
# use Template::Toolkit 2 render
$app->plugin('Newslash::Plugin::TT2Renderer');
- # user AntiCsrf ($app->anti_csrf)
- if ($app->mode ne 'test') {
- # when test mode, disable AntiCsrf.
- $app->plugin('Newslash::Plugin::AntiCsrf');
- }
+ # use ResponseFilter (Faculities)
+ $app->plugin('Newslash::Plugin::ResponseFilter');
+
+ # use CustomBoxes
+ $app->plugin('Newslash::Plugin::CustomBoxes');
+
+ # use AntiCsrf
+ $app->plugin('Newslash::Plugin::AntiCsrf');
- # compile CSS
- #$app->plugin('Newslash::Plugin::CSSCompile');
+ # contents preprocessor
+ $app->plugin('Newslash::Plugin::Preprocessor');
+ if ($maintenance_mode) {
+ $app->log->info("bypassing preprocessor...");
+ }
+ else {
+ $app->preprocessor->generate_all;
+ }
- # quasi-static content
- $app->plugin('Newslash::Plugin::QuasiStaticContent');
+ # javascript loader
+ $app->plugin('Newslash::Plugin::JavaScriptLoader');
+ if ($maintenance_mode) {
+ $app->log->info("bypassing javascriptloader...");
+ }
+ else {
+ $app->javascript_loader->load_all_bundles;
+ }
# user authorization
$app->plugin('Newslash::Plugin::UserAuth');
$app->plugin('Newslash::Plugin::AccessControl');
# ReCaptcha control
- if ($app->mode ne 'test') {
- $app->plugin('Newslash::Plugin::ReCaptcha');
- }
+ $app->plugin('Newslash::Plugin::ReCaptcha');
+
+ # set canocal (for test.srad.jp)
+ $app->plugin('Newslash::Plugin::Canonical');
+
+ # DiscussionHelper
+ $app->plugin('Newslash::Plugin::DiscussionHelper');
+
+ # use HSTS
+ $app->plugin('Newslash::Plugin::Hsts');
# Event Que
$app->plugin('Newslash::Plugin::EventQue');
+ # Statics Logger
+ $app->plugin('Newslash::Plugin::Statics');
- ############################################################
- #
- # Generate site-global used javascript file
- #
- ############################################################
- my $templ_name = "common/siteconfig.js";
- my $mod_reasons = $app->model('moderations')->reasons();
- my $topics = $app->model('tags')->get_topics;
- my @acl2_types = $app->model('users')->acl2_types;
- my $keywords = {};
- for my $topic (@$topics) {
- my $lc_keyword = lc($topic->{keyword});
- my $lc_textname = lc($topic->{textname});
- $keywords->{$lc_keyword} = {keyword => $topic->{keyword},
- textname => $topic->{textname},
- image => $topic->{image}};
- if ($lc_keyword ne $lc_textname) {
- $keywords->{$lc_textname} = $keywords->{$lc_keyword};
- }
- }
- my $vars = {
- moderate_reasons => $mod_reasons,
- topics => $keywords,
- acl2_types => \@acl2_types,
- };
- my $siteconfig = $app->tt2renderer->render($templ_name, $vars);
- $app->static_content->add_content("js/siteconfig.js", $siteconfig, "text/javascript; charset=utf-8");
+ # Request Body based routing condition
+ $app->plugin('Newslash::Plugin::RequestBodyCondition');
+
+ # NS-RPC
+ $app->plugin('Newslash::Plugin::NSRPC');
+
+ # AD renderer
+ $app->plugin('Newslash::Plugin::ADRenderer');
+
+ # HTTP Compression
+ $app->plugin('Newslash::Plugin::HttpCompression');
+
+ # Users helper
+ $app->plugin('Newslash::Plugin::Users');
+
+ # Stories helper
+ $app->plugin('Newslash::Plugin::Stories');
+
+ # Sendmail helper
+ $app->plugin('Newslash::Plugin::Sendmail');
############################################################
#
my $r = $app->routes;
# index page
- $r->get('/')->to('index#root');
+ $r->get('/')->to('timeline#stories');
$r->get('/recent')->to('timeline#recent');
- $r->get('/journals')->to('index#journals');
- $r->get('/comments')->to('index#comments');
- $r->get('/submissions')->to('index#submissions');
+ $r->get('/popular')->to('timeline#popular');
+ $r->get('/comments')->to('timeline#comments');
+ $r->get('/journals')->to('timeline#journals');
+ $r->get('/submissions')->to('timeline#submissions');
+ $r->get('/polls')->to('timeline#polls');
+
+ # RSS
+ $r->get('/rss/sradjp' => [format => ['rss', 'xml']])->to('rss#sradjp');
# Banned page
$r->get('/banned')->to('index#banned', noindex => 1);
- # archive page
- $r->get('/story/:year/:month/:day/' => [year => qr/[0-9]{2}/,
- month => qr/[0-9]{2}/,
- day => qr/[0-9]{2}/])->to('index#story_archive');
-
-
# Login / Logout
$r->get('/login')->to('login#login');
$r->post('/login')->to('login#login');
$r->get('/logout')->to('login#logout');
- # User Register
- $r->get('/my/newuser')->to('login#newuser');
- $r->post('/my/newuser')->to('login#newuser', captcha_check => 1);
-
# story page
$r->get('/story/:sid/' => [sid => qr|\d\d/\d\d/\d\d/\d+|])
- ->to('story#story');
+ ->to('story#single');
+
+ # comment page
+ $r->get('/comment/:cid/')->to('comment#single');
# journal page
$r->get('/journal/new')->to('journal#create', seclev => 1);
- $r->get('/journal/:id/')->to('journal#journal');
+ $r->get('/journal/:id/')->to('journal#single');
# submission page
$r->get('/submission/new')->to('submission#create');
- $r->get('/submission/:id/')->to('submission#submission');
+ $r->get('/submission/:id/')->to('submission#single');
#$r->post('/submission')->to('submission#create');
# polls page
$r->get('/poll/:qid')->to('poll#single');
$r->get('/vote/:qid')->to('poll#vote');
- $r->get('/polls')->to('poll#index');
- $r->post('/vote/:qid')->to('poll#post', csrf_check_id => 'vote');
+ $r->post('/vote/:qid')->to('poll#vote_post', csrf_check_id => 'vote');
+
+ # archive page
+ $r->get('/story/:year/:month/:day/')->to('archive#story');
+ $r->get('/story/:year/:month/')->to('archive#story');
+ $r->get('/story/')->to('archive#story');
+
+ $r->get('/journal/:year/:month/:day/')->to('archive#journal');
+ $r->get('/journal/:year/:month/')->to('archive#journal');
+ $r->get('/journal/')->to('archive#journal');
+
+ $r->get('/submission/:year/:month/:day/')->to('archive#submission');
+ $r->get('/submission/:year/:month/')->to('archive#submission');
+ $r->get('/submission/')->to('archive#submission');
+
+ $r->get('/poll/:year/:month/')->to('archive#poll');
+ $r->get('/poll/')->to('archive#poll');
+
+ # tag page
+ $r->get('/tag/:tagname/:type/')->to('tag#list_tagged_items');
+ $r->get('/tag/:tagname/')->to('tag#list_tagged_items');
+ $r->get('/tag/')->to('tag#list_tags');
# my page
- $r->get('/my/settings')->to('user#settings', seclev => 1);
- $r->get('/my/sidebar')->to('user#sidebar', seclev => 1);
+ $r->get('/my/settings')->to('my#settings', seclev => 1);
+ $r->get('/my/sidebar')->to('my#sidebar', seclev => 1);
$r->get('/my/messages')->to('my#messages', seclev => 1);
$r->get('/my/')->to('user#home', seclev => 1);
+ # User Register
+ $r->get('/my/newuser')->to('login#newuser');
+ $r->post('/my/newuser')->to('login#newuser', captcha_check => 1);
+ $r->get('/my/activation')->to('login#activation');
+ $r->get('/my/resetpassword')->to('login#reset_password');
+ $r->post('/my/resetpassword')->to('login#reset_password', captcha_check => 1);
+
+ # Change Email
+ $r->get('/my/change_email')->to('my#change_email', seclev => 1);
+
+ # search page
+ $r->get('/search')->to('search#search');
+
# Admin
# pages under /admin needs seclev equal or greater than 10000;
my $admin = $r->under('/admin' => sub { my $c = shift; $c->stash(seclev => 10000); return 1; });
+ $admin->get('/firehose/:id/')->to('admin-firehose#single');
$admin->get('/submissions')->to('admin-submissions#index');
- $admin->get('/submissions/list')->to('admin-submissions#list');
$admin->get('/css')->to('admin-css#edit');
$admin->get('/story/edit')->to('admin-story#edit');
$admin->get('/users')->to('admin-users#index');
+ $admin->get('/default-sidebar')->to('admin-sidebar#defaults');
+
$admin->get('/sidebar')->to('admin-sidebar#index');
- $admin->post('/sidebar/update')->to('admin-sidebar#update');
- $admin->post('/sidebar/delete')->to('admin-sidebar#delete');
- $admin->any(['GET', 'POST'] => '/sidebar/list')->to('admin-sidebar#list');
- $admin->post('/sidebar/get')->to('admin-sidebar#get');
+ $admin->get('/feed')->to('admin-feed#index');
+ $admin->get('/blocking')->to('admin-blocking#index');
+ $admin->get('/ad')->to('admin-ads#index');
+
+ $admin->get('/repository')->to('admin-repository#index');
+ $admin->get('/cache')->to('admin-config#cache');
+
+ # Admin API
+ # pages under /api/v1/admin needs seclev equal or greater than 10000;
+ my $admin_api = $r->under('/api/v1/admin' => sub { my $c = shift; $c->stash(seclev => 10000); return 1; });
+ $admin_api->get('/feed')->to('API::Admin::Feed#get');
+ $admin_api->post('/feed')->to('API::Admin::Feed#post');
+ $admin_api->get('/blocking')->to('API::Admin::Blocking#get');
+ $admin_api->post('/blocking')->to('API::Admin::Blocking#post');
+
+ $admin_api->get('/repository/export')->to('API::Admin::Repository#export');
+ $admin_api->get('/repository/import')->to('API::Admin::Repository#import');
+
+ $admin_api->post('/sidebar')->to('API::Admin::Sidebar#post');
+ $admin_api->get('/sidebar')->to('API::Admin::Sidebar#get');
+
+ # ad codes management
+ $app->rpc->route_to_model($admin_api->get('/ad/code/'), 'ad_codes', 'select');
+ $app->rpc->route_to_model($admin_api->post('/ad/code/')->over(request_body => {action => "create"}), 'ad_codes', 'create');
+ $app->rpc->route_to_model($admin_api->post('/ad/code/')->over(request_body => {action => "update"}), 'ad_codes', 'update');
+ $app->rpc->route_to_model($admin_api->post('/ad/code/')->over(request_body => {action => "delete"}), 'ad_codes', 'delete');
# API
my $api = $r->under('/api/v1');
- $api->get('/sidebars')->to('admin-sidebar#list');
-
$api->post('/login')->to('API::Login#login');
+ $api->post('/newuser/validate')->to('API::User#validate_new_user');
+ $api->post('/newuser/create')->to('API::User#create_new_user');
+ $api->post('/newuser/password')->to('API::User#newuser_password');
+
+ $api->get('/sidebar/item')->to('API::SidebarItem#get', seclev => 1);
+
$api->get('/comment')->to('API::Comment#get');
$api->post('/comment')->to('API::Comment#post', captcha_check => 1, csrf_check_id => 'comment');
$api->post('/journal')->to('API::Journal#post', seclev => 1, csrf_check_id => 'journal');
$api->get('/submission')->to('API::Submission#get');
+ $api->get('/submissions')->to('API::Submission#list');
$api->post('/submission')->to('API::Submission#post', captcha_check => 1, csrf_check_id => 'submission');
$api->get('/story')->to('API::Story#get');
my $user = $r->under('/:nickname');
$user->get('/' => [nickname => qr/~.*/])->to('user#home');
$user->get('/journals' => [nickname => qr/~.*/])->to('user#journals');
+ $user->get('/journal' => [nickname => qr/~.*/])->to('user#journals'); # for compatibility
$user->get('/comments' => [nickname => qr/~.*/])->to('user#comments');
$user->get('/submissions' => [nickname => qr/~.*/])->to('user#submissions');
$user->get('/friends' => [nickname => qr/~.*/])->to('user#friends');