mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
refactor: attachment payload typing
This commit is contained in:
parent
b57e4693c5
commit
20cf7171fa
@ -7,10 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Subscriber } from '@/chat/schemas/subscriber.schema';
|
import { Subscriber } from '@/chat/schemas/subscriber.schema';
|
||||||
import {
|
import { AttachmentPayload } from '@/chat/schemas/types/attachment';
|
||||||
AttachmentForeignKey,
|
|
||||||
AttachmentPayload,
|
|
||||||
} from '@/chat/schemas/types/attachment';
|
|
||||||
import { SubscriberChannelData } from '@/chat/schemas/types/channel';
|
import { SubscriberChannelData } from '@/chat/schemas/types/channel';
|
||||||
import {
|
import {
|
||||||
IncomingMessageType,
|
IncomingMessageType,
|
||||||
@ -251,7 +248,7 @@ export default abstract class EventWrapper<
|
|||||||
*
|
*
|
||||||
* @returns Received attachments message
|
* @returns Received attachments message
|
||||||
*/
|
*/
|
||||||
abstract getAttachments(): AttachmentPayload<AttachmentForeignKey>[];
|
abstract getAttachments(): AttachmentPayload[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of delivered messages
|
* Returns the list of delivered messages
|
||||||
@ -380,7 +377,7 @@ export class GenericEventWrapper extends EventWrapper<
|
|||||||
* @returns A list of received attachments
|
* @returns A list of received attachments
|
||||||
* @deprecated - This method is deprecated
|
* @deprecated - This method is deprecated
|
||||||
*/
|
*/
|
||||||
getAttachments(): AttachmentPayload<AttachmentForeignKey>[] {
|
getAttachments(): AttachmentPayload[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,11 +92,6 @@ const attachment: Attachment = {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachmentWithUrl: Attachment = {
|
|
||||||
...attachment,
|
|
||||||
url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const contentMessage: StdOutgoingListMessage = {
|
export const contentMessage: StdOutgoingListMessage = {
|
||||||
options: {
|
options: {
|
||||||
display: OutgoingMessageFormat.list,
|
display: OutgoingMessageFormat.list,
|
||||||
@ -121,7 +116,8 @@ export const contentMessage: StdOutgoingListMessage = {
|
|||||||
title: 'First',
|
title: 'First',
|
||||||
desc: 'About being first',
|
desc: 'About being first',
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
payload: attachmentWithUrl,
|
type: 'image',
|
||||||
|
payload: { attachment_id: attachment.id },
|
||||||
},
|
},
|
||||||
getPayload() {
|
getPayload() {
|
||||||
return this.title;
|
return this.title;
|
||||||
@ -136,7 +132,8 @@ export const contentMessage: StdOutgoingListMessage = {
|
|||||||
title: 'Second',
|
title: 'Second',
|
||||||
desc: 'About being second',
|
desc: 'About being second',
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
payload: attachmentWithUrl,
|
type: 'image',
|
||||||
|
payload: { attachment_id: attachment.id },
|
||||||
},
|
},
|
||||||
getPayload() {
|
getPayload() {
|
||||||
return this.title;
|
return this.title;
|
||||||
@ -149,14 +146,14 @@ export const contentMessage: StdOutgoingListMessage = {
|
|||||||
pagination: {
|
pagination: {
|
||||||
total: 3,
|
total: 3,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
limit: 1,
|
limit: 2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const attachmentMessage: StdOutgoingAttachmentMessage<Attachment> = {
|
export const attachmentMessage: StdOutgoingAttachmentMessage = {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
payload: attachmentWithUrl,
|
payload: { attachment_id: attachment.id },
|
||||||
},
|
},
|
||||||
quickReplies: [
|
quickReplies: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
|
||||||
|
|
||||||
export enum FileType {
|
export enum FileType {
|
||||||
image = 'image',
|
image = 'image',
|
||||||
video = 'video',
|
video = 'video',
|
||||||
@ -17,14 +15,12 @@ export enum FileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AttachmentForeignKey = {
|
export type AttachmentForeignKey = {
|
||||||
attachment_id: string;
|
attachment_id: string | null;
|
||||||
/** @deprecated use "attachment_id" instead */
|
/** @deprecated use "attachment_id" instead */
|
||||||
url?: string;
|
url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AttachmentPayload<
|
export interface AttachmentPayload {
|
||||||
A extends Attachment | AttachmentForeignKey,
|
|
||||||
> {
|
|
||||||
type: FileType;
|
type: FileType;
|
||||||
payload: A;
|
payload: AttachmentForeignKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,12 +6,11 @@
|
|||||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
|
||||||
import { PluginName } from '@/plugins/types';
|
import { PluginName } from '@/plugins/types';
|
||||||
|
|
||||||
import { Message } from '../message.schema';
|
import { Message } from '../message.schema';
|
||||||
|
|
||||||
import { AttachmentForeignKey, AttachmentPayload } from './attachment';
|
import { AttachmentPayload } from './attachment';
|
||||||
import { Button } from './button';
|
import { Button } from './button';
|
||||||
import { ContentOptions } from './options';
|
import { ContentOptions } from './options';
|
||||||
import { StdQuickReply } from './quick-reply';
|
import { StdQuickReply } from './quick-reply';
|
||||||
@ -96,11 +95,9 @@ export type StdOutgoingListMessage = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StdOutgoingAttachmentMessage<
|
export type StdOutgoingAttachmentMessage = {
|
||||||
A extends Attachment | AttachmentForeignKey,
|
|
||||||
> = {
|
|
||||||
// Stored in DB as `AttachmentPayload`, `Attachment` when populated for channels relaying
|
// Stored in DB as `AttachmentPayload`, `Attachment` when populated for channels relaying
|
||||||
attachment: AttachmentPayload<A>;
|
attachment: AttachmentPayload;
|
||||||
quickReplies?: StdQuickReply[];
|
quickReplies?: StdQuickReply[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -115,7 +112,7 @@ export type BlockMessage =
|
|||||||
| StdOutgoingQuickRepliesMessage
|
| StdOutgoingQuickRepliesMessage
|
||||||
| StdOutgoingButtonsMessage
|
| StdOutgoingButtonsMessage
|
||||||
| StdOutgoingListMessage
|
| StdOutgoingListMessage
|
||||||
| StdOutgoingAttachmentMessage<AttachmentForeignKey>
|
| StdOutgoingAttachmentMessage
|
||||||
| StdPluginMessage;
|
| StdPluginMessage;
|
||||||
|
|
||||||
export type StdOutgoingMessage =
|
export type StdOutgoingMessage =
|
||||||
@ -123,7 +120,7 @@ export type StdOutgoingMessage =
|
|||||||
| StdOutgoingQuickRepliesMessage
|
| StdOutgoingQuickRepliesMessage
|
||||||
| StdOutgoingButtonsMessage
|
| StdOutgoingButtonsMessage
|
||||||
| StdOutgoingListMessage
|
| StdOutgoingListMessage
|
||||||
| StdOutgoingAttachmentMessage<Attachment>;
|
| StdOutgoingAttachmentMessage;
|
||||||
|
|
||||||
type StdIncomingTextMessage = { text: string };
|
type StdIncomingTextMessage = { text: string };
|
||||||
|
|
||||||
@ -142,9 +139,7 @@ export type StdIncomingLocationMessage = {
|
|||||||
export type StdIncomingAttachmentMessage = {
|
export type StdIncomingAttachmentMessage = {
|
||||||
type: PayloadType.attachments;
|
type: PayloadType.attachments;
|
||||||
serialized_text: string;
|
serialized_text: string;
|
||||||
attachment:
|
attachment: AttachmentPayload | AttachmentPayload[];
|
||||||
| AttachmentPayload<AttachmentForeignKey>
|
|
||||||
| AttachmentPayload<AttachmentForeignKey>[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StdIncomingMessage =
|
export type StdIncomingMessage =
|
||||||
@ -189,7 +184,7 @@ export interface StdOutgoingListEnvelope {
|
|||||||
|
|
||||||
export interface StdOutgoingAttachmentEnvelope {
|
export interface StdOutgoingAttachmentEnvelope {
|
||||||
format: OutgoingMessageFormat.attachment;
|
format: OutgoingMessageFormat.attachment;
|
||||||
message: StdOutgoingAttachmentMessage<Attachment>;
|
message: StdOutgoingAttachmentMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StdOutgoingEnvelope =
|
export type StdOutgoingEnvelope =
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AttachmentForeignKey, AttachmentPayload } from './attachment';
|
import { AttachmentPayload } from './attachment';
|
||||||
import { PayloadType } from './message';
|
import { PayloadType } from './message';
|
||||||
|
|
||||||
export type Payload =
|
export type Payload =
|
||||||
@ -19,7 +19,7 @@ export type Payload =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: PayloadType.attachments;
|
type: PayloadType.attachments;
|
||||||
attachments: AttachmentPayload<AttachmentForeignKey>;
|
attachments: AttachmentPayload;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum QuickReplyType {
|
export enum QuickReplyType {
|
||||||
|
|||||||
@ -526,24 +526,12 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
throw new Error('Remote attachments are no longer supported!');
|
throw new Error('Remote attachments are no longer supported!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = await this.attachmentService.findOne(
|
|
||||||
attachmentPayload.attachment_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!attachment) {
|
|
||||||
this.logger.debug(
|
|
||||||
'Unable to locate the attachment for the given block',
|
|
||||||
block,
|
|
||||||
);
|
|
||||||
throw new Error('Unable to find attachment.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const envelope: StdOutgoingEnvelope = {
|
const envelope: StdOutgoingEnvelope = {
|
||||||
format: OutgoingMessageFormat.attachment,
|
format: OutgoingMessageFormat.attachment,
|
||||||
message: {
|
message: {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: blockMessage.attachment.type,
|
type: blockMessage.attachment.type,
|
||||||
payload: attachment,
|
payload: blockMessage.attachment.payload,
|
||||||
},
|
},
|
||||||
quickReplies: blockMessage.quickReplies
|
quickReplies: blockMessage.quickReplies
|
||||||
? [...blockMessage.quickReplies]
|
? [...blockMessage.quickReplies]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -13,11 +13,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
|
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
|
||||||
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
|
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
|
||||||
import { AttachmentService } from '@/attachment/services/attachment.service';
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
import { FileType } from '@/chat/schemas/types/attachment';
|
import { OutgoingMessageFormat } from '@/chat/schemas/types/message';
|
||||||
import {
|
|
||||||
ContentElement,
|
|
||||||
OutgoingMessageFormat,
|
|
||||||
} from '@/chat/schemas/types/message';
|
|
||||||
import { ContentOptions } from '@/chat/schemas/types/options';
|
import { ContentOptions } from '@/chat/schemas/types/options';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { IGNORED_TEST_FIELDS } from '@/utils/test/constants';
|
import { IGNORED_TEST_FIELDS } from '@/utils/test/constants';
|
||||||
@ -43,7 +39,6 @@ describe('ContentService', () => {
|
|||||||
let contentService: ContentService;
|
let contentService: ContentService;
|
||||||
let contentTypeService: ContentTypeService;
|
let contentTypeService: ContentTypeService;
|
||||||
let contentRepository: ContentRepository;
|
let contentRepository: ContentRepository;
|
||||||
let attachmentService: AttachmentService;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -69,7 +64,6 @@ describe('ContentService', () => {
|
|||||||
contentService = module.get<ContentService>(ContentService);
|
contentService = module.get<ContentService>(ContentService);
|
||||||
contentTypeService = module.get<ContentTypeService>(ContentTypeService);
|
contentTypeService = module.get<ContentTypeService>(ContentTypeService);
|
||||||
contentRepository = module.get<ContentRepository>(ContentRepository);
|
contentRepository = module.get<ContentRepository>(ContentRepository);
|
||||||
attachmentService = module.get<AttachmentService>(AttachmentService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -111,103 +105,6 @@ describe('ContentService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAttachmentIds', () => {
|
|
||||||
const contents: Content[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
title: 'store 1',
|
|
||||||
entity: 'stores',
|
|
||||||
status: true,
|
|
||||||
dynamicFields: {
|
|
||||||
image: {
|
|
||||||
type: FileType.image,
|
|
||||||
payload: {
|
|
||||||
attachment_id: '123',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
title: 'store 2',
|
|
||||||
entity: 'stores',
|
|
||||||
status: true,
|
|
||||||
dynamicFields: {
|
|
||||||
image: {
|
|
||||||
type: FileType.image,
|
|
||||||
payload: {
|
|
||||||
attachment_id: '456',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
title: 'store 3',
|
|
||||||
entity: 'stores',
|
|
||||||
status: true,
|
|
||||||
dynamicFields: {
|
|
||||||
image: {
|
|
||||||
type: FileType.image,
|
|
||||||
payload: {
|
|
||||||
url: 'https://remote.file/image.jpg',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
it('should return all content attachment ids', () => {
|
|
||||||
const result = contentService.getAttachmentIds(
|
|
||||||
contents.map(Content.toElement),
|
|
||||||
'image',
|
|
||||||
);
|
|
||||||
expect(result).toEqual(['123', '456']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return any of the attachment ids', () => {
|
|
||||||
const result = contentService.getAttachmentIds(contents, 'file');
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('populateAttachments', () => {
|
|
||||||
it('should return populated content', async () => {
|
|
||||||
const storeContents = await contentService.find({ title: /^store/ });
|
|
||||||
const elements: ContentElement[] = await Promise.all(
|
|
||||||
storeContents.map(Content.toElement).map(async (store) => {
|
|
||||||
const attachmentId = store.image.payload.attachment_id;
|
|
||||||
if (attachmentId) {
|
|
||||||
const attachment = await attachmentService.findOne(attachmentId);
|
|
||||||
if (attachment) {
|
|
||||||
return {
|
|
||||||
...store,
|
|
||||||
image: {
|
|
||||||
type: 'image',
|
|
||||||
payload: {
|
|
||||||
...attachment,
|
|
||||||
url: `http://localhost:4000/attachment/download/${attachment.id}/${attachment.name}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return store;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const result = await contentService.populateAttachments(
|
|
||||||
storeContents.map(Content.toElement),
|
|
||||||
'image',
|
|
||||||
);
|
|
||||||
expect(result).toEqualPayload(elements);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getContent', () => {
|
describe('getContent', () => {
|
||||||
const contentOptions: ContentOptions = {
|
const contentOptions: ContentOptions = {
|
||||||
display: OutgoingMessageFormat.list,
|
display: OutgoingMessageFormat.list,
|
||||||
|
|||||||
@ -8,12 +8,8 @@
|
|||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
|
||||||
import { AttachmentService } from '@/attachment/services/attachment.service';
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
import {
|
import { StdOutgoingListMessage } from '@/chat/schemas/types/message';
|
||||||
ContentElement,
|
|
||||||
StdOutgoingListMessage,
|
|
||||||
} from '@/chat/schemas/types/message';
|
|
||||||
import { ContentOptions } from '@/chat/schemas/types/options';
|
import { ContentOptions } from '@/chat/schemas/types/options';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { BaseService } from '@/utils/generics/base-service';
|
import { BaseService } from '@/utils/generics/base-service';
|
||||||
@ -51,93 +47,6 @@ export class ContentService extends BaseService<
|
|||||||
return await this.repository.textSearch(query);
|
return await this.repository.textSearch(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts attachment IDs from content entities, issuing warnings for any issues.
|
|
||||||
*
|
|
||||||
* @param contents - An array of content entities.
|
|
||||||
* @param attachmentFieldName - The name of the attachment field to check for.
|
|
||||||
*
|
|
||||||
* @return A list of attachment IDs.
|
|
||||||
*/
|
|
||||||
getAttachmentIds(contents: ContentElement[], attachmentFieldName: string) {
|
|
||||||
return contents.reduce((acc, content) => {
|
|
||||||
if (attachmentFieldName in content) {
|
|
||||||
const attachment = content[attachmentFieldName];
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof attachment === 'object' &&
|
|
||||||
'attachment_id' in attachment.payload
|
|
||||||
) {
|
|
||||||
acc.push(attachment.payload.attachment_id);
|
|
||||||
} else {
|
|
||||||
this.logger.error(
|
|
||||||
`Remote attachments have been deprecated, content "${content.title}" is missing the "attachment_id"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.logger.warn(
|
|
||||||
`Field "${attachmentFieldName}" not found in content "${content.title}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, [] as string[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates attachment fields within content entities with detailed attachment information.
|
|
||||||
*
|
|
||||||
* @param elements - An array of content entities.
|
|
||||||
* @param attachmentFieldName - The name of the attachment field to populate.
|
|
||||||
*
|
|
||||||
* @return A list of content with populated attachment data.
|
|
||||||
*/
|
|
||||||
async populateAttachments(
|
|
||||||
elements: ContentElement[],
|
|
||||||
attachmentFieldName: string,
|
|
||||||
): Promise<ContentElement[]> {
|
|
||||||
const attachmentIds = this.getAttachmentIds(elements, attachmentFieldName);
|
|
||||||
|
|
||||||
if (attachmentIds.length > 0) {
|
|
||||||
const attachments = await this.attachmentService.find({
|
|
||||||
_id: { $in: attachmentIds },
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachmentsById = attachments.reduce(
|
|
||||||
(acc, curr) => {
|
|
||||||
acc[curr.id] = curr;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as { [key: string]: Attachment },
|
|
||||||
);
|
|
||||||
const populatedContents = elements.map((content) => {
|
|
||||||
const attachmentField = content[attachmentFieldName];
|
|
||||||
if (
|
|
||||||
typeof attachmentField === 'object' &&
|
|
||||||
'attachment_id' in attachmentField.payload
|
|
||||||
) {
|
|
||||||
const attachmentId = attachmentField?.payload?.attachment_id;
|
|
||||||
return {
|
|
||||||
...content,
|
|
||||||
[attachmentFieldName]: {
|
|
||||||
type: attachmentField.type,
|
|
||||||
payload: {
|
|
||||||
...(attachmentsById[attachmentId] || attachmentField.payload),
|
|
||||||
url: Attachment.getAttachmentUrl(
|
|
||||||
attachmentId,
|
|
||||||
attachmentsById[attachmentId].name,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return populatedContents;
|
|
||||||
}
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves content based on the provided options and pagination settings.
|
* Retrieves content based on the provided options and pagination settings.
|
||||||
*
|
*
|
||||||
@ -175,21 +84,6 @@ export class ContentService extends BaseService<
|
|||||||
sort: ['createdAt', 'desc'],
|
sort: ['createdAt', 'desc'],
|
||||||
});
|
});
|
||||||
const elements = contents.map(Content.toElement);
|
const elements = contents.map(Content.toElement);
|
||||||
const attachmentFieldName = options.fields.image_url;
|
|
||||||
if (attachmentFieldName) {
|
|
||||||
// Populate attachment when there's an image field
|
|
||||||
return {
|
|
||||||
elements: await this.populateAttachments(
|
|
||||||
elements,
|
|
||||||
attachmentFieldName,
|
|
||||||
),
|
|
||||||
pagination: {
|
|
||||||
total,
|
|
||||||
skip,
|
|
||||||
limit,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
elements,
|
elements,
|
||||||
pagination: {
|
pagination: {
|
||||||
|
|||||||
@ -985,14 +985,14 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
* @returns A ready to be sent attachment message
|
* @returns A ready to be sent attachment message
|
||||||
*/
|
*/
|
||||||
async _attachmentFormat(
|
async _attachmentFormat(
|
||||||
message: StdOutgoingAttachmentMessage<Attachment>,
|
message: StdOutgoingAttachmentMessage,
|
||||||
_options?: BlockOptions,
|
_options?: BlockOptions,
|
||||||
): Promise<Web.OutgoingMessageBase> {
|
): Promise<Web.OutgoingMessageBase> {
|
||||||
const payload: Web.OutgoingMessageBase = {
|
const payload: Web.OutgoingMessageBase = {
|
||||||
type: Web.OutgoingMessageType.file,
|
type: Web.OutgoingMessageType.file,
|
||||||
data: {
|
data: {
|
||||||
type: message.attachment.type,
|
type: message.attachment.type,
|
||||||
url: await this.getPublicUrl(message.attachment.payload.id),
|
url: await this.getPublicUrl(message.attachment.payload.attachment_id),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (message.quickReplies && message.quickReplies.length > 0) {
|
if (message.quickReplies && message.quickReplies.length > 0) {
|
||||||
@ -1034,14 +1034,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
if (fields.image_url && item[fields.image_url]) {
|
if (fields.image_url && item[fields.image_url]) {
|
||||||
const attachmentPayload = item[fields.image_url]
|
const attachmentPayload = item[fields.image_url]
|
||||||
.payload as AttachmentForeignKey;
|
.payload as AttachmentForeignKey;
|
||||||
if (attachmentPayload.url) {
|
if (attachmentPayload.attachment_id) {
|
||||||
if (!attachmentPayload.attachment_id) {
|
|
||||||
// @deprecated
|
|
||||||
this.logger.warn(
|
|
||||||
'Web Channel Handler: Attachment remote url has been deprecated',
|
|
||||||
item,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
element.image_url = await this.getPublicUrl(
|
element.image_url = await this.getPublicUrl(
|
||||||
attachmentPayload.attachment_id,
|
attachmentPayload.attachment_id,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,10 +9,7 @@
|
|||||||
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
||||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||||
import { ChannelName } from '@/channel/types';
|
import { ChannelName } from '@/channel/types';
|
||||||
import {
|
import { AttachmentPayload } from '@/chat/schemas/types/attachment';
|
||||||
AttachmentForeignKey,
|
|
||||||
AttachmentPayload,
|
|
||||||
} from '@/chat/schemas/types/attachment';
|
|
||||||
import {
|
import {
|
||||||
IncomingMessageType,
|
IncomingMessageType,
|
||||||
PayloadType,
|
PayloadType,
|
||||||
@ -71,9 +68,11 @@ type WebEventAdapter =
|
|||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
export default class WebEventWrapper<
|
export default class WebEventWrapper<N extends ChannelName> extends EventWrapper<
|
||||||
N extends ChannelName,
|
WebEventAdapter,
|
||||||
> extends EventWrapper<WebEventAdapter, Web.Event, N> {
|
Web.Event,
|
||||||
|
N
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* Constructor : channel's event wrapper
|
* Constructor : channel's event wrapper
|
||||||
*
|
*
|
||||||
@ -298,7 +297,7 @@ export default class WebEventWrapper<
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
* @returns Received attachments message
|
* @returns Received attachments message
|
||||||
*/
|
*/
|
||||||
getAttachments(): AttachmentPayload<AttachmentForeignKey>[] {
|
getAttachments(): AttachmentPayload[] {
|
||||||
const message = this.getMessage() as any;
|
const message = this.getMessage() as any;
|
||||||
return 'attachment' in message ? [].concat(message.attachment) : [];
|
return 'attachment' in message ? [].concat(message.attachment) : [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { DialogTitle } from "@/app-components/dialogs";
|
|||||||
import { useDialog } from "@/hooks/useDialog";
|
import { useDialog } from "@/hooks/useDialog";
|
||||||
import { useTranslate } from "@/hooks/useTranslate";
|
import { useTranslate } from "@/hooks/useTranslate";
|
||||||
import {
|
import {
|
||||||
AttachmentAttrs,
|
|
||||||
FileType,
|
FileType,
|
||||||
StdIncomingAttachmentMessage,
|
StdIncomingAttachmentMessage,
|
||||||
StdOutgoingAttachmentMessage,
|
StdOutgoingAttachmentMessage,
|
||||||
@ -93,9 +92,7 @@ const componentMap: { [key in FileType]: FC<AttachmentInterface> } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AttachmentViewer = (props: {
|
export const AttachmentViewer = (props: {
|
||||||
message:
|
message: StdIncomingAttachmentMessage | StdOutgoingAttachmentMessage;
|
||||||
| StdIncomingAttachmentMessage
|
|
||||||
| StdOutgoingAttachmentMessage<AttachmentAttrs>;
|
|
||||||
}) => {
|
}) => {
|
||||||
const message = props.message;
|
const message = props.message;
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { IBlockAttributes } from "@/types/block.types";
|
import { IBlockAttributes } from "@/types/block.types";
|
||||||
import {
|
import {
|
||||||
ButtonType,
|
ButtonType,
|
||||||
@ -35,7 +36,7 @@ export const ATTACHMENT_BLOCK_TEMPLATE: Partial<IBlockAttributes> = {
|
|||||||
message: {
|
message: {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: FileType.unknown,
|
type: FileType.unknown,
|
||||||
payload: { attachment_id: undefined },
|
payload: { attachment_id: null },
|
||||||
},
|
},
|
||||||
quickReplies: [],
|
quickReplies: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { EntityType, Format } from "@/services/types";
|
import { EntityType, Format } from "@/services/types";
|
||||||
|
|
||||||
import { IBaseSchema, IFormat, OmitPopulate } from "./base.types";
|
import { IBaseSchema, IFormat, OmitPopulate } from "./base.types";
|
||||||
import { ILabel } from "./label.types";
|
import { ILabel } from "./label.types";
|
||||||
import {
|
import {
|
||||||
AttachmentForeignKey,
|
|
||||||
ContentOptions,
|
ContentOptions,
|
||||||
PayloadType,
|
PayloadType,
|
||||||
StdOutgoingAttachmentMessage,
|
StdOutgoingAttachmentMessage,
|
||||||
@ -57,7 +57,7 @@ export type BlockMessage =
|
|||||||
| StdOutgoingQuickRepliesMessage
|
| StdOutgoingQuickRepliesMessage
|
||||||
| StdOutgoingButtonsMessage
|
| StdOutgoingButtonsMessage
|
||||||
| StdOutgoingListMessage
|
| StdOutgoingListMessage
|
||||||
| StdOutgoingAttachmentMessage<AttachmentForeignKey>
|
| StdOutgoingAttachmentMessage
|
||||||
| StdPluginMessage;
|
| StdPluginMessage;
|
||||||
|
|
||||||
export interface PayloadPattern {
|
export interface PayloadPattern {
|
||||||
|
|||||||
@ -52,15 +52,14 @@ export interface AttachmentAttrs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AttachmentForeignKey = {
|
export type AttachmentForeignKey = {
|
||||||
|
attachment_id: string | null;
|
||||||
|
/** @deprecated use attachment_id instead */
|
||||||
url?: string;
|
url?: string;
|
||||||
attachment_id: string | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AttachmentPayload<
|
export interface AttachmentPayload {
|
||||||
A extends AttachmentAttrs | AttachmentForeignKey,
|
|
||||||
> {
|
|
||||||
type: FileType;
|
type: FileType;
|
||||||
payload?: A;
|
payload: AttachmentForeignKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
@ -96,7 +95,7 @@ export type Payload =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: PayloadType.attachments;
|
type: PayloadType.attachments;
|
||||||
attachments: AttachmentPayload<AttachmentForeignKey>;
|
attachments: AttachmentPayload;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum QuickReplyType {
|
export enum QuickReplyType {
|
||||||
@ -163,11 +162,9 @@ export type StdOutgoingListMessage = {
|
|||||||
limit: number;
|
limit: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type StdOutgoingAttachmentMessage<
|
export type StdOutgoingAttachmentMessage = {
|
||||||
A extends AttachmentAttrs | AttachmentForeignKey,
|
|
||||||
> = {
|
|
||||||
// Stored in DB as `AttachmentPayload`, `Attachment` when populated for channels relaying
|
// Stored in DB as `AttachmentPayload`, `Attachment` when populated for channels relaying
|
||||||
attachment: AttachmentPayload<A>;
|
attachment: AttachmentPayload;
|
||||||
quickReplies?: StdQuickReply[];
|
quickReplies?: StdQuickReply[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -190,9 +187,7 @@ export type StdIncomingLocationMessage = {
|
|||||||
export type StdIncomingAttachmentMessage = {
|
export type StdIncomingAttachmentMessage = {
|
||||||
type: PayloadType.attachments;
|
type: PayloadType.attachments;
|
||||||
serialized_text: string;
|
serialized_text: string;
|
||||||
attachment:
|
attachment: AttachmentPayload | AttachmentPayload[];
|
||||||
| AttachmentPayload<AttachmentForeignKey>
|
|
||||||
| AttachmentPayload<AttachmentForeignKey>[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StdPluginMessage = {
|
export type StdPluginMessage = {
|
||||||
@ -211,7 +206,7 @@ export type StdOutgoingMessage =
|
|||||||
| StdOutgoingQuickRepliesMessage
|
| StdOutgoingQuickRepliesMessage
|
||||||
| StdOutgoingButtonsMessage
|
| StdOutgoingButtonsMessage
|
||||||
| StdOutgoingListMessage
|
| StdOutgoingListMessage
|
||||||
| StdOutgoingAttachmentMessage<AttachmentAttrs>;
|
| StdOutgoingAttachmentMessage;
|
||||||
|
|
||||||
export interface IMessageAttributes {
|
export interface IMessageAttributes {
|
||||||
mid?: string;
|
mid?: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user