diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts index 634674ff12..accc17eb51 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts @@ -903,7 +903,7 @@ describe('admin-form.controller', () => { }) }) - describe('handleCreatePresignedPostUrlForImages', () => { + describe('createPresignedPostUrlForImages', () => { const MOCK_USER_ID = new ObjectId().toHexString() const MOCK_FORM_ID = new ObjectId().toHexString() const MOCK_USER = { @@ -953,7 +953,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -980,7 +980,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -1010,7 +1010,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -1036,7 +1036,7 @@ describe('admin-form.controller', () => { const mockRes = expressHandler.mockResponse() // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -1065,7 +1065,7 @@ describe('admin-form.controller', () => { const mockRes = expressHandler.mockResponse() // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -1094,7 +1094,7 @@ describe('admin-form.controller', () => { const mockRes = expressHandler.mockResponse() // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -1120,7 +1120,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForImages( + await AdminFormController.createPresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -1138,7 +1138,7 @@ describe('admin-form.controller', () => { }) }) - describe('handleCreatePresignedPostUrlForLogos', () => { + describe('createPresignedPostUrlForLogos', () => { const MOCK_USER_ID = new ObjectId().toHexString() const MOCK_FORM_ID = new ObjectId().toHexString() const MOCK_USER = { @@ -1188,7 +1188,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -1215,7 +1215,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -1245,7 +1245,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -1271,7 +1271,7 @@ describe('admin-form.controller', () => { const mockRes = expressHandler.mockResponse() // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -1300,7 +1300,7 @@ describe('admin-form.controller', () => { const mockRes = expressHandler.mockResponse() // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -1329,7 +1329,7 @@ describe('admin-form.controller', () => { const mockRes = expressHandler.mockResponse() // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -1355,7 +1355,7 @@ describe('admin-form.controller', () => { ) // Act - await AdminFormController.handleCreatePresignedPostUrlForLogos( + await AdminFormController.createPresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts index c8d41b87c0..d7cbfb5ba3 100644 --- a/src/app/modules/form/admin-form/admin-form.controller.ts +++ b/src/app/modules/form/admin-form/admin-form.controller.ts @@ -6,6 +6,7 @@ import { StatusCodes } from 'http-status-codes' import JSONStream from 'JSONStream' import { ResultAsync } from 'neverthrow' +import { VALID_UPLOAD_FILE_TYPES } from '../../../../shared/constants' import { AuthType, BasicField, @@ -146,6 +147,16 @@ const transferFormOwnershipValidator = celebrate({ }, }) +const fileUploadValidator = celebrate({ + [Segments.BODY]: { + fileId: Joi.string().required(), + fileMd5Hash: Joi.string().base64().required(), + fileType: Joi.string() + .valid(...VALID_UPLOAD_FILE_TYPES) + .required(), + }, +}) + /** * Handler for GET /adminform endpoint. * @security session @@ -283,7 +294,7 @@ export const handlePreviewAdminForm: RequestHandler<{ formId: string }> = ( * @returns 410 when form is archived * @returns 422 when user in session cannot be retrieved from the database */ -export const handleCreatePresignedPostUrlForImages: RequestHandler< +export const createPresignedPostUrlForImages: RequestHandler< { formId: string }, unknown, { @@ -320,7 +331,7 @@ export const handleCreatePresignedPostUrlForImages: RequestHandler< logger.error({ message: 'Presigning post data encountered an error', meta: { - action: 'handleCreatePresignedPostUrlForImages', + action: 'createPresignedPostUrlForImages', ...createReqMeta(req), }, error, @@ -332,6 +343,11 @@ export const handleCreatePresignedPostUrlForImages: RequestHandler< ) } +export const handleCreatePresignedPostUrlForImages = [ + fileUploadValidator, + createPresignedPostUrlForImages, +] as RequestHandler[] + /** * Handler for POST /:formId([a-fA-F0-9]{24})/adminform/logos. * @security session @@ -343,7 +359,7 @@ export const handleCreatePresignedPostUrlForImages: RequestHandler< * @returns 410 when form is archived * @returns 422 when user in session cannot be retrieved from the database */ -export const handleCreatePresignedPostUrlForLogos: RequestHandler< +export const createPresignedPostUrlForLogos: RequestHandler< ParamsDictionary, unknown, { @@ -380,7 +396,7 @@ export const handleCreatePresignedPostUrlForLogos: RequestHandler< logger.error({ message: 'Presigning post data encountered an error', meta: { - action: 'handleCreatePresignedPostUrlForLogos', + action: 'createPresignedPostUrlForLogos', ...createReqMeta(req), }, error, @@ -392,6 +408,11 @@ export const handleCreatePresignedPostUrlForLogos: RequestHandler< ) } +export const handleCreatePresignedPostUrlForLogos = [ + fileUploadValidator, + createPresignedPostUrlForLogos, +] as RequestHandler[] + // Validates that the ending date >= starting date const validateDateRange = celebrate({ [Segments.QUERY]: Joi.object() diff --git a/src/app/modules/form/admin-form/admin-form.routes.ts b/src/app/modules/form/admin-form/admin-form.routes.ts index c76ffd2571..e9594cb1cb 100644 --- a/src/app/modules/form/admin-form/admin-form.routes.ts +++ b/src/app/modules/form/admin-form/admin-form.routes.ts @@ -6,7 +6,6 @@ import JoiDate from '@joi/date' import { celebrate, Joi as BaseJoi, Segments } from 'celebrate' import { Router } from 'express' -import { VALID_UPLOAD_FILE_TYPES } from '../../../../shared/constants' import { ResponseMode } from '../../../../types' import { withUserAuthentication } from '../../auth/auth.middlewares' import * as EncryptSubmissionController from '../../submission/encrypt-submission/encrypt-submission.controller' @@ -45,16 +44,6 @@ const duplicateFormValidator = celebrate({ }), }) -const fileUploadValidator = celebrate({ - [Segments.BODY]: { - fileId: Joi.string().required(), - fileMd5Hash: Joi.string().base64().required(), - fileType: Joi.string() - .valid(...VALID_UPLOAD_FILE_TYPES) - .required(), - }, -}) - AdminFormsRouter.route('/adminform') // All HTTP methods of route protected with authentication. .all(withUserAuthentication) @@ -390,7 +379,6 @@ AdminFormsRouter.get( AdminFormsRouter.post( '/:formId([a-fA-F0-9]{24})/adminform/images', withUserAuthentication, - fileUploadValidator, AdminFormController.handleCreatePresignedPostUrlForImages, ) @@ -411,7 +399,6 @@ AdminFormsRouter.post( AdminFormsRouter.post( '/:formId([a-fA-F0-9]{24})/adminform/logos', withUserAuthentication, - fileUploadValidator, AdminFormController.handleCreatePresignedPostUrlForLogos, ) diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.presign.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.presign.routes.spec.ts new file mode 100644 index 0000000000..8dbcdc420c --- /dev/null +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.presign.routes.spec.ts @@ -0,0 +1,528 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { ObjectId } from 'bson-ext' +import mongoose from 'mongoose' +import SparkMD5 from 'spark-md5' +import supertest, { Session } from 'supertest-session' + +import { aws } from 'src/app/config/config' +import { getEncryptedFormModel } from 'src/app/models/form.server.model' +import getUserModel from 'src/app/models/user.server.model' +import { VALID_UPLOAD_FILE_TYPES } from 'src/shared/constants' +import { IUserSchema, ResponseMode, Status } from 'src/types' + +import { createAuthedSession } from 'tests/integration/helpers/express-auth' +import { setupApp } from 'tests/integration/helpers/express-setup' +import { buildCelebrateError } from 'tests/unit/backend/helpers/celebrate' +import dbHandler from 'tests/unit/backend/helpers/jest-db' + +import { AdminFormsRouter } from '../admin-forms.routes' + +// Prevent rate limiting. +jest.mock('src/app/utils/limit-rate') +jest.mock('nodemailer', () => ({ + createTransport: jest.fn().mockReturnValue({ + sendMail: jest.fn().mockResolvedValue(true), + }), +})) + +const UserModel = getUserModel(mongoose) +const EncryptFormModel = getEncryptedFormModel(mongoose) + +const app = setupApp('/admin/forms', AdminFormsRouter, { + setupWithAuth: true, +}) + +describe('admin-form.presign.routes', () => { + let request: Session + let defaultUser: IUserSchema + + beforeAll(async () => await dbHandler.connect()) + beforeEach(async () => { + request = supertest(app) + const { user } = await dbHandler.insertFormCollectionReqs() + // Default all requests to come from authenticated user. + request = await createAuthedSession(user.email, request) + defaultUser = user + }) + afterEach(async () => { + await dbHandler.clearDatabase() + jest.restoreAllMocks() + }) + afterAll(async () => await dbHandler.closeDatabase()) + + describe('POST /admin/forms/:formId/images/presign', () => { + const DEFAULT_POST_PARAMS = { + fileId: 'some file id', + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: VALID_UPLOAD_FILE_TYPES[0], + } + + it('should return 200 with presigned POST URL object', async () => { + // Arrange + const form = await EncryptFormModel.create({ + title: 'form', + admin: defaultUser._id, + publicKey: 'does not matter', + }) + + // Act + const response = await request + .post(`/admin/forms/${form._id}/images/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(200) + // Should equal mocked result. + expect(response.body).toEqual({ + url: expect.any(String), + fields: expect.objectContaining({ + 'Content-MD5': DEFAULT_POST_PARAMS.fileMd5Hash, + 'Content-Type': DEFAULT_POST_PARAMS.fileType, + key: DEFAULT_POST_PARAMS.fileId, + // Should have correct permissions. + acl: 'public-read', + bucket: expect.any(String), + }), + }) + }) + + it('should return 400 when body.fileId is missing', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send({ + // missing fileId. + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: VALID_UPLOAD_FILE_TYPES[0], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { key: 'fileId' }, + }), + ) + }) + + it('should return 400 when body.fileId is an empty string', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send({ + fileId: '', + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: VALID_UPLOAD_FILE_TYPES[1], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { + key: 'fileId', + message: '"fileId" is not allowed to be empty', + }, + }), + ) + }) + + it('should return 400 when body.fileType is missing', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send({ + fileId: 'some id', + fileMd5Hash: SparkMD5.hash('test file name'), + // Missing fileType. + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { key: 'fileType' }, + }), + ) + }) + + it('should return 400 when body.fileType is invalid', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send({ + fileId: 'some id', + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: 'some random type', + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { + key: 'fileType', + message: `"fileType" must be one of [${VALID_UPLOAD_FILE_TYPES.join( + ', ', + )}]`, + }, + }), + ) + }) + + it('should return 400 when body.fileMd5Hash is missing', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send({ + fileId: 'some id', + // Missing fileMd5Hash + fileType: VALID_UPLOAD_FILE_TYPES[2], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { key: 'fileMd5Hash' }, + }), + ) + }) + + it('should return 400 when body.fileMd5Hash is not a base64 string', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send({ + fileId: 'some id', + fileMd5Hash: 'rubbish hash', + fileType: VALID_UPLOAD_FILE_TYPES[2], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { + key: 'fileMd5Hash', + message: '"fileMd5Hash" must be a valid base64 string', + }, + }), + ) + }) + + it('should return 400 when creating presigned POST URL object errors', async () => { + // Arrange + // Mock error. + jest + .spyOn(aws.s3, 'createPresignedPost') + // @ts-ignore + .mockImplementationOnce((_opts, cb) => + cb(new Error('something went wrong')), + ) + const form = await EncryptFormModel.create({ + title: 'form', + admin: defaultUser._id, + publicKey: 'does not matter', + }) + + // Act + const response = await request + .post(`/admin/forms/${form._id}/images/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual({ + message: 'Error occurred whilst uploading file', + }) + }) + + it('should return 404 when form to upload image to cannot be found', async () => { + // Arrange + const invalidFormId = new ObjectId().toHexString() + + // Act + const response = await request + .post(`/admin/forms/${invalidFormId}/images/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(404) + expect(response.body).toEqual({ message: 'Form not found' }) + }) + + it('should return 410 when form to upload image to is already archived', async () => { + // Arrange + const archivedForm = await EncryptFormModel.create({ + title: 'archived form', + status: Status.Archived, + responseMode: ResponseMode.Encrypt, + publicKey: 'does not matter', + admin: defaultUser._id, + }) + + // Act + const response = await request + .post(`/admin/forms/${archivedForm._id}/images/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(410) + expect(response.body).toEqual({ message: 'Form has been archived' }) + }) + + it('should return 422 when user in session cannot be retrieved from the database', async () => { + // Arrange + // Clear user collection + await dbHandler.clearCollection(UserModel.collection.name) + + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/images/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(422) + expect(response.body).toEqual({ message: 'User not found' }) + }) + }) + + describe('POST /admin/forms/:formId/logos/presign', () => { + const DEFAULT_POST_PARAMS = { + fileId: 'some other file id', + fileMd5Hash: SparkMD5.hash('test file name again'), + fileType: VALID_UPLOAD_FILE_TYPES[2], + } + + it('should return 200 with presigned POST URL object', async () => { + // Arrange + const form = await EncryptFormModel.create({ + title: 'form', + admin: defaultUser._id, + publicKey: 'does not matter', + }) + + // Act + const response = await request + .post(`/admin/forms/${form._id}/logos/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(200) + // Should equal mocked result. + expect(response.body).toEqual({ + url: expect.any(String), + fields: expect.objectContaining({ + 'Content-MD5': DEFAULT_POST_PARAMS.fileMd5Hash, + 'Content-Type': DEFAULT_POST_PARAMS.fileType, + key: DEFAULT_POST_PARAMS.fileId, + // Should have correct permissions. + acl: 'public-read', + bucket: expect.any(String), + }), + }) + }) + + it('should return 400 when body.fileId is missing', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send({ + // missing fileId. + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: VALID_UPLOAD_FILE_TYPES[0], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { key: 'fileId' }, + }), + ) + }) + + it('should return 400 when body.fileId is an empty string', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send({ + fileId: '', + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: VALID_UPLOAD_FILE_TYPES[1], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { + key: 'fileId', + message: '"fileId" is not allowed to be empty', + }, + }), + ) + }) + + it('should return 400 when body.fileType is missing', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send({ + fileId: 'some id', + fileMd5Hash: SparkMD5.hash('test file name'), + // Missing fileType. + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { key: 'fileType' }, + }), + ) + }) + + it('should return 400 when body.fileType is invalid', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send({ + fileId: 'some id', + fileMd5Hash: SparkMD5.hash('test file name'), + fileType: 'some random type', + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { + key: 'fileType', + message: `"fileType" must be one of [${VALID_UPLOAD_FILE_TYPES.join( + ', ', + )}]`, + }, + }), + ) + }) + + it('should return 400 when body.fileMd5Hash is missing', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send({ + fileId: 'some id', + // Missing fileMd5Hash + fileType: VALID_UPLOAD_FILE_TYPES[2], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { key: 'fileMd5Hash' }, + }), + ) + }) + + it('should return 400 when body.fileMd5Hash is not a base64 string', async () => { + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send({ + fileId: 'some id', + fileMd5Hash: 'rubbish hash', + fileType: VALID_UPLOAD_FILE_TYPES[2], + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ + body: { + key: 'fileMd5Hash', + message: '"fileMd5Hash" must be a valid base64 string', + }, + }), + ) + }) + + it('should return 400 when creating presigned POST URL object errors', async () => { + // Arrange + // Mock error. + jest + .spyOn(aws.s3, 'createPresignedPost') + // @ts-ignore + .mockImplementationOnce((_opts, cb) => + cb(new Error('something went wrong')), + ) + const form = await EncryptFormModel.create({ + title: 'form', + admin: defaultUser._id, + publicKey: 'does not matter again', + }) + + // Act + const response = await request + .post(`/admin/forms/${form._id}/logos/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual({ + message: 'Error occurred whilst uploading file', + }) + }) + + it('should return 404 when form to upload logo to cannot be found', async () => { + // Arrange + const invalidFormId = new ObjectId().toHexString() + + // Act + const response = await request + .post(`/admin/forms/${invalidFormId}/logos/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(404) + expect(response.body).toEqual({ message: 'Form not found' }) + }) + + it('should return 410 when form to upload logo to is already archived', async () => { + // Arrange + const archivedForm = await EncryptFormModel.create({ + title: 'archived form', + status: Status.Archived, + responseMode: ResponseMode.Encrypt, + publicKey: 'does not matter', + admin: defaultUser._id, + }) + + // Act + const response = await request + .post(`/admin/forms/${archivedForm._id}/logos/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(410) + expect(response.body).toEqual({ message: 'Form has been archived' }) + }) + + it('should return 422 when user in session cannot be retrieved from the database', async () => { + // Arrange + // Clear user collection + await dbHandler.clearCollection(UserModel.collection.name) + + // Act + const response = await request + .post(`/admin/forms/${new ObjectId()}/logos/presign`) + .send(DEFAULT_POST_PARAMS) + + // Assert + expect(response.status).toEqual(422) + expect(response.body).toEqual({ message: 'User not found' }) + }) + }) +}) diff --git a/src/app/routes/api/v3/admin/forms/admin-forms.presign.routes.ts b/src/app/routes/api/v3/admin/forms/admin-forms.presign.routes.ts new file mode 100644 index 0000000000..3565d96eb2 --- /dev/null +++ b/src/app/routes/api/v3/admin/forms/admin-forms.presign.routes.ts @@ -0,0 +1,45 @@ +import { Router } from 'express' + +import * as AdminFormController from '../../../../../modules/form/admin-form/admin-form.controller' + +export const AdminFormsPresignRouter = Router() + +// Validators + +/** + * Upload images + * @route POST /api/v3/admin/forms/:formId/images/presign + * @security session + * + * @returns 200 with presigned POST URL object + * @returns 400 when error occurs whilst creating presigned POST URL object + * @returns 400 when Joi validation fails + * @returns 401 when user does not exist in session + * @returns 403 when user does not have write permissions for form + * @returns 404 when form cannot be found + * @returns 410 when form is archived + * @returns 422 when user in session cannot be retrieved from the database + */ +AdminFormsPresignRouter.post( + '/:formId([a-fA-F0-9]{24})/images/presign', + AdminFormController.handleCreatePresignedPostUrlForImages, +) + +/** + * Upload logos + * @route POST /api/v3/admin/forms/:formId/logos/presign + * @security session + * + * @returns 200 with presigned POST URL object + * @returns 400 when error occurs whilst creating presigned POST URL object + * @returns 400 when Joi validation fails + * @returns 401 when user does not exist in session + * @returns 403 when user does not have write permissions for form + * @returns 404 when form cannot be found + * @returns 410 when form is archived + * @returns 422 when user in session cannot be retrieved from the database + */ +AdminFormsPresignRouter.post( + '/:formId([a-fA-F0-9]{24})/logos/presign', + AdminFormController.handleCreatePresignedPostUrlForLogos, +) diff --git a/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts b/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts index 0d7f4a6884..8818b03b32 100644 --- a/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts +++ b/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts @@ -6,6 +6,7 @@ import { handleDeleteLogic } from '../../../../../modules/form/admin-form/admin- import { AdminFormsFeedbackRouter } from './admin-forms.feedback.routes' import { AdminFormsFormRouter } from './admin-forms.form.routes' import { AdminFormsLogicRouter } from './admin-forms.logic.routes' +import { AdminFormsPresignRouter } from './admin-forms.presign.routes' import { AdminFormsPreviewRouter } from './admin-forms.preview.routes' import { AdminFormsSettingsRouter } from './admin-forms.settings.routes' import { AdminFormsSubmissionsRouter } from './admin-forms.submissions.routes' @@ -20,6 +21,7 @@ AdminFormsRouter.use(AdminFormsFeedbackRouter) AdminFormsRouter.use(AdminFormsFormRouter) AdminFormsRouter.use(AdminFormsSubmissionsRouter) AdminFormsRouter.use(AdminFormsPreviewRouter) +AdminFormsRouter.use(AdminFormsPresignRouter) AdminFormsRouter.use(AdminFormsLogicRouter) /** diff --git a/src/public/services/FileHandlerService.ts b/src/public/services/FileHandlerService.ts index 719158dac5..e0e6e9a1f2 100644 --- a/src/public/services/FileHandlerService.ts +++ b/src/public/services/FileHandlerService.ts @@ -151,7 +151,7 @@ export const uploadImage = async ({ const fileId = `${formId}-${Date.now()}-${image.name.toLowerCase()}` return uploadFile({ - url: `/${formId}/adminform/images`, + url: `/api/v3/admin/forms/${formId}/images/presign`, file: image, fileId, cancelToken, @@ -172,7 +172,7 @@ export const uploadLogo = async ({ const fileId = `${Date.now()}-${image.name.toLowerCase()}` return uploadFile({ - url: `/${formId}/adminform/logos`, + url: `/api/v3/admin/forms/${formId}/logos/presign`, file: image, fileId, cancelToken, diff --git a/src/public/services/__tests__/FileHandlerService.test.ts b/src/public/services/__tests__/FileHandlerService.test.ts index 62bdff556c..db3532d4d3 100644 --- a/src/public/services/__tests__/FileHandlerService.test.ts +++ b/src/public/services/__tests__/FileHandlerService.test.ts @@ -40,7 +40,7 @@ describe('FileHandlerService', () => { // Assert expect(actual).toEqual(expected) expect(uploadSpy).toHaveBeenCalledWith({ - url: `/${mockFormId}/adminform/images`, + url: `/api/v3/admin/forms/${mockFormId}/images/presign`, file: mockImage, fileId: expectedFileId, }) @@ -80,7 +80,7 @@ describe('FileHandlerService', () => { // Assert expect(actual).toEqual(expected) expect(uploadSpy).toHaveBeenCalledWith({ - url: `/${mockFormId}/adminform/logos`, + url: `/api/v3/admin/forms/${mockFormId}/logos/presign`, file: mockLogo, fileId: expectedFileId, })