Skip to content
2 changes: 1 addition & 1 deletion webapp/migrations/Version20250323190305.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class Version20250323190305 extends AbstractMigration
{
public function getDescription(): string
{
return '';
return 'Add problem types';
}

public function up(Schema $schema): void
Expand Down
38 changes: 38 additions & 0 deletions webapp/migrations/Version20250620082406.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250620082406 extends AbstractMigration
{
public function getDescription(): string
{
return 'Change comments to reflect entities';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE judging CHANGE max_runtime_for_verdict max_runtime_for_verdict NUMERIC(32, 9) UNSIGNED DEFAULT NULL COMMENT \'The maximum runtime for all runs that resulted in the verdict\'');
$this->addSql('ALTER TABLE problem CHANGE types types INT NOT NULL COMMENT \'Bitmask of problem types, default is pass-fail.\'');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE problem CHANGE types types INT NOT NULL COMMENT \'Bitset of problem types, default is pass-fail.\'');
$this->addSql('ALTER TABLE judging CHANGE max_runtime_for_verdict max_runtime_for_verdict NUMERIC(32, 9) UNSIGNED DEFAULT NULL COMMENT \'The maximum run time for all runs that resulted in the verdict\'');
}

public function isTransactional(): bool
{
return false;
}
}
48 changes: 48 additions & 0 deletions webapp/migrations/Version20250620090108.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250620090108 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE team_category_team (categoryid INT UNSIGNED NOT NULL COMMENT \'Team category ID\', teamid INT UNSIGNED NOT NULL COMMENT \'Team ID\', INDEX IDX_3A19F9C99B32FD3 (categoryid), INDEX IDX_3A19F9C94DD6ABF3 (teamid), PRIMARY KEY(categoryid, teamid)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE team_category_team ADD CONSTRAINT FK_3A19F9C99B32FD3 FOREIGN KEY (categoryid) REFERENCES team_category (categoryid) ON DELETE CASCADE');
$this->addSql('ALTER TABLE team_category_team ADD CONSTRAINT FK_3A19F9C94DD6ABF3 FOREIGN KEY (teamid) REFERENCES team (teamid) ON DELETE CASCADE');
$this->addSql('INSERT INTO team_category_team (categoryid, teamid) SELECT categoryid, teamid FROM team');
$this->addSql('ALTER TABLE team DROP FOREIGN KEY team_ibfk_1');
$this->addSql('DROP INDEX categoryid ON team');
$this->addSql('ALTER TABLE team DROP categoryid');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE team ADD categoryid INT UNSIGNED DEFAULT NULL COMMENT \'Team category ID\'');
$this->addSql('ALTER TABLE team ADD CONSTRAINT team_ibfk_1 FOREIGN KEY (categoryid) REFERENCES team_category (categoryid) ON DELETE CASCADE');
$this->addSql('CREATE INDEX categoryid ON team (categoryid)');
$this->addSql('UPDATE team SET categoryid = (SELECT MIN(categoryid) from team_category_team WHERE team_category_team.teamid = team.teamid)');
$this->addSql('ALTER TABLE team_category_team DROP FOREIGN KEY FK_3A19F9C99B32FD3');
$this->addSql('ALTER TABLE team_category_team DROP FOREIGN KEY FK_3A19F9C94DD6ABF3');
$this->addSql('DROP TABLE team_category_team');
}

public function isTransactional(): bool
{
return false;
}
}
50 changes: 50 additions & 0 deletions webapp/migrations/Version20250801124024.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250801124024 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add category types (bitset), css_class field, and make sortorder nullable for TeamCategory';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<SQL
ALTER TABLE team_category
ADD types INT NOT NULL DEFAULT 1 COMMENT 'Bitmask of category types, default is scoring.' AFTER name,
CHANGE sortorder sortorder TINYINT UNSIGNED DEFAULT NULL COMMENT 'Where to sort this category on the scoreboard',
ADD css_class VARCHAR(255) DEFAULT NULL COMMENT 'CSS class to apply to scoreboard rows (only for TYPE_CSS_CLASS)' AFTER allow_self_registration
SQL);

// Now update the types for existing categories based on whether the color is set
$this->addSql('UPDATE team_category SET types = 7 WHERE color IS NOT NULL');
$this->addSql('UPDATE team_category SET types = 5 WHERE color IS NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<SQL
ALTER TABLE team_category
DROP types,
DROP css_class,
CHANGE sortorder sortorder TINYINT UNSIGNED DEFAULT 0 NOT NULL COMMENT 'Where to sort this category on the scoreboard'
SQL);
}

public function isTransactional(): bool
{
return false;
}
}
8 changes: 6 additions & 2 deletions webapp/public/style_domjudge.css
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,12 @@ blockquote {
color: darkgrey;
}

.category-best {
margin-right: 2em;
.category-badges {
margin-right: 1.5em;
}

.category-best, .category-badge {
margin-right: 0.5em;
font-weight: normal;
}

Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Command/ScoreboardMergeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$teamObj->setAffiliation($affiliations[$organizationName]);
}

$teamObj->setCategory($category);
$teamObj->addCategory($category);
$oldid = $team['id'];
$newid = $nextTeamId++;
$teamObj->setTeamid($newid);
Expand Down
17 changes: 12 additions & 5 deletions webapp/src/Controller/API/MetricsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
use App\Entity\QueueTask;
use App\Entity\Submission;
use App\Entity\Team;
use App\Entity\TeamCategory;
use App\Entity\User;
use App\Service\DOMJudgeService;
use App\Service\SubmissionService;
use App\Utils\Utils;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Join;
use FOS\RestBundle\Controller\AbstractFOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use OpenApi\Attributes as OA;
Expand Down Expand Up @@ -79,8 +81,9 @@ public function prometheusAction(): Response
->select('t', 'u')
->from(Team::class, 't')
->leftJoin('t.users', 'u')
->join('t.category', 'cat')
->join('t.categories', 'cat', Join::WITH, 'BIT_AND(cat.types, :scoring) = :scoring')
->andWhere('cat.visible = true')
->setParameter('scoring', TeamCategory::TYPE_SCORING)
->getQuery()
->getResult();

Expand All @@ -89,8 +92,9 @@ public function prometheusAction(): Response
->select('u')
->from(User::class, 'u')
->leftJoin('u.team', 't')
->join('t.category', 'cat')
->join('t.categories', 'cat', Join::WITH, 'BIT_AND(cat.types, :scoring) = :scoring')
->andWhere('cat.visible = true')
->setParameter('scoring', TeamCategory::TYPE_SCORING)
->getQuery()
->getResult();

Expand Down Expand Up @@ -134,10 +138,11 @@ public function prometheusAction(): Response
->from(Team::class, 't')
->leftJoin('t.users', 'u')
->leftJoin('t.contests', 'c')
->join('t.category', 'cat')
->join('t.categories', 'cat', Join::WITH, 'BIT_AND(cat.types, :scoring) = :scoring')
->leftJoin('cat.contests', 'cc')
->andWhere('c.cid = :cid OR cc.cid = :cid')
->andWhere('cat.visible = true')
->setParameter('scoring', TeamCategory::TYPE_SCORING)
->setParameter('cid', $contest->getCid())
->getQuery()
->getResult();
Expand All @@ -154,10 +159,11 @@ public function prometheusAction(): Response
->from(User::class, 'u')
->leftJoin('u.team', 't')
->leftJoin('t.contests', 'c')
->join('t.category', 'cat')
->join('t.categories', 'cat', Join::WITH, 'BIT_AND(cat.types, :scoring) = :scoring')
->leftJoin('cat.contests', 'cc')
->andWhere('c.cid = :cid OR cc.cid = :cid')
->andWhere('cat.visible = true')
->setParameter('scoring', TeamCategory::TYPE_SCORING)
->setParameter('cid', $contest->getCid())
->getQuery()
->getResult();
Expand Down Expand Up @@ -227,10 +233,11 @@ public function prometheusAction(): Response
->join('b.submission', 's')
->join('s.contest', 'c')
->join('s.team', 't')
->join('t.category', 'cat')
->join('t.categories', 'cat', Join::WITH, 'BIT_AND(cat.types, :scoring) = :scoring')
->andWhere('b.done = false')
->andWhere('c.cid = :cid')
->andWhere('cat.visible = true')
->setParameter('scoring', TeamCategory::TYPE_SCORING)
->setParameter('cid', $contest->getCid())
->getQuery()
->getResult();
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Controller/API/ScoreboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public function getScoreboardAction(
$scoreIsInSeconds = (bool)$this->config->get('score_in_seconds');

foreach ($scoreboard->getScores() as $teamScore) {
if ($teamScore->team->getCategory()->getSortorder() !== $sortorder) {
if ($teamScore->team->getSortorder() !== $sortorder) {
continue;
}

Expand Down
5 changes: 4 additions & 1 deletion webapp/src/Controller/API/SubmissionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Entity\SubmissionFile;
use App\Entity\SubmissionSource;
use App\Entity\Team;
use App\Entity\TeamCategory;
use App\Entity\User;
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
Expand All @@ -19,6 +20,7 @@
use App\Utils\Utils;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Exception;
use FOS\RestBundle\Controller\Annotations as Rest;
Expand Down Expand Up @@ -463,7 +465,8 @@ protected function getQueryBuilder(Request $request): QueryBuilder
if (!$this->dj->checkrole('api_reader') &&
!$this->dj->checkrole('judgehost')) {
$queryBuilder
->join('t.category', 'cat');
->join('t.categories', 'cat', Join::WITH, 'BIT_AND(cat.types, :scoring) = :scoring')
->setParameter('scoring', TeamCategory::TYPE_SCORING);
if ($this->dj->checkrole('team')) {
$queryBuilder
->andWhere('cat.visible = 1 OR s.team = :team')
Expand Down
11 changes: 8 additions & 3 deletions webapp/src/Controller/API/TeamController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
use App\DataTransferObject\AddTeam;
use App\Entity\Contest;
use App\Entity\Team;
use App\Entity\TeamCategory;
use App\Service\AssetUpdateService;
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
use App\Service\EventLogService;
use App\Service\ImportExportService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use FOS\RestBundle\Controller\Annotations as Rest;
use Nelmio\ApiDocBundle\Attribute\Model;
Expand Down Expand Up @@ -305,14 +307,16 @@ protected function getQueryBuilder(Request $request): QueryBuilder
$queryBuilder = $this->em->createQueryBuilder()
->from(Team::class, 't')
->leftJoin('t.affiliation', 'ta')
->leftJoin('t.category', 'tc')
->leftJoin('t.categories', 'tc')
->leftJoin('t.categories', 'tcc', Join::WITH, 'BIT_AND(tcc.types, :scoring) = :scoring')
->leftJoin('t.contests', 'c')
->leftJoin('tc.contests', 'cc')
->select('t, ta');
->setParameter('scoring', TeamCategory::TYPE_SCORING)
->select('t, ta, tc');

if ($request->query->has('category')) {
$queryBuilder
->andWhere('t.category = :category')
->andWhere('tc.categoryid = :category')
->setParameter('category', $request->query->get('category'));
}

Expand All @@ -324,6 +328,7 @@ protected function getQueryBuilder(Request $request): QueryBuilder

if (!$this->dj->checkrole('api_reader') || $request->query->getBoolean('public')) {
$queryBuilder->andWhere('tc.visible = 1');
$queryBuilder->andWhere('tcc.visible = 1');
}

if ($request->attributes->has('cid')) {
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Controller/Jury/ContestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public function indexAction(Request $request): Response
->select('COUNT(DISTINCT t.teamid)')
->from(Team::class, 't')
->leftJoin('t.contests', 'c')
->join('t.category', 'cat')
->join('t.categories', 'cat')
->leftJoin('cat.contests', 'cc')
->andWhere('c.cid = :cid OR cc.cid = :cid')
->setParameter('cid', $contest->getCid())
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Controller/Jury/ImportExportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ protected function getResultsHtml(
'rank' => null,
];
foreach ($teams as $team) {
if (!isset($categories[$team->getCategory()->getCategoryid()]) || $team->getCategory()->getSortorder() !== $sortOrder) {
if ($team->getHidden() || $team->getSortorder() !== $sortOrder) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Controller/Jury/JudgeRemainingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function judgeRemaining(int $contestId = -1, string $categoryId = '', str
->select('j')
->join('j.submission', 's')
->join('s.team', 't')
->join('t.category', 'tc')
->join('t.categories', 'tc')
->andWhere('j.valid = true')
->andWhere('j.result != :compiler_error')
->setParameter('compiler_error', 'compiler-error');
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Controller/Jury/SubmissionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ public function verifyAction(
if (!$judging->getContest()->isOpenToAllTeams()) {
$teamsQueryBuilder
->leftJoin('t.contests', 'c')
->join('t.category', 'cat')
->join('t.categories', 'cat')
->leftJoin('cat.contests', 'cc')
->andWhere('c.cid = :cid OR cc.cid = :cid')
->setParameter('cid', $judging->getContest()->getCid());
Expand Down
6 changes: 4 additions & 2 deletions webapp/src/Controller/Jury/TeamCategoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ public function indexAction(): Response
->groupBy('c.categoryid')
->getQuery()->getResult();
$table_fields = [
'categoryid' => ['title' => 'ID', 'sort' => true],
'categoryid' => ['title' => 'ID', 'sort' => true, 'default_sort' => true],
'externalid' => ['title' => 'external ID', 'sort' => true],
'icpcid' => ['title' => 'ICPC ID', 'sort' => true],
'sortorder' => ['title' => 'sort', 'sort' => true, 'default_sort' => true],
'sortorder' => ['title' => 'sort', 'sort' => true],
'name' => ['title' => 'name', 'sort' => true],
'types' => ['title' => 'types', 'sort' => false],
'num_teams' => ['title' => '# teams', 'sort' => true],
'visible' => ['title' => 'visible', 'sort' => true],
'allow_self_registration' => ['title' => 'self-registration', 'sort' => true],
Expand Down Expand Up @@ -103,6 +104,7 @@ public function indexAction(): Response
];
}

$categorydata['types'] = ['value' => implode(', ', $teamCategory->getTypeHumanNames()) ?: '-'];
$categorydata['num_teams'] = ['value' => $teamCategoryData['num_teams']];
$categorydata['visible'] = ['value' => $teamCategory->getVisible() ? 'yes' : 'no'];
$categorydata['allow_self_registration'] = ['value' => $teamCategory->getAllowSelfRegistration() ? 'yes' : 'no'];
Expand Down
Loading
Loading