diff --git a/src/lib/server/models/build.ts b/src/lib/server/models/build.ts index 8b94b57..e25c4bb 100644 --- a/src/lib/server/models/build.ts +++ b/src/lib/server/models/build.ts @@ -19,11 +19,33 @@ export namespace Build { Aborted = 'ABORTED' } - export enum Channel { - Unpublished = 'unpublished', - Alpha = 'alpha', - Beta = 'beta', - Production = 'production' + export const Channels = { + Unpublished: 'unpublished', + Alpha: 'alpha', + Beta: 'beta', + Production: 'production' + } as const; + export type Channel = (typeof Channels)[keyof typeof Channels]; + + const transitions = { + [Channels.Unpublished]: [Channels.Alpha, Channels.Beta, Channels.Production], + [Channels.Alpha]: [Channels.Alpha, Channels.Beta, Channels.Production], + [Channels.Beta]: [Channels.Beta, Channels.Production], + [Channels.Production]: [Channels.Production] + } as const as Readonly>; + + type BuildForChannel = Prisma.buildGetPayload<{ + select: { channel: true }; + }>; + + export function verifyChannel(target: Channel, build: BuildForChannel): boolean { + return ( + // if unpublished OK + !build.channel || + build.channel === Channels.Unpublished || + // check if transition valid + transitions[build.channel as Channel]?.includes(target) + ); } export enum Artifact { diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index eae6d10..7b4a4c6 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -66,7 +66,10 @@ export const PUT: RequestHandler = async ({ request, params }) => { const parsed = v.safeParse(releaseSchema, await request.json()); if (!parsed.success) return ErrorResponse(400, JSON.stringify(v.flatten(parsed.issues))); const runningRelease = await prisma.release.findFirst({ - where: { build_id: Number(params.buildId), status: { in: ['accepted', 'active'] } } + where: { + build_id: Number(params.buildId), + status: { in: [Release.Status.Initialized, Release.Status.Accepted, Release.Status.Active] } + } }); if (runningRelease) { return ErrorResponse(500, 'Release already in progress'); @@ -77,7 +80,7 @@ export const PUT: RequestHandler = async ({ request, params }) => { id: true, build: { where: { id: Number(params.buildId) }, - select: { id: true, version_code: true } + select: { id: true, channel: true, status: true, result: true } } } }); @@ -85,7 +88,15 @@ export const PUT: RequestHandler = async ({ request, params }) => { const build = job.build.at(0); if (!build) return ErrorResponse(404, 'Build not found'); - // TODO verify channel + if (build.status !== Build.Status.Completed) + return ErrorResponse(409, `Build is incomplete. Current Status: ${build.status}`); + + if (build.result !== Build.Result.Success) + return ErrorResponse(403, `Build was unsuccessful. Result: ${build.result}`); + + if (!Build.verifyChannel(parsed.output.channel as Build.Channel, build)) + return ErrorResponse(400, `Cannot promote from ${build.channel} to ${parsed.output.channel}`); + const release = await prisma.release.create({ data: { ...parsed.output,