diff --git a/apps/api/openapi/lib/openapi.libsonnet b/apps/api/openapi/lib/openapi.libsonnet index 349cf1dfb..11b94f2fa 100644 --- a/apps/api/openapi/lib/openapi.libsonnet +++ b/apps/api/openapi/lib/openapi.libsonnet @@ -25,7 +25,7 @@ providerIdParam():: self.stringParam('providerId', 'ID of the resource provider'), relationshipRuleIdParam():: self.stringParam('relationshipRuleId', 'ID of the relationship rule'), - limitParam(defaultValue = 50):: { + limitParam(defaultValue=50):: { name: 'limit', 'in': 'query', required: false, @@ -37,7 +37,7 @@ default: defaultValue, }, }, - + offsetParam():: { name: 'offset', 'in': 'query', @@ -59,16 +59,16 @@ type: 'string', }, }, - + // Response helpers - schemaRef(name, nullable = false):: ( + schemaRef(name, nullable=false):: ( if nullable then { '$ref': '#/components/schemas/' + name, nullable: true } else { '$ref': '#/components/schemas/' + name } ), - - okResponse(schema, description = "OK response"):: { + + okResponse(schema, description='OK response'):: { '200': { description: description, content: { @@ -78,8 +78,8 @@ }, }, }, - - acceptedResponse(schema, description = "Accepted response"):: { + + acceptedResponse(schema, description='Accepted response'):: { '202': { description: description, content: { @@ -90,7 +90,7 @@ }, }, - createdResponse(schema, description = "Resource created successfully"):: { + createdResponse(schema, description='Resource created successfully'):: { '201': { description: description, content: { @@ -101,6 +101,12 @@ }, }, + noContent():: { + '204': { + description: 'No content', + }, + }, + notFoundResponse():: { '404': { description: 'Resource not found', @@ -111,7 +117,7 @@ }, }, }, - + badRequestResponse():: { '400': { description: 'Invalid request', @@ -123,7 +129,18 @@ }, }, - paginatedResponse(itemsSchema, description = "Paginated list of items"):: { + internalServerError():: { + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { '$ref': '#/components/schemas/ErrorResponse' }, + }, + }, + }, + }, + + paginatedResponse(itemsSchema, description='Paginated list of items'):: { '200': { description: description, content: { diff --git a/apps/api/openapi/openapi.json b/apps/api/openapi/openapi.json index bc2406531..a0c36221e 100644 --- a/apps/api/openapi/openapi.json +++ b/apps/api/openapi/openapi.json @@ -5647,6 +5647,59 @@ } }, "/v1/workspaces/{workspaceId}/resources/identifier/{identifier}": { + "delete": { + "description": "Deletes a resource by its identifier.", + "operationId": "deleteResourceByIdentifier", + "parameters": [ + { + "description": "ID of the workspace", + "in": "path", + "name": "workspaceId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Identifier of the resource", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid request" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Resource not found" + } + }, + "summary": "Delete resource by identifier", + "tags": [ + "Resources" + ] + }, "get": { "description": "Returns a resource by its identifier.", "operationId": "getResourceByIdentifier", diff --git a/apps/api/openapi/paths/resources.jsonnet b/apps/api/openapi/paths/resources.jsonnet index 62b4649dc..5f7d6a432 100644 --- a/apps/api/openapi/paths/resources.jsonnet +++ b/apps/api/openapi/paths/resources.jsonnet @@ -29,6 +29,17 @@ local openapi = import '../lib/openapi.libsonnet'; ], responses: openapi.okResponse(openapi.schemaRef('Resource')), }, + delete: { + tags: ['Resources'], + summary: 'Delete resource by identifier', + operationId: 'deleteResourceByIdentifier', + description: 'Deletes a resource by its identifier.', + parameters: [ + openapi.workspaceIdParam(), + openapi.identifierParam(), + ], + responses: openapi.noContent() + openapi.notFoundResponse() + openapi.badRequestResponse(), + }, }, '/v1/workspaces/{workspaceId}/resources/identifier/{identifier}/variables': { get: { @@ -42,9 +53,9 @@ local openapi = import '../lib/openapi.libsonnet'; openapi.limitParam(), openapi.offsetParam(), ], - responses: openapi.paginatedResponse(openapi.schemaRef('ResourceVariable'), 'The requested variables') + - openapi.notFoundResponse() + - openapi.badRequestResponse(), + responses: openapi.paginatedResponse(openapi.schemaRef('ResourceVariable'), 'The requested variables') + + openapi.notFoundResponse() + + openapi.badRequestResponse(), }, patch: { tags: ['Resources'], @@ -87,8 +98,8 @@ local openapi = import '../lib/openapi.libsonnet'; openapi.deploymentIdParam(), ], responses: openapi.okResponse(openapi.schemaRef('ReleaseTarget'), 'The requested release target') + - openapi.notFoundResponse() + - openapi.badRequestResponse(), + openapi.notFoundResponse() + + openapi.badRequestResponse(), }, }, } diff --git a/apps/api/src/routes/v1/workspaces/resources.ts b/apps/api/src/routes/v1/workspaces/resources.ts index 61a8fe550..95b10fc39 100644 --- a/apps/api/src/routes/v1/workspaces/resources.ts +++ b/apps/api/src/routes/v1/workspaces/resources.ts @@ -58,6 +58,41 @@ const getResourceByIdentifier: AsyncTypedHandler< res.status(200).json(result.data); }; +const deleteResourceByIdentifier: AsyncTypedHandler< + "/v1/workspaces/{workspaceId}/resources/identifier/{identifier}", + "delete" +> = async (req, res) => { + const { workspaceId, identifier } = req.params; + + const resourceIdentifier = encodeURIComponent(identifier); + const resourceResponse = await getClientFor(workspaceId).GET( + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}", + { params: { path: { workspaceId, resourceIdentifier } } }, + ); + if (resourceResponse.error != null) { + const status = resourceResponse.response.status; + if (status >= 500) { + throw new ApiError( + resourceResponse.error.error ?? "Internal server error", + status, + ); + } + throw new ApiError( + resourceResponse.error.error ?? "Resource not found", + status, + ); + } + + await sendGoEvent({ + workspaceId, + eventType: Event.ResourceDeleted, + timestamp: Date.now(), + data: resourceResponse.data, + }); + + res.status(204).end(); +}; + const getVariablesForResource: AsyncTypedHandler< "/v1/workspaces/{workspaceId}/resources/identifier/{identifier}/variables", "get" @@ -147,6 +182,7 @@ const getReleaseTargetForResourceInDeployment: AsyncTypedHandler< export const resourceRouter = Router({ mergeParams: true }) .get("/", asyncHandler(listResources)) .get("/identifier/:identifier", asyncHandler(getResourceByIdentifier)) + .delete("/identifier/:identifier", asyncHandler(deleteResourceByIdentifier)) .get( "/identifier/:identifier/variables", asyncHandler(getVariablesForResource), diff --git a/apps/api/src/types/openapi.ts b/apps/api/src/types/openapi.ts index a303b31da..6d94ddb04 100644 --- a/apps/api/src/types/openapi.ts +++ b/apps/api/src/types/openapi.ts @@ -662,7 +662,11 @@ export interface paths { get: operations["getResourceByIdentifier"]; put?: never; post?: never; - delete?: never; + /** + * Delete resource by identifier + * @description Deletes a resource by its identifier. + */ + delete: operations["deleteResourceByIdentifier"]; options?: never; head?: never; patch?: never; @@ -3754,6 +3758,47 @@ export interface operations { }; }; }; + deleteResourceByIdentifier: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + identifier: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; getVariablesForResource: { parameters: { query?: {