2 use Mojo::Base 'Mojolicious';
3 use Mojo::Util qw(dumper);
4 use List::Util qw(any);
6 use constant CONFIG_FILE => '/etc/newslash/newslash.conf';
9 # This method will run once at server start
14 $app->subcommand($ARGV[0]);
17 # add commands in Newslash::Command
18 push @{$app->commands->namespaces}, 'Newslash::Command';
21 # first, check existence of /etc/newslash.conf
22 if ($app->mode eq 'production' && -e CONFIG_FILE) {
23 $app->plugin('Newslash::Plugin::YAMLConfig', file => CONFIG_FILE);
26 if ($app->subcommand eq "configdump") {
27 $app->plugin('Newslash::Plugin::YAMLConfig', default => {});
30 $app->plugin('Newslash::Plugin::YAMLConfig');
34 # load default configuration values
35 $app->plugin('Newslash::Plugin::DefaultConfig');
37 # TODO: load/save configs with database
39 if ($app->config->{Log} && $app->config->{Log}->{backtrace}) {
45 if ($app->config->{Log} && $app->config->{Log}->{system_log}) {
46 my $cnf = $app->config->{Log}->{system_log};
50 my $loglv = $cnf->{level};
51 if (grep { $loglv eq $_ } qw(debug info warn error fatal)) {
52 $app->log->level($loglv);
53 $app->log->info("set log level to $loglv");
56 $app->log->warn('invalid log level given in config file');
60 # file output settings
61 if ($cnf->{mode} eq "local_file") {
62 my $pathname = $cnf->{local_file};
63 # check log is writable
65 $app->log->error("cannot write system log to file: filename not given");
67 elsif (-e $pathname) {
69 $app->log->debug("logs will be outputed to $pathname ...");
70 $app->log(Mojo::Log->new);
71 $app->log->path($pathname);
74 $app->log->error("cannot write system log to file: $pathname");
78 if (open(my $fh, ">", $pathname)) {
80 $app->log->debug("logs will be outputed to $pathname ...");
81 $app->log(Mojo::Log->new);
82 $app->log->path($pathname);
85 $app->log->error("cannot create system log file: $pathname");
91 # add static contents directories
92 if ($app->config->{System} && $app->config->{System}->{static_dir}) {
93 for my $dir (@{$app->config->{System}->{static_dir}}) {
94 push @{$app->static->paths}, $dir;
98 # secret key for hasing
99 $app->secrets([$app->config->{System}->{secret_key},]);
101 # when "test" mode, output debug logs.
102 $app->log->level('debug') if $app->mode eq 'test';
104 # check if 'maintenance' mode
105 my $maintenance_mode = 0;
107 && any { $app->subcommand eq $_ } qw[configdump configimport databaseinit
108 testdatainsert useradd usermod
110 $maintenance_mode = 1;
113 ############################################################
117 ############################################################
120 if ($app->mode eq 'development'
121 && $app->config->{Profiler}
122 && $app->config->{Profiler}->{enable}) {
123 if ($app->config->{Profiler}->{profiler} eq 'nytprof') {
124 $app->plugin('Mojolicious::Plugin::NYTProf', { nytprof => {} });
128 # enable access logging
129 $app->plugin('Newslash::Plugin::AccessLog');
132 $app->plugin('Newslash::Plugin::Redirect');
134 # Helpers for Newslash
135 $app->plugin('Newslash::Plugin::NewslashHelpers');
138 $app->plugin('Newslash::Plugin::Epoch');
141 $app->plugin('Newslash::Plugin::BasicAuth');
143 # use TimeLimitedCache ($app->cache)
144 $app->plugin('Newslash::Plugin::TimeLimitedCache');
146 # use KeyValue Store ($app->kvs)
147 $app->plugin('Newslash::Plugin::KeyValueStore');
150 $app->plugin('Newslash::Plugin::Model', { bypass_startup => $maintenance_mode });
152 # use CompositeCache Cache ($app->ccache)
153 $app->plugin('Newslash::Plugin::CompositeCache');
155 # use Template::Toolkit 2 render
156 $app->plugin('Newslash::Plugin::TT2Renderer');
158 # use ResponseFilter (Faculities)
159 $app->plugin('Newslash::Plugin::ResponseFilter');
162 $app->plugin('Newslash::Plugin::CustomBoxes');
165 $app->plugin('Newslash::Plugin::AntiCsrf');
167 # contents preprocessor
168 $app->plugin('Newslash::Plugin::Preprocessor');
169 if ($maintenance_mode) {
170 $app->log->info("bypassing preprocessor...");
173 $app->preprocessor->generate_all;
177 $app->plugin('Newslash::Plugin::JavaScriptLoader');
178 if ($maintenance_mode) {
179 $app->log->info("bypassing javascriptloader...");
182 $app->javascript_loader->load_all_bundles;
186 $app->plugin('Newslash::Plugin::UserAuth');
189 $app->plugin('Newslash::Plugin::AccessControl');
192 $app->plugin('Newslash::Plugin::ReCaptcha');
195 $app->plugin('Newslash::Plugin::PostFilter');
198 $app->plugin('Newslash::Plugin::DiscussionHelper');
201 $app->plugin('Newslash::Plugin::Hsts');
204 $app->plugin('Newslash::Plugin::EventQue');
207 $app->plugin('Newslash::Plugin::Stats');
209 # Request Body based routing condition
210 $app->plugin('Newslash::Plugin::RequestBodyCondition');
213 $app->plugin('Newslash::Plugin::NSRPC');
216 $app->plugin('Newslash::Plugin::ADRenderer');
219 $app->plugin('Newslash::Plugin::HttpCompression');
222 $app->plugin('Newslash::Plugin::Users');
225 $app->plugin('Newslash::Plugin::Stories');
228 $app->plugin('Newslash::Plugin::Sendmail');
230 # Wiki contents reader helper
231 $app->plugin('Newslash::Plugin::WikiContentsReader');
233 # Set page's property automatically
234 $app->plugin('Newslash::Plugin::AddPageProperty');
237 ############################################################
241 ############################################################
243 my $r = $app->routes;
246 $r->get('/')->to('timeline#top_page');
247 $r->get('/recent')->to('timeline#recent');
248 $r->get('/popular')->to('timeline#popular');
249 $r->get('/comments')->to('timeline#comments');
250 $r->get('/journals')->to('timeline#journals');
251 $r->get('/submissions')->to('timeline#submissions');
252 $r->get('/polls')->to('timeline#polls');
255 $r->get('/:rss_type' => [format => ['rss', 'xml']])->to('rss#stories');
256 $r->get('/journals/rss')->to('rss#journals');
259 $r->get('/banned')->to('index#banned', noindex => 1, no_ad => 1);
262 $r->get('/my/login')->to('login#login', no_ad => 1);
263 $r->post('/my/login')->to('login#login');
264 $r->get('/my/logout')->to('login#logout');
265 $r->get('/login' => sub {my $c = shift; $c->res->code(301); $c->redirect_to('/my/login');});
266 $r->get('/logout' => sub {my $c = shift; $c->res->code(301); $c->redirect_to('/my/logout');});
269 $r->get('/story/:sid/' => [sid => qr|\d\d/\d\d/\d\d/\d+|])
270 ->to('story#single');
273 $r->get('/comment/:cid/')->to('comment#single');
276 # also see user page settings for /~username/journal/
277 $r->get('/journal/new')->to('journal#create', seclev => 1, no_ad => 1, use_captcha => 1);
278 $r->get('/journal/:id/')->to('journal#single');
281 $r->get('/submission/new')->to('submission#create', no_ad => 1, use_captcha => 1);
282 $r->get('/submission/:id/')->to('submission#single');
283 #$r->post('/submission')->to('submission#create');
286 $r->get('/poll/:qid')->to('poll#single');
287 $r->get('/vote/:qid')->to('poll#vote');
288 #$r->post('/vote/:qid')->to('poll#vote_post', csrf_check_id => 'vote');
289 $r->post('/vote/:qid')->to('poll#vote_post');
292 $r->get('/story/:year/:month/:day/')->to('archive#story');
293 $r->get('/story/:year/:month/')->to('archive#story');
295 # redirect rule for /stroy/<tags or topics>/
296 $r->get('/story/:tag/' => [tag => qr{[^/ ]+}] => sub {
298 my $tag = $c->stash('tag');
300 $c->redirect_to("/tag/$tag/story/");
302 $r->get('/story/')->to('archive#story');
304 $r->get('/journal/:year/:month/:day/')->to('archive#journal');
305 $r->get('/journal/:year/:month/')->to('archive#journal');
306 $r->get('/journal/')->to('archive#journal');
308 $r->get('/submission/:year/:month/:day/')->to('archive#submission');
309 $r->get('/submission/:year/:month/')->to('archive#submission');
310 $r->get('/submission/')->to('archive#submission');
312 $r->get('/comment/:year/:month/:day/')->to('archive#comment');
313 $r->get('/comment/:year/:month/')->to('archive#comment');
314 $r->get('/comment/')->to('archive#comment');
316 $r->get('/poll/:year/:month/')->to('archive#poll');
317 $r->get('/poll/')->to('archive#poll');
320 $r->get('/tag/:tagname/:type/:page/')->to('tag#list_tagged_items');
321 $r->get('/tag/:tagname/:page/' => [page => qr/\d+/])->to('tag#list_tagged_items');
322 $r->get('/tag/:tagname/:type/')->to('tag#list_tagged_items');
323 $r->get('/tag/:tagname/')->to('tag#list_tagged_items');
324 $r->get('/tag/')->to('tag#list_tags');
327 $r->get('/my/settings')->to('my#settings', seclev => 1);
328 $r->get('/my/sidebar')->to('my#sidebar', seclev => 1);
329 $r->get('/my/inbox')->to('my#messages', seclev => 1);
330 $r->get('/my/')->to('user#home', seclev => 1);
333 $r->get('/my/newuser')->to('login#newuser', no_ad => 1);
334 $r->post('/my/newuser')->to('login#newuser', captcha_check => 1);
335 $r->get('/my/activation')->to('login#activation', no_ad => 1);
336 $r->get('/my/resetpassword')->to('login#reset_password', no_ad => 1);
337 $r->post('/my/resetpassword')->to('login#reset_password', captcha_check => 1);
340 $r->get('/my/change_email')->to('my#change_email', seclev => 1, captcha_check => 1);
343 $r->get('/search')->to('search#search');
345 # firehose, index (for compatibility)
346 $r->get('/firehose')->to('redirect#firehose');
347 $r->get('/index2')->to('redirect#firehose');
348 $r->get('/index')->to('redirect#index');
349 $r->get('/users.pl')->to('redirect#users');
350 $r->get('/:section/index')->to('redirect#index');
353 $r->get('/about')->to('about#about');
356 $r->get('/hof')->to('hof#hof');
359 $r->get('/faq/editorial')->to('faq#editorial');
360 $r->get('/faq/UI')->to('faq#UI');
361 $r->get('/faq/firehose')->to('faq#firehose');
362 $r->get('/faq/com-mod')->to('faq#com_mod');
363 $r->get('/faq/metamod')->to('faq#metamod');
364 $r->get('/faq/accounts')->to('faq#accounts');
365 $r->get('/faq/friends')->to('faq#friends');
366 $r->get('/faq/tech')->to('faq#tech');
367 $r->get('/faq/')->to('faq#faq');
369 $r->get('/supporters')->to('faq#supporters');
372 $r->get('/topics.pl' => sub {my $c = shift; $c->res->code(301); $c->redirect_to('/topics/');});
373 $r->get('/topics')->to('topics#topics');
376 $r->get('/authors')->to('authors#authors');
379 # pages under /admin needs seclev equal or greater than 10000;
380 my $admin = $r->under('/admin' => sub { my $c = shift; $c->stash(seclev => 10000); return 1; })->detour(no_ad => 1);
382 $admin->get('/firehose/:id/')->to('admin-firehose#single');
383 $admin->get('/submissions')->to('admin-submissions#index');
385 $admin->get('/css')->to('admin-css#edit');
386 $admin->get('/story/edit')->to('admin-story#edit');
387 $admin->get('/story/list')->to('admin-story#list');
389 $admin->get('/users')->to('admin-users#index');
391 $admin->get('/default-sidebar')->to('admin-sidebar#defaults');
393 $admin->get('/sidebar')->to('admin-sidebar#index');
394 $admin->get('/feed')->to('admin-feed#index');
395 $admin->get('/filter')->to('admin-filter#index');
396 $admin->get('/blocking')->to('admin-blocking#index');
397 $admin->get('/ad')->to('admin-ads#index');
399 $admin->get('/repository')->to('admin-repository#index');
400 $admin->get('/cache')->to('admin-config#cache');
403 # pages under /api/v1/admin needs seclev equal or greater than 10000;
404 my $admin_api = $r->under('/api/v1/admin' => sub { my $c = shift; $c->stash(seclev => 10000); return 1; });
406 $admin_api->get('/feed')->to('API::Admin::Feed#get');
407 $admin_api->post('/feed')->to('API::Admin::Feed#post');
408 $admin_api->get('/filter')->to('API::Admin::Filter#get');
409 $admin_api->post('/filter')->to('API::Admin::Filter#post');
410 $admin_api->get('/blocking')->to('API::Admin::Blocking#get');
411 $admin_api->post('/blocking')->to('API::Admin::Blocking#post');
413 $admin_api->get('/repository/export')->to('API::Admin::Repository#export');
414 $admin_api->get('/repository/import')->to('API::Admin::Repository#import');
416 $admin_api->post('/sidebar')->to('API::Admin::Sidebar#post');
417 $admin_api->get('/sidebar')->to('API::Admin::Sidebar#get');
419 $admin_api->get('/story')->to('API::Story#get');
420 $admin_api->post('/story')->to('API::Story#post');
422 # ad codes management
423 $app->rpc->route_to_model($admin_api->get('/ad/code/'), 'ad_codes', 'select');
424 $app->rpc->route_to_model($admin_api->post('/ad/code/')->over(request_body => {action => "create"}), 'ad_codes', 'create');
425 $app->rpc->route_to_model($admin_api->post('/ad/code/')->over(request_body => {action => "update"}), 'ad_codes', 'update');
426 $app->rpc->route_to_model($admin_api->post('/ad/code/')->over(request_body => {action => "delete"}), 'ad_codes', 'delete');
430 my $api = $r->under('/api/v1');
431 $api->post('/login')->to('API::Login#login');
433 $api->post('/newuser/validate')->to('API::User#validate_new_user');
434 $api->post('/newuser/create')->to('API::User#create_new_user');
435 $api->post('/newuser/password')->to('API::User#newuser_password');
437 $api->get('/sidebar/item')->to('API::SidebarItem#get', seclev => 1);
439 $api->get('/comment')->to('API::Comment#get');
440 $api->post('/comment')->to('API::Comment#post', captcha_check => 1, csrf_check_id => 'comment');
442 $api->get('/user')->to('API::User#get');
443 $api->post('/user')->to('API::User#post', seclev => 1);
445 $api->get('/journal')->to('API::Journal#get');
446 $api->post('/journal')->to('API::Journal#post', seclev => 1, csrf_check_id => 'journal');
448 $api->get('/submission')->to('API::Submission#get');
449 $api->get('/submissions')->to('API::Submission#list');
450 $api->post('/submission')->to('API::Submission#post', captcha_check => 1, csrf_check_id => 'submission');
452 $api->get('/timeline')->to('API::Timeline#get');
454 $api->get('/poll')->to('API::Poll#get');
455 $api->post('/poll')->to('API::Poll#post');
456 $api->post('/vote')->to('API::Poll#vote', csrf_check_id => 'vote');
458 $api->get('/moderation')->to('API::Moderation#get');
459 $api->post('/moderation')->to('API::Moderation#post', seclev => 1, csrf_check_id => 'moderation');
461 $api->get('/metamoderation')->to('API::Metamoderation#get');
462 $api->post('/metamoderation')->to('API::Metamoderation#post', seclev => 1, csrf_check_id => 'moderation');
464 $api->post('/relation')->to('API::Relation#post', seclev => 1, csrf_check_id => 'relation');
466 $api->get('/token')->to('API::Token#get');
468 $api->post('/tag')->to('API::Tag#post');
469 $api->post('/tagmoderation')->to('API::TagModeration#post');
472 # warning: these pathes uses regexp matching, so must write in tail of route definitions.
473 my $user = $r->under('/~#nickname');
474 $user->get('/' => [nickname => qr/.*/])->to('user#home');
475 $user->get('/journal/:id' => [nickname => qr/.*/])->to('journal#single');
477 $user->get('/journal/:year/:month:day/' => [nickname => qr/.*/])->to('archive#user_journal');
478 $user->get('/journal/:year/:month/' => [nickname => qr/.*/])->to('archive#user_journal');
480 $user->get('/journals' => [nickname => qr/.*/])->to('user#journals');
481 $user->get('/journal' => [nickname => qr/.*/])->to('user#journals'); # for compatibility
482 $user->get('/comments' => [nickname => qr/.*/])->to('user#comments');
483 $user->get('/submissions' => [nickname => qr/.*/])->to('user#submissions');
484 $user->get('/friends' => [nickname => qr/.*/])->to('user#friends');
485 $user->get('/foes' => [nickname => qr/.*/])->to('user#foes');
486 $user->get('/fans' => [nickname => qr/.*/])->to('user#fans');
487 $user->get('/freaks' => [nickname => qr/.*/])->to('user#freaks');
488 $user->get('/achievements' => [nickname => qr/.*/])->to('user#achievements');