Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2297669
feat: added basic functioning event templates for defining multiple b…
PaulicStudios Feb 10, 2026
2c8f6fb
feat: team create design improvements
PaulicStudios Feb 10, 2026
f615372
feat: emitting right docker image to queue now
PaulicStudios Feb 10, 2026
92fe71d
feat: fix github token update and implemented coderabbit improvements
PaulicStudios Feb 10, 2026
66f8ffe
fix: now it does not try to format the game config and server config …
PaulicStudios Feb 10, 2026
5ec5e3b
feat: Add Suspense to the event dashboard page to display a loading f…
PaulicStudios Feb 10, 2026
eec5a4a
feat: added template repo version endpoint and replace url in script …
PaulicStudios Feb 11, 2026
dcbff86
chore: updated k8s-service dependencies
PaulicStudios Feb 10, 2026
db83576
feat: added more output to git clone container; fixes #529
PaulicStudios Feb 10, 2026
d5572c3
feat: added replacing of event url in image update script; fixes #539
PaulicStudios Feb 10, 2026
6dc7890
feat: added replacing of event url in image update script; fixes #539
PaulicStudios Feb 10, 2026
35dd1c7
feat: added template repo version endpoint and replace url in script …
PaulicStudios Feb 11, 2026
824e5a4
feat: added replacing of event url in image update script; fixes #539
PaulicStudios Feb 10, 2026
a1a7967
Merge branch 'dev' into 537-ability-to-add-different-repo-templates
PaulicStudios Feb 17, 2026
6a4a30a
chore: pnpm i
PaulicStudios Feb 17, 2026
0585367
feat: changed git clone script
PaulicStudios Feb 17, 2026
eaa5999
feat: refactor config writing logic into a single utility function in…
PaulicStudios Feb 17, 2026
27a62be
fix: github-service replace right url in scripts
PaulicStudios Feb 17, 2026
35280d0
fix: added validation that template exists in event when creating team
PaulicStudios Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions api/db/migrations/1770757438463-eventTemplates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class EventTemplates1770757438463 implements MigrationInterface {
name = 'EventTemplates1770757438463'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "event_starter_templates" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "basePath" character varying NOT NULL, "myCoreBotDockerImage" character varying NOT NULL, "eventId" uuid, CONSTRAINT "PK_71a1105f3641c6830dc2ca59178" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "teams" ADD "starterTemplateId" uuid`);
await queryRunner.query(`ALTER TABLE "event_starter_templates" ADD CONSTRAINT "FK_6c691c5b56253870cacd70e0413" FOREIGN KEY ("eventId") REFERENCES "events"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "teams" ADD CONSTRAINT "FK_1d9ef4aae433eba4679f8c7671d" FOREIGN KEY ("starterTemplateId") REFERENCES "event_starter_templates"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "teams" DROP CONSTRAINT "FK_1d9ef4aae433eba4679f8c7671d"`);
await queryRunner.query(`ALTER TABLE "event_starter_templates" DROP CONSTRAINT "FK_6c691c5b56253870cacd70e0413"`);
await queryRunner.query(`ALTER TABLE "teams" DROP COLUMN "starterTemplateId"`);
await queryRunner.query(`DROP TABLE "event_starter_templates"`);
}

}
15 changes: 15 additions & 0 deletions api/src/event/dtos/createEventStarterTemplateDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsString, IsNotEmpty } from "class-validator";

export class CreateEventStarterTemplateDto {
@IsString()
@IsNotEmpty()
name: string;

@IsString()
@IsNotEmpty()
basePath: string;

@IsString()
@IsNotEmpty()
myCoreBotDockerImage: string;
}
15 changes: 15 additions & 0 deletions api/src/event/dtos/updateEventStarterTemplateDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsString, IsNotEmpty, IsOptional } from "class-validator";

export class UpdateEventStarterTemplateDto {
@IsString()
@IsOptional()
name?: string;

@IsString()
@IsOptional()
basePath?: string;

@IsString()
@IsOptional()
myCoreBotDockerImage?: string;
}
24 changes: 24 additions & 0 deletions api/src/event/entities/event-starter-template.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { EventEntity } from "./event.entity";
import { Exclude } from "class-transformer";

@Entity("event_starter_templates")
export class EventStarterTemplateEntity {
@PrimaryGeneratedColumn("uuid")
id: string;

@Column()
name: string;

@Column()
basePath: string;

@Column()
myCoreBotDockerImage: string;

@Exclude()
@ManyToOne(() => EventEntity, (event) => event.starterTemplates, {
onDelete: "CASCADE",
})
event: EventEntity;
}
6 changes: 6 additions & 0 deletions api/src/event/entities/event.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "../../user/entities/user.entity";
import { TeamEntity } from "../../team/entities/team.entity";
import { Exclude } from "class-transformer";
import { EventStarterTemplateEntity } from "./event-starter-template.entity";

@Entity("events")
export class EventEntity {
Expand Down Expand Up @@ -115,4 +116,9 @@ export class EventEntity {
},
)
permissions: UserEventPermissionEntity[];

@OneToMany(() => EventStarterTemplateEntity, (template) => template.event, {
cascade: true,
})
starterTemplates: EventStarterTemplateEntity[];
}
63 changes: 62 additions & 1 deletion api/src/event/event.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { UserService } from "../user/user.service";
import { CreateEventDto } from "./dtos/createEventDto";
import { SetLockTeamsDateDto } from "./dtos/setLockTeamsDateDto";
import { UpdateEventSettingsDto } from "./dtos/updateEventSettingsDto";
import { CreateEventStarterTemplateDto } from "./dtos/createEventStarterTemplateDto";
import { UpdateEventStarterTemplateDto } from "./dtos/updateEventStarterTemplateDto";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
import { UserId } from "../guards/UserGuard";

Expand All @@ -26,7 +28,7 @@ export class EventController {
private readonly eventService: EventService,
private readonly teamService: TeamService,
private readonly userService: UserService,
) { }
) {}

@UseGuards(JwtAuthGuard)
@Get("my")
Expand All @@ -49,6 +51,14 @@ export class EventController {
return await this.eventService.getEventVersion(id);
}

@Get(":id/templates/:templateId/version")
async getStarterTemplateVersion(
@Param("id", new ParseUUIDPipe()) id: string,
@Param("templateId", new ParseUUIDPipe()) templateId: string,
) {
return await this.eventService.getTemplateVersion(id, templateId);
}

@Get(":id/game-config")
async getEventGameConfig(@Param("id", new ParseUUIDPipe()) id: string) {
return await this.eventService.getEventGameConfig(id);
Expand Down Expand Up @@ -258,4 +268,55 @@ export class EventController {
}
return this.eventService.removeEventAdmin(eventId, adminIdToRemove);
}

@UseGuards(JwtAuthGuard)
@Get(":id/templates")
async getStarterTemplates(@Param("id", new ParseUUIDPipe()) eventId: string) {
return this.eventService.getStarterTemplates(eventId);
}

@UseGuards(JwtAuthGuard)
@Post(":id/templates")
async createStarterTemplate(
@Param("id", new ParseUUIDPipe()) eventId: string,
@UserId() userId: string,
@Body() body: CreateEventStarterTemplateDto,
) {
if (!(await this.eventService.isEventAdmin(eventId, userId))) {
throw new UnauthorizedException("You are not an admin of this event");
}
return this.eventService.createStarterTemplate(
eventId,
body.name,
body.basePath,
body.myCoreBotDockerImage,
);
}

@UseGuards(JwtAuthGuard)
@Put(":id/templates/:templateId")
async updateStarterTemplate(
@Param("id", new ParseUUIDPipe()) eventId: string,
@Param("templateId", new ParseUUIDPipe()) templateId: string,
@UserId() userId: string,
@Body() body: UpdateEventStarterTemplateDto,
) {
if (!(await this.eventService.isEventAdmin(eventId, userId))) {
throw new UnauthorizedException("You are not an admin of this event");
}
return this.eventService.updateStarterTemplate(eventId, templateId, body);
}

@UseGuards(JwtAuthGuard)
@Delete(":id/templates/:templateId")
async deleteStarterTemplate(
@Param("id", new ParseUUIDPipe()) eventId: string,
@Param("templateId", new ParseUUIDPipe()) templateId: string,
@UserId() userId: string,
) {
if (!(await this.eventService.isEventAdmin(eventId, userId))) {
throw new UnauthorizedException("You are not an admin of this event");
}
return this.eventService.deleteStarterTemplate(eventId, templateId);
}
}
10 changes: 8 additions & 2 deletions api/src/event/event.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ import { UserModule } from "../user/user.module";
import { UserEventPermissionEntity } from "../user/entities/user.entity";
import { CheckController } from "./check.controller";

import { EventStarterTemplateEntity } from "./entities/event-starter-template.entity";

@Module({
imports: [
TypeOrmModule.forFeature([EventEntity, UserEventPermissionEntity]),
TypeOrmModule.forFeature([
EventEntity,
UserEventPermissionEntity,
EventStarterTemplateEntity,
]),
UserModule,
forwardRef(() => TeamModule),
],
controllers: [EventController, CheckController],
providers: [EventService],
exports: [EventService],
})
export class EventModule { }
export class EventModule {}
111 changes: 111 additions & 0 deletions api/src/event/event.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BadRequestException,
ConflictException,
forwardRef,
Inject,
Injectable,
Expand All @@ -8,6 +9,7 @@ import {
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { EventEntity } from "./entities/event.entity";
import { EventStarterTemplateEntity } from "./entities/event-starter-template.entity";
import {
DataSource,
IsNull,
Expand Down Expand Up @@ -35,6 +37,8 @@ export class EventService {
private readonly eventRepository: Repository<EventEntity>,
@InjectRepository(UserEventPermissionEntity)
private readonly permissionRepository: Repository<UserEventPermissionEntity>,
@InjectRepository(EventStarterTemplateEntity)
private readonly templateRepository: Repository<EventStarterTemplateEntity>,
private readonly configService: ConfigService,
@Inject(forwardRef(() => TeamService))
private readonly teamService: TeamService,
Expand Down Expand Up @@ -176,6 +180,48 @@ export class EventService {
};
}

async getStarterTemplateVersions(id: string) {
const templates = await this.templateRepository.find({
where: { event: { id } },
select: {
name: true,
myCoreBotDockerImage: true,
},
});

return templates.map((t) => ({
name: t.name,
version: t.myCoreBotDockerImage,
}));
}

async getTemplateVersion(
eventId: string,
templateId: string,
): Promise<EventVersionDto> {
const event = await this.eventRepository.findOneOrFail({
where: { id: eventId },
select: {
gameServerDockerImage: true,
visualizerDockerImage: true,
},
});

const template = await this.templateRepository.findOneOrFail({
where: { id: templateId, event: { id: eventId } },
select: {
id: true,
myCoreBotDockerImage: true,
},
});

return {
gameServerVersion: event.gameServerDockerImage,
myCoreBotVersion: template.myCoreBotDockerImage,
visualizerVersion: event.visualizerDockerImage,
};
}

async getEventGameConfig(id: string): Promise<string | null> {
const event = await this.eventRepository.findOneOrFail({
where: { id },
Expand Down Expand Up @@ -261,6 +307,71 @@ export class EventService {
});
}

async getStarterTemplates(eventId: string) {
return this.templateRepository.find({
where: { event: { id: eventId } },
});
}

isStarterTemplateInEvent(
templateId: string,
eventId: string,
): Promise<boolean> {
return this.templateRepository.existsBy({
id: templateId,
event: { id: eventId },
});
}

async createStarterTemplate(
eventId: string,
name: string,
basePath: string,
myCoreBotDockerImage: string,
) {
const event = await this.getEventById(eventId);
const template = this.templateRepository.create({
name,
basePath,
myCoreBotDockerImage,
event,
});
return this.templateRepository.save(template);
}

async updateStarterTemplate(
eventId: string,
templateId: string,
data: { name?: string; basePath?: string; myCoreBotDockerImage?: string },
) {
const template = await this.templateRepository.findOneOrFail({
where: { id: templateId, event: { id: eventId } },
});

if (data.name) template.name = data.name;
if (data.basePath) template.basePath = data.basePath;
if (data.myCoreBotDockerImage)
template.myCoreBotDockerImage = data.myCoreBotDockerImage;

return this.templateRepository.save(template);
}

async deleteStarterTemplate(eventId: string, templateId: string) {
try {
await this.templateRepository.delete({
id: templateId,
event: { id: eventId },
});
} catch (e: any) {
if (e.code === "23503") {
throw new ConflictException(
"Cannot delete template because it is still in use by one or more teams.",
);
}
throw e;
}
}

increaseEventRound(eventId: string): Promise<UpdateResult> {
return this.eventRepository.increment({ id: eventId }, "currentRound", 1);
}
Expand Down
2 changes: 2 additions & 0 deletions api/src/github-api/github-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class GithubApiService {
basePath: string,
gameConfig: string,
serverConfig: string,
starterTemplateId?: string,
) {
this.githubClient.emit("create_team_repository", {
name,
Expand All @@ -134,6 +135,7 @@ export class GithubApiService {
basePath,
gameConfig,
serverConfig,
starterTemplateId,
apiBaseUrl: this.configService.getOrThrow<string>("API_BASE_URL"),
});
}
Expand Down
9 changes: 7 additions & 2 deletions api/src/match/match.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export class MatchService {
relations: {
teams: {
event: true,
starterTemplate: true,
},
winner: true,
},
Expand Down Expand Up @@ -371,13 +372,17 @@ export class MatchService {
bots: [
{
id: match.teams[0].id,
image: event.myCoreBotDockerImage,
image:
match.teams[0].starterTemplate?.myCoreBotDockerImage ??
event.myCoreBotDockerImage,
repoURL: repoPrefix + match.teams[0].repo,
name: match.teams[0].name,
},
{
id: match.teams[1].id,
image: event.myCoreBotDockerImage,
image:
match.teams[1].starterTemplate?.myCoreBotDockerImage ??
event.myCoreBotDockerImage,
repoURL: repoPrefix + match.teams[1].repo,
name: match.teams[1].name,
},
Expand Down
Loading