fix: leave support for url in attachment payload (case for external urls)

This commit is contained in:
Mohamed Marrouchi 2025-01-13 16:20:01 +01:00
parent d7cb39f9f4
commit f399416553
8 changed files with 83 additions and 37 deletions

View File

@ -98,7 +98,7 @@ export default abstract class EventWrapper<
* *
* @returns The current instance of the channel handler. * @returns The current instance of the channel handler.
*/ */
getHandler(): ChannelHandler { getHandler(): C {
return this._handler; return this._handler;
} }
@ -189,6 +189,16 @@ export default abstract class EventWrapper<
this._profile = profile; this._profile = profile;
} }
/**
* Pre-Process messageevent
*
* Child class can perform operations such as storing files as attachments.
*/
preprocess() {
// Nothing ...
return Promise.resolve();
}
/** /**
* Returns event recipient id * Returns event recipient id
* *

View File

@ -21,6 +21,7 @@ import { NextFunction, Request, Response } from 'express';
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 { SubscriberCreateDto } from '@/chat/dto/subscriber.dto'; import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
import { AttachmentRef } from '@/chat/schemas/types/attachment';
import { import {
StdOutgoingEnvelope, StdOutgoingEnvelope,
StdOutgoingMessage, StdOutgoingMessage,
@ -234,11 +235,15 @@ export default abstract class ChannelHandler<
* @param attachment The attachment ID or object to generate a signed URL for. * @param attachment The attachment ID or object to generate a signed URL for.
* @return A signed URL string for downloading the specified attachment. * @return A signed URL string for downloading the specified attachment.
*/ */
public async getPublicUrl(attachment: string | Attachment) { public async getPublicUrl(attachment: AttachmentRef | Attachment) {
const resource = if ('id' in attachment) {
typeof attachment === 'string' if (!attachment.id) {
? await this.attachmentService.findOne(attachment) throw new TypeError(
: attachment; 'Attachment ID is empty, unable to generate public URL.',
);
}
const resource = await this.attachmentService.findOne(attachment.id);
if (!resource) { if (!resource) {
throw new NotFoundException('Unable to find attachment'); throw new NotFoundException('Unable to find attachment');
@ -250,6 +255,12 @@ export default abstract class ChannelHandler<
config.apiBaseUrl, config.apiBaseUrl,
`/webhook/${name}/download/${resource.name}?t=${encodeURIComponent(token)}`, `/webhook/${name}/download/${resource.name}?t=${encodeURIComponent(token)}`,
); );
} else if ('url' in attachment && attachment.url) {
// In case the url is external
return attachment.url;
} else {
throw new TypeError('Unable to resolve the attachment public URL.');
}
} }
/** /**

View File

@ -78,14 +78,14 @@ export const urlButtonsMessage: StdOutgoingButtonsMessage = {
}; };
const attachment: Attachment = { const attachment: Attachment = {
id: '1', id: '1'.repeat(24),
name: 'attachment.jpg', name: 'attachment.jpg',
type: 'image/jpeg', type: 'image/jpeg',
size: 3539, size: 3539,
location: '39991e51-55c6-4a26-9176-b6ba04f180dc.jpg', location: '39991e51-55c6-4a26-9176-b6ba04f180dc.jpg',
channel: { channel: {
['dimelo']: { ['any-channel']: {
id: 'attachment-id-dimelo', id: 'any-channel-attachment-id',
}, },
}, },
createdAt: new Date(), createdAt: new Date(),

View File

@ -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.
@ -111,6 +111,16 @@ export class SubscriberCreateDto {
@IsNotEmpty() @IsNotEmpty()
@IsChannelData() @IsChannelData()
channel: SubscriberChannelData<ChannelName>; channel: SubscriberChannelData<ChannelName>;
@ApiPropertyOptional({
description: 'Subscriber Avatar',
type: String,
default: null,
})
@IsOptional()
@IsString()
@IsObjectId({ message: 'Avatar Attachment ID must be a valid ObjectId' })
avatar?: string | null = null;
} }
export class SubscriberUpdateDto extends PartialType(SubscriberCreateDto) {} export class SubscriberUpdateDto extends PartialType(SubscriberCreateDto) {}

View File

@ -14,13 +14,24 @@ export enum FileType {
unknown = 'unknown', unknown = 'unknown',
} }
export type AttachmentForeignKey = { /**
* The `AttachmentRef` type defines two possible ways to reference an attachment:
* 1. By `id`: This is used when the attachment is uploaded and stored in the Hexabot system.
* The `id` field represents the unique identifier of the uploaded attachment in the system.
* 2. By `url`: This is used when the attachment is externally hosted, especially when
* the content is generated or retrieved by a plugin that consumes a third-party API.
* In this case, the `url` field contains the direct link to the external resource.
*/
export type AttachmentRef =
| {
id: string | null; id: string | null;
/** @deprecated use "id" instead */ }
url?: string; | {
/** To be used only for external URLs (plugins), for attachments use "id" instead */
url: string;
}; };
export interface AttachmentPayload { export interface AttachmentPayload {
type: FileType; type: FileType;
payload: AttachmentForeignKey; payload: AttachmentRef;
} }

View File

@ -411,7 +411,7 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
'url' in block.message.attachment.payload 'url' in block.message.attachment.payload
) { ) {
this.logger.error( this.logger.error(
'Attachment Model : `url` payload has been deprecated in favor of `id`', 'Attachment Block : `url` payload has been deprecated in favor of `id`',
block.id, block.id,
block.message, block.message,
); );
@ -521,9 +521,11 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
} }
} else if (blockMessage && 'attachment' in blockMessage) { } else if (blockMessage && 'attachment' in blockMessage) {
const attachmentPayload = blockMessage.attachment.payload; const attachmentPayload = blockMessage.attachment.payload;
if (!attachmentPayload.id) { if (!('id' in attachmentPayload)) {
this.checkDeprecatedAttachmentUrl(block); this.checkDeprecatedAttachmentUrl(block);
throw new Error('Remote attachments are no longer supported!'); throw new Error(
'Remote attachments in blocks are no longer supported!',
);
} }
const envelope: StdOutgoingEnvelope = { const envelope: StdOutgoingEnvelope = {

View File

@ -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.
@ -256,6 +256,8 @@ export class ChatService {
event.setSender(subscriber); event.setSender(subscriber);
await event.preprocess();
// Trigger message received event // Trigger message received event
this.eventEmitter.emit('hook:chatbot:received', event); this.eventEmitter.emit('hook:chatbot:received', event);

View File

@ -22,7 +22,7 @@ import { MessageCreateDto } from '@/chat/dto/message.dto';
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto'; import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
import { VIEW_MORE_PAYLOAD } from '@/chat/helpers/constants'; import { VIEW_MORE_PAYLOAD } from '@/chat/helpers/constants';
import { Subscriber, SubscriberFull } from '@/chat/schemas/subscriber.schema'; import { Subscriber, SubscriberFull } from '@/chat/schemas/subscriber.schema';
import { AttachmentForeignKey } from '@/chat/schemas/types/attachment'; import { AttachmentRef } from '@/chat/schemas/types/attachment';
import { Button, ButtonType } from '@/chat/schemas/types/button'; import { Button, ButtonType } from '@/chat/schemas/types/button';
import { import {
AnyMessage, AnyMessage,
@ -155,7 +155,7 @@ export default abstract class BaseWebChannelHandler<
type: Web.IncomingMessageType.file, type: Web.IncomingMessageType.file,
data: { data: {
type: attachmentPayload.type, type: attachmentPayload.type,
url: await this.getPublicUrl(attachmentPayload.payload.id), url: await this.getPublicUrl(attachmentPayload.payload),
}, },
}; };
} }
@ -994,7 +994,7 @@ export default abstract class BaseWebChannelHandler<
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),
}, },
}; };
if (message.quickReplies && message.quickReplies.length > 0) { if (message.quickReplies && message.quickReplies.length > 0) {
@ -1034,11 +1034,11 @@ 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 attachmentRef =
.payload as AttachmentForeignKey; typeof item[fields.image_url] === 'string'
if (attachmentPayload.id) { ? { url: item[fields.image_url] }
element.image_url = await this.getPublicUrl(attachmentPayload.id); : (item[fields.image_url].payload as AttachmentRef);
} element.image_url = await this.getPublicUrl(attachmentRef);
} }
buttons.forEach((button: Button, index) => { buttons.forEach((button: Button, index) => {