OSDN Git Service

01c6db5a0156443852dd049309b66ff2f1e6e79b
[newslash/newslash.git] / src / newslash_web / lib / Newslash / Web.pm
1 package Newslash::Web;
2 use Mojo::Base 'Mojolicious';
3 use Mojo::Util qw(dumper);
4 use List::Util qw(any);
5
6 use constant CONFIG_FILE => '/etc/newslash/newslash.conf';
7 has subcommand => "";
8
9 # This method will run once at server start
10 sub startup {
11     my $app = shift;
12
13     if ($ARGV[0]) {
14         $app->subcommand($ARGV[0]);
15     }
16
17     # add commands in Newslash::Command
18     push @{$app->commands->namespaces}, 'Newslash::Command';
19
20     # load config file
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);
24     }
25     else {
26         if ($app->subcommand eq "configdump") {
27             $app->plugin('Newslash::Plugin::YAMLConfig', default => {});
28         }
29         else {
30             $app->plugin('Newslash::Plugin::YAMLConfig');
31         }
32     }
33
34     # load default configuration values
35     $app->plugin('Newslash::Plugin::DefaultConfig');
36
37     # TODO: load/save configs with database
38
39     if ($app->config->{Log} && $app->config->{Log}->{backtrace}) {
40         require Carp::Always;
41         Carp::Always->import;
42     }
43
44     # system log config
45     if ($app->config->{Log} && $app->config->{Log}->{system_log}) {
46         my $cnf = $app->config->{Log}->{system_log};
47
48         # set log level
49         if ($cnf->{level}) {
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");
54             }
55             else {
56                 $app->log->warn('invalid log level given in config file');
57             }
58         }
59
60         # file output settings
61         if ($cnf->{mode} eq "local_file") {
62             my $pathname = $cnf->{local_file};
63             # check log is writable
64             if (!$pathname) {
65                 $app->log->error("cannot write system log to file: filename not given");
66             }
67             elsif (-e $pathname) {
68                 if (-w $pathname) {
69                     $app->log->debug("logs will be outputed to $pathname ...");
70                     $app->log(Mojo::Log->new);
71                     $app->log->path($pathname);
72                 }
73                 else {
74                     $app->log->error("cannot write system log to file: $pathname");
75                 }
76             }
77             else {
78                 if (open(my $fh, ">", $pathname)) {
79                     close($fh);
80                     $app->log->debug("logs will be outputed to $pathname ...");
81                     $app->log(Mojo::Log->new);
82                     $app->log->path($pathname);
83                 }
84                 else {
85                     $app->log->error("cannot create system log file: $pathname");
86                 }
87             }
88         }
89     }
90
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;
95         }
96     }
97
98     # secret key for hasing
99     $app->secrets([$app->config->{System}->{secret_key},]);
100
101     # when "test" mode, output debug logs.
102     $app->log->level('debug') if $app->mode eq 'test';
103
104     # check if 'maintenance' mode
105     my $maintenance_mode = 0;
106     if ($app->subcommand
107         && any { $app->subcommand eq $_ } qw[configdump configimport databaseinit
108                                              testdatainsert useradd usermod
109                                            ]) {
110         $maintenance_mode = 1;
111     }
112
113     ############################################################
114     #
115     # Plugin Settings
116     #
117     ############################################################
118
119     # profiler settings
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 => {} });
125         }
126     }
127
128     # enable access logging
129     $app->plugin('Newslash::Plugin::AccessLog');
130
131     # redirect handlers
132     $app->plugin('Newslash::Plugin::Redirect');
133
134     # Helpers for Newslash
135     $app->plugin('Newslash::Plugin::NewslashHelpers');
136
137     # use Epoch
138     $app->plugin('Newslash::Plugin::Epoch');
139
140     # support BasicAuth
141     $app->plugin('Newslash::Plugin::BasicAuth');
142
143     # use TimeLimitedCache ($app->cache)
144     $app->plugin('Newslash::Plugin::TimeLimitedCache');
145
146     # use KeyValue Store ($app->kvs)
147     $app->plugin('Newslash::Plugin::KeyValueStore');
148
149     # add Model Loader
150     $app->plugin('Newslash::Plugin::Model', { bypass_startup => $maintenance_mode });
151
152     # use CompositeCache Cache ($app->ccache)
153     $app->plugin('Newslash::Plugin::CompositeCache');
154
155     # use Template::Toolkit 2 render
156     $app->plugin('Newslash::Plugin::TT2Renderer');
157
158     # use ResponseFilter (Faculities)
159     $app->plugin('Newslash::Plugin::ResponseFilter');
160
161     # use CustomBoxes
162     $app->plugin('Newslash::Plugin::CustomBoxes');
163
164     # use AntiCsrf
165     $app->plugin('Newslash::Plugin::AntiCsrf');
166
167     # contents preprocessor
168     $app->plugin('Newslash::Plugin::Preprocessor');
169     if ($maintenance_mode) {
170         $app->log->info("bypassing preprocessor...");
171     }
172     else {
173         $app->preprocessor->generate_all;
174     }
175
176     # javascript loader
177     $app->plugin('Newslash::Plugin::JavaScriptLoader');
178     if ($maintenance_mode) {
179         $app->log->info("bypassing javascriptloader...");
180     }
181     else {
182         $app->javascript_loader->load_all_bundles;
183     }
184
185     # user authorization
186     $app->plugin('Newslash::Plugin::UserAuth');
187
188     # access control
189     $app->plugin('Newslash::Plugin::AccessControl');
190
191     # ReCaptcha control
192     $app->plugin('Newslash::Plugin::ReCaptcha');
193
194     # Post Filter
195     $app->plugin('Newslash::Plugin::PostFilter');
196
197     # DiscussionHelper
198     $app->plugin('Newslash::Plugin::DiscussionHelper');
199
200     # use HSTS
201     $app->plugin('Newslash::Plugin::Hsts');
202
203     # Event Que
204     $app->plugin('Newslash::Plugin::EventQue');
205
206     # Stats Logger
207     $app->plugin('Newslash::Plugin::Stats');
208
209     # Request Body based routing condition
210     $app->plugin('Newslash::Plugin::RequestBodyCondition');
211
212     # NS-RPC
213     $app->plugin('Newslash::Plugin::NSRPC');
214
215     # AD renderer
216     $app->plugin('Newslash::Plugin::ADRenderer');
217
218     # HTTP Compression
219     $app->plugin('Newslash::Plugin::HttpCompression');
220
221     # Users helper
222     $app->plugin('Newslash::Plugin::Users');
223
224     # Stories helper
225     $app->plugin('Newslash::Plugin::Stories');
226
227     # Sendmail helper
228     $app->plugin('Newslash::Plugin::Sendmail');
229
230     # Wiki contents reader helper
231     $app->plugin('Newslash::Plugin::WikiContentsReader');
232
233     # Set page's property automatically
234     $app->plugin('Newslash::Plugin::AddPageProperty');
235
236
237     ############################################################
238     #
239     # Routing Settings
240     #
241     ############################################################
242
243     my $r = $app->routes;
244
245     # index page
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');
253
254     # RSS
255     $r->get('/:rss_type' => [format => ['rss', 'xml']])->to('rss#stories');
256     $r->get('/journals/rss')->to('rss#journals');
257
258     # Banned page
259     $r->get('/banned')->to('index#banned', noindex => 1, no_ad => 1);
260
261     # Login / Logout
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');});
267
268     # story page
269     $r->get('/story/:sid/' => [sid => qr|\d\d/\d\d/\d\d/\d+|])
270       ->to('story#single');
271
272     # comment page
273     $r->get('/comment/:cid/')->to('comment#single');
274
275     # journal page
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');
279
280     # submission page
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');
284
285     # polls page
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');
290
291     # archive page
292     $r->get('/story/:year/:month/:day/')->to('archive#story');
293     $r->get('/story/:year/:month/')->to('archive#story');
294
295     # redirect rule for /stroy/<tags or topics>/
296     $r->get('/story/:tag/' => [tag => qr{[^/ ]+}] => sub {
297         my $c = shift;
298         my $tag = $c->stash('tag');
299         $c->res->code(301);
300         $c->redirect_to("/tag/$tag/story/");
301     });
302     $r->get('/story/')->to('archive#story');
303
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');
307
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');
311
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');
315
316     $r->get('/poll/:year/:month/')->to('archive#poll');
317     $r->get('/poll/')->to('archive#poll');
318
319     # tag page
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');
325
326     # my page
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);
331
332     # User Register
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);
338
339     # Change Email
340     $r->get('/my/change_email')->to('my#change_email', seclev => 1, captcha_check => 1);
341
342     # search page
343     $r->get('/search')->to('search#search');
344
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');
351
352     # about
353     $r->get('/about')->to('about#about');
354
355     # Hall of Fames
356     $r->get('/hof')->to('hof#hof');
357
358     # faq
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');
368
369     $r->get('/supporters')->to('faq#supporters');
370
371     # topics
372     $r->get('/topics.pl' => sub {my $c = shift; $c->res->code(301); $c->redirect_to('/topics/');});
373     $r->get('/topics')->to('topics#topics');
374
375     # authors
376     $r->get('/authors')->to('authors#authors');
377
378     # Admin
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);
381
382     $admin->get('/firehose/:id/')->to('admin-firehose#single');
383     $admin->get('/submissions')->to('admin-submissions#index');
384
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');
388
389     $admin->get('/users')->to('admin-users#index');
390
391     $admin->get('/default-sidebar')->to('admin-sidebar#defaults');
392
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');
398
399     $admin->get('/repository')->to('admin-repository#index');
400     $admin->get('/cache')->to('admin-config#cache');
401
402     # Admin API
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; });
405
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');
412
413     $admin_api->get('/repository/export')->to('API::Admin::Repository#export');
414     $admin_api->get('/repository/import')->to('API::Admin::Repository#import');
415
416     $admin_api->post('/sidebar')->to('API::Admin::Sidebar#post');
417     $admin_api->get('/sidebar')->to('API::Admin::Sidebar#get');
418
419     $admin_api->get('/story')->to('API::Story#get');
420     $admin_api->post('/story')->to('API::Story#post');
421
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');
427
428
429     # API
430     my $api = $r->under('/api/v1');
431     $api->post('/login')->to('API::Login#login');
432
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');
436
437     $api->get('/sidebar/item')->to('API::SidebarItem#get', seclev => 1);
438
439     $api->get('/comment')->to('API::Comment#get');
440     $api->post('/comment')->to('API::Comment#post', captcha_check => 1, csrf_check_id => 'comment');
441
442     $api->get('/user')->to('API::User#get');
443     $api->post('/user')->to('API::User#post', seclev => 1);
444
445     $api->get('/journal')->to('API::Journal#get');
446     $api->post('/journal')->to('API::Journal#post', seclev => 1, csrf_check_id => 'journal');
447
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');
451
452     $api->get('/timeline')->to('API::Timeline#get');
453
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');
457
458     $api->get('/moderation')->to('API::Moderation#get');
459     $api->post('/moderation')->to('API::Moderation#post', seclev => 1, csrf_check_id => 'moderation');
460
461     $api->get('/metamoderation')->to('API::Metamoderation#get');
462     $api->post('/metamoderation')->to('API::Metamoderation#post', seclev => 1, csrf_check_id => 'moderation');
463
464     $api->post('/relation')->to('API::Relation#post', seclev => 1, csrf_check_id => 'relation');
465
466     $api->get('/token')->to('API::Token#get');
467
468     $api->post('/tag')->to('API::Tag#post');
469     $api->post('/tagmoderation')->to('API::TagModeration#post');
470
471     # user page
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');
476     $user->get('/journals'     => [nickname => qr/.*/])->to('user#journals');
477     $user->get('/journal'      => [nickname => qr/.*/])->to('user#journals'); # for compatibility
478     $user->get('/comments'     => [nickname => qr/.*/])->to('user#comments');
479     $user->get('/submissions'  => [nickname => qr/.*/])->to('user#submissions');
480     $user->get('/friends'      => [nickname => qr/.*/])->to('user#friends');
481     $user->get('/foes'         => [nickname => qr/.*/])->to('user#foes');
482     $user->get('/fans'         => [nickname => qr/.*/])->to('user#fans');
483     $user->get('/freaks'       => [nickname => qr/.*/])->to('user#freaks');
484     $user->get('/achievements' => [nickname => qr/.*/])->to('user#achievements');
485
486 }
487
488 1;