From 16a34bfd0224596a4b3d5e296bd45716c80953c9 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 12 Jan 2025 21:18:03 -0800 Subject: [PATCH] fix: Update release endpoint --- .../api/v1/releases/[releaseId]/openapi.ts | 87 +++++++++++++ .../app/api/v1/releases/[releaseId]/route.ts | 69 ++++++++++ openapi.v1.json | 119 ++++++++++++++++++ packages/db/src/schema/release.ts | 1 + 4 files changed, 276 insertions(+) create mode 100644 apps/webservice/src/app/api/v1/releases/[releaseId]/openapi.ts create mode 100644 apps/webservice/src/app/api/v1/releases/[releaseId]/route.ts diff --git a/apps/webservice/src/app/api/v1/releases/[releaseId]/openapi.ts b/apps/webservice/src/app/api/v1/releases/[releaseId]/openapi.ts new file mode 100644 index 00000000..041e819d --- /dev/null +++ b/apps/webservice/src/app/api/v1/releases/[releaseId]/openapi.ts @@ -0,0 +1,87 @@ +import type { Swagger } from "atlassian-openapi"; + +import { ReleaseStatus } from "@ctrlplane/validators/releases"; + +export const openapi: Swagger.SwaggerV3 = { + openapi: "3.0.0", + info: { title: "Ctrlplane API", version: "1.0.0" }, + paths: { + "/v1/releases/{releaseId}": { + patch: { + summary: "Updates a release", + operationId: "updateRelease", + parameters: [ + { + name: "releaseId", + in: "path", + required: true, + schema: { type: "string" }, + description: "The release ID", + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + properties: { + version: { type: "string" }, + deploymentId: { type: "string" }, + createdAt: { type: "string", format: "date-time" }, + name: { type: "string" }, + config: { type: "object", additionalProperties: true }, + status: { + type: "string", + enum: Object.values(ReleaseStatus), + }, + message: { type: "string" }, + metadata: { + type: "object", + additionalProperties: { type: "string" }, + }, + }, + }, + }, + }, + }, + responses: { + "200": { + description: "OK", + content: { + "application/json": { + schema: { + type: "object", + properties: { + id: { type: "string" }, + version: { type: "string" }, + deploymentId: { type: "string" }, + createdAt: { type: "string", format: "date-time" }, + name: { type: "string" }, + config: { type: "object", additionalProperties: true }, + status: { type: "string" }, + message: { type: "string" }, + metadata: { + type: "object", + additionalProperties: { type: "string" }, + }, + }, + required: [ + "id", + "version", + "deploymentId", + "createdAt", + "name", + "config", + "status", + "metadata", + ], + }, + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/apps/webservice/src/app/api/v1/releases/[releaseId]/route.ts b/apps/webservice/src/app/api/v1/releases/[releaseId]/route.ts new file mode 100644 index 00000000..b74857d4 --- /dev/null +++ b/apps/webservice/src/app/api/v1/releases/[releaseId]/route.ts @@ -0,0 +1,69 @@ +import { NextResponse } from "next/server"; +import httpStatus from "http-status"; +import { z } from "zod"; + +import { buildConflictUpdateColumns, eq, takeFirst } from "@ctrlplane/db"; +import * as SCHEMA from "@ctrlplane/db/schema"; +import { logger } from "@ctrlplane/logger"; +import { Permission } from "@ctrlplane/validators/auth"; + +import { authn, authz } from "../../auth"; +import { parseBody } from "../../body-parser"; +import { request } from "../../middleware"; + +const patchSchema = SCHEMA.updateRelease.and( + z.object({ metadata: z.record(z.string()).optional() }), +); + +export const PATCH = request() + .use(authn) + .use(parseBody(patchSchema)) + .use( + authz(({ can, extra: { params } }) => + can + .perform(Permission.ReleaseUpdate) + .on({ type: "release", id: params.releaseId }), + ), + ) + .handle< + { body: z.infer }, + { params: { releaseId: string } } + >(async (ctx, { params }) => { + const { releaseId } = params; + const { body } = ctx; + + try { + const release = await ctx.db + .update(SCHEMA.release) + .set(body) + .where(eq(SCHEMA.release.id, releaseId)) + .returning() + .then(takeFirst); + + if (Object.keys(body.metadata ?? {}).length > 0) + await ctx.db + .insert(SCHEMA.releaseMetadata) + .values( + Object.entries(body.metadata ?? {}).map(([key, value]) => ({ + releaseId, + key, + value, + })), + ) + .onConflictDoUpdate({ + target: [ + SCHEMA.releaseMetadata.key, + SCHEMA.releaseMetadata.releaseId, + ], + set: buildConflictUpdateColumns(SCHEMA.releaseMetadata, ["value"]), + }); + + return NextResponse.json(release); + } catch (error) { + logger.error(error); + return NextResponse.json( + { error: "Failed to update release" }, + { status: httpStatus.INTERNAL_SERVER_ERROR }, + ); + } + }); diff --git a/openapi.v1.json b/openapi.v1.json index eb714d90..b04e828f 100644 --- a/openapi.v1.json +++ b/openapi.v1.json @@ -1312,6 +1312,125 @@ ] } }, + "/v1/releases/{releaseId}": { + "patch": { + "summary": "Updates a release", + "operationId": "updateRelease", + "parameters": [ + { + "name": "releaseId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The release ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "deploymentId": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": true + }, + "status": { + "type": "string", + "enum": [ + "ready", + "building", + "failed" + ] + }, + "message": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "deploymentId": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": true + }, + "status": { + "type": "string" + }, + "message": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "id", + "version", + "deploymentId", + "createdAt", + "name", + "config", + "status", + "metadata" + ] + } + } + } + } + } + } + }, "/v1/releases": { "post": { "summary": "Upserts a release", diff --git a/packages/db/src/schema/release.ts b/packages/db/src/schema/release.ts index 41b6acdd..b1156f58 100644 --- a/packages/db/src/schema/release.ts +++ b/packages/db/src/schema/release.ts @@ -154,6 +154,7 @@ export const createRelease = createInsertSchema(release, { }); export const updateRelease = createRelease.partial(); +export type UpdateRelease = z.infer; export const releaseMetadata = pgTable( "release_metadata", {