+sub user_list { scalar _query('user_list_sth')->hashes }
+
+sub user_entry {
+ my ($id) = @_;
+ my $ret = _query('user_entry_sth', $id)->hash;
+ $ret->{problems} = _add_names _query('problem_status_sth', $id)->hashes;
+ $ret->{contests} = _add_names _query('contest_status_sth', $id)->hashes;
+
+ $ret;
+}
+
+sub problem_list {
+ my (%args) = @_;
+ my @columns = @{PROBLEM_PUBLIC_COLUMNS()};
+ push @columns, 'solution' if $args{solution};
+ my %where;
+ $where{private} = 0 unless $args{contest} || $args{private};
+ $where{'cp.contest'} = $args{contest} if $args{contest};
+ $where{owner} = $args{owner} if $args{owner};
+
+ my $table = $args{contest} ? 'problems JOIN contest_problems cp ON cp.problem = id' : 'problems';
+ _add_names $db->select(\$table, \@columns, \%where, 'name')->hashes
+}
+
+sub problem_entry {
+ my ($id, $contest) = @_;
+ $contest = contest_entry ($contest) if $contest;
+ my $ret = _add_names _query(problem_entry_sth => $id)->hash;
+ my $limits = _query(limits_sth => $id)->hashes;
+ $ret->{limits} = $limits if @$limits;
+
+ if ($contest) {
+ $ret->{contest_start} = $contest->{start};
+ $ret->{contest_stop} = $contest->{stop};
+ delete $ret->{solution}
+ }
+
+ $ret
+}
+
+sub contest_list { _add_names _query('contest_list_sth')->hashes }
+
+sub contest_entry { _add_names _query(contest_entry_sth => $_[0])->hash }
+
+sub contest_has_problem { _query('contest_has_problem_sth', @_[0, 1])->flat }
+
+sub job_list {
+ my (%args) = @_;
+ $args{page} = int ($args{page} // 1);
+ my %where = (
+ maybe contest => $args{contest},
+ maybe owner => $args{owner},
+ maybe problem => $args{problem},
+ maybe result => $args{result},
+ );
+ $where{private} = 0 unless $args{private};
+
+ my $rows = $db->select('job_entry', 'COUNT(*)', \%where)->list;
+ my $pages = int (($rows + JOBS_PER_PAGE - 1) / JOBS_PER_PAGE);
+ my ($stmt, @bind) = $db->abstract->select('job_entry', '*', \%where, {-desc => 'id'});
+ my $jobs = _add_names $db->query("$stmt LIMIT " . JOBS_PER_PAGE . ' OFFSET ' . ($args{page} - 1) * JOBS_PER_PAGE, @bind)->hashes;
+ my $pageinfo = {
+ current_page => $args{page},
+ last_page => $pages,
+ ($args{page} - 1) ? (previous_page => $args{page} - 1) : (),
+ ($args{page} < $pages) ? (next_page => $args{page} + 1) : (),
+ };
+ wantarray ? ($jobs, $pageinfo) : $jobs;
+}
+
+sub job_entry {
+ my $ret = _add_names _query(job_entry_sth => $_[0])->hash;
+ $ret->{results} = decode_json $ret->{results} if $ret->{results};
+ $ret
+}
+
+sub create_job {
+ my (%args) = @_;
+ $db->update('users', {lastjob => time}, {id => $args{owner}});
+ purge '/log/';
+ scalar $db->insert('jobs', \%args, {returning => 'id'})->list
+}
+
+sub _calc_score {