mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
feat(upload): increase document upload size limits to 50MB across the application
This commit is contained in:
30
apps/edr-freight-api/src/common/document-upload.options.ts
Normal file
30
apps/edr-freight-api/src/common/document-upload.options.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { MulterOptions } from "@nestjs/platform-express/multer/interfaces/multer-options.interface";
|
||||
|
||||
/**
|
||||
* Ceiling for a single uploaded document, in bytes.
|
||||
*
|
||||
* Mirrors the 50MB `max_size_mb` the file-upload settings hand the portal, so
|
||||
* the client-side gate and the server-side cap agree. Raising this alone is not
|
||||
* enough to accept a 50MB upload: the reverse proxy in front of the API applies
|
||||
* its own `client_max_body_size`, and nginx's 1MB default rejects the request
|
||||
* with a 413 before it ever reaches Nest (see docs/uploads.md).
|
||||
*/
|
||||
export const DOCUMENT_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
|
||||
|
||||
/** Upper bound on parts in one multipart document post. */
|
||||
export const DOCUMENT_UPLOAD_MAX_FILES = 20;
|
||||
|
||||
/**
|
||||
* Multer caps for the document upload routes.
|
||||
*
|
||||
* Without an explicit `fileSize`, multer's default is unlimited and every byte
|
||||
* is buffered in memory, so an oversized post is absorbed in full before
|
||||
* anything can reject it. With the limit set, multer stops reading the socket
|
||||
* at the ceiling instead.
|
||||
*/
|
||||
export const documentUploadMulterOptions: MulterOptions = {
|
||||
limits: {
|
||||
fileSize: DOCUMENT_UPLOAD_MAX_BYTES,
|
||||
files: DOCUMENT_UPLOAD_MAX_FILES,
|
||||
},
|
||||
};
|
||||
@@ -16,11 +16,17 @@ import { AppModule } from "./app.module";
|
||||
|
||||
/**
|
||||
* JSON body ceiling. Signing posts the signature AND the company stamp as
|
||||
* base64 in one JSON body, and base64 inflates bytes by ~4/3 — a 10MB stamp is
|
||||
* ~13.4MB on the wire. Express defaults to 100kb, which rejected any real stamp
|
||||
* base64 in one JSON body, and base64 inflates bytes by ~4/3 — a 50MB asset is
|
||||
* ~67MB on the wire. Express defaults to 100kb, which rejected any real stamp
|
||||
* image with a 413 "request entity too large".
|
||||
*
|
||||
* Sized to clear the 50MB per-document ceiling
|
||||
* (`DOCUMENT_UPLOAD_MAX_BYTES`) after base64 inflation, with room for the
|
||||
* surrounding JSON. Note that the reverse proxy applies its own
|
||||
* `client_max_body_size` and rejects oversized bodies before Nest sees them —
|
||||
* raising this alone does not lift the limit end to end (see docs/uploads.md).
|
||||
*/
|
||||
const JSON_BODY_LIMIT = "20mb";
|
||||
const JSON_BODY_LIMIT = "100mb";
|
||||
|
||||
/**
|
||||
* Static /etc/hosts-style overrides from `DNS_HOST_OVERRIDES`, formatted as
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
/**
|
||||
* Raises the per-field document ceiling from 10MB to 50MB.
|
||||
*
|
||||
* `max_size_mb` is what the portal enforces client-side (SmartFileInput blocks
|
||||
* the file and shows "File size exceeds the limit of NMB"), so the seeded 10
|
||||
* was the visible limit for every existing form even after the server-side caps
|
||||
* were lifted. The seeder only writes these rows on first insert, so deployed
|
||||
* environments keep their old value until this runs.
|
||||
*
|
||||
* Only rows still sitting at the old default are touched — a field an admin has
|
||||
* deliberately tuned to something else keeps that value.
|
||||
*/
|
||||
export class RaiseDocumentUploadSizeLimit3380000000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = "RaiseDocumentUploadSizeLimit3380000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.file_upload_fields
|
||||
ALTER COLUMN max_size_mb SET DEFAULT 50
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE freight.file_upload_fields
|
||||
SET max_size_mb = 50
|
||||
WHERE max_size_mb = 10
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the column default only. The old per-row values are not
|
||||
* recoverable (10 and an admin-chosen 10 are indistinguishable after `up`),
|
||||
* and shrinking a customer's limit back down would reject documents they have
|
||||
* already uploaded, so the rows are deliberately left at 50.
|
||||
*/
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.file_upload_fields
|
||||
ALTER COLUMN max_size_mb SET DEFAULT 10
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { ApiOperation, ApiTags, ApiConsumes } from "@nestjs/swagger";
|
||||
import { CurrentUser } from "@edr/api-common";
|
||||
import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type";
|
||||
import { BookingStaff, MixedAudience, PortalCustomer } from "../../common/booking-guards";
|
||||
import { documentUploadMulterOptions } from "../../common/document-upload.options";
|
||||
import {
|
||||
assertFreightPermission,
|
||||
hasFreightPermission,
|
||||
@@ -726,7 +727,7 @@ export class CompaniesController {
|
||||
|
||||
@Post(":companyId/documents")
|
||||
@MixedAudience(FREIGHT_PERMS.customers.update)
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
@UseInterceptors(AnyFilesInterceptor(documentUploadMulterOptions))
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({ summary: "Upload documents for a company (onboarding)" })
|
||||
async uploadDocuments(
|
||||
|
||||
@@ -50,7 +50,7 @@ export class FileUploadField extends BaseEntity {
|
||||
})
|
||||
allowedExtensions!: string[];
|
||||
|
||||
@Column({ name: "max_size_mb", type: "integer", default: 10 })
|
||||
@Column({ name: "max_size_mb", type: "integer", default: 50 })
|
||||
maxSizeMb!: number;
|
||||
|
||||
@Column({ name: "display_order", type: "integer", default: 0 })
|
||||
|
||||
@@ -46,7 +46,7 @@ export function poaDelegationField(displayOrder: number): FileUploadField {
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: ["pdf", "jpg", "jpeg", "png"],
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder,
|
||||
} as FileUploadField;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const ETHIOPIAN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 1,
|
||||
},
|
||||
{
|
||||
@@ -49,7 +49,7 @@ const ETHIOPIAN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 2,
|
||||
},
|
||||
{
|
||||
@@ -60,7 +60,7 @@ const ETHIOPIAN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 3,
|
||||
},
|
||||
poaDelegationDefault(4),
|
||||
@@ -76,7 +76,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 1,
|
||||
},
|
||||
{
|
||||
@@ -87,7 +87,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 2,
|
||||
},
|
||||
{
|
||||
@@ -98,7 +98,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 3,
|
||||
},
|
||||
{
|
||||
@@ -109,7 +109,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 4,
|
||||
},
|
||||
poaDelegationDefault(5),
|
||||
@@ -127,7 +127,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
// isMultiple: false,
|
||||
// maxFiles: 1,
|
||||
// allowedExtensions: DOC_EXTENSIONS,
|
||||
// maxSizeMb: 10,
|
||||
// maxSizeMb: 50,
|
||||
// displayOrder: 1,
|
||||
// },
|
||||
// {
|
||||
@@ -138,7 +138,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
// isMultiple: false,
|
||||
// maxFiles: 1,
|
||||
// allowedExtensions: DOC_EXTENSIONS,
|
||||
// maxSizeMb: 10,
|
||||
// maxSizeMb: 50,
|
||||
// displayOrder: 2,
|
||||
// },
|
||||
// {
|
||||
@@ -149,7 +149,7 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
|
||||
// isMultiple: false,
|
||||
// maxFiles: 1,
|
||||
// allowedExtensions: DOC_EXTENSIONS,
|
||||
// maxSizeMb: 10,
|
||||
// maxSizeMb: 50,
|
||||
// displayOrder: 3,
|
||||
// },
|
||||
// ];
|
||||
@@ -232,7 +232,7 @@ function clearanceField(
|
||||
isMultiple: false,
|
||||
maxFiles: 1,
|
||||
allowedExtensions: opts?.extensions ?? DOC_EXTENSIONS,
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder,
|
||||
};
|
||||
}
|
||||
@@ -556,7 +556,7 @@ const DRIVER_DOCUMENT_FIELDS: OnboardingField[] = [
|
||||
isMultiple: true,
|
||||
maxFiles: 20,
|
||||
allowedExtensions: ["pdf", "jpg", "jpeg", "png", "doc", "docx"],
|
||||
maxSizeMb: 10,
|
||||
maxSizeMb: 50,
|
||||
displayOrder: 1,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user