|
| 1 | +import Joi = require("joi"); |
| 2 | +import { VError } from "verror"; |
| 3 | + |
| 4 | +import { extractUser, parseMultiPartRequest } from "./handlerUtils"; |
| 5 | +import { toHttpError } from "./http_errors"; |
| 6 | +import * as NotAuthenticated from "./http_errors/not_authenticated"; |
| 7 | +import { AuthenticatedRequest } from "./httpd/lib"; |
| 8 | +import { Ctx } from "./lib/ctx"; |
| 9 | +import * as Result from "./result"; |
| 10 | +import { UploadedDocumentOrLink, uploadedDocumentSchema } from "./service/domain/document/document"; |
| 11 | +import * as Project from "./service/domain/workflow/project"; |
| 12 | +import * as Subproject from "./service/domain/workflow/subproject"; |
| 13 | +import * as Workflowitem from "./service/domain/workflow/workflowitem"; |
| 14 | +import * as WorkflowitemUpdated from "./service/domain/workflow/workflowitem_updated"; |
| 15 | +import { AugmentedFastifyInstance } from "./types"; |
| 16 | + |
| 17 | +import { WorkflowitemUpdateServiceInterface } from "./index"; |
| 18 | + |
| 19 | +/** |
| 20 | + * Represents the request body of the endpoint |
| 21 | + */ |
| 22 | +interface UpdateWorkflowV2RequestBody { |
| 23 | + apiVersion: "2.0"; |
| 24 | + data: { |
| 25 | + projectId: Project.Id; |
| 26 | + subprojectId: Subproject.Id; |
| 27 | + workflowitemId: Workflowitem.Id; |
| 28 | + displayName?: string; |
| 29 | + description?: string; |
| 30 | + amountType?: "N/A" | "disbursed" | "allocated"; |
| 31 | + amount?: string; |
| 32 | + currency?: string; |
| 33 | + exchangeRate?: string; |
| 34 | + billingDate?: string; |
| 35 | + dueDate?: string; |
| 36 | + documents?: UploadedDocumentOrLink[]; |
| 37 | + additionalData?: object; |
| 38 | + tags?: string[]; |
| 39 | + }; |
| 40 | +} |
| 41 | + |
| 42 | +const requestBodyV2Schema = Joi.object({ |
| 43 | + apiVersion: Joi.valid("2.0").required(), |
| 44 | + data: Joi.object({ |
| 45 | + projectId: Project.idSchema.required(), |
| 46 | + subprojectId: Subproject.idSchema.required(), |
| 47 | + workflowitemId: Workflowitem.idSchema.required(), |
| 48 | + }) |
| 49 | + .concat(WorkflowitemUpdated.modificationSchema) |
| 50 | + .keys({ documents: Joi.array().items(uploadedDocumentSchema) }) |
| 51 | + .required(), |
| 52 | +}); |
| 53 | + |
| 54 | +type RequestBody = UpdateWorkflowV2RequestBody; |
| 55 | +const requestBodySchema = Joi.alternatives([requestBodyV2Schema]); |
| 56 | + |
| 57 | +/** |
| 58 | + * Validates the request body of the http request |
| 59 | + * |
| 60 | + * @param body the request body |
| 61 | + * @returns the request body wrapped in a {@link Result.Type}. Contains either the object or an error |
| 62 | + */ |
| 63 | +function validateRequestBody(body: unknown): Result.Type<RequestBody> { |
| 64 | + const { error, value } = requestBodySchema.validate(body); |
| 65 | + return !error ? value : error; |
| 66 | +} |
| 67 | + |
| 68 | +/** |
| 69 | + * Creates the swagger schema for the `/v2/workflowitem.update` endpoint |
| 70 | + * |
| 71 | + * @param server fastify server |
| 72 | + * @returns the swagger schema for this endpoint |
| 73 | + */ |
| 74 | +function mkSwaggerSchema(server: AugmentedFastifyInstance): Object { |
| 75 | + return { |
| 76 | + preValidation: [server.authenticate], |
| 77 | + schema: { |
| 78 | + description: |
| 79 | + "Partially update a workflowitem. Only properties mentioned in the request body are touched, " + |
| 80 | + "others are not affected. The assigned user will be notified about the change.\n" + |
| 81 | + "Note that the only possible values for 'amountType' are: 'disbursed', 'allocated', 'N/A'\n.\n" + |
| 82 | + "The only possible values for 'status' are: 'open' and 'closed'", |
| 83 | + tags: ["workflowitem"], |
| 84 | + summary: "Update a workflowitem", |
| 85 | + security: [ |
| 86 | + { |
| 87 | + bearerToken: [], |
| 88 | + }, |
| 89 | + ], |
| 90 | + response: { |
| 91 | + 200: { |
| 92 | + description: "successful response", |
| 93 | + type: "object", |
| 94 | + properties: { |
| 95 | + apiVersion: { type: "string", example: "1.0" }, |
| 96 | + data: { |
| 97 | + type: "object", |
| 98 | + }, |
| 99 | + }, |
| 100 | + 401: NotAuthenticated.schema, |
| 101 | + }, |
| 102 | + }, |
| 103 | + }, |
| 104 | + }; |
| 105 | +} |
| 106 | + |
| 107 | +/** |
| 108 | + * Creates an http handler that handles incoming http requests for the `/workflowitem.update` route |
| 109 | + * |
| 110 | + * @param server the current fastify server instance |
| 111 | + * @param urlPrefix the prefix of the http url |
| 112 | + * @param service the service {@link Service} object used to offer an interface to the domain logic |
| 113 | + */ |
| 114 | +export function addHttpHandler( |
| 115 | + server: AugmentedFastifyInstance, |
| 116 | + urlPrefix: string, |
| 117 | + service: WorkflowitemUpdateServiceInterface, |
| 118 | +): void { |
| 119 | + server.register(async function () { |
| 120 | + server.post( |
| 121 | + `${urlPrefix}/v2/workflowitem.update`, |
| 122 | + mkSwaggerSchema(server), |
| 123 | + async (request: AuthenticatedRequest, reply) => { |
| 124 | + let body = { |
| 125 | + apiVersion: "2.0", |
| 126 | + data: await parseMultiPartRequest(request), |
| 127 | + }; |
| 128 | + const ctx: Ctx = { requestId: request.id, source: "http" }; |
| 129 | + |
| 130 | + const user = extractUser(request as AuthenticatedRequest); |
| 131 | + |
| 132 | + const bodyResult = validateRequestBody(body); |
| 133 | + |
| 134 | + if (Result.isErr(bodyResult)) { |
| 135 | + const { code, body } = toHttpError(new VError(bodyResult, "failed to update project")); |
| 136 | + request.log.error({ err: bodyResult }, "Invalid request body"); |
| 137 | + reply.status(code).send(body); |
| 138 | + return; |
| 139 | + } |
| 140 | + |
| 141 | + const { projectId, subprojectId, workflowitemId, ...data } = bodyResult.data; |
| 142 | + |
| 143 | + try { |
| 144 | + const result = await service.updateWorkflowitem( |
| 145 | + ctx, |
| 146 | + user, |
| 147 | + projectId, |
| 148 | + subprojectId, |
| 149 | + workflowitemId, |
| 150 | + data, |
| 151 | + ); |
| 152 | + |
| 153 | + if (Result.isErr(result)) { |
| 154 | + throw new VError(result, "workflowitem.update failed"); |
| 155 | + } |
| 156 | + |
| 157 | + const response = { |
| 158 | + apiVersion: "2.0", |
| 159 | + data: {}, |
| 160 | + }; |
| 161 | + reply.status(200).send(response); |
| 162 | + } catch (err) { |
| 163 | + const { code, body } = toHttpError(err); |
| 164 | + request.log.error({ err }, "Error while updating workflowitem"); |
| 165 | + reply.status(code).send(body); |
| 166 | + } |
| 167 | + }, |
| 168 | + ); |
| 169 | + }); |
| 170 | +} |
0 commit comments