Implement cloning cloud attachments into messages
This commit is contained in:
parent
19ca94fc32
commit
c167a7b541
@ -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;
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user