Notes/src/routes/api/files.ts

258 lines
7.4 KiB
TypeScript
Raw Normal View History

2018-02-14 23:31:20 -05:00
"use strict";
import protectedSessionService from "../../services/protected_session.js";
import utils from "../../services/utils.js";
import log from "../../services/log.js";
import noteService from "../../services/notes.js";
import tmp from "tmp";
import fs from "fs";
2025-01-09 18:07:02 +02:00
import { Readable } from "stream";
import chokidar from "chokidar";
import ws from "../../services/ws.js";
import becca from "../../becca/becca.js";
import ValidationError from "../../errors/validation_error.js";
2025-01-09 18:07:02 +02:00
import { Request, Response } from "express";
import BNote from "../../becca/entities/bnote.js";
import BAttachment from "../../becca/entities/battachment.js";
2024-04-05 22:22:18 +03:00
function updateFile(req: Request) {
const note = becca.getNoteOrThrow(req.params.noteId);
2018-02-14 23:31:20 -05:00
2024-04-07 14:29:08 +03:00
const file = req.file;
2024-04-07 16:56:45 +03:00
if (!file) {
return {
uploaded: false,
message: `Missing file.`
};
}
note.saveRevision();
2019-11-09 11:58:52 +01:00
note.mime = file.mimetype.toLowerCase();
note.save();
2019-11-09 11:58:52 +01:00
2020-06-20 12:31:38 +02:00
note.setContent(file.buffer);
2019-11-09 11:58:52 +01:00
2025-01-09 18:07:02 +02:00
note.setLabel("originalFileName", file.originalname);
2019-11-09 11:58:52 +01:00
noteService.asyncPostProcessContent(note, file.buffer);
return {
2019-11-09 11:58:52 +01:00
uploaded: true
};
}
function updateAttachment(req: Request) {
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
2024-04-07 14:29:08 +03:00
const file = req.file;
2024-04-07 16:56:45 +03:00
if (!file) {
return {
uploaded: false,
message: `Missing file.`
};
}
2023-06-14 00:28:59 +02:00
attachment.getNote().saveRevision();
2023-05-03 22:49:24 +02:00
attachment.mime = file.mimetype.toLowerCase();
2025-01-09 18:07:02 +02:00
attachment.setContent(file.buffer, { forceSave: true });
2023-05-03 22:49:24 +02:00
return {
uploaded: true
};
}
2024-04-05 22:22:18 +03:00
function downloadData(noteOrAttachment: BNote | BAttachment, res: Response, contentDisposition: boolean) {
2023-05-03 10:23:20 +02:00
if (noteOrAttachment.isProtected && !protectedSessionService.isProtectedSessionAvailable()) {
return res.status(401).send("Protected session not available");
}
if (contentDisposition) {
const fileName = noteOrAttachment.getFileName();
2025-01-09 18:07:02 +02:00
res.setHeader("Content-Disposition", utils.getContentDisposition(fileName));
2023-05-03 10:23:20 +02:00
}
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
2025-01-09 18:07:02 +02:00
res.setHeader("Content-Type", noteOrAttachment.mime);
2023-05-03 10:23:20 +02:00
res.send(noteOrAttachment.getContent());
}
2024-04-05 22:22:18 +03:00
function downloadNoteInt(noteId: string, res: Response, contentDisposition = true) {
2021-05-02 11:23:58 +02:00
const note = becca.getNote(noteId);
if (!note) {
2025-01-09 18:07:02 +02:00
return res.setHeader("Content-Type", "text/plain").status(404).send(`Note '${noteId}' doesn't exist.`);
}
2023-05-03 10:23:20 +02:00
return downloadData(note, res, contentDisposition);
}
2024-04-05 22:22:18 +03:00
function downloadAttachmentInt(attachmentId: string, res: Response, contentDisposition = true) {
2023-05-03 10:23:20 +02:00
const attachment = becca.getAttachment(attachmentId);
2023-05-03 10:23:20 +02:00
if (!attachment) {
2025-01-09 18:07:02 +02:00
return res.setHeader("Content-Type", "text/plain").status(404).send(`Attachment '${attachmentId}' doesn't exist.`);
}
2023-05-03 10:23:20 +02:00
return downloadData(attachment, res, contentDisposition);
}
2024-04-05 22:22:18 +03:00
const downloadFile = (req: Request, res: Response) => downloadNoteInt(req.params.noteId, res, true);
const openFile = (req: Request, res: Response) => downloadNoteInt(req.params.noteId, res, false);
2019-01-27 15:47:40 +01:00
2024-04-05 22:22:18 +03:00
const downloadAttachment = (req: Request, res: Response) => downloadAttachmentInt(req.params.attachmentId, res, true);
const openAttachment = (req: Request, res: Response) => downloadAttachmentInt(req.params.attachmentId, res, false);
2024-04-05 22:22:18 +03:00
function fileContentProvider(req: Request) {
2023-05-05 23:41:11 +02:00
// Read the file name from route params.
const note = becca.getNoteOrThrow(req.params.noteId);
2019-01-27 15:47:40 +01:00
2023-05-03 10:23:20 +02:00
return streamContent(note.getContent(), note.getFileName(), note.mime);
2019-01-27 15:47:40 +01:00
}
2024-04-05 22:22:18 +03:00
function attachmentContentProvider(req: Request) {
2023-05-05 23:41:11 +02:00
// Read the file name from route params.
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
2023-05-03 10:23:20 +02:00
return streamContent(attachment.getContent(), attachment.getFileName(), attachment.mime);
}
2024-04-11 23:00:24 +03:00
async function streamContent(content: string | Buffer, fileName: string, mimeType: string) {
if (typeof content === "string") {
2025-01-09 18:07:02 +02:00
content = Buffer.from(content, "utf8");
}
const totalSize = content.byteLength;
2025-01-09 18:07:02 +02:00
const getStream = (range: { start: number; end: number }) => {
if (!range) {
// Request if for complete content.
return Readable.from(content);
}
// Partial content request.
2025-01-09 18:07:02 +02:00
const { start, end } = range;
return Readable.from(content.slice(start, end + 1));
2025-01-09 18:07:02 +02:00
};
return {
fileName,
totalSize,
mimeType,
getStream
};
}
2024-04-05 22:22:18 +03:00
function saveNoteToTmpDir(req: Request) {
const note = becca.getNoteOrThrow(req.params.noteId);
2023-05-03 10:23:20 +02:00
const fileName = note.getFileName();
const content = note.getContent();
2025-01-09 18:07:02 +02:00
return saveToTmpDir(fileName, content, "notes", note.noteId);
2023-05-03 10:23:20 +02:00
}
2024-04-05 22:22:18 +03:00
function saveAttachmentToTmpDir(req: Request) {
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
2023-05-03 10:23:20 +02:00
const fileName = attachment.getFileName();
const content = attachment.getContent();
2024-04-05 22:22:18 +03:00
if (!attachment.attachmentId) {
throw new ValidationError("Missing attachment ID.");
}
2025-01-09 18:07:02 +02:00
return saveToTmpDir(fileName, content, "attachments", attachment.attachmentId);
2023-05-03 10:23:20 +02:00
}
const createdTemporaryFiles = new Set<string>();
2024-04-05 22:22:18 +03:00
function saveToTmpDir(fileName: string, content: string | Buffer, entityType: string, entityId: string) {
2023-05-03 10:23:20 +02:00
const tmpObj = tmp.fileSync({ postfix: fileName });
2024-04-05 22:22:18 +03:00
if (typeof content === "string") {
fs.writeSync(tmpObj.fd, content);
} else {
fs.writeSync(tmpObj.fd, content);
2024-04-05 22:22:18 +03:00
}
fs.closeSync(tmpObj.fd);
createdTemporaryFiles.add(tmpObj.name);
2023-05-03 10:23:20 +02:00
log.info(`Saved temporary file ${tmpObj.name}`);
2021-04-24 11:39:59 +02:00
if (utils.isElectron()) {
2025-01-09 18:07:02 +02:00
chokidar.watch(tmpObj.name).on("change", (path, stats) => {
2021-04-24 11:39:59 +02:00
ws.sendMessageToAllClients({
2025-01-09 18:07:02 +02:00
type: "openedFileUpdated",
2023-05-03 10:23:20 +02:00
entityType: entityType,
entityId: entityId,
2024-04-05 22:22:18 +03:00
lastModifiedMs: stats?.atimeMs,
2021-04-24 11:39:59 +02:00
filePath: tmpObj.name
});
});
}
return {
tmpFilePath: tmpObj.name
};
}
2024-04-05 22:22:18 +03:00
function uploadModifiedFileToNote(req: Request) {
2023-05-03 10:23:20 +02:00
const noteId = req.params.noteId;
2025-01-09 18:07:02 +02:00
const { filePath } = req.body;
2023-05-03 10:23:20 +02:00
if (!createdTemporaryFiles.has(filePath)) {
throw new ValidationError(`File '${filePath}' is not a temporary file.`);
}
const note = becca.getNoteOrThrow(noteId);
2023-05-03 10:23:20 +02:00
log.info(`Updating note '${noteId}' with content from '${filePath}'`);
note.saveRevision();
2023-05-03 10:23:20 +02:00
const fileContent = fs.readFileSync(filePath);
if (!fileContent) {
throw new ValidationError(`File '${fileContent}' is empty`);
}
note.setContent(fileContent);
}
2024-04-05 22:22:18 +03:00
function uploadModifiedFileToAttachment(req: Request) {
2025-01-09 18:07:02 +02:00
const { attachmentId } = req.params;
const { filePath } = req.body;
2023-05-03 10:23:20 +02:00
const attachment = becca.getAttachmentOrThrow(attachmentId);
2023-05-03 10:23:20 +02:00
log.info(`Updating attachment '${attachmentId}' with content from '${filePath}'`);
attachment.getNote().saveRevision();
2023-05-03 10:23:20 +02:00
const fileContent = fs.readFileSync(filePath);
if (!fileContent) {
throw new ValidationError(`File '${fileContent}' is empty`);
}
attachment.setContent(fileContent);
}
export default {
2019-11-09 11:58:52 +01:00
updateFile,
2023-05-03 22:49:24 +02:00
updateAttachment,
openFile,
fileContentProvider,
2019-01-27 15:47:40 +01:00
downloadFile,
2023-05-03 10:23:20 +02:00
downloadNoteInt,
saveNoteToTmpDir,
openAttachment,
downloadAttachment,
saveAttachmentToTmpDir,
attachmentContentProvider,
uploadModifiedFileToNote,
uploadModifiedFileToAttachment
};