refactor: rename context to resourceRef

This commit is contained in:
Mohamed Marrouchi 2025-01-16 08:36:21 +01:00
parent 505cd247a1
commit 3f9dd692bf
27 changed files with 153 additions and 139 deletions

View File

@ -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),
},

View File

@ -67,7 +67,7 @@ export class AttachmentController extends BaseController<Attachment> {
async filterCount(
@Query(
new SearchFilterPipe<Attachment>({
allowedFields: ['name', 'type', 'context'],
allowedFields: ['name', 'type', 'resourceRef'],
}),
)
filters?: TFilterQuery<Attachment>,
@ -97,7 +97,7 @@ export class AttachmentController extends BaseController<Attachment> {
@Query(PageQueryPipe) pageQuery: PageQueryDto<Attachment>,
@Query(
new SearchFilterPipe<Attachment>({
allowedFields: ['name', 'type', 'context'],
allowedFields: ['name', 'type', 'resourceRef'],
}),
)
filters: TFilterQuery<Attachment>,
@ -130,7 +130,8 @@ export class AttachmentController extends BaseController<Attachment> {
async uploadFile(
@UploadedFiles() files: { file: Express.Multer.File[] },
@Req() req: Request,
@Query() { context, access = 'public' }: AttachmentContextParamDto,
@Query()
{ resourceRef, access = 'public' }: AttachmentContextParamDto,
): Promise<Attachment[]> {
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<Attachment> {
name: file.originalname,
size: file.size,
type: file.mimetype,
context,
resourceRef,
access,
createdBy: userId,
createdByRef: 'User',

View File

@ -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<Record<ChannelName, any>>;
/**
* 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',

View File

@ -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,
}),

View File

@ -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<TAttachmentContext, [TModel, Action][]>
Record<TAttachmentResourceRef, [TModel, Action][]>
> = {
// 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<boolean> {
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');

View File

@ -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(),

View File

@ -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;

View File

@ -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<Attachment> {
}
/**
* 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<Attachment> {
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<Attachment> {
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)) {

View File

@ -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',

View File

@ -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)
);
};

View File

@ -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,

View File

@ -88,7 +88,7 @@ const attachment: Attachment = {
id: 'any-channel-attachment-id',
},
},
context: 'block_attachment',
resourceRef: 'block_attachment',
access: 'public',
createdByRef: 'User',
createdBy: null,

View File

@ -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,

View File

@ -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,

View File

@ -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',

View File

@ -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,

View File

@ -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),

View File

@ -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<HTMLDivElement, AttachmentThumbnailProps>(
@ -44,7 +44,7 @@ const AttachmentInput = forwardRef<HTMLDivElement, AttachmentThumbnailProps>(
onChange,
error,
helperText,
context,
resourceRef,
},
ref,
) => {
@ -84,7 +84,7 @@ const AttachmentInput = forwardRef<HTMLDivElement, AttachmentThumbnailProps>(
accept={accept}
enableMediaLibrary={enableMediaLibrary}
onChange={handleChange}
context={context}
resourceRef={resourceRef}
/>
) : null}
{helperText ? (

View File

@ -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<FileUploadProps> = ({
@ -76,7 +76,7 @@ const AttachmentUploader: FC<FileUploadProps> = ({
enableMediaLibrary,
onChange,
onUploadComplete,
context,
resourceRef,
}) => {
const [attachment, setAttachment] = useState<IAttachment | undefined>(
undefined,
@ -119,7 +119,7 @@ const AttachmentUploader: FC<FileUploadProps> = ({
return;
}
uploadAttachment({ file, context });
uploadAttachment({ file, resourceRef });
}
};
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {

View File

@ -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 && (

View File

@ -116,7 +116,7 @@ const ContentFieldInput: React.FC<ContentFieldInput> = ({
value={field.value?.payload?.id}
accept={MIME_TYPES["images"].join(",")}
format="full"
context="content_attachment"
resourceRef="content_attachment"
/>
);
default:

View File

@ -81,7 +81,7 @@ export const ContentImportDialog: FC<ContentImportDialogProps> = ({
}}
label=""
value={attachmentId}
context="content_attachment"
resourceRef="content_attachment"
/>
</ContentItem>
</ContentContainer>

View File

@ -186,7 +186,7 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
accept={MIME_TYPES["images"].join(",")}
format="full"
size={128}
context="setting_attachment"
resourceRef="setting_attachment"
/>
);
@ -199,7 +199,7 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
accept={MIME_TYPES["images"].join(",")}
format="full"
size={128}
context="setting_attachment"
resourceRef="setting_attachment"
/>
);
default:

View File

@ -69,7 +69,7 @@ const AttachmentMessageForm = () => {
},
});
}}
context="block_attachment"
resourceRef="block_attachment"
/>
);
}}

View File

@ -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

View File

@ -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<TAttr, TBasic, TFull> 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<TAttr, TBasic, TFull> extends ApiClient {
FormData
>(
`${ROUTES[this.type]}/upload?_csrf=${_csrf}${
context ? `&context=${context}` : ""
resourceRef ? `&resourceRef=${resourceRef}` : ""
}`,
formData,
{

View File

@ -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<string, any>;
context: TAttachmentContext;
resourceRef: TAttachmentResourceRef;
createdByRef: TAttachmentCreatedByRef;
createdBy: string | null;
}