diff --git a/api/src/attachment/controllers/attachment.controller.spec.ts b/api/src/attachment/controllers/attachment.controller.spec.ts index e22622d3..12a902f3 100644 --- a/api/src/attachment/controllers/attachment.controller.spec.ts +++ b/api/src/attachment/controllers/attachment.controller.spec.ts @@ -110,7 +110,7 @@ describe('AttachmentController', () => { file: [], }, {} as Request, - { context: 'block_attachment' }, + { resourceRef: 'block_attachment' }, ); await expect(promiseResult).rejects.toThrow( new BadRequestException('No file was selected'), @@ -128,7 +128,7 @@ describe('AttachmentController', () => { { session: { passport: { user: { id: '9'.repeat(24) } } }, } as unknown as Request, - { context: 'block_attachment' }, + { resourceRef: 'block_attachment' }, ); const [name] = attachmentFile.filename.split('.'); expect(attachmentService.create).toHaveBeenCalledWith({ @@ -136,7 +136,7 @@ describe('AttachmentController', () => { type: attachmentFile.mimetype, name: attachmentFile.originalname, location: expect.stringMatching(new RegExp(`^/${name}`)), - context: 'block_attachment', + resourceRef: 'block_attachment', access: 'public', createdByRef: 'User', createdBy: '9'.repeat(24), @@ -145,7 +145,7 @@ describe('AttachmentController', () => { [ { ...attachment, - context: 'block_attachment', + resourceRef: 'block_attachment', createdByRef: 'User', createdBy: '9'.repeat(24), }, diff --git a/api/src/attachment/controllers/attachment.controller.ts b/api/src/attachment/controllers/attachment.controller.ts index 8286f31f..278fa7c4 100644 --- a/api/src/attachment/controllers/attachment.controller.ts +++ b/api/src/attachment/controllers/attachment.controller.ts @@ -67,7 +67,7 @@ export class AttachmentController extends BaseController { async filterCount( @Query( new SearchFilterPipe({ - allowedFields: ['name', 'type', 'context'], + allowedFields: ['name', 'type', 'resourceRef'], }), ) filters?: TFilterQuery, @@ -97,7 +97,7 @@ export class AttachmentController extends BaseController { @Query(PageQueryPipe) pageQuery: PageQueryDto, @Query( new SearchFilterPipe({ - allowedFields: ['name', 'type', 'context'], + allowedFields: ['name', 'type', 'resourceRef'], }), ) filters: TFilterQuery, @@ -130,7 +130,8 @@ export class AttachmentController extends BaseController { async uploadFile( @UploadedFiles() files: { file: Express.Multer.File[] }, @Req() req: Request, - @Query() { context, access = 'public' }: AttachmentContextParamDto, + @Query() + { resourceRef, access = 'public' }: AttachmentContextParamDto, ): Promise { if (!files || !Array.isArray(files?.file) || files.file.length === 0) { throw new BadRequestException('No file was selected'); @@ -149,7 +150,7 @@ export class AttachmentController extends BaseController { name: file.originalname, size: file.size, type: file.mimetype, - context, + resourceRef, access, createdBy: userId, createdByRef: 'User', diff --git a/api/src/attachment/dto/attachment.dto.ts b/api/src/attachment/dto/attachment.dto.ts index cb91bbe5..d5e0f68a 100644 --- a/api/src/attachment/dto/attachment.dto.ts +++ b/api/src/attachment/dto/attachment.dto.ts @@ -25,11 +25,11 @@ import { IsObjectId } from '@/utils/validation-rules/is-object-id'; import { AttachmentAccess, - AttachmentContext, AttachmentCreatedByRef, + AttachmentResourceRef, TAttachmentAccess, - TAttachmentContext, TAttachmentCreatedByRef, + TAttachmentResourceRef, } from '../types'; export class AttachmentMetadataDto { @@ -67,16 +67,16 @@ export class AttachmentMetadataDto { channel?: Partial>; /** - * Attachment context + * Attachment resource reference */ @ApiProperty({ - description: 'Attachment Context', - enum: Object.values(AttachmentContext), + description: 'Attachment Resource Ref', + enum: Object.values(AttachmentResourceRef), }) @IsString() @IsNotEmpty() - @IsIn(Object.values(AttachmentContext)) - context: TAttachmentContext; + @IsIn(Object.values(AttachmentResourceRef)) + resourceRef: TAttachmentResourceRef; /** * Attachment Owner Type @@ -141,13 +141,13 @@ export class AttachmentDownloadDto extends ObjectIdDto { export class AttachmentContextParamDto { @ApiProperty({ - description: 'Attachment Context', - enum: Object.values(AttachmentContext), + description: 'Attachment Resource Reference', + enum: Object.values(AttachmentResourceRef), }) @IsString() - @IsIn(Object.values(AttachmentContext)) + @IsIn(Object.values(AttachmentResourceRef)) @IsNotEmpty() - context: TAttachmentContext; + resourceRef: TAttachmentResourceRef; @ApiPropertyOptional({ description: 'Attachment Access', diff --git a/api/src/attachment/guards/attachment-ability.guard.spec.ts b/api/src/attachment/guards/attachment-ability.guard.spec.ts index 782cb040..75d0a4d5 100644 --- a/api/src/attachment/guards/attachment-ability.guard.spec.ts +++ b/api/src/attachment/guards/attachment-ability.guard.spec.ts @@ -53,9 +53,9 @@ describe('AttachmentGuard', () => { }); describe('canActivate', () => { - it('should allow GET requests with valid context', async () => { + it('should allow GET requests with valid ref', async () => { const mockUser = { roles: ['admin-id'] } as any; - const mockContext = ['user_avatar']; + const mockRef = ['user_avatar']; jest.spyOn(modelService, 'findOne').mockImplementation((criteria) => { return typeof criteria === 'string' || @@ -84,7 +84,7 @@ describe('AttachmentGuard', () => { const mockExecutionContext = { switchToHttp: jest.fn().mockReturnValue({ getRequest: jest.fn().mockReturnValue({ - query: { where: { context: mockContext } }, + query: { where: { resourceRef: mockRef } }, method: 'GET', user: mockUser, }), @@ -95,11 +95,11 @@ describe('AttachmentGuard', () => { expect(result).toBe(true); }); - it('should throw BadRequestException for GET requests with invalid context', async () => { + it('should throw BadRequestException for GET requests with invalid ref', async () => { const mockExecutionContext = { switchToHttp: jest.fn().mockReturnValue({ getRequest: jest.fn().mockReturnValue({ - query: { where: { context: 'invalid_context' } }, + query: { where: { resourceRef: 'invalid_ref' } }, method: 'GET', }), }), @@ -120,7 +120,7 @@ describe('AttachmentGuard', () => { ? Promise.reject('Invalid ID') : Promise.resolve({ id: '9'.repeat(24), - context: `user_avatar`, + resourceRef: `user_avatar`, } as Attachment); }); @@ -162,7 +162,7 @@ describe('AttachmentGuard', () => { expect(result).toBe(true); }); - it('should allow POST requests with valid context', async () => { + it('should allow POST requests with a valid ref', async () => { const mockUser = { roles: ['editor-id'] } as any; jest.spyOn(modelService, 'findOne').mockImplementation((criteria) => { @@ -191,7 +191,7 @@ describe('AttachmentGuard', () => { const mockExecutionContext = { switchToHttp: jest.fn().mockReturnValue({ getRequest: jest.fn().mockReturnValue({ - query: { context: 'block_attachment' }, + query: { resourceRef: 'block_attachment' }, method: 'POST', user: mockUser, }), diff --git a/api/src/attachment/guards/attachment-ability.guard.ts b/api/src/attachment/guards/attachment-ability.guard.ts index 16588b94..cfa1a583 100644 --- a/api/src/attachment/guards/attachment-ability.guard.ts +++ b/api/src/attachment/guards/attachment-ability.guard.ts @@ -26,8 +26,11 @@ import { Action } from '@/user/types/action.type'; import { TModel } from '@/user/types/model.type'; import { AttachmentService } from '../services/attachment.service'; -import { TAttachmentContext } from '../types'; -import { isAttachmentContext, isAttachmentContextArray } from '../utilities'; +import { TAttachmentResourceRef } from '../types'; +import { + isAttachmentResourceRef, + isAttachmentResourceRefArray, +} from '../utilities'; @Injectable() export class AttachmentGuard implements CanActivate { @@ -39,9 +42,9 @@ export class AttachmentGuard implements CanActivate { private permissionMap: Record< Action, - Record + Record > = { - // Read attachments by context + // Read attachments by ref [Action.READ]: { setting_attachment: [ ['setting', Action.READ], @@ -62,7 +65,7 @@ export class AttachmentGuard implements CanActivate { ['attachment', Action.READ], ], }, - // Create attachments by context + // Create attachments by ref [Action.CREATE]: { setting_attachment: [ ['setting', Action.UPDATE], @@ -88,7 +91,7 @@ export class AttachmentGuard implements CanActivate { ['attachment', Action.CREATE], ], }, - // Delete attachments by context + // Delete attachments by ref [Action.DELETE]: { setting_attachment: [ ['setting', Action.UPDATE], @@ -156,27 +159,27 @@ export class AttachmentGuard implements CanActivate { } /** - * Checks if the user is authorized to perform a given action on a attachment based on its context and user roles. + * Checks if the user is authorized to perform a given action on a attachment based on the resource reference and user roles. * * @param action - The action on the attachment. * @param user - The current user. - * @param context - The context of the attachment (e.g., user_avatar, setting_attachment). + * @param resourceRef - The resource ref of the attachment (e.g., user_avatar, setting_attachment). * @returns A promise that resolves to `true` if the user has the required upload permission, otherwise `false`. */ private async isAuthorized( action: Action, user: Express.User & User, - context: TAttachmentContext, + resourceRef: TAttachmentResourceRef, ): Promise { if (!action) { throw new TypeError('Invalid action'); } - if (!context) { - throw new TypeError('Invalid context'); + if (!resourceRef) { + throw new TypeError('Invalid resource ref'); } - const permissions = this.permissionMap[action][context]; + const permissions = this.permissionMap[action][resourceRef]; if (!permissions.length) { return false; @@ -214,17 +217,21 @@ export class AttachmentGuard implements CanActivate { throw new NotFoundException('Attachment not found!'); } - return await this.isAuthorized(Action.READ, user, attachment.context); + return await this.isAuthorized( + Action.READ, + user, + attachment.resourceRef, + ); } else if (query.where) { - const { context = [] } = query.where as qs.ParsedQs; + const { resourceRef = [] } = query.where as qs.ParsedQs; - if (!isAttachmentContextArray(context)) { - throw new BadRequestException('Invalid context param'); + if (!isAttachmentResourceRefArray(resourceRef)) { + throw new BadRequestException('Invalid resource ref'); } return ( await Promise.all( - context.map((c) => this.isAuthorized(Action.READ, user, c)), + resourceRef.map((c) => this.isAuthorized(Action.READ, user, c)), ) ).every(Boolean); } else { @@ -233,12 +240,12 @@ export class AttachmentGuard implements CanActivate { } // upload() endpoint case 'POST': { - const { context = '' } = query; - if (!isAttachmentContext(context)) { - throw new BadRequestException('Invalid context param'); + const { resourceRef = '' } = query; + if (!isAttachmentResourceRef(resourceRef)) { + throw new BadRequestException('Invalid resource ref'); } - return await this.isAuthorized(Action.CREATE, user, context); + return await this.isAuthorized(Action.CREATE, user, resourceRef); } // deleteOne() endpoint case 'DELETE': { @@ -252,7 +259,7 @@ export class AttachmentGuard implements CanActivate { return await this.isAuthorized( Action.DELETE, user, - attachment.context, + attachment.resourceRef, ); } else { throw new BadRequestException('Invalid params'); diff --git a/api/src/attachment/mocks/attachment.mock.ts b/api/src/attachment/mocks/attachment.mock.ts index dde89f84..fd54334f 100644 --- a/api/src/attachment/mocks/attachment.mock.ts +++ b/api/src/attachment/mocks/attachment.mock.ts @@ -16,7 +16,7 @@ export const attachment: Attachment = { size: 343370, location: '/Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png', - context: 'block_attachment', + resourceRef: 'block_attachment', access: 'public', id: '65940d115178607da65c82b6', createdAt: new Date(), @@ -47,7 +47,7 @@ export const attachments: Attachment[] = [ location: '/app/src/attachment/uploads/Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png', channel: { ['some-channel']: {} }, - context: 'block_attachment', + resourceRef: 'block_attachment', access: 'public', id: '65940d115178607da65c82b7', createdAt: new Date(), @@ -62,7 +62,7 @@ export const attachments: Attachment[] = [ location: '/app/src/attachment/uploads/Screenshot from 2022-03-18 08-58-15-af61e7f71281f9fd3f1ad7ad10107741c.png', channel: { ['some-channel']: {} }, - context: 'block_attachment', + resourceRef: 'block_attachment', access: 'public', id: '65940d115178607da65c82b8', createdAt: new Date(), diff --git a/api/src/attachment/schemas/attachment.schema.ts b/api/src/attachment/schemas/attachment.schema.ts index 1a23f370..7d8e6b8e 100644 --- a/api/src/attachment/schemas/attachment.schema.ts +++ b/api/src/attachment/schemas/attachment.schema.ts @@ -25,11 +25,11 @@ import { import { AttachmentAccess, - AttachmentContext, AttachmentCreatedByRef, + AttachmentResourceRef, TAttachmentAccess, - TAttachmentContext, TAttachmentCreatedByRef, + TAttachmentResourceRef, } from '../types'; import { MIME_REGEX } from '../utilities'; @@ -97,13 +97,13 @@ export class AttachmentStub extends BaseSchema { createdByRef: TAttachmentCreatedByRef; /** - * Context of the attachment + * Resource reference of the attachment */ - @Prop({ type: String, enum: Object.values(AttachmentContext) }) - context: TAttachmentContext; + @Prop({ type: String, enum: Object.values(AttachmentResourceRef) }) + resourceRef: TAttachmentResourceRef; /** - * Context of the attachment + * Access level of the attachment */ @Prop({ type: String, enum: Object.values(AttachmentAccess) }) access: TAttachmentAccess; diff --git a/api/src/attachment/services/attachment.service.ts b/api/src/attachment/services/attachment.service.ts index b81e84b6..c26dea8d 100644 --- a/api/src/attachment/services/attachment.service.ts +++ b/api/src/attachment/services/attachment.service.ts @@ -30,7 +30,7 @@ import { BaseService } from '@/utils/generics/base-service'; import { AttachmentMetadataDto } from '../dto/attachment.dto'; import { AttachmentRepository } from '../repositories/attachment.repository'; import { Attachment } from '../schemas/attachment.schema'; -import { TAttachmentContext } from '../types'; +import { TAttachmentResourceRef } from '../types'; import { fileExists, generateUniqueFilename, @@ -158,13 +158,13 @@ export class AttachmentService extends BaseService { } /** - * Get the attachment root directory given the context + * Get the attachment root directory given the resource reference * - * @param context The attachment context + * @param ref The attachment resource reference * @returns The root directory path */ - getRootDirByContext(context: TAttachmentContext) { - return context === 'subscriber_avatar' || context === 'user_avatar' + getRootDirByResourceRef(ref: TAttachmentResourceRef) { + return ref === 'subscriber_avatar' || ref === 'user_avatar' ? config.parameters.avatarDir : config.parameters.uploadDir; } @@ -186,7 +186,7 @@ export class AttachmentService extends BaseService { const storedDto = await this.getStoragePlugin()?.store?.(file, metadata); return storedDto ? await this.create(storedDto) : null; } else { - const rootDir = this.getRootDirByContext(metadata.context); + const rootDir = this.getRootDirByResourceRef(metadata.resourceRef); const uniqueFilename = generateUniqueFilename(metadata.name); const filePath = resolve(join(rootDir, sanitizeFilename(uniqueFilename))); @@ -244,7 +244,7 @@ export class AttachmentService extends BaseService { return streamableFile; } else { - const rootDir = this.getRootDirByContext(attachment.context); + const rootDir = this.getRootDirByResourceRef(attachment.resourceRef); const path = resolve(join(rootDir, attachment.location)); if (!fileExists(path)) { diff --git a/api/src/attachment/types/index.ts b/api/src/attachment/types/index.ts index cf3f0ce3..267410bb 100644 --- a/api/src/attachment/types/index.ts +++ b/api/src/attachment/types/index.ts @@ -20,10 +20,10 @@ export enum AttachmentCreatedByRef { export type TAttachmentCreatedByRef = `${AttachmentCreatedByRef}`; /** - * Defines the various contexts in which an attachment can exist. - * These contexts influence how the attachment is uploaded, stored, and accessed: + * Defines the various resource references in which an attachment can exist. + * These resource references influence how the attachment is uploaded, stored, and accessed: */ -export enum AttachmentContext { +export enum AttachmentResourceRef { SettingAttachment = 'setting_attachment', // Attachments related to app settings, restricted to users with specific permissions. UserAvatar = 'user_avatar', // Avatar files for users, only the current user can upload, accessible to those with appropriate permissions. SubscriberAvatar = 'subscriber_avatar', // Avatar files for subscribers, uploaded programmatically, accessible to authorized users. @@ -32,7 +32,7 @@ export enum AttachmentContext { MessageAttachment = 'message_attachment', // Files sent or received via messages, uploaded programmatically, accessible to users with inbox permissions.; } -export type TAttachmentContext = `${AttachmentContext}`; +export type TAttachmentResourceRef = `${AttachmentResourceRef}`; export enum AttachmentAccess { Public = 'public', diff --git a/api/src/attachment/utilities/index.ts b/api/src/attachment/utilities/index.ts index 99c1c580..79649f76 100644 --- a/api/src/attachment/utilities/index.ts +++ b/api/src/attachment/utilities/index.ts @@ -15,7 +15,7 @@ import { v4 as uuidv4 } from 'uuid'; import { config } from '@/config'; -import { AttachmentContext, TAttachmentContext } from '../types'; +import { AttachmentResourceRef, TAttachmentResourceRef } from '../types'; export const MIME_REGEX = /^[a-z-]+\/[0-9a-z\-.]+$/gm; @@ -82,27 +82,30 @@ export const generateUniqueFilename = (originalname: string) => { }; /** - * Checks if the given context is of type TAttachmentContext. + * Checks if the given ref is of type TAttachmentResourceRef. * - * @param ctx - The context to check. - * @returns True if the context is of type TAttachmentContext, otherwise false. + * @param ref - The ref to check. + * @returns True if the ref is of type TAttachmentResourceRef, otherwise false. */ -export const isAttachmentContext = (ctx: any): ctx is TAttachmentContext => { - return Object.values(AttachmentContext).includes(ctx); +export const isAttachmentResourceRef = ( + ref: any, +): ref is TAttachmentResourceRef => { + return Object.values(AttachmentResourceRef).includes(ref); }; +AttachmentResourceRef; /** - * Checks if the given list is an array of TAttachmentContext. + * Checks if the given list is an array of TAttachmentResourceRef. * - * @param ctxList - The list of contexts to check. - * @returns True if all items in the list are of type TAttachmentContext, otherwise false. + * @param refList - The list of resource references to check. + * @returns True if all items in the list are of type TAttachmentResourceRef, otherwise false. */ -export const isAttachmentContextArray = ( - ctxList: any, -): ctxList is TAttachmentContext[] => { +export const isAttachmentResourceRefArray = ( + refList: any, +): refList is TAttachmentResourceRef[] => { return ( - Array.isArray(ctxList) && - ctxList.length > 0 && - ctxList.every(isAttachmentContext) + Array.isArray(refList) && + refList.length > 0 && + refList.every(isAttachmentResourceRef) ); }; diff --git a/api/src/channel/lib/Handler.ts b/api/src/channel/lib/Handler.ts index 53ccdefc..f524d819 100644 --- a/api/src/channel/lib/Handler.ts +++ b/api/src/channel/lib/Handler.ts @@ -258,7 +258,7 @@ export default abstract class ChannelHandler< name: `${name ? `${name}-` : ''}${uuidv4()}.${mime.extension(type)}`, type, size, - context: 'message_attachment', + resourceRef: 'message_attachment', access: 'private', createdByRef: 'Subscriber', createdBy: subscriber.id, diff --git a/api/src/channel/lib/__test__/common.mock.ts b/api/src/channel/lib/__test__/common.mock.ts index e825cac4..c23b6d06 100644 --- a/api/src/channel/lib/__test__/common.mock.ts +++ b/api/src/channel/lib/__test__/common.mock.ts @@ -88,7 +88,7 @@ const attachment: Attachment = { id: 'any-channel-attachment-id', }, }, - context: 'block_attachment', + resourceRef: 'block_attachment', access: 'public', createdByRef: 'User', createdBy: null, diff --git a/api/src/chat/services/chat.service.ts b/api/src/chat/services/chat.service.ts index 72b63a1a..25090cc5 100644 --- a/api/src/chat/services/chat.service.ts +++ b/api/src/chat/services/chat.service.ts @@ -280,7 +280,7 @@ export class ChatService { name: `avatar-${uuidv4()}.${extension}`, size, type, - context: 'subscriber_avatar', + resourceRef: 'subscriber_avatar', access: 'private', createdByRef: 'Subscriber', createdBy: subscriber.id, diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 5cc56eb0..eb70e8aa 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -625,7 +625,7 @@ export default abstract class BaseWebChannelHandler< name: data.name, size: Buffer.byteLength(data.file), type: data.type, - context: 'message_attachment', + resourceRef: 'message_attachment', access: 'private', createdByRef: 'Subscriber', createdBy: req.session?.web?.profile?.id, @@ -692,7 +692,7 @@ export default abstract class BaseWebChannelHandler< name: file.originalname, size: file.size, type: file.mimetype, - context: 'message_attachment', + resourceRef: 'message_attachment', access: 'private', createdByRef: 'Subscriber', createdBy: req.session.web.profile?.id, diff --git a/api/src/migration/migrations/1735836154221-v-2-2-0.migration.ts b/api/src/migration/migrations/1735836154221-v-2-2-0.migration.ts index dfcebfa0..e9c039e8 100644 --- a/api/src/migration/migrations/1735836154221-v-2-2-0.migration.ts +++ b/api/src/migration/migrations/1735836154221-v-2-2-0.migration.ts @@ -15,7 +15,10 @@ import { v4 as uuidv4 } from 'uuid'; import attachmentSchema, { Attachment, } from '@/attachment/schemas/attachment.schema'; -import { AttachmentContext, AttachmentCreatedByRef } from '@/attachment/types'; +import { + AttachmentCreatedByRef, + AttachmentResourceRef, +} from '@/attachment/types'; import blockSchema, { Block } from '@/chat/schemas/block.schema'; import messageSchema, { Message } from '@/chat/schemas/message.schema'; import subscriberSchema, { Subscriber } from '@/chat/schemas/subscriber.schema'; @@ -79,7 +82,7 @@ const populateBlockAttachments = async ({ logger }: MigrationServices) => { { _id: attachmentId }, { $set: { - context: AttachmentContext.BlockAttachment, + resourceRef: AttachmentResourceRef.BlockAttachment, access: 'public', createdByRef: AttachmentCreatedByRef.User, createdBy: user._id, @@ -103,7 +106,7 @@ const populateBlockAttachments = async ({ logger }: MigrationServices) => { }; /** - * Updates setting attachment documents to populate new attributes (context, createdBy, createdByRef) + * Updates setting attachment documents to populate new attributes (resourceRef, createdBy, createdByRef) * * @returns Resolves when the migration process is complete. */ @@ -130,7 +133,7 @@ const populateSettingAttachments = async ({ logger }: MigrationServices) => { { _id: setting.value }, { $set: { - context: AttachmentContext.SettingAttachment, + resourceRef: AttachmentResourceRef.SettingAttachment, access: 'public', createdByRef: AttachmentCreatedByRef.User, createdBy: user._id, @@ -148,7 +151,7 @@ const populateSettingAttachments = async ({ logger }: MigrationServices) => { }; /** - * Updates user attachment documents to populate new attributes (context, createdBy, createdByRef) + * Updates user attachment documents to populate new attributes (resourceRef, createdBy, createdByRef) * * @returns Resolves when the migration process is complete. */ @@ -169,7 +172,7 @@ const populateUserAvatars = async ({ logger }: MigrationServices) => { { _id: user.avatar }, { $set: { - context: AttachmentContext.UserAvatar, + resourceRef: AttachmentResourceRef.UserAvatar, access: 'private', createdByRef: AttachmentCreatedByRef.User, createdBy: user._id, @@ -187,7 +190,7 @@ const populateUserAvatars = async ({ logger }: MigrationServices) => { /** * Updates subscriber documents with their corresponding avatar attachments, - * populate new attributes (context, createdBy, createdByRef) and moves avatar files to a new directory. + * populate new attributes (resourceRef, createdBy, createdByRef) and moves avatar files to a new directory. * * @returns Resolves when the migration process is complete. */ @@ -231,7 +234,7 @@ const populateSubscriberAvatars = async ({ logger }: MigrationServices) => { { _id: attachment._id }, { $set: { - context: AttachmentContext.SubscriberAvatar, + resourceRef: AttachmentResourceRef.SubscriberAvatar, access: 'private', createdByRef: AttachmentCreatedByRef.Subscriber, createdBy: subscriber._id, @@ -353,18 +356,18 @@ const undoPopulateAttachments = async ({ logger }: MigrationServices) => { try { const result = await AttachmentModel.updateMany( { - context: { + resourceRef: { $in: [ - AttachmentContext.BlockAttachment, - AttachmentContext.SettingAttachment, - AttachmentContext.UserAvatar, - AttachmentContext.SubscriberAvatar, + AttachmentResourceRef.BlockAttachment, + AttachmentResourceRef.SettingAttachment, + AttachmentResourceRef.UserAvatar, + AttachmentResourceRef.SubscriberAvatar, ], }, }, { $unset: { - context: '', + resourceRef: '', access: '', createdByRef: '', createdBy: '', @@ -373,11 +376,11 @@ const undoPopulateAttachments = async ({ logger }: MigrationServices) => { ); logger.log( - `Successfully reverted attributes for ${result.modifiedCount} attachments with context 'setting_attachment'`, + `Successfully reverted attributes for ${result.modifiedCount} attachments with ref 'setting_attachment'`, ); } catch (error) { logger.error( - `Failed to revert attributes for attachments with context 'setting_attachment': ${error.message}`, + `Failed to revert attributes for attachments with ref 'setting_attachment': ${error.message}`, ); } }; @@ -645,7 +648,7 @@ const migrateAndPopulateAttachmentMessages = async ({ await attachmentService.updateOne( msg.message.attachment.payload.attachment_id as string, { - context: 'message_attachment', + resourceRef: 'message_attachment', access: 'private', createdByRef: msg.sender ? 'Subscriber' : 'User', createdBy: msg.sender ? msg.sender : adminUser.id, @@ -678,7 +681,7 @@ const migrateAndPopulateAttachmentMessages = async ({ size: fileBuffer.length, type: response.headers['content-type'], channel: {}, - context: 'message_attachment', + resourceRef: 'message_attachment', access: msg.sender ? 'private' : 'public', createdBy: msg.sender ? msg.sender : adminUser.id, createdByRef: msg.sender ? 'Subscriber' : 'User', diff --git a/api/src/user/controllers/user.controller.ts b/api/src/user/controllers/user.controller.ts index a322ceff..1af1674b 100644 --- a/api/src/user/controllers/user.controller.ts +++ b/api/src/user/controllers/user.controller.ts @@ -294,7 +294,7 @@ export class ReadWriteUserController extends ReadOnlyUserController { name: avatarFile.originalname, size: avatarFile.size, type: avatarFile.mimetype, - context: 'user_avatar', + resourceRef: 'user_avatar', access: 'private', createdByRef: 'User', createdBy: req.user.id, diff --git a/api/src/utils/test/fixtures/attachment.ts b/api/src/utils/test/fixtures/attachment.ts index cfbe046f..cece4802 100644 --- a/api/src/utils/test/fixtures/attachment.ts +++ b/api/src/utils/test/fixtures/attachment.ts @@ -22,7 +22,7 @@ export const attachmentFixtures: AttachmentCreateDto[] = [ id: '1', }, }, - context: 'content_attachment', + resourceRef: 'content_attachment', access: 'public', createdByRef: 'User', createdBy: '9'.repeat(24), @@ -37,7 +37,7 @@ export const attachmentFixtures: AttachmentCreateDto[] = [ id: '2', }, }, - context: 'content_attachment', + resourceRef: 'content_attachment', access: 'public', createdByRef: 'User', createdBy: '9'.repeat(24), diff --git a/frontend/src/app-components/attachment/AttachmentInput.tsx b/frontend/src/app-components/attachment/AttachmentInput.tsx index 67edd5da..7ed77c6d 100644 --- a/frontend/src/app-components/attachment/AttachmentInput.tsx +++ b/frontend/src/app-components/attachment/AttachmentInput.tsx @@ -13,7 +13,7 @@ import { forwardRef } from "react"; import { useGet } from "@/hooks/crud/useGet"; import { useHasPermission } from "@/hooks/useHasPermission"; import { EntityType } from "@/services/types"; -import { IAttachment, TAttachmentContext } from "@/types/attachment.types"; +import { IAttachment, TAttachmentResourceRef } from "@/types/attachment.types"; import { PermissionAction } from "@/types/permission.types"; import AttachmentThumbnail from "./AttachmentThumbnail"; @@ -29,7 +29,7 @@ type AttachmentThumbnailProps = { onChange?: (id: string | null, mimeType: string | null) => void; error?: boolean; helperText?: string; - context: TAttachmentContext; + resourceRef: TAttachmentResourceRef; }; const AttachmentInput = forwardRef( @@ -44,7 +44,7 @@ const AttachmentInput = forwardRef( onChange, error, helperText, - context, + resourceRef, }, ref, ) => { @@ -84,7 +84,7 @@ const AttachmentInput = forwardRef( accept={accept} enableMediaLibrary={enableMediaLibrary} onChange={handleChange} - context={context} + resourceRef={resourceRef} /> ) : null} {helperText ? ( diff --git a/frontend/src/app-components/attachment/AttachmentUploader.tsx b/frontend/src/app-components/attachment/AttachmentUploader.tsx index a2d662c1..112f111c 100644 --- a/frontend/src/app-components/attachment/AttachmentUploader.tsx +++ b/frontend/src/app-components/attachment/AttachmentUploader.tsx @@ -17,7 +17,7 @@ import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { useToast } from "@/hooks/useToast"; import { useTranslate } from "@/hooks/useTranslate"; import { EntityType } from "@/services/types"; -import { IAttachment, TAttachmentContext } from "@/types/attachment.types"; +import { IAttachment, TAttachmentResourceRef } from "@/types/attachment.types"; import { AttachmentDialog } from "./AttachmentDialog"; import AttachmentThumbnail from "./AttachmentThumbnail"; @@ -68,7 +68,7 @@ export type FileUploadProps = { enableMediaLibrary?: boolean; onChange?: (data?: IAttachment | null) => void; onUploadComplete?: () => void; - context: TAttachmentContext; + resourceRef: TAttachmentResourceRef; }; const AttachmentUploader: FC = ({ @@ -76,7 +76,7 @@ const AttachmentUploader: FC = ({ enableMediaLibrary, onChange, onUploadComplete, - context, + resourceRef, }) => { const [attachment, setAttachment] = useState( undefined, @@ -119,7 +119,7 @@ const AttachmentUploader: FC = ({ return; } - uploadAttachment({ file, context }); + uploadAttachment({ file, resourceRef }); } }; const handleChange = (event: ChangeEvent) => { diff --git a/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx b/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx index 80b76283..11a3e069 100644 --- a/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx +++ b/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx @@ -12,7 +12,7 @@ import { forwardRef, useState } from "react"; import { useHasPermission } from "@/hooks/useHasPermission"; import { EntityType } from "@/services/types"; -import { IAttachment, TAttachmentContext } from "@/types/attachment.types"; +import { IAttachment, TAttachmentResourceRef } from "@/types/attachment.types"; import { PermissionAction } from "@/types/permission.types"; import AttachmentThumbnail from "./AttachmentThumbnail"; @@ -28,7 +28,7 @@ type MultipleAttachmentInputProps = { onChange?: (ids: string[]) => void; error?: boolean; helperText?: string; - context: TAttachmentContext; + resourceRef: TAttachmentResourceRef; }; const MultipleAttachmentInput = forwardRef< @@ -46,7 +46,7 @@ const MultipleAttachmentInput = forwardRef< onChange, error, helperText, - context, + resourceRef, }, ref, ) => { @@ -109,7 +109,7 @@ const MultipleAttachmentInput = forwardRef< accept={accept} enableMediaLibrary={enableMediaLibrary} onChange={(attachment) => handleChange(attachment)} - context={context} + resourceRef={resourceRef} /> )} {helperText && ( diff --git a/frontend/src/components/contents/ContentDialog.tsx b/frontend/src/components/contents/ContentDialog.tsx index 885dfe73..761e9eec 100644 --- a/frontend/src/components/contents/ContentDialog.tsx +++ b/frontend/src/components/contents/ContentDialog.tsx @@ -116,7 +116,7 @@ const ContentFieldInput: React.FC = ({ value={field.value?.payload?.id} accept={MIME_TYPES["images"].join(",")} format="full" - context="content_attachment" + resourceRef="content_attachment" /> ); default: diff --git a/frontend/src/components/contents/ContentImportDialog.tsx b/frontend/src/components/contents/ContentImportDialog.tsx index 61855149..ba1bece0 100644 --- a/frontend/src/components/contents/ContentImportDialog.tsx +++ b/frontend/src/components/contents/ContentImportDialog.tsx @@ -81,7 +81,7 @@ export const ContentImportDialog: FC = ({ }} label="" value={attachmentId} - context="content_attachment" + resourceRef="content_attachment" /> diff --git a/frontend/src/components/settings/SettingInput.tsx b/frontend/src/components/settings/SettingInput.tsx index fd41381c..174f3eea 100644 --- a/frontend/src/components/settings/SettingInput.tsx +++ b/frontend/src/components/settings/SettingInput.tsx @@ -186,7 +186,7 @@ const SettingInput: React.FC = ({ accept={MIME_TYPES["images"].join(",")} format="full" size={128} - context="setting_attachment" + resourceRef="setting_attachment" /> ); @@ -199,7 +199,7 @@ const SettingInput: React.FC = ({ accept={MIME_TYPES["images"].join(",")} format="full" size={128} - context="setting_attachment" + resourceRef="setting_attachment" /> ); default: diff --git a/frontend/src/components/visual-editor/form/AttachmentMessageForm.tsx b/frontend/src/components/visual-editor/form/AttachmentMessageForm.tsx index 0579d9dd..a5b12b2e 100644 --- a/frontend/src/components/visual-editor/form/AttachmentMessageForm.tsx +++ b/frontend/src/components/visual-editor/form/AttachmentMessageForm.tsx @@ -69,7 +69,7 @@ const AttachmentMessageForm = () => { }, }); }} - context="block_attachment" + resourceRef="block_attachment" /> ); }} diff --git a/frontend/src/hooks/crud/useUpload.tsx b/frontend/src/hooks/crud/useUpload.tsx index 342645a9..5f5a5e44 100644 --- a/frontend/src/hooks/crud/useUpload.tsx +++ b/frontend/src/hooks/crud/useUpload.tsx @@ -9,7 +9,7 @@ import { useMutation, useQueryClient } from "react-query"; import { QueryType, TMutationOptions } from "@/services/types"; -import { TAttachmentContext } from "@/types/attachment.types"; +import { TAttachmentResourceRef } from "@/types/attachment.types"; import { IBaseSchema, IDynamicProps, TType } from "@/types/base.types"; import { useEntityApiClient } from "../useApiClient"; @@ -27,7 +27,7 @@ export const useUpload = < TMutationOptions< TBasic, Error, - { file: File; context: TAttachmentContext }, + { file: File; resourceRef: TAttachmentResourceRef }, TBasic >, "mutationFn" | "mutationKey" @@ -39,8 +39,8 @@ export const useUpload = < const { invalidate = true, ...otherOptions } = options || {}; return useMutation({ - mutationFn: async ({ file, context }) => { - const data = await api.upload(file, context); + mutationFn: async ({ file, resourceRef }) => { + const data = await api.upload(file, resourceRef); const { entities, result } = normalizeAndCache(data); // Invalidate all counts & collections diff --git a/frontend/src/services/api.class.ts b/frontend/src/services/api.class.ts index dc062504..ddf14d0c 100644 --- a/frontend/src/services/api.class.ts +++ b/frontend/src/services/api.class.ts @@ -9,7 +9,7 @@ import { AxiosInstance, AxiosResponse } from "axios"; -import { TAttachmentContext } from "@/types/attachment.types"; +import { TAttachmentResourceRef } from "@/types/attachment.types"; import { ILoginAttributes } from "@/types/auth/login.types"; import { IUserPermissions } from "@/types/auth/permission.types"; import { StatsType } from "@/types/bot-stat.types"; @@ -302,7 +302,7 @@ export class EntityApiClient extends ApiClient { return data; } - async upload(file: File, context?: TAttachmentContext) { + async upload(file: File, resourceRef?: TAttachmentResourceRef) { const { _csrf } = await this.getCsrf(); const formData = new FormData(); @@ -314,7 +314,7 @@ export class EntityApiClient extends ApiClient { FormData >( `${ROUTES[this.type]}/upload?_csrf=${_csrf}${ - context ? `&context=${context}` : "" + resourceRef ? `&resourceRef=${resourceRef}` : "" }`, formData, { diff --git a/frontend/src/types/attachment.types.ts b/frontend/src/types/attachment.types.ts index aafd5aac..213ea268 100644 --- a/frontend/src/types/attachment.types.ts +++ b/frontend/src/types/attachment.types.ts @@ -25,10 +25,10 @@ export enum AttachmentCreatedByRef { export type TAttachmentCreatedByRef = `${AttachmentCreatedByRef}`; /** - * Defines the various contexts in which an attachment can exist. - * These contexts influence how the attachment is uploaded, stored, and accessed: + * Defines the various resource references in which an attachment can exist. + * These references influence how the attachment is uploaded, stored, and accessed: */ -export enum AttachmentContext { +export enum AttachmentResourceRef { SettingAttachment = "setting_attachment", // Attachments related to app settings, restricted to users with specific permissions. UserAvatar = "user_avatar", // Avatar files for users, only the current user can upload, accessible to those with appropriate permissions. SubscriberAvatar = "subscriber_avatar", // Avatar files for subscribers, uploaded programmatically, accessible to authorized users. @@ -37,7 +37,7 @@ export enum AttachmentContext { MessageAttachment = "message_attachment", // Files sent or received via messages, uploaded programmatically, accessible to users with inbox permissions.; } -export type TAttachmentContext = `${AttachmentContext}`; +export type TAttachmentResourceRef = `${AttachmentResourceRef}`; export interface IAttachmentAttributes { name: string; @@ -46,7 +46,7 @@ export interface IAttachmentAttributes { location: string; url: string; channel?: Record; - context: TAttachmentContext; + resourceRef: TAttachmentResourceRef; createdByRef: TAttachmentCreatedByRef; createdBy: string | null; }