Skip to content

Commit

Permalink
work on message attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
targoninc-alex committed Jun 29, 2024
1 parent 8149071 commit 889f3a1
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

.idea/

database-type-generation/sql-to-ts.json
database-type-generation/sql-to-ts.json
/files
1 change: 1 addition & 0 deletions .idea/venel-api.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/features/database/mariaDbDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,12 @@ WHERE ur.userId = ?`, [userId]);
return await this.query("SELECT * FROM venel.userSettings WHERE userId = ?", [id]);
}

async createAttachment(id: Id, type: string, data: Buffer | null) {
await this.query("INSERT INTO venel.attachments (messageId, type, data) VALUES (?, ?, ?)", [id, type, data]);
async createAttachment(id: Id, type: string, filename: string) {
await this.query("INSERT INTO venel.attachments (messageId, type, filename) VALUES (?, ?, ?)", [id, type, filename]);
}

async getMessageAttachment(messageId: Id, name: string): Promise<Attachment | null> {
const rows = await this.query("SELECT * FROM venel.attachments WHERE messageId = ? AND filename = ?", [messageId, name]);
return rows[0] ?? null;
}
}
14 changes: 13 additions & 1 deletion src/features/database/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
export type Id = number;

export interface Attachment {
'data': Buffer | null;
'filename': string;
'id': Id;
'messageId': Id | null;
'type': string;
}

export interface AudioAttachment {
'binaryContent': Buffer | null;
'id': Id;
'messageId': Id | null;
}

export interface BridgedUser {
'createdAt': Date;
'instanceId': Id;
Expand Down Expand Up @@ -40,6 +46,12 @@ export interface Channel {
'updatedAt': Date;
}

export interface ImageAttachment {
'binaryContent': Buffer | null;
'id': Id;
'messageId': Id | null;
}

export interface MessageReaction {
'messageId': Id;
'reactionId': Id;
Expand Down
42 changes: 36 additions & 6 deletions src/features/liveFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import {Attachment, User} from "./database/models";
import {Application} from "express";
import {createServer} from "http";
import {UserWebSocket} from "./live/UserWebSocket";
import {avatarUser, safeUser} from "./authentication/actions";
import {safeUser} from "./authentication/actions";
import {PermissionsList} from "../enums/permissionsList";
import {LiveEndpoints} from "./live/endpoints";
import {ReceivableMessage} from "../models/receivableMessage";
import Jimp from "jimp";
import {UiChannel} from "../models/uiChannel";
import {ChannelProcessor} from "./messaging/channelProcessor";
import {hashSync} from "bcryptjs";
import fs from "fs";
import {WritableAttachment} from "../models/writableAttachment";

export class LiveFeature {
static enable(app: Application, userMap: Map<string, User>, db: MariaDbDatabase) {
Expand Down Expand Up @@ -148,7 +151,7 @@ export class LiveFeature {
return;
}

let attachments: Attachment[] = [];
let attachments: WritableAttachment[] = [];
if (data.attachments) {
for (const attachment of data.attachments) {
if (!attachment.type) {
Expand All @@ -163,14 +166,33 @@ export class LiveFeature {
messageId: message.id,
id: -1,
type: attachment.type,
data: Buffer.from(attachment.data, "base64")
filename: attachment.filename,
data: attachment.data
});
}

CLI.debug(`Creating ${attachments.length} attachments`);
const fileFolder = process.env.FILE_FOLDER;
if (!fileFolder) {
client.send(JSON.stringify({error: "FILE_FOLDER is not set"}));
return;
}
if (!fs.existsSync(fileFolder)) {
fs.mkdirSync(fileFolder);
}
const messageFolder = fileFolder + "/" + message.id;
if (attachments.length > 0) {
fs.mkdirSync(messageFolder);
}
for (const attachment of attachments) {
CLI.debug(`Creating attachment with length ${attachment.data?.length} of type ${attachment.type}`);
await db.createAttachment(message.id, attachment.type, attachment.data);
if (attachment.data) {
await db.createAttachment(message.id, attachment.type, attachment.filename);
const attachmentPath = messageFolder + "/" + attachment.filename;
// @ts-ignore
const data = Buffer.from(attachment.data, "base64");
fs.writeFileSync(attachmentPath, data);
CLI.debug(`Created attachment with length ${attachment.data?.length} of type ${attachment.type} at ${attachmentPath}`);
}
}
}

Expand All @@ -182,7 +204,10 @@ export class LiveFeature {

message.sender = safeUser(sender);
message.reactions = [];
message.attachments = attachments;
message.attachments = attachments.map(a => {
a.data = null;
return a;
});

const payload = JSON.stringify({
type: "message",
Expand Down Expand Up @@ -367,6 +392,11 @@ export class LiveFeature {

await db.deleteMessage(messageId);

const messageFolder = process.env.FILE_FOLDER + "/" + messageId;
if (fs.existsSync(messageFolder)) {
fs.rmSync(messageFolder, {recursive: true, force: true});
}

const payload = JSON.stringify({
type: "removeMessage",
channelId: message.channelId,
Expand Down
9 changes: 8 additions & 1 deletion src/features/messaging/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {MariaDbDatabase} from "../database/mariaDbDatabase";
import {Request, Response} from "express";
import {ChannelMember, Reaction, User} from "../database/models";
import {ChannelMember, User} from "../database/models";
import {CLI} from "../../tooling/CLI";
import {PermissionsList} from "../../enums/permissionsList";
import {safeUser} from "../authentication/actions";
import {ReceivableMessage} from "../../models/receivableMessage";
import {UiChannel} from "../../models/uiChannel";
import {ChannelProcessor} from "./channelProcessor";
import fs from "fs";

export class MessagingEndpoints {
static async checkChannelAccess(db: MariaDbDatabase, user: User, channelId: number) {
Expand Down Expand Up @@ -102,6 +103,12 @@ export class MessagingEndpoints {
return;
}
await db.deleteMessage(messageId);

const messageFolder = process.env.FILE_FOLDER + "/" + messageId;
if (fs.existsSync(messageFolder)) {
fs.rmSync(messageFolder, {recursive: true, force: true});
}

CLI.success(`Message ${messageId} deleted by user ${user.id}.`);
res.json({message: "Message deleted"});
}
Expand Down
39 changes: 39 additions & 0 deletions src/features/messagingFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ import {Application} from "express";
import {AuthActions} from "./authentication/actions";
import {MariaDbDatabase} from "./database/mariaDbDatabase";
import {MessagingEndpoints} from "./messaging/endpoints";
import fs from "fs";
import {CLI} from "../tooling/CLI";

export class MessagingFeature {
static enable(app: Application, db: MariaDbDatabase) {
if (!process.env.FILE_FOLDER) {
throw new Error("FILE_FOLDER is not set");
}

// Messages
const mPrefix = "/api/messaging";
app.post(`${mPrefix}/sendMessage`, AuthActions.checkAuthenticated, MessagingEndpoints.sendMessage(db));
Expand All @@ -26,6 +32,39 @@ export class MessagingFeature {
app.get(`${cPrefix}/getMessages`, AuthActions.checkAuthenticated, MessagingEndpoints.getMessages(db));
app.get(`${cPrefix}/getChannels`, AuthActions.checkAuthenticated, MessagingEndpoints.getChannels(db));

// Attachments
const aPrefix = "/attachments";
app.get(`${aPrefix}/:messageId/:filename`, AuthActions.checkAuthenticated, async (req, res, next) => {
const messageId = req.params.messageId;
if (!messageId) {
res.status(400).send("Message ID is required");
return;
}
const filename = req.params.filename;
if (!filename) {
res.status(400).send("Filename is required");
return;
}

const messageFolder = process.env.FILE_FOLDER + "/" + messageId;
if (!fs.existsSync(messageFolder)) {
res.status(404).send("Message not found");
return;
}
const attachmentPath = process.env.FILE_FOLDER + "/" + messageId + "/" + req.params.filename;
if (!fs.existsSync(attachmentPath)) {
res.status(404).send("Attachment not found");
return;
}
CLI.debug(`Sending attachment ${attachmentPath}`);
const stat = fs.statSync(attachmentPath);
const messageAttachment = await db.getMessageAttachment(parseInt(messageId.toString()), filename.toString());
res.setHeader("Content-Type", messageAttachment?.type ?? "application/octet-stream");
res.setHeader("Content-Length", stat.size);
const base64 = fs.readFileSync(attachmentPath).toString("base64");
res.send(base64);
});

return app;
}
}
5 changes: 5 additions & 0 deletions src/models/writableAttachment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {Attachment} from "../features/database/models";

export interface WritableAttachment extends Attachment {
data: Buffer | null;
}
36 changes: 32 additions & 4 deletions updateDb.sql
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ create table if not exists venel.messages

create table if not exists venel.attachments
(
id bigint auto_increment
id bigint auto_increment
primary key,
messageId bigint null,
type varchar(16) default 'file' not null,
data mediumblob null,
messageId bigint null,
type varchar(16) default 'file' not null,
filename text not null,
constraint attachments_ibfk_1
foreign key (messageId) references venel.messages (id)
on delete cascade
Expand All @@ -169,6 +169,34 @@ create table if not exists venel.attachments
create index if not exists messageId
on venel.attachments (messageId);

create table if not exists venel.audioAttachments
(
id bigint auto_increment
primary key,
messageId bigint null,
binaryContent mediumblob null,
constraint audioAttachments_ibfk_1
foreign key (messageId) references venel.messages (id)
on delete cascade
);

create index if not exists messageId
on venel.audioAttachments (messageId);

create table if not exists venel.imageAttachments
(
id bigint auto_increment
primary key,
messageId bigint null,
binaryContent mediumblob null,
constraint imageAttachments_ibfk_1
foreign key (messageId) references venel.messages (id)
on delete cascade
);

create index if not exists messageId
on venel.imageAttachments (messageId);

create table if not exists venel.messageReactions
(
messageId bigint not null,
Expand Down

0 comments on commit 889f3a1

Please sign in to comment.