Prepare for Dancer2 port
[gruntmaster-page.git] / lib / Plack / App / Gruntmaster.pm
CommitLineData
7dc32473
MG
1package Plack::App::Gruntmaster;
2
3use 5.014000;
4use strict;
5use warnings;
6use parent qw/Plack::Component/;
7no if $] >= 5.017011, warnings => 'experimental::smartmatch';
8our $VERSION = '5999.000_001';
9
e03e380b
MG
10use Apache2::Authen::Passphrase qw/pwcheck pwset/;
11use CSS::Minifier::XS;
f34254b8 12use File::Slurp qw/read_file/;
7dc32473 13use HTTP::Negotiate qw/choose/;
e03e380b 14use JavaScript::Minifier::XS;
7dc32473 15use Plack::Request;
fdbf59e5 16use Gruntmaster::Page::Generic;
7dc32473
MG
17
18my %handlers;
19
20sub call {
21 my $env = $_[1];
22 my $r = Plack::Request->new($env);
23 my @handlers = @{ $handlers{$r->method} // [] };
24 for my $handler (@handlers) {
25 my ($re, $obj) = @$handler;
26 my @args;
27 next unless @args = $r->path =~ m/^$re$/a;
e03e380b 28 return $obj->($env, map { $_ // '' } @args);
7dc32473
MG
29 }
30
f34254b8
MG
31 if ($r->method eq 'GET' || $r->method eq 'HEAD') {
32 my $article = $r->path eq '/' ? '/index' : $r->path;
33 $article = substr $article, 1;
34 $article =~ tr,/,_,;
35 my @variants = grep { !/\.title$/ } <a/$article.*>;
36 if (@variants) {
37 my $lang = choose [ map { [$_, 1, 'text/html', undef, undef, $_, undef] } map { /\.(.+)$/ } @variants ], $r->headers;
38 my $content = read_file "a/$article.$lang";
39 my $title = read_file "a/$article.$lang.title";
40 my $html = Gruntmaster::Page::Base::header($lang, $title) . $content . Gruntmaster::Page::Base::footer($lang);
21d35251 41 return [200, ['Content-Type' => 'text/html', 'Content-Language' => $lang, 'Vary' => 'Accept-Language', 'X-Forever' => 1, 'Cache-Control' => 'max-age=300'], [$html] ]
f34254b8
MG
42 }
43 }
44
7dc32473
MG
45 [404, ['Content-Type' => 'text/plain'], ['Not found']]
46}
47
e03e380b
MG
48sub get { push @{$handlers{GET }}, [ @_ ] }
49sub post { push @{$handlers{POST}}, [ @_ ] }
50sub db { $_[0]->{'gruntmaster.dbic'} }
51sub reply { [200, ['Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache'], [ @_ ] ] }
52sub admin { 0 }
7dc32473 53
e03e380b
MG
54sub old_handler {
55 my ($pkg) = @_;
56 $pkg = "Gruntmaster::Page::$pkg";
57 eval "require $pkg" or die $@;
58 sub {
59 my ($env, @args) = @_;
60 my $format = choose $pkg->variants, Plack::Request->new($env)->headers;
61 $pkg->generate($format, $env, @args)
62 }
7dc32473
MG
63}
64
e03e380b
MG
65use constant CONTENT_TYPES => +{
66 c => 'text/x-csrc',
67 cpp => 'text/x-c++src',
68 cs => 'text/x-csharp', # Used by GNOME. Not in mime.types.
69 java => 'text/x-java',
70 pas => 'text/x-pascal',
71 pl => 'text/x-perl',
72 py => 'text/x-python',
73};
74use constant NOT_FOUND => [404, ['Content-Type' => 'text/plain'], ['Not found']];
75use constant FORBIDDEN => [403, ['Content-Type' => 'text/plain'], ['Forbidden']];
76
7dc32473 77BEGIN{
491e82eb
MG
78 my $word = qr,(\w+),a;
79 my $number = qr,(\d+),a;
7dc32473 80
e03e380b
MG
81 get qr,/css/$word\.css, => sub {
82 my ($env, $theme) = @_;
83 return [404, ['Content-Type' => 'text/plain'], [ 'Not found' ]] unless -e "css/themes/$theme.css";
84 my $css = read_file "css/themes/$theme.css";
85 $css .= read_file $_ for <css/*.css>;
86 [200, ['Content-Type' => 'text/css', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [CSS::Minifier::XS::minify $css] ]
87 };
88
89 get qr,/js\.js, => sub {
90 my $js;
91 $js .= read_file $_ for <js/*.js>;
92 [200, ['Content-Type' => 'application/javascript', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [JavaScript::Minifier::XS::minify $js] ]
93 };
94
95 get qr,/log/src/$number\.$word, => sub {
96 my ($env, $job, $ext) = @_;
97 $job = db($env)->job($job);
98 return NOT_FOUND if $job->contest;
99 return FORBIDDEN if !admin && ($job->private || $job->problem->private);
100 [200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [$job->source] ]
101 };
102
103 get qr,/ct/$word/log/src/$number\.$word, => sub {
104 my ($env, $ct, $job, $ext) = @_;
105 $job = db($env)->job($job);
106 $ct = db($env)->contest($ct);
107 return NOT_FOUND if $job->contest->id ne $ct->id;
108 return FORBIDDEN if !admin && ($job->private || !$ct->is_finished);
109 [200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [$job->source] ]
110 };
111
112 get qr,/ct/$word/log/st, => sub {
113 my($env, $ct) = @_;
114 $env->{'gruntmaster.contest'} = $ct;
115 old_handler('St')->($env);
116 };
117
118 get qr,/us/, => old_handler 'Us';
119 get qr,/us/$word, => old_handler 'Us::Entry';
120
121 get qr,/pb/, => old_handler 'Pb';
122
123 get qr,/us/$word/pb/, => sub {
124 my ($env, $us) = @_;
125 $env->{'gruntmaster.user'} = $us;
126 old_handler('Pb')->($env);
127 };
128
129 get qr,/ct/$word/pb/, => sub {
130 my ($env, $ct) = @_;
131 return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
132 $env->{'gruntmaster.contest'} = $ct;
133 old_handler('Pb')->($env);
134 };
135
136 get qr,/pb/$word, => sub {
137 my ($env, $pb) = @_;
138 return NOT_FOUND if !db($env)->problem($pb)->is_in_archive;
139 return FORBIDDEN if !admin && db($env)->problem($pb)->private;
140 old_handler('Pb::Entry')->(@_);
141 };
142
143 get qr,/ct/$word/pb/$word, => sub{
144 my ($env, $ct, $pb) = @_;
145 return NOT_FOUND if !db($env)->contest_problems->find($ct, $pb);
146 return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
147 $env->{'gruntmaster.contest'} = $ct;
148 old_handler('Pb::Entry')->($env, $pb);
149 };
150
151 post qr,/pb/$word/submit, => sub {
152 my ($env, $pb) = @_;
153 return NOT_FOUND if !db($env)->problem($pb)->is_in_archive;
154 old_handler('Submit')->($env, $pb);
155 };
156
157 post qr,/ct/$word/pb/$word/submit, => sub {
158 my ($env, $ct, $pb) = @_;
159 return NOT_FOUND if !db($env)->contest_problems->find($ct, $pb);
160 return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
161 $env->{'gruntmaster.contest'} = $ct;
162 old_handler('Submit')->($env, $pb);
163 };
164
165 get qr,/log/(?:page-$number)?, => sub {
166 my ($env, $page) = @_;
167 $env->{'gruntmaster.page'} = $page || -1;
168 old_handler('Log')->($env)
169 };
170
171 get qr,/ct/$word/log/(?:page-$number)?, => sub {
172 my ($env, $ct, $page) = @_;
173 return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
174 $env->{'gruntmaster.contest'} = $ct;
175 $env->{'gruntmaster.page'} = $page || -1;
176 old_handler('Log')->($env)
177 };
178
179 get qr,/pb/$word/log/(?:page-$number)?, => sub {
180 my ($env, $pb, $page) = @_;
181 #return FORBIDDEN if !admin && db($pb)-> TODO
182 $env->{'gruntmaster.problem'} = $pb;
183 $env->{'gruntmaster.page'} = $page || -1;
184 old_handler('Log')->($env)
185 };
186
187 get qr,/us/$word/log/(?:page-$number)?, => sub {
188 my ($env, $us, $page) = @_;
189 $env->{'gruntmaster.user'} = $us;
190 $env->{'gruntmaster.page'} = $page || -1;
191 old_handler('Log')->($env);
192 };
193
194 get qr,/log/$number, => sub{
195 my ($env, $job) = @_;
196 my $j = db($env)->job($job);
197 return FORBIDDEN if !admin && ($j->private || $j->problem->private || ($j->contest && !$j->contest->is_finished));
198 old_handler('Log::Entry')->($env, $job);
199 };
200
201 get qr,/ct/, => old_handler 'Ct';
202
203 get qr,/ct/$word, => sub {
204 my ($env, $ct) = @_;
205 return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
206 old_handler('Ct::Entry')->($env, $ct);
207 };
208
209 post qr,/action/passwd, => sub {
210 my ($env) = @_;
211 my $r = Plack::Request->new($env);
212 my ($oldpass, $newpass, $confirm) = map {scalar $r->param($_)} 'password', 'new_password', 'confirm_new_password';
81cce380 213
e03e380b
MG
214 return reply 'Incorrect password' unless eval { pwcheck $r->user, $oldpass; 1 };
215 return reply 'The two passwords do not match' unless $newpass eq $confirm;
7dc32473 216
e03e380b
MG
217 pwset $r->user, $newpass;
218 reply 'Password changed successfully';
219 };
7dc32473 220
e03e380b 221 post qr,/action/register, => old_handler 'Register';
31d70015 222
7dc32473
MG
223}
224
2251;
226__END__
This page took 0.02611 seconds and 4 git commands to generate.