diff --git a/apps/server/src/routes/management.ts b/apps/server/src/routes/management.ts index 19128ff..d5bb731 100644 --- a/apps/server/src/routes/management.ts +++ b/apps/server/src/routes/management.ts @@ -1771,20 +1771,13 @@ router.openapi(routes.deployments.history, async (c) => { }); } - // getPackageHistory는 uploadTime 오름차순 전체 배열을 반환한다(OTA·롤백·중복검사 등이 - // 공유하는 계약이므로 그대로 둔다). 페이지네이션은 이 핸들러에서만 최신순으로 뒤집어 - // 슬라이스한다. 파라미터가 없으면 page=1/pageSize=20 → 최신 페이지가 기본. - const fullHistory = await storage.getPackageHistory( - accountId, - app.id, + // DB 레벨 LIMIT/OFFSET 페이지네이션(최신순). 파라미터가 없으면 page=1/pageSize=20이 + // 기본이라 쿼리 없이 호출하는 CLI(code-push-standalone)도 기존과 동일하게 최신 20개를 받는다. + const { history, totalCount } = await storage.getPackageHistoryPage( deployment.id, + page, + pageSize, ); - const totalCount = fullHistory.length; - const start = (page - 1) * pageSize; - const history = fullHistory - .slice() - .reverse() - .slice(start, start + pageSize); return c.json({ history, totalCount, page, pageSize }); }); diff --git a/apps/server/src/storage/d1.ts b/apps/server/src/storage/d1.ts index a6a6000..c3bc950 100644 --- a/apps/server/src/storage/d1.ts +++ b/apps/server/src/storage/d1.ts @@ -1,4 +1,4 @@ -import { and, eq, isNull } from "drizzle-orm"; +import { and, count, desc, eq, isNull, sql } from "drizzle-orm"; import { type DrizzleD1Database, drizzle } from "drizzle-orm/d1"; import type { Context } from "hono"; import * as schema from "../db/schema"; @@ -915,6 +915,46 @@ export class D1StorageProvider implements StorageProvider { return result; } + // 관리용 history 엔드포인트 전용. OTA(updateCheck)·롤백·중복검사가 쓰는 + // getPackageHistory(전체·오름차순·diff·presigned·캐시)와 별개 경로다. + // 웹·CLI가 blobUrl/diffPackageMap을 쓰지 않으므로 서명·diff 조회를 생략하고, + // 캐시하지 않는다(캐시하면 commitPackage/updatePackage 등 4곳 무효화에 전부 걸어야 함). + async getPackageHistoryPage( + deploymentId: string, + page: number, + pageSize: number, + ): Promise<{ history: Package[]; totalCount: number }> { + const where = and( + eq(schema.packages.deploymentId, deploymentId), + isNull(schema.packages.deletedAt), + ); + + const [countResult] = await this.db + .select({ totalCount: count() }) + .from(schema.packages) + .where(where); + + // uploadTime 동률 대비 보조 정렬키. label("v12")은 문자열 비교 시 "v10" < "v2"라 + // 숫자로 캐스팅해 배포 순서와 일치시킨다. + const packages = await this.db.query.packages.findMany({ + where, + orderBy: [ + desc(schema.packages.uploadTime), + sql`CAST(substr(${schema.packages.label}, 2) AS INTEGER) DESC`, + ], + limit: pageSize, + offset: (page - 1) * pageSize, + }); + + const history = packages.map((p) => ({ + ...this.mapPackageFromDB(p), + blobUrl: "", + manifestBlobUrl: "", + })); + + return { history, totalCount: countResult?.totalCount ?? 0 }; + } + async getPackageHistoryFromDeploymentKey( deploymentKey: string, ): Promise { diff --git a/apps/server/src/storage/storage.ts b/apps/server/src/storage/storage.ts index 3391fcf..1f26e63 100644 --- a/apps/server/src/storage/storage.ts +++ b/apps/server/src/storage/storage.ts @@ -97,6 +97,11 @@ export interface StorageProvider { appId: string, deploymentId: string, ): Promise; + getPackageHistoryPage( + deploymentId: string, + page: number, + pageSize: number, + ): Promise<{ history: Package[]; totalCount: number }>; getPackageHistoryFromDeploymentKey(deploymentKey: string): Promise; updatePackageHistory( accountId: string,