mirror of
https://github.com/hexastack/hexabot
synced 2025-01-22 10:35:37 +00:00
feat: rename enum instead of string
This commit is contained in:
parent
3f9dd692bf
commit
c27f37a6e6
@ -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),
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
|
@ -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(
|
||||||
|
@ -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(),
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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}`;
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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',
|
||||||
|
@ -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,
|
||||||
|
5
api/src/utils/test/fixtures/attachment.ts
vendored
5
api/src/utils/test/fixtures/attachment.ts
vendored
@ -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),
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -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:
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -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}`;
|
||||||
|
Loading…
Reference in New Issue
Block a user