mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	added support for trilium-sender
This commit is contained in:
		
							
								
								
									
										7
									
								
								db/migrations/0075__add_api_token.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								db/migrations/0075__add_api_token.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | CREATE TABLE IF NOT EXISTS "api_tokens" | ||||||
|  | ( | ||||||
|  |   apiTokenId TEXT PRIMARY KEY NOT NULL, | ||||||
|  |   token TEXT NOT NULL, | ||||||
|  |   dateCreated TEXT NOT NULL, | ||||||
|  |   isDeleted INT NOT NULL DEFAULT 0 | ||||||
|  | ); | ||||||
| @@ -119,3 +119,11 @@ CREATE INDEX IDX_note_images_noteId ON note_images (noteId); | |||||||
| CREATE INDEX IDX_note_images_imageId ON note_images (imageId); | CREATE INDEX IDX_note_images_imageId ON note_images (imageId); | ||||||
| CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId); | CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId); | ||||||
| CREATE INDEX IDX_attributes_noteId ON attributes (noteId); | CREATE INDEX IDX_attributes_noteId ON attributes (noteId); | ||||||
|  |  | ||||||
|  | CREATE TABLE IF NOT EXISTS "api_tokens" | ||||||
|  | ( | ||||||
|  |   apiTokenId TEXT PRIMARY KEY NOT NULL, | ||||||
|  |   token TEXT NOT NULL, | ||||||
|  |   dateCreated TEXT NOT NULL, | ||||||
|  |   isDeleted INT NOT NULL DEFAULT 0 | ||||||
|  | ); | ||||||
| @@ -4,16 +4,8 @@ const express = require('express'); | |||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const utils = require('../../services/utils'); | const image = require('../../services/image'); | ||||||
| const sync_table = require('../../services/sync_table'); |  | ||||||
| const multer = require('multer')(); | const multer = require('multer')(); | ||||||
| const imagemin = require('imagemin'); |  | ||||||
| const imageminMozJpeg = require('imagemin-mozjpeg'); |  | ||||||
| const imageminPngQuant = require('imagemin-pngquant'); |  | ||||||
| const imageminGifLossy = require('imagemin-giflossy'); |  | ||||||
| const jimp = require('jimp'); |  | ||||||
| const imageType = require('image-type'); |  | ||||||
| const sanitizeFilename = require('sanitize-filename'); |  | ||||||
| const wrap = require('express-promise-wrap').wrap; | const wrap = require('express-promise-wrap').wrap; | ||||||
| const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | ||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
| @@ -49,45 +41,7 @@ router.post('', auth.checkApiAuthOrElectron, multer.single('upload'), wrap(async | |||||||
|         return res.status(400).send("Unknown image type: " + file.mimetype); |         return res.status(400).send("Unknown image type: " + file.mimetype); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const now = utils.nowDate(); |     const {fileName, imageId} = await image.saveImage(file, sourceId, noteId); | ||||||
|  |  | ||||||
|     const resizedImage = await resize(file.buffer); |  | ||||||
|     const optimizedImage = await optimize(resizedImage); |  | ||||||
|  |  | ||||||
|     const imageFormat = imageType(optimizedImage); |  | ||||||
|  |  | ||||||
|     const fileNameWithouExtension = file.originalname.replace(/\.[^/.]+$/, ""); |  | ||||||
|     const fileName = sanitizeFilename(fileNameWithouExtension + "." + imageFormat.ext); |  | ||||||
|  |  | ||||||
|     const imageId = utils.newImageId(); |  | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |  | ||||||
|         await sql.insert("images", { |  | ||||||
|             imageId: imageId, |  | ||||||
|             format: imageFormat.ext, |  | ||||||
|             name: fileName, |  | ||||||
|             checksum: utils.hash(optimizedImage), |  | ||||||
|             data: optimizedImage, |  | ||||||
|             isDeleted: 0, |  | ||||||
|             dateModified: now, |  | ||||||
|             dateCreated: now |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         await sync_table.addImageSync(imageId, sourceId); |  | ||||||
|  |  | ||||||
|         const noteImageId = utils.newNoteImageId(); |  | ||||||
|  |  | ||||||
|         await sql.insert("note_images", { |  | ||||||
|             noteImageId: noteImageId, |  | ||||||
|             noteId: noteId, |  | ||||||
|             imageId: imageId, |  | ||||||
|             isDeleted: 0, |  | ||||||
|             dateModified: now, |  | ||||||
|             dateCreated: now |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         await sync_table.addNoteImageSync(noteImageId, sourceId); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|         uploaded: true, |         uploaded: true, | ||||||
| @@ -95,54 +49,4 @@ router.post('', auth.checkApiAuthOrElectron, multer.single('upload'), wrap(async | |||||||
|     }); |     }); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| const MAX_SIZE = 1000; |  | ||||||
| const MAX_BYTE_SIZE = 200000; // images should have under 100 KBs |  | ||||||
|  |  | ||||||
| async function resize(buffer) { |  | ||||||
|     const image = await jimp.read(buffer); |  | ||||||
|  |  | ||||||
|     if (image.bitmap.width > image.bitmap.height && image.bitmap.width > MAX_SIZE) { |  | ||||||
|         image.resize(MAX_SIZE, jimp.AUTO); |  | ||||||
|     } |  | ||||||
|     else if (image.bitmap.height > MAX_SIZE) { |  | ||||||
|         image.resize(jimp.AUTO, MAX_SIZE); |  | ||||||
|     } |  | ||||||
|     else if (buffer.byteLength <= MAX_BYTE_SIZE) { |  | ||||||
|         return buffer; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // we do resizing with max quality which will be trimmed during optimization step next |  | ||||||
|     image.quality(100); |  | ||||||
|  |  | ||||||
|     // when converting PNG to JPG we lose alpha channel, this is replaced by white to match Trilium white background |  | ||||||
|     image.background(0xFFFFFFFF); |  | ||||||
|  |  | ||||||
|     // getBuffer doesn't support promises so this workaround |  | ||||||
|     return await new Promise((resolve, reject) => image.getBuffer(jimp.MIME_JPEG, (err, data) => { |  | ||||||
|         if (err) { |  | ||||||
|             reject(err); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             resolve(data); |  | ||||||
|         } |  | ||||||
|     })); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function optimize(buffer) { |  | ||||||
|     return await imagemin.buffer(buffer, { |  | ||||||
|         plugins: [ |  | ||||||
|             imageminMozJpeg({ |  | ||||||
|                 quality: 50 |  | ||||||
|             }), |  | ||||||
|             imageminPngQuant({ |  | ||||||
|                 quality: "0-70" |  | ||||||
|             }), |  | ||||||
|             imageminGifLossy({ |  | ||||||
|                 lossy: 80, |  | ||||||
|                 optimize: '3' // needs to be string |  | ||||||
|             }) |  | ||||||
|         ] |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = router; | module.exports = router; | ||||||
| @@ -66,7 +66,7 @@ async function importNotes(dir, parentNoteId) { | |||||||
|         const noteText = fs.readFileSync(path, "utf8"); |         const noteText = fs.readFileSync(path, "utf8"); | ||||||
|  |  | ||||||
|         const noteId = utils.newNoteId(); |         const noteId = utils.newNoteId(); | ||||||
|         const noteTreeId = utils.newnoteRevisionId(); |         const noteTreeId = utils.newNoteRevisionId(); | ||||||
|  |  | ||||||
|         const now = utils.nowDate(); |         const now = utils.nowDate(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										91
									
								
								src/routes/api/sender.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/routes/api/sender.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const express = require('express'); | ||||||
|  | const router = express.Router(); | ||||||
|  | const image = require('../../services/image'); | ||||||
|  | const utils = require('../../services/utils'); | ||||||
|  | const date_notes = require('../../services/date_notes'); | ||||||
|  | const sql = require('../../services/sql'); | ||||||
|  | const wrap = require('express-promise-wrap').wrap; | ||||||
|  | const notes = require('../../services/notes'); | ||||||
|  | const multer = require('multer')(); | ||||||
|  | const password_encryption = require('../../services/password_encryption'); | ||||||
|  | const options = require('../../services/options'); | ||||||
|  | const sync_table = require('../../services/sync_table'); | ||||||
|  |  | ||||||
|  | router.post('/login', wrap(async (req, res, next) => { | ||||||
|  |     const username = req.body.username; | ||||||
|  |     const password = req.body.password; | ||||||
|  |  | ||||||
|  |     const isUsernameValid = username === await options.getOption('username'); | ||||||
|  |     const isPasswordValid = await password_encryption.verifyPassword(password); | ||||||
|  |  | ||||||
|  |     if (!isUsernameValid || !isPasswordValid) { | ||||||
|  |         res.status(401).send("Incorrect username/password"); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         const token = utils.randomSecureToken(); | ||||||
|  |  | ||||||
|  |         await sql.doInTransaction(async () => { | ||||||
|  |             const apiTokenId = utils.newApiTokenId(); | ||||||
|  |  | ||||||
|  |             await sql.insert("api_tokens", { | ||||||
|  |                 apiTokenId: apiTokenId, | ||||||
|  |                 token: token, | ||||||
|  |                 dateCreated: utils.nowDate(), | ||||||
|  |                 isDeleted: false | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             await sync_table.addApiTokenSync(apiTokenId); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         res.send({ | ||||||
|  |             token: token | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | })); | ||||||
|  |  | ||||||
|  | async function checkSenderToken(req, res, next) { | ||||||
|  |     const token = req.headers.authorization; | ||||||
|  |  | ||||||
|  |     if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) { | ||||||
|  |         res.status(401).send("Not authorized"); | ||||||
|  |     } | ||||||
|  |     else if (await sql.isDbUpToDate()) { | ||||||
|  |         next(); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         res.status(409).send("Mismatched app versions"); // need better response than that | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | router.post('/image', checkSenderToken, multer.single('upload'), wrap(async (req, res, next) => { | ||||||
|  |     const file = req.file; | ||||||
|  |  | ||||||
|  |     if (!["image/png", "image/jpeg", "image/gif"].includes(file.mimetype)) { | ||||||
|  |         return res.status(400).send("Unknown image type: " + file.mimetype); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const parentNoteId = await date_notes.getDateNoteId(utils.nowDate()); | ||||||
|  |  | ||||||
|  |     const noteId = (await notes.createNewNote(parentNoteId, { | ||||||
|  |         title: "Sender image", | ||||||
|  |         content: "", | ||||||
|  |         target: 'into', | ||||||
|  |         isProtected: false, | ||||||
|  |         type: 'text', | ||||||
|  |         mime: 'text/html' | ||||||
|  |     })).noteId; | ||||||
|  |  | ||||||
|  |     const {fileName, imageId} = await image.saveImage(file, null, noteId); | ||||||
|  |  | ||||||
|  |     const url = `/api/images/${imageId}/${fileName}`; | ||||||
|  |  | ||||||
|  |     const content = `<img src="${url}"/>`; | ||||||
|  |  | ||||||
|  |     await sql.execute("UPDATE notes SET content = ? WHERE noteId = ?", [content, noteId]); | ||||||
|  |  | ||||||
|  |     res.send({}); | ||||||
|  | })); | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
| @@ -147,6 +147,12 @@ router.get('/attributes/:attributeId', auth.checkApiAuth, wrap(async (req, res, | |||||||
|     res.send(await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [attributeId])); |     res.send(await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [attributeId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
|  | router.get('/api_tokens/:apiTokenId', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|  |     const apiTokenId = req.params.apiTokenId; | ||||||
|  |  | ||||||
|  |     res.send(await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [apiTokenId])); | ||||||
|  | })); | ||||||
|  |  | ||||||
| router.put('/notes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.put('/notes', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     await syncUpdate.updateNote(req.body.entity, req.body.sourceId); |     await syncUpdate.updateNote(req.body.entity, req.body.sourceId); | ||||||
|  |  | ||||||
| @@ -201,4 +207,10 @@ router.put('/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | |||||||
|     res.send({}); |     res.send({}); | ||||||
| })); | })); | ||||||
|  |  | ||||||
|  | router.put('/api_tokens', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|  |     await syncUpdate.updateApiToken(req.body.entity, req.body.sourceId); | ||||||
|  |  | ||||||
|  |     res.send({}); | ||||||
|  | })); | ||||||
|  |  | ||||||
| module.exports = router; | module.exports = router; | ||||||
| @@ -28,6 +28,7 @@ const cleanupRoute = require('./api/cleanup'); | |||||||
| const imageRoute = require('./api/image'); | const imageRoute = require('./api/image'); | ||||||
| const attributesRoute = require('./api/attributes'); | const attributesRoute = require('./api/attributes'); | ||||||
| const scriptRoute = require('./api/script'); | const scriptRoute = require('./api/script'); | ||||||
|  | const senderRoute = require('./api/sender'); | ||||||
|  |  | ||||||
| function register(app) { | function register(app) { | ||||||
|     app.use('/', indexRoute); |     app.use('/', indexRoute); | ||||||
| @@ -59,6 +60,7 @@ function register(app) { | |||||||
|     app.use('/api/cleanup', cleanupRoute); |     app.use('/api/cleanup', cleanupRoute); | ||||||
|     app.use('/api/images', imageRoute); |     app.use('/api/images', imageRoute); | ||||||
|     app.use('/api/script', scriptRoute); |     app.use('/api/script', scriptRoute); | ||||||
|  |     app.use('/api/sender', senderRoute); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 74; | const APP_DB_VERSION = 75; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     app_version: packageJson.version, |     app_version: packageJson.version, | ||||||
|   | |||||||
| @@ -223,6 +223,8 @@ async function runAllChecks() { | |||||||
|     await runSyncRowChecks("recent_notes", "noteTreeId", errorList); |     await runSyncRowChecks("recent_notes", "noteTreeId", errorList); | ||||||
|     await runSyncRowChecks("images", "imageId", errorList); |     await runSyncRowChecks("images", "imageId", errorList); | ||||||
|     await runSyncRowChecks("note_images", "noteImageId", errorList); |     await runSyncRowChecks("note_images", "noteImageId", errorList); | ||||||
|  |     await runSyncRowChecks("attributes", "attributeId", errorList); | ||||||
|  |     await runSyncRowChecks("api_tokens", "apiTokenId", errorList); | ||||||
|  |  | ||||||
|     if (errorList.length === 0) { |     if (errorList.length === 0) { | ||||||
|         // we run this only if basic checks passed since this assumes basic data consistency |         // we run this only if basic checks passed since this assumes basic data consistency | ||||||
|   | |||||||
							
								
								
									
										108
									
								
								src/services/image.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/services/image.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const utils = require('./utils'); | ||||||
|  | const sql = require('./sql'); | ||||||
|  | const sync_table = require('./sync_table'); | ||||||
|  | const imagemin = require('imagemin'); | ||||||
|  | const imageminMozJpeg = require('imagemin-mozjpeg'); | ||||||
|  | const imageminPngQuant = require('imagemin-pngquant'); | ||||||
|  | const imageminGifLossy = require('imagemin-giflossy'); | ||||||
|  | const jimp = require('jimp'); | ||||||
|  | const imageType = require('image-type'); | ||||||
|  | const sanitizeFilename = require('sanitize-filename'); | ||||||
|  |  | ||||||
|  | async function saveImage(file, sourceId, noteId) { | ||||||
|  |     const resizedImage = await resize(file.buffer); | ||||||
|  |     const optimizedImage = await optimize(resizedImage); | ||||||
|  |  | ||||||
|  |     const imageFormat = imageType(optimizedImage); | ||||||
|  |  | ||||||
|  |     const fileNameWithouExtension = file.originalname.replace(/\.[^/.]+$/, ""); | ||||||
|  |     const fileName = sanitizeFilename(fileNameWithouExtension + "." + imageFormat.ext); | ||||||
|  |  | ||||||
|  |     const imageId = utils.newImageId(); | ||||||
|  |     const now = utils.nowDate(); | ||||||
|  |  | ||||||
|  |     await sql.doInTransaction(async () => { | ||||||
|  |         await sql.insert("images", { | ||||||
|  |             imageId: imageId, | ||||||
|  |             format: imageFormat.ext, | ||||||
|  |             name: fileName, | ||||||
|  |             checksum: utils.hash(optimizedImage), | ||||||
|  |             data: optimizedImage, | ||||||
|  |             isDeleted: 0, | ||||||
|  |             dateModified: now, | ||||||
|  |             dateCreated: now | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         await sync_table.addImageSync(imageId, sourceId); | ||||||
|  |  | ||||||
|  |         const noteImageId = utils.newNoteImageId(); | ||||||
|  |  | ||||||
|  |         await sql.insert("note_images", { | ||||||
|  |             noteImageId: noteImageId, | ||||||
|  |             noteId: noteId, | ||||||
|  |             imageId: imageId, | ||||||
|  |             isDeleted: 0, | ||||||
|  |             dateModified: now, | ||||||
|  |             dateCreated: now | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         await sync_table.addNoteImageSync(noteImageId, sourceId); | ||||||
|  |     }); | ||||||
|  |     return {fileName, imageId}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const MAX_SIZE = 1000; | ||||||
|  | const MAX_BYTE_SIZE = 200000; // images should have under 100 KBs | ||||||
|  |  | ||||||
|  | async function resize(buffer) { | ||||||
|  |     const image = await jimp.read(buffer); | ||||||
|  |  | ||||||
|  |     if (image.bitmap.width > image.bitmap.height && image.bitmap.width > MAX_SIZE) { | ||||||
|  |         image.resize(MAX_SIZE, jimp.AUTO); | ||||||
|  |     } | ||||||
|  |     else if (image.bitmap.height > MAX_SIZE) { | ||||||
|  |         image.resize(jimp.AUTO, MAX_SIZE); | ||||||
|  |     } | ||||||
|  |     else if (buffer.byteLength <= MAX_BYTE_SIZE) { | ||||||
|  |         return buffer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // we do resizing with max quality which will be trimmed during optimization step next | ||||||
|  |     image.quality(100); | ||||||
|  |  | ||||||
|  |     // when converting PNG to JPG we lose alpha channel, this is replaced by white to match Trilium white background | ||||||
|  |     image.background(0xFFFFFFFF); | ||||||
|  |  | ||||||
|  |     // getBuffer doesn't support promises so this workaround | ||||||
|  |     return await new Promise((resolve, reject) => image.getBuffer(jimp.MIME_JPEG, (err, data) => { | ||||||
|  |         if (err) { | ||||||
|  |             reject(err); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             resolve(data); | ||||||
|  |         } | ||||||
|  |     })); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function optimize(buffer) { | ||||||
|  |     return await imagemin.buffer(buffer, { | ||||||
|  |         plugins: [ | ||||||
|  |             imageminMozJpeg({ | ||||||
|  |                 quality: 50 | ||||||
|  |             }), | ||||||
|  |             imageminPngQuant({ | ||||||
|  |                 quality: "0-70" | ||||||
|  |             }), | ||||||
|  |             imageminGifLossy({ | ||||||
|  |                 lossy: 80, | ||||||
|  |                 optimize: '3' // needs to be string | ||||||
|  |             }) | ||||||
|  |         ] | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     saveImage | ||||||
|  | }; | ||||||
| @@ -154,10 +154,10 @@ async function saveNoteHistory(noteId, dataKey, sourceId, nowStr) { | |||||||
|         note.isProtected = false; |         note.isProtected = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const newnoteRevisionId = utils.newnoteRevisionId(); |     const newNoteRevisionId = utils.newNoteRevisionId(); | ||||||
|  |  | ||||||
|     await sql.insert('note_revisions', { |     await sql.insert('note_revisions', { | ||||||
|         noteRevisionId: newnoteRevisionId, |         noteRevisionId: newNoteRevisionId, | ||||||
|         noteId: noteId, |         noteId: noteId, | ||||||
|         // title and text should be decrypted now |         // title and text should be decrypted now | ||||||
|         title: oldNote.title, |         title: oldNote.title, | ||||||
| @@ -167,7 +167,7 @@ async function saveNoteHistory(noteId, dataKey, sourceId, nowStr) { | |||||||
|         dateModifiedTo: nowStr |         dateModifiedTo: nowStr | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     await sync_table.addNoteHistorySync(newnoteRevisionId, sourceId); |     await sync_table.addNoteHistorySync(newNoteRevisionId, sourceId); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function saveNoteImages(noteId, noteText, sourceId) { | async function saveNoteImages(noteId, noteText, sourceId) { | ||||||
|   | |||||||
| @@ -149,6 +149,9 @@ async function pullSync(syncContext) { | |||||||
|         else if (sync.entityName === 'attributes') { |         else if (sync.entityName === 'attributes') { | ||||||
|             await syncUpdate.updateAttribute(resp, syncContext.sourceId); |             await syncUpdate.updateAttribute(resp, syncContext.sourceId); | ||||||
|         } |         } | ||||||
|  |         else if (sync.entityName === 'api_tokens') { | ||||||
|  |             await syncUpdate.updateApiToken(resp, syncContext.sourceId); | ||||||
|  |         } | ||||||
|         else { |         else { | ||||||
|             throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`); |             throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`); | ||||||
|         } |         } | ||||||
| @@ -233,6 +236,9 @@ async function pushEntity(sync, syncContext) { | |||||||
|     else if (sync.entityName === 'attributes') { |     else if (sync.entityName === 'attributes') { | ||||||
|         entity = await sql.getRow('SELECT * FROM attributes WHERE attributeId = ?', [sync.entityId]); |         entity = await sql.getRow('SELECT * FROM attributes WHERE attributeId = ?', [sync.entityId]); | ||||||
|     } |     } | ||||||
|  |     else if (sync.entityName === 'api_tokens') { | ||||||
|  |         entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]); | ||||||
|  |     } | ||||||
|     else { |     else { | ||||||
|         throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`); |         throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -40,6 +40,10 @@ async function addAttributeSync(attributeId, sourceId) { | |||||||
|     await addEntitySync("attributes", attributeId, sourceId); |     await addEntitySync("attributes", attributeId, sourceId); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function addApiTokenSync(apiTokenId, sourceId) { | ||||||
|  |     await addEntitySync("api_tokens", apiTokenId, sourceId); | ||||||
|  | } | ||||||
|  |  | ||||||
| async function addEntitySync(entityName, entityId, sourceId) { | async function addEntitySync(entityName, entityId, sourceId) { | ||||||
|     await sql.replace("sync", { |     await sql.replace("sync", { | ||||||
|         entityName: entityName, |         entityName: entityName, | ||||||
| @@ -93,6 +97,7 @@ async function fillAllSyncRows() { | |||||||
|     await fillSyncRows("images", "imageId"); |     await fillSyncRows("images", "imageId"); | ||||||
|     await fillSyncRows("note_images", "noteImageId"); |     await fillSyncRows("note_images", "noteImageId"); | ||||||
|     await fillSyncRows("attributes", "attributeId"); |     await fillSyncRows("attributes", "attributeId"); | ||||||
|  |     await fillSyncRows("api_tokens", "apiTokenId"); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
| @@ -105,6 +110,7 @@ module.exports = { | |||||||
|     addImageSync, |     addImageSync, | ||||||
|     addNoteImageSync, |     addNoteImageSync, | ||||||
|     addAttributeSync, |     addAttributeSync, | ||||||
|  |     addApiTokenSync, | ||||||
|     addEntitySync, |     addEntitySync, | ||||||
|     cleanupSyncRowsForMissingEntities, |     cleanupSyncRowsForMissingEntities, | ||||||
|     fillAllSyncRows |     fillAllSyncRows | ||||||
|   | |||||||
| @@ -137,6 +137,20 @@ async function updateAttribute(entity, sourceId) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function updateApiToken(entity, sourceId) { | ||||||
|  |     const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]); | ||||||
|  |  | ||||||
|  |     if (!apiTokenId) { | ||||||
|  |         await sql.doInTransaction(async () => { | ||||||
|  |             await sql.replace("api_tokens", entity); | ||||||
|  |  | ||||||
|  |             await sync_table.addApiTokenSync(entity.apiTokenId, sourceId); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         log.info("Update/sync API token " + entity.apiTokenId); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     updateNote, |     updateNote, | ||||||
|     updateNoteTree, |     updateNoteTree, | ||||||
| @@ -146,5 +160,6 @@ module.exports = { | |||||||
|     updateRecentNotes, |     updateRecentNotes, | ||||||
|     updateImage, |     updateImage, | ||||||
|     updateNoteImage, |     updateNoteImage, | ||||||
|     updateAttribute |     updateAttribute, | ||||||
|  |     updateApiToken | ||||||
| }; | }; | ||||||
| @@ -11,7 +11,7 @@ function newNoteTreeId() { | |||||||
|     return randomString(12); |     return randomString(12); | ||||||
| } | } | ||||||
|  |  | ||||||
| function newnoteRevisionId() { | function newNoteRevisionId() { | ||||||
|     return randomString(12); |     return randomString(12); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -27,6 +27,10 @@ function newAttributeId() { | |||||||
|     return randomString(12); |     return randomString(12); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function newApiTokenId() { | ||||||
|  |     return randomString(12); | ||||||
|  | } | ||||||
|  |  | ||||||
| function randomString(length) { | function randomString(length) { | ||||||
|     return randtoken.generate(length); |     return randtoken.generate(length); | ||||||
| } | } | ||||||
| @@ -126,10 +130,11 @@ module.exports = { | |||||||
|     parseDateTime, |     parseDateTime, | ||||||
|     newNoteId, |     newNoteId, | ||||||
|     newNoteTreeId, |     newNoteTreeId, | ||||||
|     newnoteRevisionId, |     newNoteRevisionId, | ||||||
|     newImageId, |     newImageId, | ||||||
|     newNoteImageId, |     newNoteImageId, | ||||||
|     newAttributeId, |     newAttributeId, | ||||||
|  |     newApiTokenId, | ||||||
|     toBase64, |     toBase64, | ||||||
|     fromBase64, |     fromBase64, | ||||||
|     hmac, |     hmac, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user