mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	allow specifying date created in the ETAPI, #4199
This commit is contained in:
		
							
								
								
									
										689
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										689
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								package.json
									
									
									
									
									
								
							| @@ -68,10 +68,10 @@ | |||||||
|     "jimp": "0.22.10", |     "jimp": "0.22.10", | ||||||
|     "joplin-turndown-plugin-gfm": "1.0.12", |     "joplin-turndown-plugin-gfm": "1.0.12", | ||||||
|     "jsdom": "22.1.0", |     "jsdom": "22.1.0", | ||||||
|     "marked": "7.0.3", |     "marked": "7.0.5", | ||||||
|     "mime-types": "2.1.35", |     "mime-types": "2.1.35", | ||||||
|     "multer": "1.4.5-lts.1", |     "multer": "1.4.5-lts.1", | ||||||
|     "node-abi": "3.46.0", |     "node-abi": "3.47.0", | ||||||
|     "normalize-strings": "1.1.1", |     "normalize-strings": "1.1.1", | ||||||
|     "open": "8.4.1", |     "open": "8.4.1", | ||||||
|     "rand-token": "1.0.1", |     "rand-token": "1.0.1", | ||||||
| @@ -97,14 +97,14 @@ | |||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "cross-env": "7.0.3", |     "cross-env": "7.0.3", | ||||||
|     "electron": "25.5.0", |     "electron": "25.7.0", | ||||||
|     "electron-builder": "24.6.3", |     "electron-builder": "24.6.3", | ||||||
|     "electron-packager": "17.1.1", |     "electron-packager": "17.1.2", | ||||||
|     "electron-rebuild": "3.2.9", |     "electron-rebuild": "3.2.9", | ||||||
|     "eslint": "8.47.0", |     "eslint": "8.48.0", | ||||||
|     "eslint-config-airbnb-base": "15.0.0", |     "eslint-config-airbnb-base": "15.0.0", | ||||||
|     "eslint-config-prettier": "9.0.0", |     "eslint-config-prettier": "9.0.0", | ||||||
|     "eslint-plugin-import": "2.28.0", |     "eslint-plugin-import": "2.28.1", | ||||||
|     "eslint-plugin-jsonc": "2.9.0", |     "eslint-plugin-jsonc": "2.9.0", | ||||||
|     "eslint-plugin-prettier": "5.0.0", |     "eslint-plugin-prettier": "5.0.0", | ||||||
|     "esm": "3.2.25", |     "esm": "3.2.25", | ||||||
| @@ -115,13 +115,13 @@ | |||||||
|     "lint-staged": "14.0.0", |     "lint-staged": "14.0.0", | ||||||
|     "lorem-ipsum": "2.0.8", |     "lorem-ipsum": "2.0.8", | ||||||
|     "nodemon": "3.0.1", |     "nodemon": "3.0.1", | ||||||
|     "prettier": "3.0.2", |     "prettier": "3.0.3", | ||||||
|     "rcedit": "3.1.0", |     "rcedit": "3.1.0", | ||||||
|     "webpack": "5.88.2", |     "webpack": "5.88.2", | ||||||
|     "webpack-cli": "5.1.4" |     "webpack-cli": "5.1.4" | ||||||
|   }, |   }, | ||||||
|   "optionalDependencies": { |   "optionalDependencies": { | ||||||
|     "electron-installer-debian": "3.1.0" |     "electron-installer-debian": "3.2.0" | ||||||
|   }, |   }, | ||||||
|   "lint-staged": { |   "lint-staged": { | ||||||
|     "*.js": "eslint --cache --fix" |     "*.js": "eslint --cache --fix" | ||||||
|   | |||||||
| @@ -802,6 +802,12 @@ components: | |||||||
|         branchId: |         branchId: | ||||||
|           $ref: '#/components/schemas/EntityId' |           $ref: '#/components/schemas/EntityId' | ||||||
|           description: DON'T specify unless you want to force a specific branchId |           description: DON'T specify unless you want to force a specific branchId | ||||||
|  |         dateCreated: | ||||||
|  |           $ref: '#/components/schemas/LocalDateTime' | ||||||
|  |           description: Local timestap of the note creation. Specify only if you want to override the default (current datetime in the current timezone/offset). | ||||||
|  |         utcDateCreated: | ||||||
|  |           $ref: '#/components/schemas/UtcDateTime' | ||||||
|  |           description: UTC timestap of the note creation. Specify only if you want to override the default (current datetime). | ||||||
|     Note: |     Note: | ||||||
|       type: object |       type: object | ||||||
|       properties: |       properties: | ||||||
| @@ -838,13 +844,11 @@ components: | |||||||
|           readOnly: true |           readOnly: true | ||||||
|         dateCreated: |         dateCreated: | ||||||
|           $ref: '#/components/schemas/LocalDateTime' |           $ref: '#/components/schemas/LocalDateTime' | ||||||
|           readOnly: true |  | ||||||
|         dateModified: |         dateModified: | ||||||
|           $ref: '#/components/schemas/LocalDateTime' |           $ref: '#/components/schemas/LocalDateTime' | ||||||
|           readOnly: true |           readOnly: true | ||||||
|         utcDateCreated: |         utcDateCreated: | ||||||
|           $ref: '#/components/schemas/UtcDateTime' |           $ref: '#/components/schemas/UtcDateTime' | ||||||
|           readOnly: true |  | ||||||
|         utcDateModified: |         utcDateModified: | ||||||
|           $ref: '#/components/schemas/UtcDateTime' |           $ref: '#/components/schemas/UtcDateTime' | ||||||
|           readOnly: true |           readOnly: true | ||||||
| @@ -937,11 +941,11 @@ components: | |||||||
|     LocalDateTime: |     LocalDateTime: | ||||||
|       type: string |       type: string | ||||||
|       pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[\+\-][0-9]{4}' |       pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[\+\-][0-9]{4}' | ||||||
|       example: 2021-12-31 20:18:11.939+0100 |       example: 2021-12-31 20:18:11.930+0100 | ||||||
|     UtcDateTime: |     UtcDateTime: | ||||||
|       type: string |       type: string | ||||||
|       pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z' |       pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z' | ||||||
|       example: 2021-12-31 19:18:11.939Z |       example: 2021-12-31 19:18:11.930Z | ||||||
|     AppInfo: |     AppInfo: | ||||||
|       type: object |       type: object | ||||||
|       properties: |       properties: | ||||||
|   | |||||||
| @@ -50,7 +50,9 @@ function register(router) { | |||||||
|         'notePosition': [v.notNull, v.isInteger], |         'notePosition': [v.notNull, v.isInteger], | ||||||
|         'prefix': [v.notNull, v.isString], |         'prefix': [v.notNull, v.isString], | ||||||
|         'isExpanded': [v.notNull, v.isBoolean], |         'isExpanded': [v.notNull, v.isBoolean], | ||||||
|         'noteId': [v.notNull, v.isValidEntityId] |         'noteId': [v.notNull, v.isValidEntityId], | ||||||
|  |         'dateCreated': [v.notNull, v.isString, v.isLocalDateTime], | ||||||
|  |         'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime] | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => { |     eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => { | ||||||
| @@ -74,7 +76,9 @@ function register(router) { | |||||||
|     const ALLOWED_PROPERTIES_FOR_PATCH = { |     const ALLOWED_PROPERTIES_FOR_PATCH = { | ||||||
|         'title': [v.notNull, v.isString], |         'title': [v.notNull, v.isString], | ||||||
|         'type': [v.notNull, v.isString], |         'type': [v.notNull, v.isString], | ||||||
|         'mime': [v.notNull, v.isString] |         'mime': [v.notNull, v.isString], | ||||||
|  |         'dateCreated': [v.notNull, v.isString, v.isLocalDateTime], | ||||||
|  |         'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime] | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => { |     eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| const noteTypeService = require("../services/note_types"); | const noteTypeService = require("../services/note_types"); | ||||||
|  | const dateUtils = require("../services/date_utils"); | ||||||
|  |  | ||||||
| function mandatory(obj) { | function mandatory(obj) { | ||||||
|     if (obj === undefined ) { |     if (obj === undefined ) { | ||||||
| @@ -22,6 +23,22 @@ function isString(obj) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isLocalDateTime(obj) { | ||||||
|  |     if (obj === undefined || obj === null) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return dateUtils.validateLocalDateTime(obj); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function isUtcDateTime(obj) { | ||||||
|  |     if (obj === undefined || obj === null) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return dateUtils.validateUtcDateTime(obj); | ||||||
|  | } | ||||||
|  |  | ||||||
| function isBoolean(obj) { | function isBoolean(obj) { | ||||||
|     if (obj === undefined || obj === null) { |     if (obj === undefined || obj === null) { | ||||||
|         return; |         return; | ||||||
| @@ -99,5 +116,7 @@ module.exports = { | |||||||
|     isNoteId, |     isNoteId, | ||||||
|     isNoteType, |     isNoteType, | ||||||
|     isAttributeType, |     isAttributeType, | ||||||
|     isValidEntityId |     isValidEntityId, | ||||||
|  |     isLocalDateTime, | ||||||
|  |     isUtcDateTime | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| const dayjs = require('dayjs'); | const dayjs = require('dayjs'); | ||||||
| const cls = require('./cls'); | const cls = require('./cls'); | ||||||
|  |  | ||||||
|  | const LOCAL_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSZZ'; | ||||||
|  | const UTC_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ssZ'; | ||||||
|  |  | ||||||
| function utcNowDateTime() { | function utcNowDateTime() { | ||||||
|     return utcDateTimeStr(new Date()); |     return utcDateTimeStr(new Date()); | ||||||
| } | } | ||||||
| @@ -10,7 +13,7 @@ function utcNowDateTime() { | |||||||
| // "trilium-local-now-datetime" header which is then stored in CLS | // "trilium-local-now-datetime" header which is then stored in CLS | ||||||
| function localNowDateTime() { | function localNowDateTime() { | ||||||
|     return cls.getLocalNowDateTime() |     return cls.getLocalNowDateTime() | ||||||
|         || dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ') |         || dayjs().format(LOCAL_DATETIME_FORMAT) | ||||||
| } | } | ||||||
|  |  | ||||||
| function localNowDate() { | function localNowDate() { | ||||||
| @@ -62,6 +65,36 @@ function getDateTimeForFile() { | |||||||
|     return new Date().toISOString().substr(0, 19).replace(/:/g, ''); |     return new Date().toISOString().substr(0, 19).replace(/:/g, ''); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function validateLocalDateTime(str) { | ||||||
|  |     if (!str) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[+-][0-9]{4}/.test(str)) { | ||||||
|  |         return `Invalid local date time format in '${str}'. Correct format shoud follow example: '2023-08-21 23:38:51.110+0200'`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     if (!dayjs(str, LOCAL_DATETIME_FORMAT)) { | ||||||
|  |         return `Date '${str}' appears to be in the correct format, but cannot be parsed. It likely represents an invalid date.`; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function validateUtcDateTime(str) { | ||||||
|  |     if (!str) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z/.test(str)) { | ||||||
|  |         return `Invalid UTC date time format in '${str}'. Correct format shoud follow example: '2023-08-21 23:38:51.110Z'`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     if (!dayjs(str, UTC_DATETIME_FORMAT)) { | ||||||
|  |         return `Date '${str}' appears to be in the correct format, but cannot be parsed. It likely represents an invalid date.`; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     utcNowDateTime, |     utcNowDateTime, | ||||||
|     localNowDateTime, |     localNowDateTime, | ||||||
| @@ -70,5 +103,7 @@ module.exports = { | |||||||
|     utcDateTimeStr, |     utcDateTimeStr, | ||||||
|     parseDateTime, |     parseDateTime, | ||||||
|     parseLocalDate, |     parseLocalDate, | ||||||
|     getDateTimeForFile |     getDateTimeForFile, | ||||||
|  |     validateLocalDateTime, | ||||||
|  |     validateUtcDateTime | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
| const sqlInit = require('./sql_init'); |  | ||||||
| const optionService = require('./options'); | const optionService = require('./options'); | ||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
| const entityChangesService = require('./entity_changes'); | const entityChangesService = require('./entity_changes'); | ||||||
| @@ -169,6 +168,15 @@ function createNewNote(params) { | |||||||
|         throw new Error(`Note content must be set`); |         throw new Error(`Note content must be set`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     let error; | ||||||
|  |     if (error = dateUtils.validateLocalDateTime(params.dateCreated)) { | ||||||
|  |         throw new Error(error); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (error = dateUtils.validateUtcDateTime(params.utcDateCreated)) { | ||||||
|  |         throw new Error(error); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return sql.transactional(() => { |     return sql.transactional(() => { | ||||||
|         let note, branch, isEntityEventsDisabled; |         let note, branch, isEntityEventsDisabled; | ||||||
|  |  | ||||||
| @@ -189,7 +197,9 @@ function createNewNote(params) { | |||||||
|                 title: params.title, |                 title: params.title, | ||||||
|                 isProtected: !!params.isProtected, |                 isProtected: !!params.isProtected, | ||||||
|                 type: params.type, |                 type: params.type, | ||||||
|                 mime: deriveMime(params.type, params.mime) |                 mime: deriveMime(params.type, params.mime), | ||||||
|  |                 dateCreated: params.dateCreated, | ||||||
|  |                 utcDateCreated: params.utcDateCreated | ||||||
|             }).save(); |             }).save(); | ||||||
|  |  | ||||||
|             note.setContent(params.content); |             note.setContent(params.content); | ||||||
|   | |||||||
| @@ -7,13 +7,17 @@ Content-Type: application/json | |||||||
|   "parentNoteId": "root", |   "parentNoteId": "root", | ||||||
|   "title": "Hello", |   "title": "Hello", | ||||||
|   "type": "text", |   "type": "text", | ||||||
|   "content": "Hi there!" |   "content": "Hi there!", | ||||||
|  |   "dateCreated": "2023-08-21 23:38:51.123+0200", | ||||||
|  |   "utcDateCreated": "2023-08-21 23:38:51.123Z" | ||||||
| } | } | ||||||
|  |  | ||||||
| > {% | > {% | ||||||
|     client.assert(response.status === 201); |     client.assert(response.status === 201); | ||||||
|     client.assert(response.body.note.noteId.startsWith("forcedId")); |     client.assert(response.body.note.noteId.startsWith("forcedId")); | ||||||
|     client.assert(response.body.note.title == "Hello"); |     client.assert(response.body.note.title == "Hello"); | ||||||
|  |     client.assert(response.body.note.dateCreated == "2023-08-21 23:38:51.123+0200"); | ||||||
|  |     client.assert(response.body.note.utcDateCreated == "2023-08-21 23:38:51.123Z"); | ||||||
|     client.assert(response.body.branch.parentNoteId == "root"); |     client.assert(response.body.branch.parentNoteId == "root"); | ||||||
|  |  | ||||||
|     client.log(`Created note ` + response.body.note.noteId + ` and branch ` + response.body.branch.branchId); |     client.log(`Created note ` + response.body.note.noteId + ` and branch ` + response.body.branch.branchId); | ||||||
|   | |||||||
| @@ -17,11 +17,11 @@ Content-Type: application/json | |||||||
| GET {{triliumHost}}/etapi/notes/{{createdNoteId}} | GET {{triliumHost}}/etapi/notes/{{createdNoteId}} | ||||||
| Authorization: {{authToken}} | Authorization: {{authToken}} | ||||||
|  |  | ||||||
| > {%  | > {% | ||||||
| client.assert(response.status === 200); | client.assert(response.status === 200); | ||||||
| client.assert(response.body.title === 'Hello');  | client.assert(response.body.title === 'Hello'); | ||||||
| client.assert(response.body.type === 'code');  | client.assert(response.body.type === 'code'); | ||||||
| client.assert(response.body.mime === 'application/json');  | client.assert(response.body.mime === 'application/json'); | ||||||
| %} | %} | ||||||
|  |  | ||||||
| ### | ### | ||||||
| @@ -33,7 +33,9 @@ Content-Type: application/json | |||||||
| { | { | ||||||
|   "title": "Wassup", |   "title": "Wassup", | ||||||
|   "type": "html", |   "type": "html", | ||||||
|   "mime": "text/html" |   "mime": "text/html", | ||||||
|  |   "dateCreated": "2023-08-21 23:38:51.123+0200", | ||||||
|  |   "utcDateCreated": "2023-08-21 23:38:51.123Z" | ||||||
| } | } | ||||||
|  |  | ||||||
| ### | ### | ||||||
| @@ -41,11 +43,13 @@ Content-Type: application/json | |||||||
| GET {{triliumHost}}/etapi/notes/{{createdNoteId}} | GET {{triliumHost}}/etapi/notes/{{createdNoteId}} | ||||||
| Authorization: {{authToken}} | Authorization: {{authToken}} | ||||||
|  |  | ||||||
| > {%  | > {% | ||||||
| client.assert(response.status === 200); | client.assert(response.status === 200); | ||||||
| client.assert(response.body.title === 'Wassup');  | client.assert(response.body.title === 'Wassup'); | ||||||
| client.assert(response.body.type === 'html');  | client.assert(response.body.type === 'html'); | ||||||
| client.assert(response.body.mime === 'text/html');  | client.assert(response.body.mime === 'text/html'); | ||||||
|  | client.assert(response.body.dateCreated == "2023-08-21 23:38:51.123+0200"); | ||||||
|  | client.assert(response.body.utcDateCreated == "2023-08-21 23:38:51.123Z"); | ||||||
| %} | %} | ||||||
|  |  | ||||||
| ### | ### | ||||||
| @@ -58,8 +62,8 @@ Content-Type: application/json | |||||||
|   "isProtected": true |   "isProtected": true | ||||||
| } | } | ||||||
|  |  | ||||||
| > {%  | > {% | ||||||
|     client.assert(response.status === 400);  |     client.assert(response.status === 400); | ||||||
|     client.assert(response.body.code == "PROPERTY_NOT_ALLOWED"); |     client.assert(response.body.code == "PROPERTY_NOT_ALLOWED"); | ||||||
| %} | %} | ||||||
|  |  | ||||||
| @@ -73,7 +77,7 @@ Content-Type: application/json | |||||||
|   "title": true |   "title": true | ||||||
| } | } | ||||||
|  |  | ||||||
| > {%  | > {% | ||||||
|     client.assert(response.status === 400);  |     client.assert(response.status === 400); | ||||||
|     client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR"); |     client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR"); | ||||||
| %} | %} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user