Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 72 additions & 0 deletions .agents/skills/conventional-commit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
name: conventional-commit
description: 'Prompt and workflow for generating conventional commit messages using a structured XML format. Guides users to create standardized, descriptive commit messages in line with the Conventional Commits specification, including instructions, examples, and validation.'
---

### Instructions

```xml
<description>This file contains a prompt template for generating conventional commit messages. It provides instructions, examples, and formatting guidelines to help users write standardized, descriptive commit messages in accordance with the Conventional Commits specification.</description>
```

### Workflow

**Follow these steps:**

1. Run `git status` to review changed files.
2. Run `git diff` or `git diff --cached` to inspect changes.
3. Stage your changes with `git add <file>`.
4. Construct your commit message using the following XML structure.
5. After generating your commit message, Copilot will automatically run the following command in your integrated terminal (no confirmation needed):

```bash
git commit -m "type(scope): description"
```

6. Just execute this prompt and Copilot will handle the commit for you in the terminal.

### Commit Message Structure

```xml
<commit-message>
<type>feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert</type>
<scope>()</scope>
<description>A short, imperative summary of the change</description>
<body>(optional: more detailed explanation)</body>
<footer>(optional: e.g. BREAKING CHANGE: details, or issue references)</footer>
</commit-message>
```

### Examples

```xml
<examples>
<example>feat(parser): add ability to parse arrays</example>
<example>fix(ui): correct button alignment</example>
<example>docs: update README with usage instructions</example>
<example>refactor: improve performance of data processing</example>
<example>chore: update dependencies</example>
<example>feat!: send email on registration (BREAKING CHANGE: email service required)</example>
</examples>
```

### Validation

```xml
<validation>
<type>Must be one of the allowed types. See <reference>https://www.conventionalcommits.org/en/v1.0.0/#specification</reference></type>
<scope>Optional, but recommended for clarity.</scope>
<description>Required. Use the imperative mood (e.g., "add", not "added").</description>
<body>Optional. Use for additional context.</body>
<footer>Use for breaking changes or issue references.</footer>
</validation>
```

### Final Step

```xml
<final-step>
<cmd>git commit -m "type(scope): description"</cmd>
<note>Replace with your constructed message. Include body and footer if needed.</note>
</final-step>
```
1 change: 1 addition & 0 deletions .claude/skills/conventional-commit
10 changes: 10 additions & 0 deletions skills-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"skills": {
"conventional-commit": {
"source": "github/awesome-copilot",
"sourceType": "github",
"computedHash": "f29c9486cede6c7b2df0cfb0a2e4aa67446552b991bcf17d1b309e903171f03d"
}
}
}
1 change: 1 addition & 0 deletions src/modules/event/dto/match/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './create-match.dto';
export * from './update-match.dto';
export * from './update-score.dto';
export * from './upsert-match-score.dto';
29 changes: 29 additions & 0 deletions src/modules/event/dto/match/upsert-match-score.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNumber, IsNotEmpty, IsOptional, IsObject, Min } from 'class-validator';

export class UpsertMatchScoreDto {
@ApiProperty({ description: 'Team 1 points in this set', example: 21 })
@IsNumber()
@IsNotEmpty()
@Min(0)
team1Points: number;

@ApiProperty({ description: 'Team 2 points in this set', example: 19 })
@IsNumber()
@IsNotEmpty()
@Min(0)
team2Points: number;

@ApiPropertyOptional({ description: 'Winner team for this set (1 or 2)', example: 1 })
@IsNumber()
@IsOptional()
winnerTeam?: number;

@ApiPropertyOptional({
description: 'Point-by-point details',
example: { points: [{ team: 1, score: [1, 0] }] },
})
@IsObject()
@IsOptional()
details?: Record<string, any>;
}
2 changes: 2 additions & 0 deletions src/modules/event/event.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Stage } from './entities/stage.entity';
import { EventController } from './event.controller';
import { PublicEventController } from './public-event.controller';
import { MatchController } from './match.controller';
import { MatchScoreController } from './match-score.controller';
import { ParticipantController } from './participant.controller';
import { StageController } from './stage.controller';

Expand Down Expand Up @@ -51,6 +52,7 @@ import { FlexStrategy } from './strategies/flex.strategy';
EventController,
PublicEventController,
MatchController,
MatchScoreController,
ParticipantController,
StageController,
],
Expand Down
46 changes: 46 additions & 0 deletions src/modules/event/match-score.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Body, Controller, Get, Param, ParseIntPipe, ParseUUIDPipe, Put } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { MatchService } from './match.service';
import { UpsertMatchScoreDto } from './dto/match';
import { MatchScore } from './entities';

@ApiTags('match-scores')
@Controller('matches/:matchId/scores')
export class MatchScoreController {
constructor(private readonly matchService: MatchService) {}

@Get()
@ApiOperation({ summary: 'Get all scores of a match' })
@ApiParam({ name: 'matchId', description: 'Match ID' })
@ApiResponse({
status: 200,
description: 'Returns all scores for the match ordered by set number',
type: [MatchScore],
})
async findAll(
@Param('matchId', ParseUUIDPipe) matchId: string,
): Promise<MatchScore[]> {
return await this.matchService.getScores(matchId);
}

@Put(':setNumber')
@ApiOperation({
summary: 'Create or update a match score',
description:
'Creates or updates the score for a specific set. Match must be IN_PROGRESS. ' +
'Set N can only be created after set N-1 exists.',
})
@ApiParam({ name: 'matchId', description: 'Match ID' })
@ApiParam({ name: 'setNumber', description: 'Set number (1-based)', example: 1 })
@ApiBody({ type: UpsertMatchScoreDto })
@ApiResponse({ status: 200, description: 'Score created or updated', type: MatchScore })
@ApiResponse({ status: 400, description: 'Match not IN_PROGRESS or previous set missing' })
@ApiResponse({ status: 404, description: 'Match not found' })
async upsert(
@Param('matchId', ParseUUIDPipe) matchId: string,
@Param('setNumber', ParseIntPipe) setNumber: number,
@Body() dto: UpsertMatchScoreDto,
): Promise<MatchScore> {
return await this.matchService.upsertScore(matchId, setNumber, dto);
}
}
36 changes: 35 additions & 1 deletion src/modules/event/match.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Match, MatchScore, EventParticipant } from './entities';
import { CreateMatchDto, UpdateMatchDto, UpdateScoreDto } from './dto/match';
import { CreateMatchDto, UpdateMatchDto, UpdateScoreDto, UpsertMatchScoreDto } from './dto/match';
import { MatchStatus } from './entities/match.entity';

@Injectable()
Expand Down Expand Up @@ -144,6 +144,40 @@ export class MatchService {
await this.matchRepository.save(match);
}

async upsertScore(matchId: string, setNumber: number, dto: UpsertMatchScoreDto): Promise<MatchScore> {
const match = await this.matchRepository.findOne({ where: { id: matchId } });
if (!match) {
throw new NotFoundException(`Match ${matchId} not found`);
}
if (match.status !== MatchStatus.IN_PROGRESS) {
throw new BadRequestException('Match must be IN_PROGRESS to modify scores');
}

const existing = await this.scoreRepository.findOne({ where: { matchId, setNumber } });

if (!existing && setNumber > 1) {
const prevSet = await this.scoreRepository.findOne({
where: { matchId, setNumber: setNumber - 1 },
});
if (!prevSet) {
throw new BadRequestException(
`Set ${setNumber - 1} must exist before creating set ${setNumber}`,
);
}
}

let score = existing;
if (score) {
Object.assign(score, dto);
} else {
score = this.scoreRepository.create({ matchId, setNumber, ...dto });
}

const savedScore = await this.scoreRepository.save(score);
await this.updateMatchAggregateScores(matchId);
return savedScore;
}

async getScores(matchId: string): Promise<MatchScore[]> {
return await this.scoreRepository.find({
where: { matchId },
Expand Down
Loading