Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 69 additions & 7 deletions src/app/api/v2/builds/[uuid]/services/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { NextRequest } from 'next/server';
import { createApiHandler } from 'server/lib/createApiHandler';
import { errorResponse, successResponse } from 'server/lib/response';
import OverrideService, {
ServiceOverrideNotEditableError,
ServiceOverrideNotFoundError,
type ServiceOverridePatchInput,
} from 'server/services/override';
Expand All @@ -40,12 +41,12 @@ function validateServiceOverride(value: unknown, index: number): ServiceOverride
return new Error(`serviceOverrides[${index}] must be an object`);
}

const serviceName = value.serviceName;
const name = value.name;
const hasActive = hasOwn(value, 'active');
const hasBranchOrExternalUrl = hasOwn(value, 'branchOrExternalUrl');

if (typeof serviceName !== 'string' || serviceName.length === 0) {
return new Error(`serviceOverrides[${index}].serviceName must be a non-empty string`);
if (typeof name !== 'string' || name.length === 0) {
return new Error(`serviceOverrides[${index}].name must be a non-empty string`);
}

if (!hasActive && !hasBranchOrExternalUrl) {
Expand All @@ -61,7 +62,7 @@ function validateServiceOverride(value: unknown, index: number): ServiceOverride
}

return {
serviceName,
name,
...(hasActive ? { active: value.active as boolean } : {}),
...(hasBranchOrExternalUrl ? { branchOrExternalUrl: value.branchOrExternalUrl as string } : {}),
};
Expand All @@ -70,6 +71,38 @@ function validateServiceOverride(value: unknown, index: number): ServiceOverride
/**
* @openapi
* /api/v2/builds/{uuid}/services:
* get:
* summary: Get service override edit state
* description: Returns the current PR-comment-aligned service state for editing service selection and branch or external URL overrides.
* tags:
* - Builds
* operationId: getBuildServiceOverrides
* parameters:
* - in: path
* name: uuid
* required: true
* schema:
* type: string
* description: The UUID of the build.
* responses:
* '200':
* description: Service override edit state.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/GetBuildServiceOverridesSuccessResponse'
* '404':
* description: Build not found.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ApiErrorResponse'
* '500':
* description: Server error.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ApiErrorResponse'
* patch:
* summary: Update service overrides in a batch
* description: Updates selected state and/or branch or external URL overrides for one or more services in a build.
Expand All @@ -95,7 +128,7 @@ function validateServiceOverride(value: unknown, index: number): ServiceOverride
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/BuildOverrideUpdateSuccessResponse'
* $ref: '#/components/schemas/UpdateBuildServiceOverridesSuccessResponse'
* '400':
* description: Invalid request body.
* content:
Expand All @@ -115,6 +148,21 @@ function validateServiceOverride(value: unknown, index: number): ServiceOverride
* schema:
* $ref: '#/components/schemas/ApiErrorResponse'
*/
const getHandler = async (req: NextRequest, { params }: { params: { uuid: string } }) => {
const override = new OverrideService();
const build = await override.db.models.Build.query()
.findOne({ uuid: params.uuid })
.withGraphFetched('[environment.[defaultServices, optionalServices], deploys.[service, deployable]]');

if (!build) {
return errorResponse(new Error(`Build with UUID ${params.uuid} not found`), { status: 404 }, req);
}

const serviceOverrides = await override.getServiceOverrideStates(build, build.deploys || []);

return successResponse({ serviceOverrides }, { status: 200 }, req);
};

const patchHandler = async (req: NextRequest, { params }: { params: { uuid: string } }) => {
const body = (await req.json().catch(() => null)) as UpdateServiceOverridesRequest | null;
const serviceOverridesBody = body?.serviceOverrides;
Expand All @@ -136,7 +184,7 @@ const patchHandler = async (req: NextRequest, { params }: { params: { uuid: stri
const override = new OverrideService();
const build = await override.db.models.Build.query()
.findOne({ uuid: params.uuid })
.withGraphFetched('[pullRequest, deploys.[service, deployable]]');
.withGraphFetched('[pullRequest, environment.[defaultServices, optionalServices], deploys.[service, deployable]]');

if (!build) {
return errorResponse(new Error(`Build with UUID ${params.uuid} not found`), { status: 404 }, req);
Expand All @@ -150,15 +198,29 @@ const patchHandler = async (req: NextRequest, { params }: { params: { uuid: stri
serviceOverrides,
runUuid: nanoid(),
});
const updatedBuild = await override.db.models.Build.query()
.findOne({ uuid: params.uuid })
.withGraphFetched('[environment.[defaultServices, optionalServices], deploys.[service, deployable]]');

return successResponse(result, { status: 200 }, req);
if (!updatedBuild) {
return errorResponse(new Error(`Build with UUID ${params.uuid} not found`), { status: 404 }, req);
}

const updatedServiceOverrides = await override.getServiceOverrideStates(updatedBuild, updatedBuild.deploys || []);

return successResponse({ serviceOverrides: updatedServiceOverrides, queued: result.queued }, { status: 200 }, req);
} catch (error) {
if (error instanceof ServiceOverrideNotFoundError) {
return errorResponse(error, { status: 404 }, req);
}

if (error instanceof ServiceOverrideNotEditableError) {
return errorResponse(error, { status: 400 }, req);
}

throw error;
}
};

export const GET = createApiHandler(getHandler);
export const PATCH = createApiHandler(patchHandler);
Loading
Loading