/
WebSite.pm
173 lines (148 loc) · 5.44 KB
/
WebSite.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package WebSite;
use Mojo::Base 'Mojolicious';
use AnyEvent::DBI::MySQL;
use DBIx::SecureCGI;
use JSON::RPC2::Server;
use List::Util 1.33 qw( none );
use MojoX::JSONRPC2::HTTP;
use Text::MiniTmpl qw( encode_js encode_js_data );
use _init;
use _svc;
use constant CONFIG_LINE => qw( title );
use constant CONFIG_FULL => qw( );
sub startup {
my $app = shift;
#--- Narada
$app->config($_ => get_config_line($_)) for CONFIG_LINE; ## no critic (ProhibitPostfixControls)
$app->config($_ => get_config($_)) for CONFIG_FULL; ## no critic (ProhibitPostfixControls)
# $app->config(db => Narada::Config::get_db_config());
$app->plugin('Narada', log => $LOG);
# initialization done, release bootstrap lock
unlock();
# undo _init.pm: die logging will be handled by Mojo
undef $::SIG{__DIE__};
#--- Plugins
# use .tmpl instead of .ep to avoid conflict with POWER.js on <%…%>
$app->plugin('EPRenderer', name => 'tmpl', template => {
tag_start => '[%',
tag_end => '%]',
});
$app->plugin('ValidateTiny');
$app->plugin('JSONRPC2');
#--- Helpers
$app->helper(render_cb => sub { shift->render_later->proxy(@_) });
$app->helper(encode_js => sub { shift; goto &encode_js });
$app->helper(encode_js_data => sub { shift; goto &encode_js_data});
$app->helper(add_mtime => \&_helper_add_mtime);
$app->helper(dbh => sub { shift->{dbh} });
$app->helper(new_dbh => sub {
state $db = shift->app->config('db') or return;
return AnyEvent::DBI::MySQL->connect(@{$db}{qw(dsn login pass)},
{mysql_enable_utf8 => 1});
});
$app->helper(validate => sub {
my ($c, $rules) = @_;
# make sure do_validate() called with $c->{_} in second param
# (otherwise it'll use crap from $c->param)
return $c->do_validation(_sane_validation_rules($rules), $c->{_});
});
$app->helper(validate_json => sub {
my ($c, $rules) = @_;
my $json = $c->req->json;
if (!$json || ref $json ne 'HASH') {
return _set_validation_errors($c, json => 'JSON not an Object');
}
return $c->do_validation(_sane_validation_rules($rules), $json);
});
$app->helper(validator_set_errors => \&_set_validation_errors);
$app->helper(jsonrpc2 => sub { state $jsonrpc2 = MojoX::JSONRPC2::HTTP->new });
#--- Hooks
$app->hook(before_routes => sub {
my $c = shift;
# each connection have own dbh
$c->{dbh} = $c->new_dbh;
# sane params hash
$c->{_} = {};
for my $name (@{ $c->req->params->names }) {
next if $name =~ /\A_(?!_)/ms; # DBIx::SecureCGI protected fields
my $vals = $c->req->params->every_param($name);
$c->{_}{$name} = $name =~ /\A\@|__|\[\]\z/ms ? $vals : $vals->[0];
}
});
$app->hook(after_render => sub {
my $c = shift;
# cache control for dynamic content
$c->res->headers->header('Expires' => 'Sat, 01 Jan 2000 00:00:00 GMT');
});;
#--- Defaults
$app->defaults(appjs => 'main');
#--- RPC
my $server = JSON::RPC2::Server->new;
$server->register_nb(version => \&_svc::version);
$server->register_nb(internal_version => \&_svc::internal_version);
#--- Routes
my $r = $app->routes;
$r->get(q{/})->to('root#index');
$r->get(q{/version})->to('version#project');
$r->under(sub{ $::c = $::c = shift })
->jsonrpc2(q{/}, $server);
return;
}
# add_mtime '/css/main.css' => '/css/main.1234567890.css'
# add_mtime 'main' (test '/js/main.js') => 'main.1234567890'
use constant MTIME => 9;
sub _helper_add_mtime {
my (undef, $url) = @_;
my $is_js = $url !~ m{\A/}ms;
my $file = $is_js ? "public/js/$url.js" : "public/$url";
if (-f $file) {
my $mtime = (stat $file)[MTIME];
if ($is_js) {
$url .= ".$mtime";
} else {
$url =~ s/[.](\w+)\z/.$mtime.$1/ms;
}
}
return $url;
}
sub _sane_validation_rules {
my ($rules) = @_;
if (ref $rules eq 'ARRAY') {
$rules = { checks => $rules };
}
# sanity check for Regexp in 'checks'
use Carp;
for ( my $i = 0; $i < @{$rules->{checks}}; $i += 2 ) {
my $field = $rules->{checks}[$i];
if (ref $field eq 'Regexp') {
if (!$rules->{fields}) {
croak 'You must use {fields} when using Regexp in {checks}';
}
if (none {/$field/ms} @{ $rules->{fields} }) {
croak "No fields in {fields} match /$field/ check";
}
}
}
# fix Mojolicious::Plugin::ValidateTiny bug 84959
$rules->{fields} = [@{ $rules->{fields} || [] }];
for ( my $i = 0; $i < @{$rules->{checks}}; $i += 2 ) {
my $field = $rules->{checks}[$i];
if (ref $field eq 'ARRAY') {
push @{ $rules->{fields} }, @{ $field };
}
}
return $rules;
}
sub _set_validation_errors {
my ($c, %err) = @_;
my @checks = map {my $e=$err{$_}; $_=>sub{$e}} keys %err; ## no critic (ProhibitComplexMappings)
my $r = Validate::Tiny->check(\%err, {fields=>[],checks=>\@checks});
$c->stash( 'validate_tiny.was_called', 1 );
$c->stash( 'validate_tiny.result' => $r );
if (!$r->success) {
$c->stash( 'validate_tiny.errors' => $r->error );
$c->app->log->debug('ValidateTiny: Failed: '.join ', ', keys %{ $r->error });
}
return;
}
1;