feat: release notes

Each release can carry notes in Markdown, written before or after it
ships. A draft is generated from its finished tickets grouped by type, to
edit and copy from the releases page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:19:23 +02:00
parent 45a3dc1e16
commit 24b37b6628
40 changed files with 1251 additions and 31 deletions

View File

@@ -6,6 +6,7 @@ import {
Param,
Patch,
Post,
Put,
Query,
} from '@nestjs/common';
import {
@@ -34,6 +35,7 @@ import {
CreateReleaseDto,
PageQueryDto,
PublishReleaseDto,
ReleaseNotesDto,
UpcomingReleasesQueryDto,
UpdateReleaseDto,
} from './dto/requests.dto';
@@ -41,6 +43,7 @@ import {
CommandResultDto,
IssuePageDto,
ReleaseDto,
ReleaseNotesDraftDto,
ReleaseBurnupDto,
ReleaseProgressDto,
} from './dto/responses.dto';
@@ -194,6 +197,38 @@ export class ReleasesController {
return this.releases.publish(me.id, releaseId, body.force);
}
@Put('releases/:releaseId/notes')
@ApiOperation({
summary: 'Write the release notes (Markdown)',
description: 'Also after the release ships; not once archived.',
})
@ApiOkResponse({ type: CommandResultDto })
@ApiCommandErrors()
writeNotes(
@CurrentUser() me: User,
@Param('releaseId') releaseId: string,
@Body() body: ReleaseNotesDto,
) {
return this.releases.writeNotes(me.id, releaseId, body.notes);
}
@Get('releases/:releaseId/notes/draft')
@ApiOptionalPireonAuth()
@ApiOperation({
summary: 'A Markdown draft of the notes from the finished tickets',
description: 'Grouped by type; nothing is saved.',
})
@ApiOkResponse({ type: ReleaseNotesDraftDto })
@ApiReadErrors()
async notesDraft(
@CurrentUser() me: User | undefined,
@Param('releaseId') releaseId: string,
) {
return {
markdown: await this.releases.notesDraft(viewerOf(me), releaseId),
};
}
@Post('releases/:releaseId/archive')
@HttpCode(200)
@ApiOperation({ summary: 'Archive a planned release (its tickets leave it)' })