feat: a public showcase of each project
A public endpoint sums up a public project for websites: epics with their dates and progress, the latest releases with the notes of shipped ones, the sprint under way and open tickets labelled good-first-issue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -234,6 +234,72 @@ export class BulkResultDto {
|
||||
@ApiProperty({ type: [BulkIssueResultDto] }) results!: BulkIssueResultDto[];
|
||||
}
|
||||
|
||||
class ShowcaseProjectDto {
|
||||
@ApiProperty({ format: 'uuid' }) id!: string;
|
||||
@ApiProperty({ example: 'WEB' }) key!: string;
|
||||
@ApiProperty() name!: string;
|
||||
@ApiProperty({ type: String, nullable: true }) description!: string | null;
|
||||
}
|
||||
|
||||
class ShowcaseSprintDto {
|
||||
@ApiProperty() name!: string;
|
||||
@ApiProperty({ type: String, nullable: true }) goal!: string | null;
|
||||
@ApiProperty({ format: 'date' }) startDate!: string;
|
||||
@ApiProperty({ format: 'date' }) endDate!: string;
|
||||
@ApiProperty() percent!: number;
|
||||
}
|
||||
|
||||
class ShowcaseEpicDto {
|
||||
@ApiProperty() key!: string;
|
||||
@ApiProperty() title!: string;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
startDate!: string | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
dueDate!: string | null;
|
||||
@ApiProperty() percent!: number;
|
||||
@ApiProperty() done!: boolean;
|
||||
@ApiProperty({
|
||||
example: '/p/WEB/issues/WEB-3',
|
||||
description: 'In the web app',
|
||||
})
|
||||
path!: string;
|
||||
}
|
||||
|
||||
class ShowcaseReleaseDto {
|
||||
@ApiProperty() name!: string;
|
||||
@ApiProperty({ enum: ReleaseStatus, enumName: 'ReleaseStatus' })
|
||||
status!: ReleaseStatus;
|
||||
@ApiProperty({ type: String, nullable: true }) targetDate!: string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) releasedAt!: string | null;
|
||||
@ApiProperty() percent!: number;
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
nullable: true,
|
||||
description: 'Shipped ones only',
|
||||
})
|
||||
notes!: string | null;
|
||||
}
|
||||
|
||||
class ShowcaseIssueDto {
|
||||
@ApiProperty() key!: string;
|
||||
@ApiProperty() title!: string;
|
||||
@ApiProperty({ description: 'A ticket type id' }) type!: string;
|
||||
@ApiProperty({ example: '/p/WEB/issues/WEB-9' }) path!: string;
|
||||
}
|
||||
|
||||
export class ShowcaseDto {
|
||||
@ApiProperty({ type: ShowcaseProjectDto }) project!: ShowcaseProjectDto;
|
||||
@ApiProperty({ type: ShowcaseSprintDto, nullable: true })
|
||||
activeSprint!: ShowcaseSprintDto | null;
|
||||
@ApiProperty({ type: [ShowcaseEpicDto] }) roadmap!: ShowcaseEpicDto[];
|
||||
@ApiProperty({ type: [ShowcaseReleaseDto] }) releases!: ShowcaseReleaseDto[];
|
||||
@ApiProperty({
|
||||
type: [ShowcaseIssueDto],
|
||||
description: 'Open tickets labelled good-first-issue',
|
||||
})
|
||||
goodFirst!: ShowcaseIssueDto[];
|
||||
}
|
||||
|
||||
export class GitDeliveryResultDto {
|
||||
@ApiProperty({ description: 'Tickets that got something new' })
|
||||
recorded!: number;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { Controller, Get, Header, Param } from '@nestjs/common';
|
||||
import {
|
||||
ApiNotFoundResponse,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { Public } from '@task-stream/api/identity/presenters';
|
||||
import { ProjectsService } from '@task-stream/api/tracker/application';
|
||||
import { ProjectDto } from './dto/responses.dto';
|
||||
import { ProjectDto, ShowcaseDto } from './dto/responses.dto';
|
||||
import { toProjectDto } from './refs';
|
||||
|
||||
/** What anyone can read without signing in. */
|
||||
@@ -22,4 +28,23 @@ export class ExploreController {
|
||||
async publicProjects() {
|
||||
return (await this.projects.findPublic()).map(toProjectDto);
|
||||
}
|
||||
|
||||
@Get('projects/:project/showcase')
|
||||
@Public()
|
||||
@Header('Cache-Control', 'public, max-age=300')
|
||||
@ApiParam({
|
||||
name: 'project',
|
||||
description: 'Project id or key',
|
||||
example: 'WEB',
|
||||
})
|
||||
@ApiOperation({
|
||||
summary: "A public project's showcase (no token needed)",
|
||||
description:
|
||||
'For public websites: epics with their dates and progress, the latest releases (with the notes of shipped ones), the sprint under way and open tickets labelled good-first-issue. Paths point into the web app.',
|
||||
})
|
||||
@ApiOkResponse({ type: ShowcaseDto })
|
||||
@ApiNotFoundResponse({ description: 'Not a public project' })
|
||||
showcase(@Param('project') project: string) {
|
||||
return this.projects.showcase(project);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user