uploading image to attachment

This commit is contained in:
zadam 2023-03-16 18:34:39 +01:00
parent 9cd5bdeb53
commit 1fdb23746a
8 changed files with 90 additions and 26 deletions

View File

@ -6,11 +6,11 @@ CREATE TABLE IF NOT EXISTS "attachments"
mime TEXT not null,
title TEXT not null,
isProtected INT not null DEFAULT 0,
blobId TEXT not null,
blobId TEXT DEFAULT null,
utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
utcDateModified TEXT not null,
isDeleted INT not null,
deleteId TEXT DEFAULT NULL);
CREATE UNIQUE INDEX IDX_attachments_parentId_role
CREATE INDEX IDX_attachments_parentId_role
on attachments (parentId, role);

View File

@ -112,9 +112,10 @@ CREATE TABLE IF NOT EXISTS "attachments"
mime TEXT not null,
title TEXT not null,
isProtected INT not null DEFAULT 0,
blobId TEXT not null,
blobId TEXT DEFAULT null,
utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
utcDateModified TEXT not null,
isDeleted INT not null,
deleteId TEXT DEFAULT NULL);
CREATE UNIQUE INDEX IDX_attachments_parentId_role
CREATE INDEX IDX_attachments_parentId_role
on attachments (parentId, role);

View File

@ -21,7 +21,7 @@ class BAttachment extends AbstractBeccaEntity {
super();
if (!row.parentId?.trim()) {
throw new Error("'noteId' must be given to initialize a Attachment entity");
throw new Error("'parentId' must be given to initialize a Attachment entity");
} else if (!row.role?.trim()) {
throw new Error("'role' must be given to initialize a Attachment entity");
} else if (!row.mime?.trim()) {
@ -40,6 +40,8 @@ class BAttachment extends AbstractBeccaEntity {
this.mime = row.mime;
/** @type {string} */
this.title = row.title;
/** @type {string} */
this.blobId = row.blobId;
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
@ -71,15 +73,7 @@ class BAttachment extends AbstractBeccaEntity {
this._setContent(content, opts);
}
calculateCheckSum(content) {
return utils.hash(`${this.attachmentId}|${content.toString()}`);
}
beforeSaving() {
if (!this.name.match(/^[a-z0-9]+$/i)) {
throw new Error(`Name must be alphanumerical, "${this.name}" given.`);
}
super.beforeSaving();
this.utcDateModified = dateUtils.utcNowDateTime();
@ -92,6 +86,7 @@ class BAttachment extends AbstractBeccaEntity {
role: this.role,
mime: this.mime,
title: this.title,
blobId: this.blobId,
isProtected: !!this.isProtected,
isDeleted: false,
utcDateScheduledForDeletionSince: this.utcDateScheduledForDeletionSince,

View File

@ -1429,7 +1429,7 @@ class BNote extends AbstractBeccaEntity {
}
} else {
attachment = new BAttachment({
noteId: this.noteId,
parentId: this.noteId,
title,
role,
mime,
@ -1437,7 +1437,11 @@ class BNote extends AbstractBeccaEntity {
});
}
attachment.setContent(content, { forceSave: true });
if (content !== undefined && content !== null) {
attachment.setContent(content, {forceSave: true});
} else {
attachment.save();
}
return attachment;
}

View File

@ -11,15 +11,12 @@ function returnImage(req, res) {
const image = becca.getNote(req.params.noteId);
if (!image) {
return res.sendStatus(404);
res.set('Content-Type', 'image/png');
return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
}
else if (!["image", "canvas"].includes(image.type)){
return res.sendStatus(400);
}
else if (image.isDeleted || image.data === null) {
res.set('Content-Type', 'image/png');
return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
}
/**
* special "image" type. the canvas is actually type application/json
@ -46,6 +43,29 @@ function returnImage(req, res) {
}
}
function returnAttachedImage(req, res) {
const note = becca.getNote(req.params.noteId);
if (!note) {
return res.sendStatus(404);
}
const attachment = becca.getAttachment(req.params.attachmentId);
if (!attachment || attachment.parentId !== note.noteId) {
res.set('Content-Type', 'image/png');
return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
}
if (!["image"].includes(attachment.role)) {
return res.sendStatus(400);
}
res.set('Content-Type', attachment.mime);
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(attachment.getContent());
}
function uploadImage(req) {
const {noteId} = req.query;
const {file} = req;
@ -57,10 +77,10 @@ function uploadImage(req) {
}
if (!["image/png", "image/jpg", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
throw new ValidationError(`Unknown image type: ${file.mimetype}`);
throw new ValidationError(`Unknown image type '${file.mimetype}'`);
}
const {url} = imageService.saveImage(noteId, file.buffer, file.originalname, true, true);
const {url} = imageService.saveImageToAttachment(noteId, file.buffer, file.originalname, true, true);
return {
uploaded: true,
@ -92,6 +112,7 @@ function updateImage(req) {
module.exports = {
returnImage,
returnAttachedImage,
uploadImage,
updateImage
};

View File

@ -195,6 +195,7 @@ function register(app) {
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
route(GET, '/api/notes/:noteId/images/:attachmentId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnAttachedImage);
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.uploadImage, apiResultHandler);
route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);

View File

@ -12,6 +12,8 @@ const sanitizeFilename = require('sanitize-filename');
const isSvg = require('is-svg');
const isAnimated = require('is-animated');
const htmlSanitizer = require("./html_sanitizer");
const {attach} = require("jsdom/lib/jsdom/living/helpers/svg/basic-types.js");
const NotFoundError = require("../errors/not_found_error.js");
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
const compressImages = optionService.getOptionBool("compressImages");
@ -119,9 +121,7 @@ function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch,
note.title = sanitizeFilename(originalName);
}
note.save();
note.setContent(buffer);
note.setContent(buffer, { forceSave: true });
});
});
@ -133,6 +133,47 @@ function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch,
};
}
function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSwitch, trimFilename = false) {
log.info(`Saving image '${originalName}' as attachment into note '${noteId}'`);
if (trimFilename && originalName.length > 40) {
// https://github.com/zadam/trilium/issues/2307
originalName = "image";
}
const fileName = sanitizeFilename(originalName);
const note = becca.getNote(noteId);
if (!note) {
throw new NotFoundError(`Could not find note '${noteId}'`);
}
const attachment = note.saveAttachment({
role: 'image',
mime: 'unknown',
title: fileName
});
// resizing images asynchronously since JIMP does not support sync operation
processImage(uploadBuffer, originalName, shrinkImageSwitch).then(({buffer, imageFormat}) => {
sql.transactional(() => {
attachment.mime = getImageMimeFromExtension(imageFormat.ext);
if (!originalName.includes(".")) {
originalName += `.${imageFormat.ext}`;
attachment.title = sanitizeFilename(originalName);
}
attachment.setContent(buffer, { forceSave: true });
});
});
return {
attachment,
url: `api/notes/${note.noteId}/images/${attachment.attachmentId}/${encodeURIComponent(fileName)}`
};
}
async function shrinkImage(buffer, originalName) {
let jpegQuality = optionService.getOptionInt('imageJpegQuality');
@ -187,5 +228,6 @@ async function resize(buffer, quality) {
module.exports = {
saveImage,
saveImageToAttachment,
updateImage
};

View File

@ -165,7 +165,7 @@ function sanitizeFilenameForHeader(filename) {
sanitizedFilename = "file";
}
return encodeURIComponent(sanitizedFilename)
return encodeURIComponent(sanitizedFilename);
}
function getContentDisposition(filename) {