+sub forbid {
+ return if !shift || admin;
+ unwind FORBIDDEN, SUB UP
+}
+
+sub dispatch_request{
+ $env = $_[PSGI_ENV];
+ sub (GET) {
+ sub (/css/:theme) {
+ my $theme = $_{theme};
+ return NOT_FOUND unless -e "css/themes/$theme.css";
+ my $css = read_file "css/themes/$theme.css";
+ $css .= read_file $_ for <css/*.css>;
+ my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => 'text/css');
+ [200, \@headers, [CSS::Minifier::XS::minify $css]]
+ },
+
+ sub (/js.js) {
+ my $js;
+ $js .= read_file $_ for <js/*.js>;
+ my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => 'application/javascript');
+ [200, \@headers, [JavaScript::Minifier::XS::minify $js]]
+ },
+
+ sub (/src/:job) {
+ return NOT_FOUND if !job;
+ forbid job->private || job->problem->private || job->contest && job->contest->private;
+ my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => CONTENT_TYPES->{job->format});
+ [200, \@headers, [job->source]]
+ },
+
+ sub (?:contest=) {
+ return NOT_FOUND if !contest;
+ forbid contest->is_pending;
+ response_filter { return shift }
+ },
+
+ sub (?:problem=) {
+ return NOT_FOUND if !problem;
+ forbid problem->is_private;
+ response_filter { return shift }
+ },
+
+ sub () {
+ response_filter {
+ my ($r) = @_;
+ return $r if ref $r ne 'Plack::App::Gruntmaster::Response';
+ return [200, ['Content-Type' => 'application/json', 'X-Forever' => 1], [encode_json $r->{params}]] if $env->{HTTP_ACCEPT} =~ m,^\s*application/json\s*$,g;
+ render $r->{template}, $r->{title}, %{$r->{params}}
+ },
+ },
+
+ sub (/st/) {
+ response st => 'Standings', { st => [db->standings] }
+ },
+
+ sub (/st/:contest) {
+ response st => 'Standings', {
+ st => [ db->standings($_{contest}) ],
+ problems => [map { $_->problem } contest->contest_problems]
+ }
+ },
+
+ sub (/ct/:contest/log/st) { redirect "/st/$_{contest}" },