CONSTRAINT positive_duration CHECK (stop > start)
);
+CREATE TABLE contest_status (
+ contest TEXT NOT NULL REFERENCES contests ON DELETE CASCADE,
+ owner TEXT NOT NULL REFERENCES users ON DELETE CASCADE,
+ score INT NOT NULL,
+ rank INT NOT NULL,
+
+ PRIMARY KEY (owner, contest)
+);
+
CREATE TABLE problems (
id TEXT PRIMARY KEY,
author TEXT,
owner TEXT NOT NULL REFERENCES users ON DELETE CASCADE
);
+CREATE TABLE problem_status (
+ problem TEXT NOT NULL REFERENCES problems ON DELETE CASCADE,
+ owner TEXT NOT NULL REFERENCES users ON DELETE CASCADE,
+ job SERIAL NOT NULL REFERENCES jobs ON DELETE CASCADE,
+ solved BOOLEAN NOT NULL DEFAULT FALSE,
+
+ PRIMARY KEY (owner, problem)
+);
+
CREATE TABLE opens (
contest TEXT NOT NULL REFERENCES contests ON DELETE CASCADE,
problem TEXT NOT NULL REFERENCES problems ON DELETE CASCADE,
}
BEGIN {
- for my $rs (qw/contest contest_problem job open problem user/) {
+ for my $rs (qw/contest contest_problem job open problem user problem_status contest_status/) {
my $rsname = ucfirst $rs;
$rsname =~ s/_([a-z])/\u$1/gs;
dynsub PL_N($rs) => sub { $_[0]->resultset($rsname) };
sub user_entry {
my ($self, $id) = @_;
- +{ $self->users->find($id, {columns => USER_PUBLIC_COLUMNS})->get_columns }
+ my $user = $self->users->find($id, {columns => USER_PUBLIC_COLUMNS, prefetch => [qw/problem_statuses contest_statuses/]});
+ my @problems = map { {problem => $_->get_column('problem'), solved => $_->solved} } $user->problem_statuses;
+ my @contests = map { {contest => $_->get_column('contest'), rank => $_->rank, score => $_->score} } $user->contest_statuses;
+ +{ $user->get_columns, problems => \@problems, contests => \@contests }
}
sub problem_list {
\%params
}
+sub update_status {
+ my ($self) = @_;
+ my @jobs = $self->jobs->search(undef, {cache => 1})->all;
+ my %hash;
+ $hash{$_->get_column('problem'), $_->get_column('owner')} = [$_, $_->result ? 1 : 0] for @jobs;
+ my @problem_statuses = map { [split ($;), @{$hash{$_}} ] } keys %hash;
+
+ my @contest_statuses = map {
+ my $contest = $_->id;
+ my @standings = $self->standings($contest);
+ map { [$contest, $_->{user}, $_->{score}, $_->{rank}] } @standings;
+ } $self->contests->all;
+
+ my $txn = sub {
+ $self->problem_statuses->delete;
+ $self->problem_statuses->populate([[qw/problem owner job solved/], @problem_statuses]);
+ $self->contest_statuses->delete;
+ $self->contest_statuses->populate([[qw/contest owner score rank/], @contest_statuses]);
+ };
+
+ $self->txn_do($txn);
+}
+
1;
__END__
{ cascade_copy => 0, cascade_delete => 0 },
);
+=head2 contest_statuses
+
+Type: has_many
+
+Related object: L<Gruntmaster::Data::Result::ContestStatus>
+
+=cut
+
+__PACKAGE__->has_many(
+ "contest_statuses",
+ "Gruntmaster::Data::Result::ContestStatus",
+ { "foreign.contest" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
=head2 jobs
Type: has_many
__PACKAGE__->many_to_many("problems", "contest_problems", "problem");
-# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-05-16 15:03:32
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8PPzBpDmSTq4ukKuxIlLlQ
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-11 23:51:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nu+Io9AhYkzYCky5UpCaKQ
sub is_pending {
my ($self, $time) = @_;
--- /dev/null
+use utf8;
+package Gruntmaster::Data::Result::ContestStatus;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Gruntmaster::Data::Result::ContestStatus
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<contest_status>
+
+=cut
+
+__PACKAGE__->table("contest_status");
+
+=head1 ACCESSORS
+
+=head2 contest
+
+ data_type: 'text'
+ is_foreign_key: 1
+ is_nullable: 0
+
+=head2 owner
+
+ data_type: 'text'
+ is_foreign_key: 1
+ is_nullable: 0
+
+=head2 score
+
+ data_type: 'integer'
+ is_nullable: 0
+
+=head2 rank
+
+ data_type: 'integer'
+ is_nullable: 0
+
+=cut
+
+__PACKAGE__->add_columns(
+ "contest",
+ { data_type => "text", is_foreign_key => 1, is_nullable => 0 },
+ "owner",
+ { data_type => "text", is_foreign_key => 1, is_nullable => 0 },
+ "score",
+ { data_type => "integer", is_nullable => 0 },
+ "rank",
+ { data_type => "integer", is_nullable => 0 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</owner>
+
+=item * L</contest>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("owner", "contest");
+
+=head1 RELATIONS
+
+=head2 contest
+
+Type: belongs_to
+
+Related object: L<Gruntmaster::Data::Result::Contest>
+
+=cut
+
+__PACKAGE__->belongs_to(
+ "contest",
+ "Gruntmaster::Data::Result::Contest",
+ { id => "contest" },
+ { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
+);
+
+=head2 owner
+
+Type: belongs_to
+
+Related object: L<Gruntmaster::Data::Result::User>
+
+=cut
+
+__PACKAGE__->belongs_to(
+ "owner",
+ "Gruntmaster::Data::Result::User",
+ { id => "owner" },
+ { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-11 23:51:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vfOfZeATPRODifpgHO4L0A
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
{ is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
);
+=head2 problem_statuses
-# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-05-16 15:03:32
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:k3Oq7pNqFoCI5NwY5GaWfQ
+Type: has_many
+
+Related object: L<Gruntmaster::Data::Result::ProblemStatus>
+
+=cut
+
+__PACKAGE__->has_many(
+ "problem_statuses",
+ "Gruntmaster::Data::Result::ProblemStatus",
+ { "foreign.job" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-11 23:51:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:D49ekK0vGg/7b8xXZoYTWQ
sub rerun {
shift->update({daemon => undef, result => -2, result_text => undef});
{ is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
);
+=head2 problem_statuses
+
+Type: has_many
+
+Related object: L<Gruntmaster::Data::Result::ProblemStatus>
+
+=cut
+
+__PACKAGE__->has_many(
+ "problem_statuses",
+ "Gruntmaster::Data::Result::ProblemStatus",
+ { "foreign.problem" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
=head2 contests
Type: many_to_many
__PACKAGE__->many_to_many("contests", "contest_problems", "contest");
-# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-07 00:51:56
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:KjAtOerTqBqtcMrBtwJ3Bw
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-11 23:51:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1SnNCeJdFr5lM3mmO6rtqA
sub is_private {
my ($self, $time) = @_;
--- /dev/null
+use utf8;
+package Gruntmaster::Data::Result::ProblemStatus;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Gruntmaster::Data::Result::ProblemStatus
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<problem_status>
+
+=cut
+
+__PACKAGE__->table("problem_status");
+
+=head1 ACCESSORS
+
+=head2 problem
+
+ data_type: 'text'
+ is_foreign_key: 1
+ is_nullable: 0
+
+=head2 owner
+
+ data_type: 'text'
+ is_foreign_key: 1
+ is_nullable: 0
+
+=head2 job
+
+ data_type: 'integer'
+ is_auto_increment: 1
+ is_foreign_key: 1
+ is_nullable: 0
+ sequence: 'problem_status_job_seq'
+
+=head2 solved
+
+ data_type: 'boolean'
+ default_value: false
+ is_nullable: 0
+
+=cut
+
+__PACKAGE__->add_columns(
+ "problem",
+ { data_type => "text", is_foreign_key => 1, is_nullable => 0 },
+ "owner",
+ { data_type => "text", is_foreign_key => 1, is_nullable => 0 },
+ "job",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_foreign_key => 1,
+ is_nullable => 0,
+ sequence => "problem_status_job_seq",
+ },
+ "solved",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</owner>
+
+=item * L</problem>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("owner", "problem");
+
+=head1 RELATIONS
+
+=head2 job
+
+Type: belongs_to
+
+Related object: L<Gruntmaster::Data::Result::Job>
+
+=cut
+
+__PACKAGE__->belongs_to(
+ "job",
+ "Gruntmaster::Data::Result::Job",
+ { id => "job" },
+ { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
+);
+
+=head2 owner
+
+Type: belongs_to
+
+Related object: L<Gruntmaster::Data::Result::User>
+
+=cut
+
+__PACKAGE__->belongs_to(
+ "owner",
+ "Gruntmaster::Data::Result::User",
+ { id => "owner" },
+ { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
+);
+
+=head2 problem
+
+Type: belongs_to
+
+Related object: L<Gruntmaster::Data::Result::Problem>
+
+=cut
+
+__PACKAGE__->belongs_to(
+ "problem",
+ "Gruntmaster::Data::Result::Problem",
+ { id => "problem" },
+ { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-11 23:51:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SUAwYQhgBtoCjtFSOMc4FQ
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
=head1 RELATIONS
+=head2 contest_statuses
+
+Type: has_many
+
+Related object: L<Gruntmaster::Data::Result::ContestStatus>
+
+=cut
+
+__PACKAGE__->has_many(
+ "contest_statuses",
+ "Gruntmaster::Data::Result::ContestStatus",
+ { "foreign.owner" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
=head2 contests
Type: has_many
{ cascade_copy => 0, cascade_delete => 0 },
);
+=head2 problem_statuses
+
+Type: has_many
+
+Related object: L<Gruntmaster::Data::Result::ProblemStatus>
+
+=cut
+
+__PACKAGE__->has_many(
+ "problem_statuses",
+ "Gruntmaster::Data::Result::ProblemStatus",
+ { "foreign.owner" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
=head2 problems
Type: has_many
);
-# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-05-16 15:23:08
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Cho4zmn58Mytf2jHvgP+4g
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2014-12-11 23:51:27
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JcVHC/n8J+NgJge9LkckYA
use Authen::Passphrase;
use Authen::Passphrase::BlowfishCrypt;