feat: rename enum instead of string

This commit is contained in:
Mohamed Marrouchi 2025-01-16 10:14:50 +01:00
parent 3f9dd692bf
commit c27f37a6e6
18 changed files with 116 additions and 70 deletions

View File

@ -39,6 +39,7 @@ import { attachment, attachmentFile } from '../mocks/attachment.mock';
import { AttachmentRepository } from '../repositories/attachment.repository'; import { AttachmentRepository } from '../repositories/attachment.repository';
import { Attachment, AttachmentModel } from '../schemas/attachment.schema'; import { Attachment, AttachmentModel } from '../schemas/attachment.schema';
import { AttachmentService } from '../services/attachment.service'; import { AttachmentService } from '../services/attachment.service';
import { AttachmentResourceRef } from '../types';
import { AttachmentController } from './attachment.controller'; import { AttachmentController } from './attachment.controller';
@ -110,7 +111,7 @@ describe('AttachmentController', () => {
file: [], file: [],
}, },
{} as Request, {} as Request,
{ resourceRef: 'block_attachment' }, { resourceRef: AttachmentResourceRef.BlockAttachment },
); );
await expect(promiseResult).rejects.toThrow( await expect(promiseResult).rejects.toThrow(
new BadRequestException('No file was selected'), new BadRequestException('No file was selected'),
@ -128,7 +129,7 @@ describe('AttachmentController', () => {
{ {
session: { passport: { user: { id: '9'.repeat(24) } } }, session: { passport: { user: { id: '9'.repeat(24) } } },
} as unknown as Request, } as unknown as Request,
{ resourceRef: 'block_attachment' }, { resourceRef: AttachmentResourceRef.BlockAttachment },
); );
const [name] = attachmentFile.filename.split('.'); const [name] = attachmentFile.filename.split('.');
expect(attachmentService.create).toHaveBeenCalledWith({ expect(attachmentService.create).toHaveBeenCalledWith({
@ -136,7 +137,7 @@ describe('AttachmentController', () => {
type: attachmentFile.mimetype, type: attachmentFile.mimetype,
name: attachmentFile.originalname, name: attachmentFile.originalname,
location: expect.stringMatching(new RegExp(`^/${name}`)), location: expect.stringMatching(new RegExp(`^/${name}`)),
resourceRef: 'block_attachment', resourceRef: AttachmentResourceRef.BlockAttachment,
access: 'public', access: 'public',
createdByRef: 'User', createdByRef: 'User',
createdBy: '9'.repeat(24), createdBy: '9'.repeat(24),
@ -145,7 +146,7 @@ describe('AttachmentController', () => {
[ [
{ {
...attachment, ...attachment,
resourceRef: 'block_attachment', resourceRef: AttachmentResourceRef.BlockAttachment,
createdByRef: 'User', createdByRef: 'User',
createdBy: '9'.repeat(24), createdBy: '9'.repeat(24),
}, },

View File

@ -18,6 +18,7 @@ import { Action } from '@/user/types/action.type';
import { attachment } from '../mocks/attachment.mock'; import { attachment } from '../mocks/attachment.mock';
import { Attachment } from '../schemas/attachment.schema'; import { Attachment } from '../schemas/attachment.schema';
import { AttachmentService } from '../services/attachment.service'; import { AttachmentService } from '../services/attachment.service';
import { AttachmentResourceRef } from '../types';
import { AttachmentGuard } from './attachment-ability.guard'; import { AttachmentGuard } from './attachment-ability.guard';
@ -55,7 +56,7 @@ describe('AttachmentGuard', () => {
describe('canActivate', () => { describe('canActivate', () => {
it('should allow GET requests with valid ref', async () => { it('should allow GET requests with valid ref', async () => {
const mockUser = { roles: ['admin-id'] } as any; const mockUser = { roles: ['admin-id'] } as any;
const mockRef = ['user_avatar']; const mockRef = [AttachmentResourceRef.UserAvatar];
jest.spyOn(modelService, 'findOne').mockImplementation((criteria) => { jest.spyOn(modelService, 'findOne').mockImplementation((criteria) => {
return typeof criteria === 'string' || return typeof criteria === 'string' ||
@ -120,7 +121,7 @@ describe('AttachmentGuard', () => {
? Promise.reject('Invalid ID') ? Promise.reject('Invalid ID')
: Promise.resolve({ : Promise.resolve({
id: '9'.repeat(24), id: '9'.repeat(24),
resourceRef: `user_avatar`, resourceRef: AttachmentResourceRef.UserAvatar,
} as Attachment); } as Attachment);
}); });
@ -191,7 +192,7 @@ describe('AttachmentGuard', () => {
const mockExecutionContext = { const mockExecutionContext = {
switchToHttp: jest.fn().mockReturnValue({ switchToHttp: jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue({ getRequest: jest.fn().mockReturnValue({
query: { resourceRef: 'block_attachment' }, query: { resourceRef: AttachmentResourceRef.BlockAttachment },
method: 'POST', method: 'POST',
user: mockUser, user: mockUser,
}), }),

View File

@ -26,7 +26,7 @@ import { Action } from '@/user/types/action.type';
import { TModel } from '@/user/types/model.type'; import { TModel } from '@/user/types/model.type';
import { AttachmentService } from '../services/attachment.service'; import { AttachmentService } from '../services/attachment.service';
import { TAttachmentResourceRef } from '../types'; import { AttachmentResourceRef, TAttachmentResourceRef } from '../types';
import { import {
isAttachmentResourceRef, isAttachmentResourceRef,
isAttachmentResourceRefArray, isAttachmentResourceRefArray,
@ -46,46 +46,46 @@ export class AttachmentGuard implements CanActivate {
> = { > = {
// Read attachments by ref // Read attachments by ref
[Action.READ]: { [Action.READ]: {
setting_attachment: [ [AttachmentResourceRef.SettingAttachment]: [
['setting', Action.READ], ['setting', Action.READ],
['attachment', Action.READ], ['attachment', Action.READ],
], ],
user_avatar: [['user', Action.READ]], [AttachmentResourceRef.UserAvatar]: [['user', Action.READ]],
block_attachment: [ [AttachmentResourceRef.BlockAttachment]: [
['block', Action.READ], ['block', Action.READ],
['attachment', Action.READ], ['attachment', Action.READ],
], ],
content_attachment: [ [AttachmentResourceRef.ContentAttachment]: [
['content', Action.READ], ['content', Action.READ],
['attachment', Action.READ], ['attachment', Action.READ],
], ],
subscriber_avatar: [['subscriber', Action.READ]], [AttachmentResourceRef.SubscriberAvatar]: [['subscriber', Action.READ]],
message_attachment: [ [AttachmentResourceRef.MessageAttachment]: [
['message', Action.READ], ['message', Action.READ],
['attachment', Action.READ], ['attachment', Action.READ],
], ],
}, },
// Create attachments by ref // Create attachments by ref
[Action.CREATE]: { [Action.CREATE]: {
setting_attachment: [ [AttachmentResourceRef.SettingAttachment]: [
['setting', Action.UPDATE], ['setting', Action.UPDATE],
['attachment', Action.CREATE], ['attachment', Action.CREATE],
], ],
user_avatar: [ [AttachmentResourceRef.UserAvatar]: [
// Not authorized, done via /user/:id/edit endpoint // Not authorized, done via /user/:id/edit endpoint
], ],
block_attachment: [ [AttachmentResourceRef.BlockAttachment]: [
['block', Action.UPDATE], ['block', Action.UPDATE],
['attachment', Action.CREATE], ['attachment', Action.CREATE],
], ],
content_attachment: [ [AttachmentResourceRef.ContentAttachment]: [
['content', Action.UPDATE], ['content', Action.UPDATE],
['attachment', Action.CREATE], ['attachment', Action.CREATE],
], ],
subscriber_avatar: [ [AttachmentResourceRef.SubscriberAvatar]: [
// Not authorized, done programmatically by the channel // Not authorized, done programmatically by the channel
], ],
message_attachment: [ [AttachmentResourceRef.MessageAttachment]: [
// Unless we're in case of a handover, done programmatically by the channel // Unless we're in case of a handover, done programmatically by the channel
['message', Action.CREATE], ['message', Action.CREATE],
['attachment', Action.CREATE], ['attachment', Action.CREATE],
@ -93,36 +93,36 @@ export class AttachmentGuard implements CanActivate {
}, },
// Delete attachments by ref // Delete attachments by ref
[Action.DELETE]: { [Action.DELETE]: {
setting_attachment: [ [AttachmentResourceRef.SettingAttachment]: [
['setting', Action.UPDATE], ['setting', Action.UPDATE],
['attachment', Action.DELETE], ['attachment', Action.DELETE],
], ],
user_avatar: [ [AttachmentResourceRef.UserAvatar]: [
// Not authorized // Not authorized
], ],
block_attachment: [ [AttachmentResourceRef.BlockAttachment]: [
['block', Action.UPDATE], ['block', Action.UPDATE],
['attachment', Action.DELETE], ['attachment', Action.DELETE],
], ],
content_attachment: [ [AttachmentResourceRef.ContentAttachment]: [
['content', Action.UPDATE], ['content', Action.UPDATE],
['attachment', Action.DELETE], ['attachment', Action.DELETE],
], ],
subscriber_avatar: [ [AttachmentResourceRef.SubscriberAvatar]: [
// Not authorized, done programmatically by the channel // Not authorized, done programmatically by the channel
], ],
message_attachment: [ [AttachmentResourceRef.MessageAttachment]: [
// Not authorized // Not authorized
], ],
}, },
// Update attachments is not possible // Update attachments is not possible
[Action.UPDATE]: { [Action.UPDATE]: {
setting_attachment: [], [AttachmentResourceRef.SettingAttachment]: [],
user_avatar: [], [AttachmentResourceRef.UserAvatar]: [],
block_attachment: [], [AttachmentResourceRef.BlockAttachment]: [],
content_attachment: [], [AttachmentResourceRef.ContentAttachment]: [],
subscriber_avatar: [], [AttachmentResourceRef.SubscriberAvatar]: [],
message_attachment: [], [AttachmentResourceRef.MessageAttachment]: [],
}, },
}; };
@ -163,7 +163,7 @@ export class AttachmentGuard implements CanActivate {
* *
* @param action - The action on the attachment. * @param action - The action on the attachment.
* @param user - The current user. * @param user - The current user.
* @param resourceRef - The resource ref of the attachment (e.g., user_avatar, setting_attachment). * @param resourceRef - The resource ref of the attachment (e.g., [AttachmentResourceRef.UserAvatar], [AttachmentResourceRef.SettingAttachment]).
* @returns A promise that resolves to `true` if the user has the required upload permission, otherwise `false`. * @returns A promise that resolves to `true` if the user has the required upload permission, otherwise `false`.
*/ */
private async isAuthorized( private async isAuthorized(

View File

@ -9,6 +9,7 @@
import { Stream } from 'node:stream'; import { Stream } from 'node:stream';
import { Attachment } from '../schemas/attachment.schema'; import { Attachment } from '../schemas/attachment.schema';
import { AttachmentResourceRef } from '../types';
export const attachment: Attachment = { export const attachment: Attachment = {
name: 'Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png', name: 'Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png',
@ -16,7 +17,7 @@ export const attachment: Attachment = {
size: 343370, size: 343370,
location: location:
'/Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png', '/Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png',
resourceRef: 'block_attachment', resourceRef: AttachmentResourceRef.BlockAttachment,
access: 'public', access: 'public',
id: '65940d115178607da65c82b6', id: '65940d115178607da65c82b6',
createdAt: new Date(), createdAt: new Date(),
@ -47,7 +48,7 @@ export const attachments: Attachment[] = [
location: location:
'/app/src/attachment/uploads/Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png', '/app/src/attachment/uploads/Screenshot from 2022-03-11 08-41-27-2a9799a8b6109c88fd9a7a690c1101934c.png',
channel: { ['some-channel']: {} }, channel: { ['some-channel']: {} },
resourceRef: 'block_attachment', resourceRef: AttachmentResourceRef.BlockAttachment,
access: 'public', access: 'public',
id: '65940d115178607da65c82b7', id: '65940d115178607da65c82b7',
createdAt: new Date(), createdAt: new Date(),
@ -62,7 +63,7 @@ export const attachments: Attachment[] = [
location: location:
'/app/src/attachment/uploads/Screenshot from 2022-03-18 08-58-15-af61e7f71281f9fd3f1ad7ad10107741c.png', '/app/src/attachment/uploads/Screenshot from 2022-03-18 08-58-15-af61e7f71281f9fd3f1ad7ad10107741c.png',
channel: { ['some-channel']: {} }, channel: { ['some-channel']: {} },
resourceRef: 'block_attachment', resourceRef: AttachmentResourceRef.BlockAttachment,
access: 'public', access: 'public',
id: '65940d115178607da65c82b8', id: '65940d115178607da65c82b8',
createdAt: new Date(), createdAt: new Date(),

View File

@ -30,7 +30,7 @@ import { BaseService } from '@/utils/generics/base-service';
import { AttachmentMetadataDto } from '../dto/attachment.dto'; import { AttachmentMetadataDto } from '../dto/attachment.dto';
import { AttachmentRepository } from '../repositories/attachment.repository'; import { AttachmentRepository } from '../repositories/attachment.repository';
import { Attachment } from '../schemas/attachment.schema'; import { Attachment } from '../schemas/attachment.schema';
import { TAttachmentResourceRef } from '../types'; import { AttachmentResourceRef, TAttachmentResourceRef } from '../types';
import { import {
fileExists, fileExists,
generateUniqueFilename, generateUniqueFilename,
@ -164,7 +164,8 @@ export class AttachmentService extends BaseService<Attachment> {
* @returns The root directory path * @returns The root directory path
*/ */
getRootDirByResourceRef(ref: TAttachmentResourceRef) { getRootDirByResourceRef(ref: TAttachmentResourceRef) {
return ref === 'subscriber_avatar' || ref === 'user_avatar' return ref === AttachmentResourceRef.SubscriberAvatar ||
ref === AttachmentResourceRef.UserAvatar
? config.parameters.avatarDir ? config.parameters.avatarDir
: config.parameters.uploadDir; : config.parameters.uploadDir;
} }

View File

@ -24,12 +24,12 @@ export type TAttachmentCreatedByRef = `${AttachmentCreatedByRef}`;
* These resource references influence how the attachment is uploaded, stored, and accessed: * These resource references influence how the attachment is uploaded, stored, and accessed:
*/ */
export enum AttachmentResourceRef { export enum AttachmentResourceRef {
SettingAttachment = 'setting_attachment', // Attachments related to app settings, restricted to users with specific permissions. SettingAttachment = 'Setting', // 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. UserAvatar = 'User', // 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. SubscriberAvatar = 'Subscriber', // Avatar files for subscribers, uploaded programmatically, accessible to authorized users.
BlockAttachment = 'block_attachment', // Files sent by the bot, public or private based on the channel and user authentication. BlockAttachment = 'Block', // Files sent by the bot, public or private based on the channel and user authentication.
ContentAttachment = 'content_attachment', // Files in the knowledge base, usually public but could vary based on specific needs. ContentAttachment = 'Content', // Files in the knowledge base, usually public but could vary based on specific needs.
MessageAttachment = 'message_attachment', // Files sent or received via messages, uploaded programmatically, accessible to users with inbox permissions.; MessageAttachment = 'Message', // Files sent or received via messages, uploaded programmatically, accessible to users with inbox permissions.;
} }
export type TAttachmentResourceRef = `${AttachmentResourceRef}`; export type TAttachmentResourceRef = `${AttachmentResourceRef}`;

View File

@ -22,7 +22,7 @@ import { v4 as uuidv4 } from 'uuid';
import { Attachment } from '@/attachment/schemas/attachment.schema'; import { Attachment } from '@/attachment/schemas/attachment.schema';
import { AttachmentService } from '@/attachment/services/attachment.service'; import { AttachmentService } from '@/attachment/services/attachment.service';
import { AttachmentFile } from '@/attachment/types'; import { AttachmentFile, AttachmentResourceRef } from '@/attachment/types';
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto'; import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
import { AttachmentRef } from '@/chat/schemas/types/attachment'; import { AttachmentRef } from '@/chat/schemas/types/attachment';
import { import {
@ -258,7 +258,7 @@ export default abstract class ChannelHandler<
name: `${name ? `${name}-` : ''}${uuidv4()}.${mime.extension(type)}`, name: `${name ? `${name}-` : ''}${uuidv4()}.${mime.extension(type)}`,
type, type,
size, size,
resourceRef: 'message_attachment', resourceRef: AttachmentResourceRef.MessageAttachment,
access: 'private', access: 'private',
createdByRef: 'Subscriber', createdByRef: 'Subscriber',
createdBy: subscriber.id, createdBy: subscriber.id,

View File

@ -7,6 +7,7 @@
*/ */
import { Attachment } from '@/attachment/schemas/attachment.schema'; import { Attachment } from '@/attachment/schemas/attachment.schema';
import { AttachmentResourceRef } from '@/attachment/types';
import { ButtonType } from '@/chat/schemas/types/button'; import { ButtonType } from '@/chat/schemas/types/button';
import { import {
FileType, FileType,
@ -88,7 +89,7 @@ const attachment: Attachment = {
id: 'any-channel-attachment-id', id: 'any-channel-attachment-id',
}, },
}, },
resourceRef: 'block_attachment', resourceRef: AttachmentResourceRef.BlockAttachment,
access: 'public', access: 'public',
createdByRef: 'User', createdByRef: 'User',
createdBy: null, createdBy: null,

View File

@ -12,6 +12,7 @@ import mime from 'mime';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { AttachmentService } from '@/attachment/services/attachment.service'; import { AttachmentService } from '@/attachment/services/attachment.service';
import { AttachmentResourceRef } from '@/attachment/types';
import EventWrapper from '@/channel/lib/EventWrapper'; import EventWrapper from '@/channel/lib/EventWrapper';
import { config } from '@/config'; import { config } from '@/config';
import { HelperService } from '@/helper/helper.service'; import { HelperService } from '@/helper/helper.service';
@ -280,7 +281,7 @@ export class ChatService {
name: `avatar-${uuidv4()}.${extension}`, name: `avatar-${uuidv4()}.${extension}`,
size, size,
type, type,
resourceRef: 'subscriber_avatar', resourceRef: AttachmentResourceRef.SubscriberAvatar,
access: 'private', access: 'private',
createdByRef: 'Subscriber', createdByRef: 'Subscriber',
createdBy: subscriber.id, createdBy: subscriber.id,

View File

@ -15,6 +15,7 @@ import { v4 as uuidv4 } from 'uuid';
import { Attachment } from '@/attachment/schemas/attachment.schema'; import { Attachment } from '@/attachment/schemas/attachment.schema';
import { AttachmentService } from '@/attachment/services/attachment.service'; import { AttachmentService } from '@/attachment/services/attachment.service';
import { AttachmentResourceRef } from '@/attachment/types';
import { ChannelService } from '@/channel/channel.service'; import { ChannelService } from '@/channel/channel.service';
import ChannelHandler from '@/channel/lib/Handler'; import ChannelHandler from '@/channel/lib/Handler';
import { ChannelName } from '@/channel/types'; import { ChannelName } from '@/channel/types';
@ -625,7 +626,7 @@ export default abstract class BaseWebChannelHandler<
name: data.name, name: data.name,
size: Buffer.byteLength(data.file), size: Buffer.byteLength(data.file),
type: data.type, type: data.type,
resourceRef: 'message_attachment', resourceRef: AttachmentResourceRef.MessageAttachment,
access: 'private', access: 'private',
createdByRef: 'Subscriber', createdByRef: 'Subscriber',
createdBy: req.session?.web?.profile?.id, createdBy: req.session?.web?.profile?.id,
@ -692,7 +693,7 @@ export default abstract class BaseWebChannelHandler<
name: file.originalname, name: file.originalname,
size: file.size, size: file.size,
type: file.mimetype, type: file.mimetype,
resourceRef: 'message_attachment', resourceRef: AttachmentResourceRef.MessageAttachment,
access: 'private', access: 'private',
createdByRef: 'Subscriber', createdByRef: 'Subscriber',
createdBy: req.session.web.profile?.id, createdBy: req.session.web.profile?.id,

View File

@ -362,6 +362,8 @@ const undoPopulateAttachments = async ({ logger }: MigrationServices) => {
AttachmentResourceRef.SettingAttachment, AttachmentResourceRef.SettingAttachment,
AttachmentResourceRef.UserAvatar, AttachmentResourceRef.UserAvatar,
AttachmentResourceRef.SubscriberAvatar, AttachmentResourceRef.SubscriberAvatar,
AttachmentResourceRef.ContentAttachment,
AttachmentResourceRef.MessageAttachment,
], ],
}, },
}, },
@ -376,11 +378,11 @@ const undoPopulateAttachments = async ({ logger }: MigrationServices) => {
); );
logger.log( logger.log(
`Successfully reverted attributes for ${result.modifiedCount} attachments with ref 'setting_attachment'`, `Successfully reverted attributes for ${result.modifiedCount} attachments with ref AttachmentResourceRef.SettingAttachment`,
); );
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to revert attributes for attachments with ref 'setting_attachment': ${error.message}`, `Failed to revert attributes for attachments with ref AttachmentResourceRef.SettingAttachment: ${error.message}`,
); );
} }
}; };
@ -552,7 +554,7 @@ const buildRenameAttributeCallback =
}; };
/** /**
* Traverses an content document to search for any attachment object * Traverses a content document to search for any attachment object
* @param obj * @param obj
* @param callback * @param callback
* @returns * @returns
@ -584,7 +586,14 @@ const migrateAttachmentContents = async (
const updateField = action === MigrationAction.UP ? 'id' : 'attachment_id'; const updateField = action === MigrationAction.UP ? 'id' : 'attachment_id';
const unsetField = action === MigrationAction.UP ? 'attachment_id' : 'id'; const unsetField = action === MigrationAction.UP ? 'attachment_id' : 'id';
const ContentModel = mongoose.model<Content>(Content.name, contentSchema); const ContentModel = mongoose.model<Content>(Content.name, contentSchema);
// Find blocks where "message.attachment" exists const AttachmentModel = mongoose.model<Attachment>(
Attachment.name,
attachmentSchema,
);
const adminUser = await getAdminUser();
// Process all contents
const cursor = ContentModel.find({}).cursor(); const cursor = ContentModel.find({}).cursor();
for await (const content of cursor) { for await (const content of cursor) {
@ -594,6 +603,30 @@ const migrateAttachmentContents = async (
buildRenameAttributeCallback(unsetField, updateField), buildRenameAttributeCallback(unsetField, updateField),
); );
for (const key in content.dynamicFields) {
if (
content.dynamicFields[key] &&
typeof content.dynamicFields[key] === 'object' &&
'payload' in content.dynamicFields[key] &&
'id' in content.dynamicFields[key].payload &&
content.dynamicFields[key].payload.id
) {
await AttachmentModel.updateOne(
{
_id: content.dynamicFields[key].payload.id,
},
{
$set: {
resourceRef: AttachmentResourceRef.ContentAttachment,
createdBy: adminUser.id,
createdByRef: 'User',
access: 'public',
},
},
);
}
}
await ContentModel.replaceOne({ _id: content._id }, content); await ContentModel.replaceOne({ _id: content._id }, content);
} catch (error) { } catch (error) {
logger.error(`Failed to update content ${content._id}: ${error.message}`); logger.error(`Failed to update content ${content._id}: ${error.message}`);
@ -648,7 +681,7 @@ const migrateAndPopulateAttachmentMessages = async ({
await attachmentService.updateOne( await attachmentService.updateOne(
msg.message.attachment.payload.attachment_id as string, msg.message.attachment.payload.attachment_id as string,
{ {
resourceRef: 'message_attachment', resourceRef: AttachmentResourceRef.MessageAttachment,
access: 'private', access: 'private',
createdByRef: msg.sender ? 'Subscriber' : 'User', createdByRef: msg.sender ? 'Subscriber' : 'User',
createdBy: msg.sender ? msg.sender : adminUser.id, createdBy: msg.sender ? msg.sender : adminUser.id,
@ -681,7 +714,7 @@ const migrateAndPopulateAttachmentMessages = async ({
size: fileBuffer.length, size: fileBuffer.length,
type: response.headers['content-type'], type: response.headers['content-type'],
channel: {}, channel: {},
resourceRef: 'message_attachment', resourceRef: AttachmentResourceRef.MessageAttachment,
access: msg.sender ? 'private' : 'public', access: msg.sender ? 'private' : 'public',
createdBy: msg.sender ? msg.sender : adminUser.id, createdBy: msg.sender ? msg.sender : adminUser.id,
createdByRef: msg.sender ? 'Subscriber' : 'User', createdByRef: msg.sender ? 'Subscriber' : 'User',

View File

@ -31,6 +31,7 @@ import { Session as ExpressSession } from 'express-session';
import { diskStorage, memoryStorage } from 'multer'; import { diskStorage, memoryStorage } from 'multer';
import { AttachmentService } from '@/attachment/services/attachment.service'; import { AttachmentService } from '@/attachment/services/attachment.service';
import { AttachmentResourceRef } from '@/attachment/types';
import { config } from '@/config'; import { config } from '@/config';
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor'; import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
@ -294,7 +295,7 @@ export class ReadWriteUserController extends ReadOnlyUserController {
name: avatarFile.originalname, name: avatarFile.originalname,
size: avatarFile.size, size: avatarFile.size,
type: avatarFile.mimetype, type: avatarFile.mimetype,
resourceRef: 'user_avatar', resourceRef: AttachmentResourceRef.UserAvatar,
access: 'private', access: 'private',
createdByRef: 'User', createdByRef: 'User',
createdBy: req.user.id, createdBy: req.user.id,

View File

@ -10,6 +10,7 @@ import mongoose from 'mongoose';
import { AttachmentCreateDto } from '@/attachment/dto/attachment.dto'; import { AttachmentCreateDto } from '@/attachment/dto/attachment.dto';
import { AttachmentModel } from '@/attachment/schemas/attachment.schema'; import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
import { AttachmentResourceRef } from '@/attachment/types';
export const attachmentFixtures: AttachmentCreateDto[] = [ export const attachmentFixtures: AttachmentCreateDto[] = [
{ {
@ -22,7 +23,7 @@ export const attachmentFixtures: AttachmentCreateDto[] = [
id: '1', id: '1',
}, },
}, },
resourceRef: 'content_attachment', resourceRef: AttachmentResourceRef.ContentAttachment,
access: 'public', access: 'public',
createdByRef: 'User', createdByRef: 'User',
createdBy: '9'.repeat(24), createdBy: '9'.repeat(24),
@ -37,7 +38,7 @@ export const attachmentFixtures: AttachmentCreateDto[] = [
id: '2', id: '2',
}, },
}, },
resourceRef: 'content_attachment', resourceRef: AttachmentResourceRef.ContentAttachment,
access: 'public', access: 'public',
createdByRef: 'User', createdByRef: 'User',
createdBy: '9'.repeat(24), createdBy: '9'.repeat(24),

View File

@ -38,6 +38,7 @@ import { DialogControlProps } from "@/hooks/useDialog";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
import { AttachmentResourceRef } from "@/types/attachment.types";
import { import {
ContentField, ContentField,
ContentFieldType, ContentFieldType,
@ -116,7 +117,7 @@ const ContentFieldInput: React.FC<ContentFieldInput> = ({
value={field.value?.payload?.id} value={field.value?.payload?.id}
accept={MIME_TYPES["images"].join(",")} accept={MIME_TYPES["images"].join(",")}
format="full" format="full"
resourceRef="content_attachment" resourceRef={AttachmentResourceRef.ContentAttachment}
/> />
); );
default: default:

View File

@ -20,6 +20,7 @@ import { useApiClient } from "@/hooks/useApiClient";
import { DialogControlProps } from "@/hooks/useDialog"; import { DialogControlProps } from "@/hooks/useDialog";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { AttachmentResourceRef } from "@/types/attachment.types";
import { IContentType } from "@/types/content-type.types"; import { IContentType } from "@/types/content-type.types";
export type ContentImportDialogProps = DialogControlProps<{ export type ContentImportDialogProps = DialogControlProps<{
@ -81,7 +82,7 @@ export const ContentImportDialog: FC<ContentImportDialogProps> = ({
}} }}
label="" label=""
value={attachmentId} value={attachmentId}
resourceRef="content_attachment" resourceRef={AttachmentResourceRef.ContentAttachment}
/> />
</ContentItem> </ContentItem>
</ContentContainer> </ContentContainer>

View File

@ -20,6 +20,7 @@ import MultipleInput from "@/app-components/inputs/MultipleInput";
import { PasswordInput } from "@/app-components/inputs/PasswordInput"; import { PasswordInput } from "@/app-components/inputs/PasswordInput";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { EntityType, Format } from "@/services/types"; import { EntityType, Format } from "@/services/types";
import { AttachmentResourceRef } from "@/types/attachment.types";
import { IBlock } from "@/types/block.types"; import { IBlock } from "@/types/block.types";
import { IHelper } from "@/types/helper.types"; import { IHelper } from "@/types/helper.types";
import { ISetting, SettingType } from "@/types/setting.types"; import { ISetting, SettingType } from "@/types/setting.types";
@ -186,7 +187,7 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
accept={MIME_TYPES["images"].join(",")} accept={MIME_TYPES["images"].join(",")}
format="full" format="full"
size={128} size={128}
resourceRef="setting_attachment" resourceRef={AttachmentResourceRef.SettingAttachment}
/> />
); );
@ -199,7 +200,7 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
accept={MIME_TYPES["images"].join(",")} accept={MIME_TYPES["images"].join(",")}
format="full" format="full"
size={128} size={128}
resourceRef="setting_attachment" resourceRef={AttachmentResourceRef.SettingAttachment}
/> />
); );
default: default:

View File

@ -13,6 +13,7 @@ import AttachmentInput from "@/app-components/attachment/AttachmentInput";
import { ContentItem } from "@/app-components/dialogs"; import { ContentItem } from "@/app-components/dialogs";
import AttachmentIcon from "@/app-components/svg/toolbar/AttachmentIcon"; import AttachmentIcon from "@/app-components/svg/toolbar/AttachmentIcon";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { AttachmentResourceRef } from "@/types/attachment.types";
import { IBlockAttributes } from "@/types/block.types"; import { IBlockAttributes } from "@/types/block.types";
import { FileType } from "@/types/message.types"; import { FileType } from "@/types/message.types";
import { MIME_TYPES, getFileType } from "@/utils/attachment"; import { MIME_TYPES, getFileType } from "@/utils/attachment";
@ -69,7 +70,7 @@ const AttachmentMessageForm = () => {
}, },
}); });
}} }}
resourceRef="block_attachment" resourceRef={AttachmentResourceRef.BlockAttachment}
/> />
); );
}} }}

View File

@ -29,12 +29,12 @@ export type TAttachmentCreatedByRef = `${AttachmentCreatedByRef}`;
* These references influence how the attachment is uploaded, stored, and accessed: * These references influence how the attachment is uploaded, stored, and accessed:
*/ */
export enum AttachmentResourceRef { export enum AttachmentResourceRef {
SettingAttachment = "setting_attachment", // Attachments related to app settings, restricted to users with specific permissions. SettingAttachment = "Setting", // 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. UserAvatar = "User", // 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. SubscriberAvatar = "Subscriber", // Avatar files for subscribers, uploaded programmatically, accessible to authorized users.
BlockAttachment = "block_attachment", // Files sent by the bot, public or private based on the channel and user authentication. BlockAttachment = "Block", // Files sent by the bot, public or private based on the channel and user authentication.
ContentAttachment = "content_attachment", // Files in the knowledge base, usually public but could vary based on specific needs. ContentAttachment = "Content", // Files in the knowledge base, usually public but could vary based on specific needs.
MessageAttachment = "message_attachment", // Files sent or received via messages, uploaded programmatically, accessible to users with inbox permissions.; MessageAttachment = "Message", // Files sent or received via messages, uploaded programmatically, accessible to users with inbox permissions.;
} }
export type TAttachmentResourceRef = `${AttachmentResourceRef}`; export type TAttachmentResourceRef = `${AttachmentResourceRef}`;