Notes/src/routes/api/attributes.ts

249 lines
7.6 KiB
TypeScript
Raw Normal View History

"use strict";
import sql from "../../services/sql.js";
import log from "../../services/log.js";
import attributeService from "../../services/attributes.js";
import BAttribute from "../../becca/entities/battribute.js";
import becca from "../../becca/becca.js";
import ValidationError from "../../errors/validation_error.js";
import type { Request } from "express";
function getEffectiveNoteAttributes(req: Request) {
2021-05-02 11:23:58 +02:00
const note = becca.getNote(req.params.noteId);
return note?.getAttributes();
}
function updateNoteAttribute(req: Request) {
const noteId = req.params.noteId;
const body = req.body;
let attribute;
if (body.attributeId) {
attribute = becca.getAttributeOrThrow(body.attributeId);
2022-11-26 14:57:39 +01:00
if (attribute.noteId !== noteId) {
throw new ValidationError(`Attribute '${body.attributeId}' is not owned by ${noteId}`);
}
2025-01-09 18:07:02 +02:00
if (body.type !== attribute.type || body.name !== attribute.name || (body.type === "relation" && body.value !== attribute.value)) {
let newAttribute;
2025-01-09 18:07:02 +02:00
if (body.type !== "relation" || !!body.value.trim()) {
newAttribute = attribute.createClone(body.type, body.name, body.value);
2020-06-20 12:31:38 +02:00
newAttribute.save();
}
attribute.markAsDeleted();
return {
attributeId: newAttribute ? newAttribute.attributeId : null
};
}
2025-01-09 18:07:02 +02:00
} else {
if (body.type === "relation" && !body.value?.trim()) {
2018-11-12 23:34:22 +01:00
return {};
}
attribute = new BAttribute({
2021-05-18 20:56:49 +02:00
noteId: noteId,
name: body.name,
type: body.type
});
}
2025-01-09 18:07:02 +02:00
if (attribute.type === "label" || body.value.trim()) {
2018-11-12 23:34:22 +01:00
attribute.value = body.value;
2025-01-09 18:07:02 +02:00
} else {
2018-11-12 23:34:22 +01:00
// relations should never have empty target
attribute.markAsDeleted();
2018-11-12 23:34:22 +01:00
}
2020-06-20 12:31:38 +02:00
attribute.save();
return {
attributeId: attribute.attributeId
};
}
function setNoteAttribute(req: Request) {
const noteId = req.params.noteId;
const body = req.body;
const attributeId = sql.getValue<string | null>(`SELECT attributeId FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = ? AND name = ?`, [noteId, body.type, body.name]);
2021-05-17 22:05:35 +02:00
if (attributeId) {
const attr = becca.getAttribute(attributeId);
if (!attr) {
throw new ValidationError(`Missing attribute with ID ${attributeId}.`);
}
attr.value = body.value;
2021-05-17 22:05:35 +02:00
attr.save();
} else {
2025-01-09 18:07:02 +02:00
const params = { ...body };
params.noteId = noteId; // noteId must be set before calling constructor for proper initialization
new BAttribute(params).save();
}
}
function addNoteAttribute(req: Request) {
const noteId = req.params.noteId;
const body = req.body;
2025-01-09 18:07:02 +02:00
new BAttribute({ ...body, noteId }).save();
}
function deleteNoteAttribute(req: Request) {
const noteId = req.params.noteId;
const attributeId = req.params.attributeId;
2021-04-26 22:24:55 +02:00
const attribute = becca.getAttribute(attributeId);
if (attribute) {
if (attribute.noteId !== noteId) {
throw new ValidationError(`Attribute ${attributeId} is not owned by ${noteId}`);
}
attribute.markAsDeleted();
}
}
function updateNoteAttributes(req: Request) {
const noteId = req.params.noteId;
const incomingAttributes = req.body;
2021-05-02 11:23:58 +02:00
const note = becca.getNote(noteId);
if (!note) {
throw new ValidationError(`Cannot find note with ID ${noteId}.`);
}
let existingAttrs = note.getOwnedAttributes().slice();
let position = 0;
for (const incAttr of incomingAttributes) {
position += 10;
const value = incAttr.value || "";
2025-01-09 18:07:02 +02:00
const perfectMatchAttr = existingAttrs.find((attr) => attr.type === incAttr.type && attr.name === incAttr.name && attr.isInheritable === incAttr.isInheritable && attr.value === value);
if (perfectMatchAttr) {
2025-01-09 18:07:02 +02:00
existingAttrs = existingAttrs.filter((attr) => attr.attributeId !== perfectMatchAttr.attributeId);
if (perfectMatchAttr.position !== position) {
perfectMatchAttr.position = position;
2020-06-20 12:31:38 +02:00
perfectMatchAttr.save();
}
continue; // nothing to update
}
2025-01-09 18:07:02 +02:00
if (incAttr.type === "relation") {
2021-05-02 11:23:58 +02:00
const targetNote = becca.getNote(incAttr.value);
if (!targetNote) {
log.error(`Target note of relation ${JSON.stringify(incAttr)} does not exist or is deleted`);
continue;
}
}
2025-01-09 18:07:02 +02:00
const matchedAttr = existingAttrs.find((attr) => attr.type === incAttr.type && attr.name === incAttr.name && attr.isInheritable === incAttr.isInheritable);
if (matchedAttr) {
matchedAttr.value = incAttr.value;
matchedAttr.position = position;
2020-06-20 12:31:38 +02:00
matchedAttr.save();
2025-01-09 18:07:02 +02:00
existingAttrs = existingAttrs.filter((attr) => attr.attributeId !== matchedAttr.attributeId);
continue;
}
// no existing attribute has been matched, so we need to create a new one
// type, name and isInheritable are immutable so even if there is an attribute with matching type & name, we need to create a new one and delete the former one
2020-06-20 12:31:38 +02:00
note.addAttribute(incAttr.type, incAttr.name, incAttr.value, incAttr.isInheritable, position);
}
// all the remaining existing attributes are not defined anymore and should be deleted
for (const toDeleteAttr of existingAttrs) {
if (!toDeleteAttr.isAutoLink()) {
toDeleteAttr.markAsDeleted();
}
}
}
function getAttributeNames(req: Request) {
const type = req.query.type;
const query = req.query.query;
if (typeof type !== "string" || typeof query !== "string") {
throw new ValidationError("Invalid data type.");
}
return attributeService.getAttributeNames(type, query);
}
function getValuesForAttribute(req: Request) {
const attributeName = req.params.attributeName;
2020-06-20 12:31:38 +02:00
return sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND type = 'label' AND value != '' ORDER BY value", [attributeName]);
}
function createRelation(req: Request) {
const sourceNoteId = req.params.noteId;
const targetNoteId = req.params.targetNoteId;
const name = req.params.name;
2025-01-09 18:07:02 +02:00
const attributeId = sql.getValue<string>(`SELECT attributeId FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = 'relation' AND name = ? AND value = ?`, [
sourceNoteId,
name,
targetNoteId
]);
2021-05-17 22:05:35 +02:00
let attribute = becca.getAttribute(attributeId);
if (!attribute) {
attribute = new BAttribute({
2021-05-17 22:05:35 +02:00
noteId: sourceNoteId,
name: name,
2025-01-09 18:07:02 +02:00
type: "relation",
2021-05-17 22:05:35 +02:00
value: targetNoteId
}).save();
}
return attribute;
}
function deleteRelation(req: Request) {
const sourceNoteId = req.params.noteId;
const targetNoteId = req.params.targetNoteId;
const name = req.params.name;
2025-01-09 18:07:02 +02:00
const attributeId = sql.getValue<string | null>(`SELECT attributeId FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = 'relation' AND name = ? AND value = ?`, [
sourceNoteId,
name,
targetNoteId
]);
2021-05-17 22:05:35 +02:00
if (attributeId) {
const attribute = becca.getAttribute(attributeId);
if (attribute) {
attribute.markAsDeleted();
}
}
}
export default {
updateNoteAttributes,
updateNoteAttribute,
setNoteAttribute,
addNoteAttribute,
deleteNoteAttribute,
getAttributeNames,
getValuesForAttribute,
getEffectiveNoteAttributes,
createRelation,
deleteRelation
};