diff --git a/packages/server/src/database/tenant/migrations/20260516120000_add_unique_constraint_to_documents_key.ts b/packages/server/src/database/tenant/migrations/20260516120000_add_unique_constraint_to_documents_key.ts new file mode 100644 index 000000000..a4fda5c35 --- /dev/null +++ b/packages/server/src/database/tenant/migrations/20260516120000_add_unique_constraint_to_documents_key.ts @@ -0,0 +1,11 @@ +exports.up = function (knex) { + return knex.schema.alterTable('documents', (table) => { + table.unique('key'); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable('documents', (table) => { + table.dropUnique('key'); + }); +}; diff --git a/packages/server/src/modules/Attachments/Attachment.module.ts b/packages/server/src/modules/Attachments/Attachment.module.ts index 08d3fad58..74f55f205 100644 --- a/packages/server/src/modules/Attachments/Attachment.module.ts +++ b/packages/server/src/modules/Attachments/Attachment.module.ts @@ -1,5 +1,7 @@ import { Module } from "@nestjs/common"; +import { randomUUID } from 'node:crypto'; import * as multerS3 from 'multer-s3'; +import { ClsService } from 'nestjs-cls'; import { S3_CLIENT, S3Module } from "../S3/S3.module"; import { DeleteAttachment } from "./DeleteAttachment"; import { GetAttachment } from "./GetAttachment"; @@ -59,8 +61,12 @@ const models = [ AttachmentUploadPipeline, { provide: MULTER_MODULE_OPTIONS, - inject: [ConfigService, S3_CLIENT], - useFactory: (configService: ConfigService, s3: S3Client) => ({ + inject: [ConfigService, S3_CLIENT, ClsService], + useFactory: ( + configService: ConfigService, + s3: S3Client, + cls: ClsService, + ) => ({ storage: multerS3({ s3, bucket: configService.get('s3.bucket'), @@ -69,7 +75,11 @@ const models = [ cb(null, { fieldName: file.fieldname }); }, key: function (req, file, cb) { - cb(null, Date.now().toString()); + const organizationId = cls.get('organizationId'); + if (!organizationId) { + return cb(new Error('Tenant context required for upload.'), undefined as any); + } + cb(null, `${organizationId}/${randomUUID()}`); }, acl: function(req, file, cb) { // Conditionally set file to public or private based on isPublic flag