Skip to content
Merged
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
59 changes: 59 additions & 0 deletions src/app/api/v2/builds/[uuid]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { NextRequest } from 'next/server';
import { createApiHandler } from 'server/lib/createApiHandler';
import { errorResponse, successResponse } from 'server/lib/response';
import BuildService from 'server/services/build';
/**
* @openapi
* /api/v2/builds/{uuid}:
* get:
* summary: Get a build by UUID
* description: Returns a build object corresponding to the provided UUID.
* tags:
* - Builds
* operationId: getBuildByUUID
* parameters:
* - in: path
* name: uuid
* required: true
* schema:
* type: string
* description: The UUID of the build to retrieve.
* responses:
* '200':
* description: A build object.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/GetBuildByUUIDSuccessResponse'
* '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'
*/
const getHandler = async (req: NextRequest, { params }: { params: { uuid: string } }) => {
const buildService = new BuildService();

const build = await buildService.getBuildByUUID(params.uuid);

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

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

export const GET = createApiHandler(getHandler);
32 changes: 28 additions & 4 deletions src/server/services/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default class BuildService extends BaseService {
const exclude = excludeStatuses ? excludeStatuses.split(',').map((s) => s.trim()) : [];

const baseQuery = this.db.models.Build.query()
.select('id', 'uuid', 'status', 'namespace')
.select('id', 'uuid', 'status', 'namespace', 'createdAt', 'updatedAt')
.whereNotIn('status', exclude)
.modify((qb) => {
if (filterByAuthor) {
Expand All @@ -152,9 +152,15 @@ export default class BuildService extends BaseService {
});
}
})
.withGraphFetched('pullRequest')
.modifyGraph('pullRequest', (builder) => {
builder.select('id', 'title', 'fullName', 'githubLogin', 'pullRequestNumber');
.withGraphFetched('[pullRequest, deploys.[deployable]]')
.modifyGraph('pullRequest', (b) => {
b.select('id', 'title', 'fullName', 'githubLogin', 'pullRequestNumber', 'branchName');
})
.modifyGraph('deploys', (b) => {
b.select('id', 'uuid', 'status', 'active', 'deployableId');
})
.modifyGraph('deploys.deployable', (b) => {
b.select('name');
})
.orderBy('updatedAt', 'desc');

Expand All @@ -163,6 +169,24 @@ export default class BuildService extends BaseService {
return { data, paginationMetadata };
}

async getBuildByUUID(uuid: string): Promise<Build | null> {
const build = await this.db.models.Build.query()
.findOne({ uuid })
.select('id', 'uuid', 'status', 'namespace', 'createdAt', 'updatedAt')
.withGraphFetched('[pullRequest, deploys.[deployable]]')
.modifyGraph('pullRequest', (b) => {
b.select('id', 'title', 'fullName', 'githubLogin', 'pullRequestNumber', 'branchName');
})
.modifyGraph('deploys', (b) => {
b.select('id', 'uuid', 'status', 'active', 'deployableId');
})
.modifyGraph('deploys.deployable', (b) => {
b.select('name');
});

return build;
}

/**
* Returns namespace of a build based on either id or uuid.
*/
Expand Down
54 changes: 52 additions & 2 deletions src/shared/openApiSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,42 @@ export const openApiSpecificationForV2Api: OAS3Options = {
uuid: { type: 'string', example: 'white-poetry-596195' },
status: { $ref: '#/components/schemas/BuildStatus' },
namespace: { type: 'string', example: 'env-white-poetry-596195' },
createdAt: { type: 'string', format: 'date-time' },
updatedAt: { type: 'string', format: 'date-time' },
pullRequest: { $ref: '#/components/schemas/PullRequest' },
deploys: {
type: 'array',
items: { $ref: '#/components/schemas/Deploy' },
},
},
required: ['id', 'uuid', 'status', 'namespace', 'createdAt', 'updatedAt', 'pullRequest', 'deploys'],
},

/**
* @description The Deployable associated with a Deploy.
*/
Deployable: {
type: 'object',
properties: {
name: { type: 'string', example: 'web' },
},
required: ['id', 'uuid', 'status', 'namespace', 'pullRequest'],
required: ['name'],
},

/**
* @description A Deploy associated with a Build.
*/
Deploy: {
type: 'object',
properties: {
id: { type: 'integer' },
uuid: { type: 'string', example: 'deploy-uuid' },
status: { type: 'string', example: 'active' },
active: { type: 'boolean', example: true },
deployableId: { type: 'integer' },
deployable: { $ref: '#/components/schemas/Deployable' },
},
required: ['id', 'uuid', 'status', 'active', 'deployableId', 'deployable'],
},

/**
Expand All @@ -122,8 +155,9 @@ export const openApiSpecificationForV2Api: OAS3Options = {
fullName: { type: 'string', example: 'goodrx/lifecycle' },
githubLogin: { type: 'string', example: 'lifecycle-bot' },
pullRequestNumber: { type: 'integer', example: 42 },
branchName: { type: 'string', example: 'feature/new-feature' },
},
required: ['id', 'title', 'fullName', 'githubLogin', 'pullRequestNumber'],
required: ['id', 'title', 'fullName', 'githubLogin', 'pullRequestNumber', 'branchName'],
},

/**
Expand All @@ -144,6 +178,22 @@ export const openApiSpecificationForV2Api: OAS3Options = {
},
],
},

/**
* @description The specific success response for the GET /builds/{uuid} endpoint.
*/
GetBuildByUUIDSuccessResponse: {
allOf: [
{ $ref: '#/components/schemas/SuccessApiResponse' },
{
type: 'object',
properties: {
data: { $ref: '#/components/schemas/Build' },
},
required: ['data'],
},
],
},
},
},
},
Expand Down