mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	cleanup of labels and relations from backend
This commit is contained in:
		| @@ -65,30 +65,70 @@ class Note extends Entity { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     async getLabels() { | ||||
|         return await repository.getEntities("SELECT * FROM labels WHERE noteId = ? AND isDeleted = 0", [this.noteId]); | ||||
|     async getOwnedAttributes() { | ||||
|         return await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]); | ||||
|     } | ||||
|  | ||||
|     // WARNING: this doesn't take into account the possibility to have multi-valued labels! | ||||
|     async getLabelMap() { | ||||
|         const map = {}; | ||||
|     async getAttributes() { | ||||
|         const attributes = await repository.getEntities(` | ||||
|         WITH RECURSIVE tree(noteId, level) AS ( | ||||
|         SELECT ?, 0 | ||||
|             UNION | ||||
|             SELECT branches.parentNoteId, tree.level + 1 FROM branches | ||||
|             JOIN tree ON branches.noteId = tree.noteId | ||||
|             JOIN notes ON notes.noteId = branches.parentNoteId | ||||
|             WHERE notes.isDeleted = 0 AND branches.isDeleted = 0 | ||||
|         ) | ||||
|         SELECT attributes.* FROM attributes JOIN tree ON attributes.noteId = tree.noteId  | ||||
|         WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR attributes.noteId = ?) | ||||
|         ORDER BY level, noteId, position`, [this.noteId, this.noteId]); | ||||
|         // attributes are ordered so that "closest" attributes are first | ||||
|         // we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter. | ||||
|  | ||||
|         for (const label of await this.getLabels()) { | ||||
|             map[label.name] = label.value; | ||||
|         const filteredAttributes = attributes.filter((attr, index) => { | ||||
|             if (attr.isDefinition()) { | ||||
|                 const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name); | ||||
|  | ||||
|                 // keep only if this element is the first definition for this type & name | ||||
|                 return firstDefinitionIndex === index; | ||||
|             } | ||||
|             else { | ||||
|                 const definitionAttr = attributes.find(el => el.type === attr.type + '-definition' && el.name === attr.name); | ||||
|  | ||||
|                 if (!definitionAttr) { | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 const definition = definitionAttr.value; | ||||
|  | ||||
|                 if (definition.multiplicityType === 'multivalue') { | ||||
|                     return true; | ||||
|                 } | ||||
|                 else { | ||||
|                     const firstAttrIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name); | ||||
|  | ||||
|                     // in case of single-valued attribute we'll keep it only if it's first (closest) | ||||
|                     return firstAttrIndex === index; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         for (const attr of filteredAttributes) { | ||||
|             attr.isOwned = attr.noteId === this.noteId; | ||||
|         } | ||||
|  | ||||
|         return map; | ||||
|         return filteredAttributes; | ||||
|     } | ||||
|  | ||||
|     async hasLabel(name) { | ||||
|         const map = await this.getLabelMap(); | ||||
|  | ||||
|         return map.hasOwnProperty(name); | ||||
|         return !!await this.getLabel(name); | ||||
|     } | ||||
|  | ||||
|     // WARNING: this doesn't take into account the possibility to have multi-valued labels! | ||||
|     async getLabel(name) { | ||||
|         return await repository.getEntity("SELECT * FROM labels WHERE noteId = ? AND name = ?", [this.noteId, name]); | ||||
|         const attributes = await this.getAttributes(); | ||||
|  | ||||
|         return attributes.find(attr => attr.type === 'label' && attr.name === name); | ||||
|     } | ||||
|  | ||||
|     async getRevisions() { | ||||
|   | ||||
| @@ -6,7 +6,9 @@ const repository = require('../../services/repository'); | ||||
| const Attribute = require('../../entities/attribute'); | ||||
|  | ||||
| async function getEffectiveNoteAttributes(req) { | ||||
|     return await attributeService.getEffectiveAttributes(req.params.noteId); | ||||
|     const note = await repository.getNote(req.params.noteId); | ||||
|  | ||||
|     return await note.getAttributes(); | ||||
| } | ||||
|  | ||||
| async function updateNoteAttribute(req) { | ||||
| @@ -87,7 +89,9 @@ async function updateNoteAttributes(req) { | ||||
|         await attributeEntity.save(); | ||||
|     } | ||||
|  | ||||
|     return await attributeService.getEffectiveAttributes(noteId); | ||||
|     const note = await repository.getNote(noteId); | ||||
|  | ||||
|     return await note.getAttributes(); | ||||
| } | ||||
|  | ||||
| async function getAttributeNames(req) { | ||||
|   | ||||
| @@ -113,15 +113,18 @@ async function exportToTar(branchId, res) { | ||||
|             prefix: branch.prefix, | ||||
|             type: note.type, | ||||
|             mime: note.mime, | ||||
|             labels: (await note.getLabels()).map(label => { | ||||
|             attributes: (await note.getOwnedAttributes()).map(attribute => { | ||||
|                 return { | ||||
|                     name: label.name, | ||||
|                     value: label.value | ||||
|                     type: attribute.type, | ||||
|                     name: attribute.name, | ||||
|                     value: attribute.value, | ||||
|                     isInheritable: attribute.isInheritable, | ||||
|                     position: attribute.position | ||||
|                 }; | ||||
|             }) | ||||
|         }; | ||||
|  | ||||
|         if (metadata.labels.find(label => label.name === 'excludeFromExport')) { | ||||
|         if (metadata.attributes.find(attributes => attributes.type === 'label' && attributes.name === 'excludeFromExport')) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const noteService = require('../../services/notes'); | ||||
| const labelService = require('../../services/labels'); | ||||
| const attributeService = require('../../services/attributes'); | ||||
| const protectedSessionService = require('../../services/protected_session'); | ||||
| const repository = require('../../services/repository'); | ||||
|  | ||||
| @@ -26,8 +26,8 @@ async function uploadFile(req) { | ||||
|         mime: file.mimetype | ||||
|     }); | ||||
|  | ||||
|     await labelService.createLabel(note.noteId, "originalFileName", originalName); | ||||
|     await labelService.createLabel(note.noteId, "fileSize", size); | ||||
|     await attributeService.createLabel(note.noteId, "originalFileName", originalName); | ||||
|     await attributeService.createLabel(note.noteId, "fileSize", size); | ||||
|  | ||||
|     return { | ||||
|         noteId: note.noteId | ||||
| @@ -47,8 +47,8 @@ async function downloadFile(req, res) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const labelMap = await note.getLabelMap(); | ||||
|     const fileName = labelMap.originalFileName || note.title; | ||||
|     const originalFileName = await note.getLabel('originalFileName'); | ||||
|     const fileName = originalFileName.value || note.title; | ||||
|  | ||||
|     res.setHeader('Content-Disposition', 'file; filename="' + fileName + '"'); | ||||
|     res.setHeader('Content-Type', note.mime); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const repository = require('../../services/repository'); | ||||
| const labelService = require('../../services/labels'); | ||||
| const attributeService = require('../../services/attributes'); | ||||
| const noteService = require('../../services/notes'); | ||||
| const Branch = require('../../entities/branch'); | ||||
| const tar = require('tar-stream'); | ||||
| @@ -187,8 +187,8 @@ async function importNotes(files, parentNoteId, noteIdMap) { | ||||
|  | ||||
|         noteIdMap[file.meta.noteId] = note.noteId; | ||||
|  | ||||
|         for (const label of file.meta.labels) { | ||||
|             await labelService.createLabel(note.noteId, label.name, label.value); | ||||
|         for (const attribute of file.meta.attributes) { | ||||
|             await attributeService.createAttribute(attribute); | ||||
|         } | ||||
|  | ||||
|         if (file.children.length > 0) { | ||||
|   | ||||
| @@ -1,70 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const sql = require('../../services/sql'); | ||||
| const labelService = require('../../services/labels'); | ||||
| const repository = require('../../services/repository'); | ||||
| const Label = require('../../entities/label'); | ||||
|  | ||||
| async function getNoteLabels(req) { | ||||
|     const noteId = req.params.noteId; | ||||
|  | ||||
|     return await repository.getEntities("SELECT * FROM labels WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]); | ||||
| } | ||||
|  | ||||
| async function updateNoteLabels(req) { | ||||
|     const noteId = req.params.noteId; | ||||
|     const labels = req.body; | ||||
|  | ||||
|     for (const label of labels) { | ||||
|         let labelEntity; | ||||
|  | ||||
|         if (label.labelId) { | ||||
|             labelEntity = await repository.getLabel(label.labelId); | ||||
|         } | ||||
|         else { | ||||
|             // if it was "created" and then immediatelly deleted, we just don't create it at all | ||||
|             if (label.isDeleted) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             labelEntity = new Label(); | ||||
|             labelEntity.noteId = noteId; | ||||
|         } | ||||
|  | ||||
|         labelEntity.name = label.name; | ||||
|         labelEntity.value = label.value; | ||||
|         labelEntity.position = label.position; | ||||
|         labelEntity.isDeleted = label.isDeleted; | ||||
|  | ||||
|         await labelEntity.save(); | ||||
|     } | ||||
|  | ||||
|     return await repository.getEntities("SELECT * FROM labels WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]); | ||||
| } | ||||
|  | ||||
| async function getAllLabelNames() { | ||||
|     const names = await sql.getColumn("SELECT DISTINCT name FROM labels WHERE isDeleted = 0"); | ||||
|  | ||||
|     for (const label of labelService.BUILTIN_LABELS) { | ||||
|         if (!names.includes(label)) { | ||||
|             names.push(label); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     names.sort(); | ||||
|  | ||||
|     return names; | ||||
| } | ||||
|  | ||||
| async function getValuesForLabel(req) { | ||||
|     const labelName = req.params.labelName; | ||||
|  | ||||
|     return await sql.getColumn("SELECT DISTINCT value FROM labels WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [labelName]); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     getNoteLabels, | ||||
|     updateNoteLabels, | ||||
|     getAllLabelNames, | ||||
|     getValuesForLabel | ||||
| }; | ||||
| @@ -1,64 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const sql = require('../../services/sql'); | ||||
| const relationService = require('../../services/relations'); | ||||
| const repository = require('../../services/repository'); | ||||
| const Relation = require('../../entities/relation'); | ||||
|  | ||||
| async function getNoteRelations(req) { | ||||
|     const noteId = req.params.noteId; | ||||
|  | ||||
|     return await repository.getEntities("SELECT * FROM relations WHERE isDeleted = 0 AND sourceNoteId = ? ORDER BY position, dateCreated", [noteId]); | ||||
| } | ||||
|  | ||||
| async function updateNoteRelations(req) { | ||||
|     const noteId = req.params.noteId; | ||||
|     const relations = req.body; | ||||
|  | ||||
|     for (const relation of relations) { | ||||
|         let relationEntity; | ||||
|  | ||||
|         if (relation.relationId) { | ||||
|             relationEntity = await repository.getRelation(relation.relationId); | ||||
|         } | ||||
|         else { | ||||
|             // if it was "created" and then immediatelly deleted, we just don't create it at all | ||||
|             if (relation.isDeleted) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             relationEntity = new Relation(); | ||||
|             relationEntity.sourceNoteId = noteId; | ||||
|         } | ||||
|  | ||||
|         relationEntity.name = relation.name; | ||||
|         relationEntity.targetNoteId = relation.targetNoteId; | ||||
|         relationEntity.isInheritable = relation.isInheritable; | ||||
|         relationEntity.position = relation.position; | ||||
|         relationEntity.isDeleted = relation.isDeleted; | ||||
|  | ||||
|         await relationEntity.save(); | ||||
|     } | ||||
|  | ||||
|     return await repository.getEntities("SELECT * FROM relations WHERE isDeleted = 0 AND sourceNoteId = ? ORDER BY position, dateCreated", [noteId]); | ||||
| } | ||||
|  | ||||
| async function getAllRelationNames() { | ||||
|     const names = await sql.getColumn("SELECT DISTINCT name FROM relations WHERE isDeleted = 0"); | ||||
|  | ||||
|     for (const relationName of relationService.BUILTIN_RELATIONS) { | ||||
|         if (!names.includes(relationName)) { | ||||
|             names.push(relationName); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     names.sort(); | ||||
|  | ||||
|     return names; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     getNoteRelations, | ||||
|     updateNoteRelations, | ||||
|     getAllRelationNames | ||||
| }; | ||||
| @@ -1,6 +1,5 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const labelService = require('../../services/labels'); | ||||
| const scriptService = require('../../services/script'); | ||||
| const attributeService = require('../../services/attributes'); | ||||
| const repository = require('../../services/repository'); | ||||
| @@ -21,7 +20,7 @@ async function run(req) { | ||||
| } | ||||
|  | ||||
| async function getStartupBundles() { | ||||
|     const notes = await labelService.getNotesWithLabel("run", "frontendStartup"); | ||||
|     const notes = await attributeService.getNotesWithLabel("run", "frontendStartup"); | ||||
|  | ||||
|     const bundles = []; | ||||
|  | ||||
| @@ -38,9 +37,10 @@ async function getStartupBundles() { | ||||
|  | ||||
| async function getRelationBundles(req) { | ||||
|     const noteId = req.params.noteId; | ||||
|     const note = await repository.getNote(noteId); | ||||
|     const relationName = req.params.relationName; | ||||
|  | ||||
|     const attributes = await attributeService.getEffectiveAttributes(noteId); | ||||
|     const attributes = await note.getAttributes(); | ||||
|     const filtered = attributes.filter(attr => attr.type === 'relation' && attr.name === relationName); | ||||
|     const targetNoteIds = filtered.map(relation => relation.value); | ||||
|     const uniqueNoteIds = Array.from(new Set(targetNoteIds)); | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| const sourceIdService = require('../services/source_id'); | ||||
| const sql = require('../services/sql'); | ||||
| const labelService = require('../services/labels'); | ||||
| const attributeService = require('../services/attributes'); | ||||
| const config = require('../services/config'); | ||||
| const optionService = require('../services/options'); | ||||
|  | ||||
| @@ -18,7 +18,7 @@ async function index(req, res) { | ||||
|  | ||||
| async function getAppCss() { | ||||
|     let css = ''; | ||||
|     const notes = labelService.getNotesWithLabel('appCss'); | ||||
|     const notes = attributeService.getNotesWithLabel('appCss'); | ||||
|  | ||||
|     for (const note of await notes) { | ||||
|         css += `/* ${note.noteId} */ | ||||
|   | ||||
| @@ -43,12 +43,17 @@ async function getNoteWithLabel(name, value) { | ||||
|     return notes.length > 0 ? notes[0] : null; | ||||
| } | ||||
|  | ||||
| async function createAttribute(noteId, name, value = "") { | ||||
|     return await new Attribute({ | ||||
| async function createLabel(noteId, name, value = "") { | ||||
|     return await createAttribute({ | ||||
|         noteId: noteId, | ||||
|         type: 'label', | ||||
|         name: name, | ||||
|         value: value | ||||
|     }).save(); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| async function createAttribute(attribute) { | ||||
|     return await new Attribute(attribute).save(); | ||||
| } | ||||
|  | ||||
| async function getAttributeNames(type, nameLike) { | ||||
| @@ -70,62 +75,11 @@ async function getAttributeNames(type, nameLike) { | ||||
|     return names; | ||||
| } | ||||
|  | ||||
| async function getEffectiveAttributes(noteId) { | ||||
|     const attributes = await repository.getEntities(` | ||||
|         WITH RECURSIVE tree(noteId, level) AS ( | ||||
|         SELECT ?, 0 | ||||
|             UNION | ||||
|             SELECT branches.parentNoteId, tree.level + 1 FROM branches | ||||
|             JOIN tree ON branches.noteId = tree.noteId | ||||
|             JOIN notes ON notes.noteId = branches.parentNoteId | ||||
|             WHERE notes.isDeleted = 0 AND branches.isDeleted = 0 | ||||
|         ) | ||||
|         SELECT attributes.* FROM attributes JOIN tree ON attributes.noteId = tree.noteId  | ||||
|         WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR attributes.noteId = ?) | ||||
|         ORDER BY level, noteId, position`, [noteId, noteId]); | ||||
|     // attributes are ordered so that "closest" attributes are first | ||||
|     // we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter. | ||||
|  | ||||
|     const filteredAttributes = attributes.filter((attr, index) => { | ||||
|         if (attr.isDefinition()) { | ||||
|             const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name); | ||||
|  | ||||
|             // keep only if this element is the first definition for this type & name | ||||
|             return firstDefinitionIndex === index; | ||||
|         } | ||||
|         else { | ||||
|             const definitionAttr = attributes.find(el => el.type === attr.type + '-definition' && el.name === attr.name); | ||||
|  | ||||
|             if (!definitionAttr) { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             const definition = definitionAttr.value; | ||||
|  | ||||
|             if (definition.multiplicityType === 'multivalue') { | ||||
|                 return true; | ||||
|             } | ||||
|             else { | ||||
|                 const firstAttrIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name); | ||||
|  | ||||
|                 // in case of single-valued attribute we'll keep it only if it's first (closest) | ||||
|                 return firstAttrIndex === index; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     for (const attr of filteredAttributes) { | ||||
|         attr.isOwned = attr.noteId === noteId; | ||||
|     } | ||||
|  | ||||
|     return filteredAttributes; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     getNotesWithLabel, | ||||
|     getNoteWithLabel, | ||||
|     createLabel, | ||||
|     createAttribute, | ||||
|     getAttributeNames, | ||||
|     getEffectiveAttributes, | ||||
|     BUILTIN_ATTRIBUTES | ||||
| }; | ||||
| @@ -1,8 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const sql = require('./sql'); | ||||
| const noteService = require('./notes'); | ||||
| const labelService = require('./labels'); | ||||
| const attributeService = require('./attributes'); | ||||
| const dateUtils = require('./date_utils'); | ||||
| const repository = require('./repository'); | ||||
|  | ||||
| @@ -32,7 +31,7 @@ async function getNoteStartingWith(parentNoteId, startsWith) { | ||||
|  | ||||
| async function getRootCalendarNote() { | ||||
|     // some caching here could be useful (e.g. in CLS) | ||||
|     let rootNote = await labelService.getNoteWithLabel(CALENDAR_ROOT_LABEL); | ||||
|     let rootNote = await attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL); | ||||
|  | ||||
|     if (!rootNote) { | ||||
|         rootNote = (await noteService.createNewNote('root', { | ||||
| @@ -41,8 +40,8 @@ async function getRootCalendarNote() { | ||||
|             isProtected: false | ||||
|         })).note; | ||||
|  | ||||
|         await labelService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL); | ||||
|         await labelService.createLabel(rootNote.noteId, 'sorted'); | ||||
|         await attributeService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL); | ||||
|         await attributeService.createLabel(rootNote.noteId, 'sorted'); | ||||
|     } | ||||
|  | ||||
|     return rootNote; | ||||
| @@ -51,7 +50,7 @@ async function getRootCalendarNote() { | ||||
| async function getYearNote(dateTimeStr, rootNote) { | ||||
|     const yearStr = dateTimeStr.substr(0, 4); | ||||
|  | ||||
|     let yearNote = await labelService.getNoteWithLabel(YEAR_LABEL, yearStr); | ||||
|     let yearNote = await attributeService.getNoteWithLabel(YEAR_LABEL, yearStr); | ||||
|  | ||||
|     if (!yearNote) { | ||||
|         yearNote = await getNoteStartingWith(rootNote.noteId, yearStr); | ||||
| @@ -60,8 +59,8 @@ async function getYearNote(dateTimeStr, rootNote) { | ||||
|             yearNote = await createNote(rootNote.noteId, yearStr); | ||||
|         } | ||||
|  | ||||
|         await labelService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr); | ||||
|         await labelService.createLabel(yearNote.noteId, 'sorted'); | ||||
|         await attributeService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr); | ||||
|         await attributeService.createLabel(yearNote.noteId, 'sorted'); | ||||
|     } | ||||
|  | ||||
|     return yearNote; | ||||
| @@ -71,7 +70,7 @@ async function getMonthNote(dateTimeStr, rootNote) { | ||||
|     const monthStr = dateTimeStr.substr(0, 7); | ||||
|     const monthNumber = dateTimeStr.substr(5, 2); | ||||
|  | ||||
|     let monthNote = await labelService.getNoteWithLabel(MONTH_LABEL, monthStr); | ||||
|     let monthNote = await attributeService.getNoteWithLabel(MONTH_LABEL, monthStr); | ||||
|  | ||||
|     if (!monthNote) { | ||||
|         const yearNote = await getYearNote(dateTimeStr, rootNote); | ||||
| @@ -86,8 +85,8 @@ async function getMonthNote(dateTimeStr, rootNote) { | ||||
|             monthNote = await createNote(yearNote.noteId, noteTitle); | ||||
|         } | ||||
|  | ||||
|         await labelService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr); | ||||
|         await labelService.createLabel(monthNote.noteId, 'sorted'); | ||||
|         await attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr); | ||||
|         await attributeService.createLabel(monthNote.noteId, 'sorted'); | ||||
|     } | ||||
|  | ||||
|     return monthNote; | ||||
| @@ -99,7 +98,7 @@ async function getDateNote(dateTimeStr) { | ||||
|     const dateStr = dateTimeStr.substr(0, 10); | ||||
|     const dayNumber = dateTimeStr.substr(8, 2); | ||||
|  | ||||
|     let dateNote = await labelService.getNoteWithLabel(DATE_LABEL, dateStr); | ||||
|     let dateNote = await attributeService.getNoteWithLabel(DATE_LABEL, dateStr); | ||||
|  | ||||
|     if (!dateNote) { | ||||
|         const monthNote = await getMonthNote(dateTimeStr, rootNote); | ||||
| @@ -114,7 +113,7 @@ async function getDateNote(dateTimeStr) { | ||||
|             dateNote = await createNote(monthNote.noteId, noteTitle); | ||||
|         } | ||||
|  | ||||
|         await labelService.createLabel(dateNote.noteId, DATE_LABEL, dateStr); | ||||
|         await attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr); | ||||
|     } | ||||
|  | ||||
|     return dateNote; | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| const eventService = require('./events'); | ||||
| const scriptService = require('./script'); | ||||
| const relationService = require('./relations'); | ||||
| const treeService = require('./tree'); | ||||
| const messagingService = require('./messaging'); | ||||
|  | ||||
| eventService.subscribe(eventService.NOTE_TITLE_CHANGED, async note => { | ||||
|     const relations = await relationService.getEffectiveRelations(note.noteId, 'runOnNoteTitleChange'); | ||||
|     const attributes = await note.getAttributes(); | ||||
|     const runRelations = attributes.filter(relation => relation.type === 'relation' && relation.name === 'runOnNoteTitleChange'); | ||||
|  | ||||
|     for (const relation of relations) { | ||||
|     for (const relation of runRelations) { | ||||
|         const scriptNote = await relation.getTargetNote(); | ||||
|  | ||||
|         await scriptService.executeNote(scriptNote, scriptNote, note); | ||||
|   | ||||
| @@ -1,52 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const repository = require('./repository'); | ||||
| const Label = require('../entities/label'); | ||||
|  | ||||
| const BUILTIN_LABELS = [ | ||||
|     'disableVersioning', | ||||
|     'calendarRoot', | ||||
|     'archived', | ||||
|     'excludeFromExport', | ||||
|     'run', | ||||
|     'manualTransactionHandling', | ||||
|     'disableInclusion', | ||||
|     'appCss', | ||||
|     'hideChildrenOverview' | ||||
| ]; | ||||
|  | ||||
| async function getNotesWithLabel(name, value) { | ||||
|     let notes; | ||||
|  | ||||
|     if (value !== undefined) { | ||||
|         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN labels USING(noteId)  | ||||
|           WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.value = ?`, [name, value]); | ||||
|     } | ||||
|     else { | ||||
|         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN labels USING(noteId)  | ||||
|           WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ?`, [name]); | ||||
|     } | ||||
|  | ||||
|     return notes; | ||||
| } | ||||
|  | ||||
| async function getNoteWithLabel(name, value) { | ||||
|     const notes = await getNotesWithLabel(name, value); | ||||
|  | ||||
|     return notes.length > 0 ? notes[0] : null; | ||||
| } | ||||
|  | ||||
| async function createLabel(noteId, name, value = "") { | ||||
|     return await new Label({ | ||||
|         noteId: noteId, | ||||
|         name: name, | ||||
|         value: value | ||||
|     }).save(); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     getNotesWithLabel, | ||||
|     getNoteWithLabel, | ||||
|     createLabel, | ||||
|     BUILTIN_LABELS | ||||
| }; | ||||
| @@ -2,7 +2,7 @@ const sql = require('./sql'); | ||||
| const optionService = require('./options'); | ||||
| const dateUtils = require('./date_utils'); | ||||
| const syncTableService = require('./sync_table'); | ||||
| const labelService = require('./labels'); | ||||
| const attributeService = require('./attributes'); | ||||
| const eventService = require('./events'); | ||||
| const repository = require('./repository'); | ||||
| const Note = require('../entities/note'); | ||||
| @@ -93,9 +93,10 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) | ||||
|  | ||||
|     const {note, branch} = await createNewNote(parentNoteId, noteData); | ||||
|  | ||||
|     // FIXME: need to make this more generic for all kinds of attributes | ||||
|     if (extraOptions.labels) { | ||||
|         for (const labelName in extraOptions.labels) { | ||||
|             await labelService.createLabel(note.noteId, labelName, extraOptions.labels[labelName]); | ||||
|             await attributeService.createLabel(note.noteId, labelName, extraOptions.labels[labelName]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -168,8 +169,6 @@ async function saveNoteImages(note) { | ||||
| } | ||||
|  | ||||
| async function saveNoteRevision(note) { | ||||
|     const labelsMap = await note.getLabelMap(); | ||||
|  | ||||
|     const now = new Date(); | ||||
|     const noteRevisionSnapshotTimeInterval = parseInt(await optionService.getOption('noteRevisionSnapshotTimeInterval')); | ||||
|  | ||||
| @@ -181,7 +180,7 @@ async function saveNoteRevision(note) { | ||||
|     const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.dateCreated).getTime(); | ||||
|  | ||||
|     if (note.type !== 'file' | ||||
|         && labelsMap.disableVersioning !== 'true' | ||||
|         && await note.hasLabel('disableVersioning') | ||||
|         && !existingnoteRevisionId | ||||
|         && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) { | ||||
|  | ||||
|   | ||||
| @@ -1,67 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const repository = require('./repository'); | ||||
| const Relation = require('../entities/relation'); | ||||
|  | ||||
| const BUILTIN_RELATIONS = [ | ||||
|     'runOnNoteView', | ||||
|     'runOnNoteTitleChange' | ||||
| ]; | ||||
|  | ||||
| async function getNotesWithRelation(name, targetNoteId) { | ||||
|     let notes; | ||||
|  | ||||
|     if (targetNoteId !== undefined) { | ||||
|         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN relations ON notes.noteId = relations.sourceNoteId | ||||
|           WHERE notes.isDeleted = 0 AND relations.isDeleted = 0 AND relations.name = ? AND relations.targetNoteId = ?`, [name, targetNoteId]); | ||||
|     } | ||||
|     else { | ||||
|         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN relations ON notes.noteId = relations.sourceNoteId  | ||||
|           WHERE notes.isDeleted = 0 AND relations.isDeleted = 0 AND relations.name = ?`, [name]); | ||||
|     } | ||||
|  | ||||
|     return notes; | ||||
| } | ||||
|  | ||||
| async function getNoteWithRelation(name, value) { | ||||
|     const notes = await getNotesWithRelation(name, value); | ||||
|  | ||||
|     return notes.length > 0 ? notes[0] : null; | ||||
| } | ||||
|  | ||||
| async function createRelation(sourceNoteId, name, targetNoteId) { | ||||
|     return await new Relation({ | ||||
|         sourceNoteId: sourceNoteId, | ||||
|         name: name, | ||||
|         targetNoteId: targetNoteId | ||||
|     }).save(); | ||||
| } | ||||
|  | ||||
| async function getEffectiveRelations(noteId, relationName) { | ||||
|     const relations = await repository.getEntities(` | ||||
|         WITH RECURSIVE tree(noteId) AS ( | ||||
|         SELECT ? | ||||
|             UNION | ||||
|             SELECT branches.parentNoteId FROM branches | ||||
|             JOIN tree ON branches.noteId = tree.noteId | ||||
|             JOIN notes ON notes.noteId = branches.parentNoteId | ||||
|             WHERE notes.isDeleted = 0 AND branches.isDeleted = 0 | ||||
|         ) | ||||
|         SELECT relations.* FROM relations JOIN tree ON relations.sourceNoteId = tree.noteId  | ||||
|         WHERE relations.isDeleted = 0 AND (relations.isInheritable = 1 OR relations.sourceNoteId = ?)`, [noteId, noteId]); | ||||
|  | ||||
|     if (relationName) { | ||||
|         return relations.filter(relation => relation.name === relationName); | ||||
|     } | ||||
|     else { | ||||
|         return relations; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     BUILTIN_RELATIONS, | ||||
|     getNotesWithRelation, | ||||
|     getNoteWithRelation, | ||||
|     createRelation, | ||||
|     getEffectiveRelations | ||||
| }; | ||||
| @@ -41,14 +41,6 @@ async function getAttribute(attributeId) { | ||||
|     return await getEntity("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]); | ||||
| } | ||||
|  | ||||
| async function getLabel(labelId) { | ||||
|     return await getEntity("SELECT * FROM labels WHERE labelId = ?", [labelId]); | ||||
| } | ||||
|  | ||||
| async function getRelation(relationId) { | ||||
|     return await getEntity("SELECT * FROM relations WHERE relationId = ?", [relationId]); | ||||
| } | ||||
|  | ||||
| async function getOption(name) { | ||||
|     return await getEntity("SELECT * FROM options WHERE name = ?", [name]); | ||||
| } | ||||
| @@ -61,6 +53,7 @@ async function updateEntity(entity) { | ||||
|     const clone = Object.assign({}, entity); | ||||
|  | ||||
|     delete clone.jsonContent; | ||||
|     delete clone.isOwned; | ||||
|  | ||||
|     for (const key in clone) { | ||||
|         if (clone[key] !== null && typeof clone[key] === 'object') { | ||||
| @@ -86,8 +79,6 @@ module.exports = { | ||||
|     getBranch, | ||||
|     getImage, | ||||
|     getAttribute, | ||||
|     getLabel, | ||||
|     getRelation, | ||||
|     getOption, | ||||
|     updateEntity, | ||||
|     setEntityConstructor | ||||
|   | ||||
| @@ -235,8 +235,6 @@ const primaryKeys = { | ||||
|     "recent_notes": "branchId", | ||||
|     "images": "imageId", | ||||
|     "note_images": "noteImageId", | ||||
|     "labels": "labelId", | ||||
|     "relations": "relationId", | ||||
|     "api_tokens": "apiTokenId", | ||||
|     "options": "name" | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| const sql = require('./sql'); | ||||
| const sourceIdService = require('./source_id'); | ||||
| const dateUtils = require('./date_utils'); | ||||
| const syncOptions = require('./sync_options'); | ||||
| const log = require('./log'); | ||||
| const cls = require('./cls'); | ||||
| const eventService = require('./events'); | ||||
| @@ -42,14 +41,6 @@ async function addAttributeSync(attributeId, sourceId) { | ||||
|     await addEntitySync("attributes", attributeId, sourceId); | ||||
| } | ||||
|  | ||||
| async function addLabelSync(labelId, sourceId) { | ||||
|     await addEntitySync("labels", labelId, sourceId); | ||||
| } | ||||
|  | ||||
| async function addRelationSync(relationId, sourceId) { | ||||
|     await addEntitySync("relations", relationId, sourceId); | ||||
| } | ||||
|  | ||||
| async function addApiTokenSync(apiTokenId, sourceId) { | ||||
|     await addEntitySync("api_tokens", apiTokenId, sourceId); | ||||
| } | ||||
| @@ -109,8 +100,6 @@ async function fillAllSyncRows() { | ||||
|     await fillSyncRows("images", "imageId"); | ||||
|     await fillSyncRows("note_images", "noteImageId"); | ||||
|     await fillSyncRows("attributes", "attributeId"); | ||||
|     await fillSyncRows("labels", "labelId"); | ||||
|     await fillSyncRows("relations", "relationId"); | ||||
|     await fillSyncRows("api_tokens", "apiTokenId"); | ||||
|     await fillSyncRows("options", "name", 'isSynced = 1'); | ||||
| } | ||||
| @@ -125,8 +114,6 @@ module.exports = { | ||||
|     addImageSync, | ||||
|     addNoteImageSync, | ||||
|     addAttributeSync, | ||||
|     addLabelSync, | ||||
|     addRelationSync, | ||||
|     addApiTokenSync, | ||||
|     addEntitySync, | ||||
|     cleanupSyncRowsForMissingEntities, | ||||
|   | ||||
| @@ -33,12 +33,6 @@ async function updateEntity(sync, entity, sourceId) { | ||||
|     else if (entityName === 'attributes') { | ||||
|         await updateAttribute(entity, sourceId); | ||||
|     } | ||||
|     else if (entityName === 'labels') { | ||||
|         await updateLabel(entity, sourceId); | ||||
|     } | ||||
|     else if (entityName === 'relations') { | ||||
|         await updateRelation(entity, sourceId); | ||||
|     } | ||||
|     else if (entityName === 'api_tokens') { | ||||
|         await updateApiToken(entity, sourceId); | ||||
|     } | ||||
| @@ -191,34 +185,6 @@ async function updateAttribute(entity, sourceId) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateLabel(entity, sourceId) { | ||||
|     const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]); | ||||
|  | ||||
|     if (!origLabel || origLabel.dateModified <= entity.dateModified) { | ||||
|         await sql.transactional(async () => { | ||||
|             await sql.replace("labels", entity); | ||||
|  | ||||
|             await syncTableService.addLabelSync(entity.labelId, sourceId); | ||||
|         }); | ||||
|  | ||||
|         log.info("Update/sync label " + entity.labelId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateRelation(entity, sourceId) { | ||||
|     const origRelation = await sql.getRow("SELECT * FROM relations WHERE relationId = ?", [entity.relationId]); | ||||
|  | ||||
|     if (!origRelation || origRelation.dateModified <= entity.dateModified) { | ||||
|         await sql.transactional(async () => { | ||||
|             await sql.replace("relations", entity); | ||||
|  | ||||
|             await syncTableService.addRelationSync(entity.relationId, sourceId); | ||||
|         }); | ||||
|  | ||||
|         log.info("Update/sync relation " + entity.relationId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateApiToken(entity, sourceId) { | ||||
|     const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user