mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: attachment issues wip
This commit is contained in:
parent
208ddd411f
commit
33cc3f713d
BIN
api/assets/hexavatar.png
Normal file
BIN
api/assets/hexavatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -133,6 +133,7 @@ export class AttachmentController extends BaseController<Attachment> {
|
||||
access = AttachmentAccess.Public,
|
||||
}: AttachmentContextParamDto,
|
||||
): Promise<Attachment[]> {
|
||||
debugger;
|
||||
if (!files || !Array.isArray(files?.file) || files.file.length === 0) {
|
||||
throw new BadRequestException('No file was selected');
|
||||
}
|
||||
|
@ -6,27 +6,74 @@
|
||||
* 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 fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Readable, Stream } from 'stream';
|
||||
|
||||
import { Injectable, Optional, StreamableFile } from '@nestjs/common';
|
||||
import {
|
||||
Injectable,
|
||||
OnApplicationBootstrap,
|
||||
Optional,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { HelperService } from '@/helper/helper.service';
|
||||
import { HelperType } from '@/helper/types';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
import { BaseService } from '@/utils/generics/base-service';
|
||||
|
||||
import { AttachmentMetadataDto } from '../dto/attachment.dto';
|
||||
import { AttachmentRepository } from '../repositories/attachment.repository';
|
||||
import { Attachment } from '../schemas/attachment.schema';
|
||||
import {
|
||||
AttachmentAccess,
|
||||
AttachmentCreatedByRef,
|
||||
AttachmentResourceRef,
|
||||
} from '../types';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentService extends BaseService<Attachment> {
|
||||
export class AttachmentService
|
||||
extends BaseService<Attachment>
|
||||
implements OnApplicationBootstrap
|
||||
{
|
||||
constructor(
|
||||
readonly repository: AttachmentRepository,
|
||||
@Optional() private readonly helperService: HelperService,
|
||||
private settingService: SettingService,
|
||||
) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
async onApplicationBootstrap() {
|
||||
debugger;
|
||||
const defaultAttachment = await this.repository.findOne({
|
||||
createdByRef: AttachmentCreatedByRef.System,
|
||||
});
|
||||
if (!defaultAttachment) {
|
||||
const imagePath = path.join(process.cwd(), 'assets', 'hexavatar.png');
|
||||
// console.log({ imagePath });
|
||||
const imageBuffer = fs.readFileSync(imagePath);
|
||||
const result = await this.store(imageBuffer, {
|
||||
access: AttachmentAccess.Public,
|
||||
name: 'hexavatar.png',
|
||||
createdBy: 'system',
|
||||
size: imageBuffer.length,
|
||||
type: 'png',
|
||||
createdByRef: AttachmentCreatedByRef.System,
|
||||
resourceRef: AttachmentResourceRef.SettingAttachment,
|
||||
channel: {
|
||||
'web-channel': '',
|
||||
'console-channel': '',
|
||||
'discord-channel': '',
|
||||
'whatsapp-channel': '',
|
||||
},
|
||||
});
|
||||
console.log({ result });
|
||||
console.log({ result });
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a file using the default storage helper and creates an attachment record.
|
||||
*
|
||||
|
@ -15,6 +15,7 @@ import { Readable, Stream } from 'stream';
|
||||
export enum AttachmentCreatedByRef {
|
||||
User = 'User',
|
||||
Subscriber = 'Subscriber',
|
||||
System = 'System',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,21 +308,20 @@ export default abstract class ChannelHandler<
|
||||
* @return A signed URL string for downloading the specified attachment.
|
||||
*/
|
||||
public async getPublicUrl(attachment: AttachmentRef | Attachment) {
|
||||
const [name, _suffix] = this.getName().split('-');
|
||||
if ('id' in attachment) {
|
||||
if (!attachment.id) {
|
||||
throw new TypeError(
|
||||
'Attachment ID is empty, unable to generate public URL.',
|
||||
);
|
||||
if (!attachment || !attachment.id) {
|
||||
return buildURL(config.apiBaseUrl, `/webhook/${name}/not-found`);
|
||||
}
|
||||
|
||||
const resource = await this.attachmentService.findOne(attachment.id);
|
||||
|
||||
if (!resource) {
|
||||
throw new NotFoundException('Unable to find attachment');
|
||||
this.logger.warn('Unable to find attachment sending fallback image');
|
||||
return buildURL(config.apiBaseUrl, `/webhook/${name}/not-found`);
|
||||
}
|
||||
|
||||
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)}`,
|
||||
@ -331,7 +330,9 @@ export default abstract class ChannelHandler<
|
||||
// In case the url is external
|
||||
return attachment.url;
|
||||
} else {
|
||||
throw new TypeError('Unable to resolve the attachment public URL.');
|
||||
return buildURL(config.apiBaseUrl, `/webhook/${name}/not-found`);
|
||||
|
||||
// throw new TypeError('Unable to resolve the attachment public URL.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,4 +88,10 @@ export class WebhookController {
|
||||
this.logger.log('Channel notification : ', req.method, channel);
|
||||
return await this.channelService.handle(channel, req, res);
|
||||
}
|
||||
|
||||
@Roles('public')
|
||||
@Get(':channel/not-found')
|
||||
async handleNotFound(@Res() res: Response) {
|
||||
return res.status(404).send({ error: 'Not found!' });
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,25 @@ export class SettingService extends BaseService<Setting> {
|
||||
if (count === 0) {
|
||||
await this.seeder.seed(data);
|
||||
}
|
||||
|
||||
// const imagePath = path.join(process.cwd(), 'assets', 'hexavatar.png');
|
||||
|
||||
// const imageBuffer = fs.readFileSync(imagePath);
|
||||
// await this.attachmentService.store(imageBuffer, {
|
||||
// access: AttachmentAccess.Public,
|
||||
// name: 'hexavatar.png',
|
||||
// createdBy: 'system',
|
||||
// size: imageBuffer.length,
|
||||
// type: 'png',
|
||||
// createdByRef: AttachmentCreatedByRef.System,
|
||||
// resourceRef: AttachmentResourceRef.SettingAttachment,
|
||||
// channel: {
|
||||
// 'web-channel': '',
|
||||
// 'console-channel': '',
|
||||
// 'discord-channel': '',
|
||||
// 'whatsapp-channel': '',
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,7 +116,11 @@
|
||||
"select_category": "Select a flow",
|
||||
"logout_failed": "Something went wrong during logout",
|
||||
"duplicate_labels_not_allowed": "Duplicate labels are not allowed",
|
||||
"duplicate_block_error": "Something went wrong while duplicating block"
|
||||
"duplicate_block_error": "Something went wrong while duplicating block",
|
||||
"image_error": "Image not found",
|
||||
"file_error": "File not found",
|
||||
"audio_error": "Audio not found",
|
||||
"video_error": "Video not found"
|
||||
},
|
||||
"menu": {
|
||||
"terms": "Terms of Use",
|
||||
|
@ -116,7 +116,11 @@
|
||||
"select_category": "Sélectionner une catégorie",
|
||||
"logout_failed": "Une erreur s'est produite lors de la déconnexion",
|
||||
"duplicate_labels_not_allowed": "Les étiquettes en double ne sont pas autorisées",
|
||||
"duplicate_block_error": "Une erreur est survenue lors de la duplication du bloc"
|
||||
"duplicate_block_error": "Une erreur est survenue lors de la duplication du bloc",
|
||||
"image_error": "Image introuvable",
|
||||
"file_error": "Fichier introuvable",
|
||||
"audio_error": "Audio introuvable",
|
||||
"video_error": "Vidéo introuvable"
|
||||
},
|
||||
"menu": {
|
||||
"terms": "Conditions d'utilisation",
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { FC, useState } from "react";
|
||||
|
||||
import { useDialogs } from "@/hooks/useDialogs";
|
||||
import { useGetAttachmentMetadata } from "@/hooks/useGetAttachmentMetadata";
|
||||
@ -30,11 +30,17 @@ interface AttachmentInterface {
|
||||
const componentMap: { [key in FileType]: FC<AttachmentInterface> } = {
|
||||
[FileType.image]: ({ url }: AttachmentInterface) => {
|
||||
const dialogs = useDialogs();
|
||||
const [imageErrored, setImageErrored] = useState(false);
|
||||
const { t } = useTranslate();
|
||||
|
||||
if (imageErrored) {
|
||||
return <p>{t("message.image_error")}</p>;
|
||||
}
|
||||
if (url)
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
onError={() => setImageErrored(true)}
|
||||
width="auto"
|
||||
height={200}
|
||||
style={{ objectFit: "contain", cursor: "pointer" }}
|
||||
@ -59,11 +65,24 @@ const componentMap: { [key in FileType]: FC<AttachmentInterface> } = {
|
||||
);
|
||||
},
|
||||
[FileType.audio]: (props: AttachmentInterface) => {
|
||||
return <audio controls src={props.url} />;
|
||||
const [audioErrored, setAudioErrored] = useState(false);
|
||||
const { t } = useTranslate();
|
||||
|
||||
if (audioErrored) {
|
||||
return <p>{t("message.video_error")}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<audio controls src={props.url} onError={() => setAudioErrored(true)} />
|
||||
);
|
||||
},
|
||||
[FileType.file]: (props: AttachmentInterface) => {
|
||||
const { t } = useTranslate();
|
||||
|
||||
if (!props.url) {
|
||||
return <p>{t("message.file_error")}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
@ -84,11 +103,20 @@ const componentMap: { [key in FileType]: FC<AttachmentInterface> } = {
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
[FileType.video]: ({ url }: AttachmentInterface) => (
|
||||
[FileType.video]: ({ url }: AttachmentInterface) => {
|
||||
const [videoErrored, setVideoErrored] = useState(false);
|
||||
const { t } = useTranslate();
|
||||
|
||||
if (videoErrored) {
|
||||
return <p>{t("message.video_error")}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<video controls width="250">
|
||||
<source src={url} />
|
||||
<source src={url} onError={() => setVideoErrored(true)} />
|
||||
</video>
|
||||
),
|
||||
);
|
||||
},
|
||||
[FileType.unknown]: ({ url }: AttachmentInterface) => <>Unknown Type:{url}</>,
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,15 @@
|
||||
video {
|
||||
max-width: 100%;
|
||||
}
|
||||
.error-message {
|
||||
margin: 0;
|
||||
// padding: 10px 0 10px 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
color: #666;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.sc-message--content.sent .sc-message--file {
|
||||
@ -64,3 +73,13 @@
|
||||
.sc-message--content.received .sc-message--file a:hover {
|
||||
color: #0c0c0c;
|
||||
}
|
||||
|
||||
// .error-message {
|
||||
// margin: 0;
|
||||
// padding: 10px 0;
|
||||
// text-align: center;
|
||||
// color: #666;
|
||||
// font-style: italic;
|
||||
// width: 100%;
|
||||
// display: block;
|
||||
// }
|
||||
|
@ -6,13 +6,12 @@
|
||||
* 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 React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useTranslation } from "../../hooks/useTranslation";
|
||||
import { useColors } from "../../providers/ColorProvider";
|
||||
import { Direction, TMessage } from "../../types/message.types";
|
||||
import FileIcon from "../icons/FileIcon";
|
||||
|
||||
import "./FileMessage.scss";
|
||||
|
||||
interface FileMessageProps {
|
||||
@ -23,11 +22,17 @@ const FileMessage: React.FC<FileMessageProps> = ({ message }) => {
|
||||
const { t } = useTranslation();
|
||||
const { colors: allColors } = useColors();
|
||||
const colors = allColors[message.direction || Direction.received];
|
||||
const [videoErrored, setVideoErrored] = useState(false);
|
||||
const [audioErrored, setAudioErrored] = useState(false);
|
||||
const [fileErrored, setFileErrored] = useState(false);
|
||||
const [imageErrored, setImageErrored] = useState(false);
|
||||
|
||||
if (!("type" in message.data)) {
|
||||
throw new Error("Unable to detect type for file message");
|
||||
}
|
||||
|
||||
if (message.data.type === "unknown") {
|
||||
return <p className="error-message">unknown file type</p>;
|
||||
}
|
||||
if (
|
||||
message.data &&
|
||||
message.data.type !== "image" &&
|
||||
@ -35,7 +40,7 @@ const FileMessage: React.FC<FileMessageProps> = ({ message }) => {
|
||||
message.data.type !== "video" &&
|
||||
message.data.type !== "file"
|
||||
) {
|
||||
throw new Error("Uknown type for file message");
|
||||
throw new Error("Unknown type for file message");
|
||||
}
|
||||
|
||||
return (
|
||||
@ -48,23 +53,60 @@ const FileMessage: React.FC<FileMessageProps> = ({ message }) => {
|
||||
>
|
||||
{message.data.type === "image" && (
|
||||
<div className="sc-message--file-icon">
|
||||
<img src={message.data.url || ""} className="sc-image" alt="File" />
|
||||
{imageErrored ? (
|
||||
<p
|
||||
className="error-message"
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
}}
|
||||
>
|
||||
{t("messages.file_message.image_error")}
|
||||
</p>
|
||||
) : (
|
||||
<img
|
||||
onError={() => setImageErrored(true)}
|
||||
src={message?.data?.url}
|
||||
className="sc-image"
|
||||
alt="File"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{message.data.type === "audio" && (
|
||||
<div className="sc-message--file-audio">
|
||||
<audio controls>
|
||||
{audioErrored ? (
|
||||
<p
|
||||
className="error-message"
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
}}
|
||||
>
|
||||
{t("messages.file_message.audio_error")}
|
||||
</p>
|
||||
) : (
|
||||
<audio controls onError={() => setAudioErrored(true)}>
|
||||
<source src={message.data.url} />
|
||||
{t("messages.file_message.browser_audio_unsupport")}
|
||||
</audio>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{message.data.type === "video" && (
|
||||
<div className="sc-message--file-video">
|
||||
<video controls width="100%">
|
||||
{videoErrored ? (
|
||||
<p
|
||||
className="error-message"
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
}}
|
||||
>
|
||||
{t("messages.file_message.video_error")}
|
||||
</p>
|
||||
) : (
|
||||
<video controls width="100%" onError={() => setVideoErrored(true)}>
|
||||
<source src={message.data.url} />
|
||||
{t("messages.file_message.browser_video_unsupport")}
|
||||
</video>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{message.data.type === "file" && (
|
||||
@ -75,6 +117,12 @@ const FileMessage: React.FC<FileMessageProps> = ({ message }) => {
|
||||
backgroundColor: colors.bg,
|
||||
}}
|
||||
>
|
||||
{!message?.data?.url ||
|
||||
message?.data?.url?.includes("webhook/download/not-found") ? (
|
||||
<p className="error-message" style={{ padding: 0 }}>
|
||||
{t("messages.file_message.file_error")}
|
||||
</p>
|
||||
) : (
|
||||
<a
|
||||
href={message.data.url ? message.data.url : "#"}
|
||||
target="_blank"
|
||||
@ -84,6 +132,7 @@ const FileMessage: React.FC<FileMessageProps> = ({ message }) => {
|
||||
<FileIcon />
|
||||
{t("messages.file_message.download")}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -17,7 +17,11 @@
|
||||
"browser_video_unsupport": "Browser does not support the video element.",
|
||||
"download": "Download",
|
||||
"unsupported_file_type": "This file type is not supported.",
|
||||
"unsupported_file_size": "This file size is not supported."
|
||||
"unsupported_file_size": "This file size is not supported.",
|
||||
"image_error": "Image not found",
|
||||
"file_error": "File not found",
|
||||
"audio_error": "Audio not found",
|
||||
"video_error": "Video not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,11 @@
|
||||
"browser_video_unsupport": "Le navigateur ne prend pas en charge l'élément vidéo.",
|
||||
"download": "Télécharger",
|
||||
"unsupported_file_type": "Ce type de fichier n'est pas pris en charge.",
|
||||
"unsupported_file_size": "Cette taille de fichier n'est pas prise en charge."
|
||||
"unsupported_file_size": "Cette taille de fichier n'est pas prise en charge.",
|
||||
"image_error": "Image introuvable",
|
||||
"file_error": "Fichier introuvable",
|
||||
"audio_error": "Audio introuvable",
|
||||
"video_error": "Vidéo introuvable"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user