From 0951283c8b9701ec71e2a0959d236842f4e1d4d5 Mon Sep 17 00:00:00 2001 From: chyoo Date: Tue, 7 Jul 2026 09:20:18 +0900 Subject: [PATCH] support paged launcher history queries --- src/gitea/dto.ts | 12 +++++++++ src/gitea/gitea.controller.ts | 9 +++++++ src/gitea/gitea.service.ts | 38 +++++++++++++++++++++++++++++ src/launcher/dto.ts | 18 ++++++++++++++ src/launcher/launcher.controller.ts | 8 +++--- src/launcher/launcher.service.ts | 19 +++++++++++++-- 6 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/gitea/dto.ts b/src/gitea/dto.ts index bcc1d87..1ecd982 100644 --- a/src/gitea/dto.ts +++ b/src/gitea/dto.ts @@ -23,9 +23,21 @@ export class ListGiteaCommitsQuery { @IsString() sha?: string; + @IsOptional() + @IsString() + page?: string; + @IsOptional() @IsString() limit?: string; + + @IsOptional() + @IsString() + since?: string; + + @IsOptional() + @IsString() + until?: string; } export class ListGiteaTreeQuery { diff --git a/src/gitea/gitea.controller.ts b/src/gitea/gitea.controller.ts index 885055d..0db5720 100644 --- a/src/gitea/gitea.controller.ts +++ b/src/gitea/gitea.controller.ts @@ -51,6 +51,15 @@ export class GiteaController { return this.giteaService.listCommits(owner, repo, query); } + @Get('repos/:owner/:repo/commits/:sha/diff') + getCommitDiff( + @Param('owner') owner: string, + @Param('repo') repo: string, + @Param('sha') sha: string, + ) { + return this.giteaService.getCommitDiff(owner, repo, sha); + } + @Get('repos/:owner/:repo/releases') listReleases(@Param('owner') owner: string, @Param('repo') repo: string) { return this.giteaService.listReleases(owner, repo); diff --git a/src/gitea/gitea.service.ts b/src/gitea/gitea.service.ts index 3d76a34..94af1da 100644 --- a/src/gitea/gitea.service.ts +++ b/src/gitea/gitea.service.ts @@ -97,9 +97,16 @@ export class GiteaService { async listCommits(owner: string, repo: string, query: ListGiteaCommitsQuery) { const params = new URLSearchParams(); params.set('limit', query.limit ?? '20'); + params.set('page', query.page ?? '1'); if (query.sha) { params.set('sha', query.sha); } + if (query.since) { + params.set('since', query.since); + } + if (query.until) { + params.set('until', query.until); + } const commits = await this.request( `/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits?${params.toString()}`, @@ -108,6 +115,17 @@ export class GiteaService { return { commits }; } + async getCommitDiff(owner: string, repo: string, sha: string) { + const encodedOwner = encodeURIComponent(owner); + const encodedRepo = encodeURIComponent(repo); + const encodedSha = encodeURIComponent(sha); + const diff = await this.requestText( + `/${encodedOwner}/${encodedRepo}/commit/${encodedSha}.diff`, + ); + + return { diff }; + } + async listReleases(owner: string, repo: string) { const releases = await this.request( `/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases`, @@ -255,6 +273,26 @@ export class GiteaService { return (await response.json()) as T; } + private async requestText(path: string): Promise { + const response = await fetch(`${this.baseUrl}${path}`, { + headers: { + Accept: 'text/plain', + ...(this.token ? { Authorization: `token ${this.token}` } : {}), + }, + }); + + if (!response.ok) { + const message = await response.text(); + throw new BadGatewayException({ + message: 'Gitea API 요청이 실패했습니다.', + status: response.status, + detail: message, + }); + } + + return response.text(); + } + private assertToken() { if (!this.token) { throw new UnauthorizedException( diff --git a/src/launcher/dto.ts b/src/launcher/dto.ts index 99d7535..68f5b68 100644 --- a/src/launcher/dto.ts +++ b/src/launcher/dto.ts @@ -110,6 +110,24 @@ export class CreateLauncherBuildJobDto { downloadUrl?: string; } +export class ListLauncherBuildJobsQuery { + @IsOptional() + @IsString() + target?: string; + + @IsOptional() + @IsString() + page?: string; + + @IsOptional() + @IsString() + limit?: string; + + @IsOptional() + @IsString() + since?: string; +} + export class CreateLauncherAppLinkDto { @IsUUID() diff --git a/src/launcher/launcher.controller.ts b/src/launcher/launcher.controller.ts index d72bd7f..a4a2052 100644 --- a/src/launcher/launcher.controller.ts +++ b/src/launcher/launcher.controller.ts @@ -1,6 +1,6 @@ -import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; -import { CreateLauncherAppLinkDto, CreateLauncherBuildJobDto, CreateLauncherCommonSettingDto, CreateLauncherVersionDto, UpdateLauncherSourceDto, LinkLauncherVersionSourceDto, MoveLauncherAppLinkDto, UpdateLauncherAppLinkDto, UpdateLauncherBuildEnvDto, UpdateLauncherCommonSettingDto, UpdateLauncherUserAppPermissionsDto, DeployLauncherBackendDto } from './dto'; +import { CreateLauncherAppLinkDto, CreateLauncherBuildJobDto, CreateLauncherCommonSettingDto, CreateLauncherVersionDto, UpdateLauncherSourceDto, LinkLauncherVersionSourceDto, MoveLauncherAppLinkDto, UpdateLauncherAppLinkDto, UpdateLauncherBuildEnvDto, UpdateLauncherCommonSettingDto, UpdateLauncherUserAppPermissionsDto, DeployLauncherBackendDto, ListLauncherBuildJobsQuery } from './dto'; import { LauncherService } from './launcher.service'; @Controller('launcher') @@ -49,8 +49,8 @@ export class LauncherController { } @Get('builds') - listBuildJobs() { - return this.launcherService.listBuildJobs(); + listBuildJobs(@Query() query: ListLauncherBuildJobsQuery) { + return this.launcherService.listBuildJobs(query); } @Post('builds') diff --git a/src/launcher/launcher.service.ts b/src/launcher/launcher.service.ts index 6b3e4c1..a1c07b7 100644 --- a/src/launcher/launcher.service.ts +++ b/src/launcher/launcher.service.ts @@ -16,6 +16,7 @@ import { CreateLauncherCommonSettingDto, CreateLauncherVersionDto, DeployLauncherBackendDto, + ListLauncherBuildJobsQuery, UpdateLauncherSourceDto, LinkLauncherVersionSourceDto, UpdateLauncherAppLinkDto, @@ -426,10 +427,19 @@ export class LauncherService { }; } - listBuildJobs() { + listBuildJobs(query: ListLauncherBuildJobsQuery = {}) { + const page = toPositiveInt(query.page, 1); + const limit = Math.min(toPositiveInt(query.limit, 30), 100); + const since = query.since ? new Date(query.since) : null; + return this.prisma.launcherBuildJob.findMany({ + where: { + ...(query.target ? { target: query.target } : {}), + ...(since && Number.isFinite(since.getTime()) ? { createdAt: { gte: since } } : {}), + }, orderBy: { createdAt: 'desc' }, - take: 30, + skip: (page - 1) * limit, + take: limit, }); } @@ -1693,3 +1703,8 @@ echo "설치가 완료되었습니다." function safeMetadataValue(value: string) { return value.replace(/[^\x20-\x7E]/g, '').slice(0, 512); } + +function toPositiveInt(value: string | undefined, fallback: number) { + const parsed = Number(value); + return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; +}