Implement cloning cloud attachments into messages

This commit is contained in:
Rory& 2025-09-23 23:09:47 +02:00
parent 19ca94fc32
commit c167a7b541
4 changed files with 48 additions and 3 deletions

View File

@ -189,4 +189,28 @@ router.delete("/:channel_id/:batch_id/:attachment_id/:filename", async (req: Req
return res.status(404).send("Attachment not found");
});
router.post("/:channel_id/:batch_id/:attachment_id/:filename/clone_to_message/:message_id", async (req: Request, res: Response) => {
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
const { channel_id, batch_id, attachment_id, filename } = req.params;
const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`;
const newPath = `attachments/${channel_id}/${filename}`;
const att = await CloudAttachment.findOne({
where: {
uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`,
channelId: channel_id,
userAttachmentId: attachment_id,
userFilename: filename,
},
});
if (att) {
await storage.clone(path, newPath);
return res.send({ success: true, new_path: newPath });
}
return res.status(404).send("Attachment not found");
});
export default router;

View File

@ -1,6 +1,6 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
Copyright (C) 2025 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
@ -51,6 +51,17 @@ export class FileStorage implements Storage {
}
}
async clone(path: string, newPath: string) {
path = getPath(path);
newPath = getPath(newPath);
if (!fs.existsSync(dirname(newPath)))
fs.mkdirSync(dirname(newPath), { recursive: true });
// use reflink if possible, in order to not duplicate files at the block layer...
fs.copyFileSync(path, newPath, fs.constants.COPYFILE_FICLONE)
}
async set(path: string, value: Buffer) {
path = getPath(path);
if (!fs.existsSync(dirname(path)))

View File

@ -1,6 +1,6 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
Copyright (C) 2025 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
@ -50,6 +50,15 @@ export class S3Storage implements Storage {
});
}
async clone(path: string, newPath: string): Promise<void> {
// TODO: does this even work?
await this.client.copyObject({
Bucket: this.bucket,
CopySource: `/${this.bucket}/${this.bucketBasePath}${path}`,
Key: `${this.bucketBasePath}${newPath}`,
});
}
async get(path: string): Promise<Buffer | null> {
try {
const s3Object = await this.client.getObject({

View File

@ -1,6 +1,6 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
Copyright (C) 2025 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
@ -25,6 +25,7 @@ process.cwd();
export interface Storage {
set(path: string, data: Buffer): Promise<void>;
clone(path: string, newPath: string): Promise<void>;
get(path: string): Promise<Buffer | null>;
delete(path: string): Promise<void>;
}