Skip to content

Commit

Permalink
fix: leave support for url in attachment payload (case for external u…
Browse files Browse the repository at this point in the history
…rls)
  • Loading branch information
marrouchi committed Jan 13, 2025
1 parent d7cb39f commit f399416
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 37 deletions.
12 changes: 11 additions & 1 deletion api/src/channel/lib/EventWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default abstract class EventWrapper<
*
* @returns The current instance of the channel handler.
*/
getHandler(): ChannelHandler {
getHandler(): C {
return this._handler;
}

Expand Down Expand Up @@ -189,6 +189,16 @@ export default abstract class EventWrapper<
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
*
Expand Down
39 changes: 25 additions & 14 deletions api/src/channel/lib/Handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { NextFunction, Request, Response } from 'express';
import { Attachment } from '@/attachment/schemas/attachment.schema';
import { AttachmentService } from '@/attachment/services/attachment.service';
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
import { AttachmentRef } from '@/chat/schemas/types/attachment';
import {
StdOutgoingEnvelope,
StdOutgoingMessage,
Expand Down Expand Up @@ -234,22 +235,32 @@ export default abstract class ChannelHandler<
* @param attachment The attachment ID or object to generate a signed URL for.
* @return A signed URL string for downloading the specified attachment.
*/
public async getPublicUrl(attachment: string | Attachment) {
const resource =
typeof attachment === 'string'
? await this.attachmentService.findOne(attachment)
: attachment;
public async getPublicUrl(attachment: AttachmentRef | Attachment) {
if ('id' in attachment) {
if (!attachment.id) {
throw new TypeError(
'Attachment ID is empty, unable to generate public URL.',
);
}

if (!resource) {
throw new NotFoundException('Unable to find attachment');
}
const resource = await this.attachmentService.findOne(attachment.id);

const token = this.jwtService.sign({ ...resource }, this.jwtSignOptions);
const [name, _suffix] = this.getName().split('-');
return buildURL(
config.apiBaseUrl,
`/webhook/${name}/download/${resource.name}?t=${encodeURIComponent(token)}`,
);
if (!resource) {
throw new NotFoundException('Unable to find attachment');
}

const token = this.jwtService.sign({ ...resource }, this.jwtSignOptions);
const [name, _suffix] = this.getName().split('-');
return buildURL(
config.apiBaseUrl,
`/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.');
}
}

/**
Expand Down
6 changes: 3 additions & 3 deletions api/src/channel/lib/__test__/common.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ export const urlButtonsMessage: StdOutgoingButtonsMessage = {
};

const attachment: Attachment = {
id: '1',
id: '1'.repeat(24),
name: 'attachment.jpg',
type: 'image/jpeg',
size: 3539,
location: '39991e51-55c6-4a26-9176-b6ba04f180dc.jpg',
channel: {
['dimelo']: {
id: 'attachment-id-dimelo',
['any-channel']: {
id: 'any-channel-attachment-id',
},
},
createdAt: new Date(),
Expand Down
12 changes: 11 additions & 1 deletion api/src/chat/dto/subscriber.dto.ts
Original file line number Diff line number Diff line change
@@ -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:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
Expand Down Expand Up @@ -111,6 +111,16 @@ export class SubscriberCreateDto {
@IsNotEmpty()
@IsChannelData()
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) {}
23 changes: 17 additions & 6 deletions api/src/chat/schemas/types/attachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ export enum FileType {
unknown = 'unknown',
}

export type AttachmentForeignKey = {
id: string | null;
/** @deprecated use "id" instead */
url?: string;
};
/**
* 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;
}
| {
/** To be used only for external URLs (plugins), for attachments use "id" instead */
url: string;
};

export interface AttachmentPayload {
type: FileType;
payload: AttachmentForeignKey;
payload: AttachmentRef;
}
8 changes: 5 additions & 3 deletions api/src/chat/services/block.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
'url' in block.message.attachment.payload
) {
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.message,
);
Expand Down Expand Up @@ -521,9 +521,11 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
}
} else if (blockMessage && 'attachment' in blockMessage) {
const attachmentPayload = blockMessage.attachment.payload;
if (!attachmentPayload.id) {
if (!('id' in attachmentPayload)) {
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 = {
Expand Down
4 changes: 3 additions & 1 deletion api/src/chat/services/chat.service.ts
Original file line number Diff line number Diff line change
@@ -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:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
Expand Down Expand Up @@ -256,6 +256,8 @@ export class ChatService {

event.setSender(subscriber);

await event.preprocess();

// Trigger message received event
this.eventEmitter.emit('hook:chatbot:received', event);

Expand Down
16 changes: 8 additions & 8 deletions api/src/extensions/channels/web/base-web-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { MessageCreateDto } from '@/chat/dto/message.dto';
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
import { VIEW_MORE_PAYLOAD } from '@/chat/helpers/constants';
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 {
AnyMessage,
Expand Down Expand Up @@ -155,7 +155,7 @@ export default abstract class BaseWebChannelHandler<
type: Web.IncomingMessageType.file,
data: {
type: attachmentPayload.type,
url: await this.getPublicUrl(attachmentPayload.payload.id),
url: await this.getPublicUrl(attachmentPayload.payload),
},
};
}
Expand Down Expand Up @@ -994,7 +994,7 @@ export default abstract class BaseWebChannelHandler<
type: Web.OutgoingMessageType.file,
data: {
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) {
Expand Down Expand Up @@ -1034,11 +1034,11 @@ export default abstract class BaseWebChannelHandler<
}

if (fields.image_url && item[fields.image_url]) {
const attachmentPayload = item[fields.image_url]
.payload as AttachmentForeignKey;
if (attachmentPayload.id) {
element.image_url = await this.getPublicUrl(attachmentPayload.id);
}
const attachmentRef =
typeof item[fields.image_url] === 'string'
? { url: item[fields.image_url] }
: (item[fields.image_url].payload as AttachmentRef);
element.image_url = await this.getPublicUrl(attachmentRef);
}

buttons.forEach((button: Button, index) => {
Expand Down

0 comments on commit f399416

Please sign in to comment.