Skip to content

Commit 0417587

Browse files
committed
allow specifying date created in the ETAPI, #4199
1 parent 515c541 commit 0417587

9 files changed

+390
-433
lines changed

package-lock.json

+277-400
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+8-8
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@
6868
"jimp": "0.22.10",
6969
"joplin-turndown-plugin-gfm": "1.0.12",
7070
"jsdom": "22.1.0",
71-
"marked": "7.0.3",
71+
"marked": "7.0.5",
7272
"mime-types": "2.1.35",
7373
"multer": "1.4.5-lts.1",
74-
"node-abi": "3.46.0",
74+
"node-abi": "3.47.0",
7575
"normalize-strings": "1.1.1",
7676
"open": "8.4.1",
7777
"rand-token": "1.0.1",
@@ -97,14 +97,14 @@
9797
},
9898
"devDependencies": {
9999
"cross-env": "7.0.3",
100-
"electron": "25.5.0",
100+
"electron": "25.7.0",
101101
"electron-builder": "24.6.3",
102-
"electron-packager": "17.1.1",
102+
"electron-packager": "17.1.2",
103103
"electron-rebuild": "3.2.9",
104-
"eslint": "8.47.0",
104+
"eslint": "8.48.0",
105105
"eslint-config-airbnb-base": "15.0.0",
106106
"eslint-config-prettier": "9.0.0",
107-
"eslint-plugin-import": "2.28.0",
107+
"eslint-plugin-import": "2.28.1",
108108
"eslint-plugin-jsonc": "2.9.0",
109109
"eslint-plugin-prettier": "5.0.0",
110110
"esm": "3.2.25",
@@ -115,13 +115,13 @@
115115
"lint-staged": "14.0.0",
116116
"lorem-ipsum": "2.0.8",
117117
"nodemon": "3.0.1",
118-
"prettier": "3.0.2",
118+
"prettier": "3.0.3",
119119
"rcedit": "3.1.0",
120120
"webpack": "5.88.2",
121121
"webpack-cli": "5.1.4"
122122
},
123123
"optionalDependencies": {
124-
"electron-installer-debian": "3.1.0"
124+
"electron-installer-debian": "3.2.0"
125125
},
126126
"lint-staged": {
127127
"*.js": "eslint --cache --fix"

src/etapi/etapi.openapi.yaml

+8-4
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,12 @@ components:
802802
branchId:
803803
$ref: '#/components/schemas/EntityId'
804804
description: DON'T specify unless you want to force a specific branchId
805+
dateCreated:
806+
$ref: '#/components/schemas/LocalDateTime'
807+
description: Local timestap of the note creation. Specify only if you want to override the default (current datetime in the current timezone/offset).
808+
utcDateCreated:
809+
$ref: '#/components/schemas/UtcDateTime'
810+
description: UTC timestap of the note creation. Specify only if you want to override the default (current datetime).
805811
Note:
806812
type: object
807813
properties:
@@ -838,13 +844,11 @@ components:
838844
readOnly: true
839845
dateCreated:
840846
$ref: '#/components/schemas/LocalDateTime'
841-
readOnly: true
842847
dateModified:
843848
$ref: '#/components/schemas/LocalDateTime'
844849
readOnly: true
845850
utcDateCreated:
846851
$ref: '#/components/schemas/UtcDateTime'
847-
readOnly: true
848852
utcDateModified:
849853
$ref: '#/components/schemas/UtcDateTime'
850854
readOnly: true
@@ -937,11 +941,11 @@ components:
937941
LocalDateTime:
938942
type: string
939943
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}'
940-
example: 2021-12-31 20:18:11.939+0100
944+
example: 2021-12-31 20:18:11.930+0100
941945
UtcDateTime:
942946
type: string
943947
pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z'
944-
example: 2021-12-31 19:18:11.939Z
948+
example: 2021-12-31 19:18:11.930Z
945949
AppInfo:
946950
type: object
947951
properties:

src/etapi/notes.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ function register(router) {
5050
'notePosition': [v.notNull, v.isInteger],
5151
'prefix': [v.notNull, v.isString],
5252
'isExpanded': [v.notNull, v.isBoolean],
53-
'noteId': [v.notNull, v.isValidEntityId]
53+
'noteId': [v.notNull, v.isValidEntityId],
54+
'dateCreated': [v.notNull, v.isString, v.isLocalDateTime],
55+
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
5456
};
5557

5658
eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => {
@@ -74,7 +76,9 @@ function register(router) {
7476
const ALLOWED_PROPERTIES_FOR_PATCH = {
7577
'title': [v.notNull, v.isString],
7678
'type': [v.notNull, v.isString],
77-
'mime': [v.notNull, v.isString]
79+
'mime': [v.notNull, v.isString],
80+
'dateCreated': [v.notNull, v.isString, v.isLocalDateTime],
81+
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
7882
};
7983

8084
eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {

src/etapi/validators.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const noteTypeService = require("../services/note_types");
2+
const dateUtils = require("../services/date_utils");
23

34
function mandatory(obj) {
45
if (obj === undefined ) {
@@ -22,6 +23,22 @@ function isString(obj) {
2223
}
2324
}
2425

26+
function isLocalDateTime(obj) {
27+
if (obj === undefined || obj === null) {
28+
return;
29+
}
30+
31+
return dateUtils.validateLocalDateTime(obj);
32+
}
33+
34+
function isUtcDateTime(obj) {
35+
if (obj === undefined || obj === null) {
36+
return;
37+
}
38+
39+
return dateUtils.validateUtcDateTime(obj);
40+
}
41+
2542
function isBoolean(obj) {
2643
if (obj === undefined || obj === null) {
2744
return;
@@ -99,5 +116,7 @@ module.exports = {
99116
isNoteId,
100117
isNoteType,
101118
isAttributeType,
102-
isValidEntityId
119+
isValidEntityId,
120+
isLocalDateTime,
121+
isUtcDateTime
103122
};

src/services/date_utils.js

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const dayjs = require('dayjs');
22
const cls = require('./cls');
33

4+
const LOCAL_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSZZ';
5+
const UTC_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ssZ';
6+
47
function utcNowDateTime() {
58
return utcDateTimeStr(new Date());
69
}
@@ -10,7 +13,7 @@ function utcNowDateTime() {
1013
// "trilium-local-now-datetime" header which is then stored in CLS
1114
function localNowDateTime() {
1215
return cls.getLocalNowDateTime()
13-
|| dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ')
16+
|| dayjs().format(LOCAL_DATETIME_FORMAT)
1417
}
1518

1619
function localNowDate() {
@@ -62,6 +65,36 @@ function getDateTimeForFile() {
6265
return new Date().toISOString().substr(0, 19).replace(/:/g, '');
6366
}
6467

68+
function validateLocalDateTime(str) {
69+
if (!str) {
70+
return;
71+
}
72+
73+
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)) {
74+
return `Invalid local date time format in '${str}'. Correct format shoud follow example: '2023-08-21 23:38:51.110+0200'`;
75+
}
76+
77+
78+
if (!dayjs(str, LOCAL_DATETIME_FORMAT)) {
79+
return `Date '${str}' appears to be in the correct format, but cannot be parsed. It likely represents an invalid date.`;
80+
}
81+
}
82+
83+
function validateUtcDateTime(str) {
84+
if (!str) {
85+
return;
86+
}
87+
88+
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)) {
89+
return `Invalid UTC date time format in '${str}'. Correct format shoud follow example: '2023-08-21 23:38:51.110Z'`;
90+
}
91+
92+
93+
if (!dayjs(str, UTC_DATETIME_FORMAT)) {
94+
return `Date '${str}' appears to be in the correct format, but cannot be parsed. It likely represents an invalid date.`;
95+
}
96+
}
97+
6598
module.exports = {
6699
utcNowDateTime,
67100
localNowDateTime,
@@ -70,5 +103,7 @@ module.exports = {
70103
utcDateTimeStr,
71104
parseDateTime,
72105
parseLocalDate,
73-
getDateTimeForFile
106+
getDateTimeForFile,
107+
validateLocalDateTime,
108+
validateUtcDateTime
74109
};

src/services/notes.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const sql = require('./sql');
2-
const sqlInit = require('./sql_init');
32
const optionService = require('./options');
43
const dateUtils = require('./date_utils');
54
const entityChangesService = require('./entity_changes');
@@ -169,6 +168,15 @@ function createNewNote(params) {
169168
throw new Error(`Note content must be set`);
170169
}
171170

171+
let error;
172+
if (error = dateUtils.validateLocalDateTime(params.dateCreated)) {
173+
throw new Error(error);
174+
}
175+
176+
if (error = dateUtils.validateUtcDateTime(params.utcDateCreated)) {
177+
throw new Error(error);
178+
}
179+
172180
return sql.transactional(() => {
173181
let note, branch, isEntityEventsDisabled;
174182

@@ -189,7 +197,9 @@ function createNewNote(params) {
189197
title: params.title,
190198
isProtected: !!params.isProtected,
191199
type: params.type,
192-
mime: deriveMime(params.type, params.mime)
200+
mime: deriveMime(params.type, params.mime),
201+
dateCreated: params.dateCreated,
202+
utcDateCreated: params.utcDateCreated
193203
}).save();
194204

195205
note.setContent(params.content);

test-etapi/create-entities.http

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ Content-Type: application/json
77
"parentNoteId": "root",
88
"title": "Hello",
99
"type": "text",
10-
"content": "Hi there!"
10+
"content": "Hi there!",
11+
"dateCreated": "2023-08-21 23:38:51.123+0200",
12+
"utcDateCreated": "2023-08-21 23:38:51.123Z"
1113
}
1214

1315
> {%
1416
client.assert(response.status === 201);
1517
client.assert(response.body.note.noteId.startsWith("forcedId"));
1618
client.assert(response.body.note.title == "Hello");
19+
client.assert(response.body.note.dateCreated == "2023-08-21 23:38:51.123+0200");
20+
client.assert(response.body.note.utcDateCreated == "2023-08-21 23:38:51.123Z");
1721
client.assert(response.body.branch.parentNoteId == "root");
1822

1923
client.log(`Created note ` + response.body.note.noteId + ` and branch ` + response.body.branch.branchId);

test-etapi/patch-note.http

+17-13
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ Content-Type: application/json
1717
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
1818
Authorization: {{authToken}}
1919

20-
> {%
20+
> {%
2121
client.assert(response.status === 200);
22-
client.assert(response.body.title === 'Hello');
23-
client.assert(response.body.type === 'code');
24-
client.assert(response.body.mime === 'application/json');
22+
client.assert(response.body.title === 'Hello');
23+
client.assert(response.body.type === 'code');
24+
client.assert(response.body.mime === 'application/json');
2525
%}
2626

2727
###
@@ -33,19 +33,23 @@ Content-Type: application/json
3333
{
3434
"title": "Wassup",
3535
"type": "html",
36-
"mime": "text/html"
36+
"mime": "text/html",
37+
"dateCreated": "2023-08-21 23:38:51.123+0200",
38+
"utcDateCreated": "2023-08-21 23:38:51.123Z"
3739
}
3840

3941
###
4042

4143
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
4244
Authorization: {{authToken}}
4345

44-
> {%
46+
> {%
4547
client.assert(response.status === 200);
46-
client.assert(response.body.title === 'Wassup');
47-
client.assert(response.body.type === 'html');
48-
client.assert(response.body.mime === 'text/html');
48+
client.assert(response.body.title === 'Wassup');
49+
client.assert(response.body.type === 'html');
50+
client.assert(response.body.mime === 'text/html');
51+
client.assert(response.body.dateCreated == "2023-08-21 23:38:51.123+0200");
52+
client.assert(response.body.utcDateCreated == "2023-08-21 23:38:51.123Z");
4953
%}
5054

5155
###
@@ -58,8 +62,8 @@ Content-Type: application/json
5862
"isProtected": true
5963
}
6064

61-
> {%
62-
client.assert(response.status === 400);
65+
> {%
66+
client.assert(response.status === 400);
6367
client.assert(response.body.code == "PROPERTY_NOT_ALLOWED");
6468
%}
6569

@@ -73,7 +77,7 @@ Content-Type: application/json
7377
"title": true
7478
}
7579

76-
> {%
77-
client.assert(response.status === 400);
80+
> {%
81+
client.assert(response.status === 400);
7882
client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR");
7983
%}

0 commit comments

Comments
 (0)