-sub cp { defined $contest ? "contest.$contest." : '' }
-
-sub problems () { SMEMBERS cp . 'problem' }
-sub contests () { SMEMBERS cp . 'contest' }
-sub users () { SMEMBERS cp . 'user' }
-sub jobcard () { GET cp . 'job' }
-
-sub job_results (_) { decode_json HGET cp . "job.$_[0]", 'results' }
-sub set_job_results ($+) { HSET cp . "job.$_[0]", 'results', encode_json $_[1] }
-sub job_inmeta (_) { decode_json HGET cp . "job.$_[0]", 'inmeta' }
-sub set_job_inmeta ($+) { HSET cp . "job.$_[0]", 'inmeta', encode_json $_[1] }
-sub problem_meta (_) { decode_json HGET cp . "problem.$_[0]", 'meta' }
-sub set_problem_meta ($+) { HSET cp . "problem.$_[0]", 'meta', encode_json $_[1] }
-sub job_daemon (_) { HGET cp . "job.$_[0]", 'daemon' }
-sub set_job_daemon ($$) { HSETNX cp . "job.$_[0]", 'daemon', $_[1] };
-
-sub defhash{
- my ($name, @keys) = @_;
- for my $key (@keys) {
- dynsub "${name}_$key", sub (_) { HGET cp . "$name.$_[0]", $key };
- dynsub "set_${name}_$key", sub ($$) { HSET cp . "$name.$_[0]", $key, $_[1] };
- }
-
- dynsub "edit_$name", sub {
- my ($key, %values) = @_;
- HMSET cp . "$name.$key", %values;
- };
-
- dynsub "insert_$name", sub {
- my ($key, %values) = @_;
- SADD cp . $name, $key or return;
- HMSET cp . "$name.$key", %values;
- };
- dynsub "remove_$name", sub (_) {
- my $key = shift;
- SREM cp . $name, $key;
- DEL cp . "$name.$key";
- };
+sub user_list {
+ my $rs = $_[0]->users->search(undef, {columns => USER_PUBLIC_COLUMNS, prefetch => [qw/problem_statuses contest_statuses/]} );
+ [ sort { $b->{solved} <=> $a->{solved} or $b->{attempted} <=> $a->{attempted} } map { ## no critic (ProhibitReverseSort)
+ my $solved = $_->problem_statuses->count(solved => 1);
+ my $attempted = $_->problem_statuses->count(solved => 0);
+ my $contests = $_->contest_statuses->count;
+ +{ $_->get_columns, solved => $solved, attempted => $attempted, contests => $contests }
+ } $rs->all ]
+}