mirror of
https://github.com/laurent22/joplin.git
synced 2025-04-23 11:52:59 +02:00
API: Added more API calls
This commit is contained in:
parent
b6ca3090df
commit
a1ad6c9712
@ -22,7 +22,6 @@ trap finish EXIT
|
|||||||
|
|
||||||
cd "$ROOT_DIR"
|
cd "$ROOT_DIR"
|
||||||
npm test tests-build/ArrayUtils.js
|
npm test tests-build/ArrayUtils.js
|
||||||
npm test tests-build/encryption.js
|
|
||||||
npm test tests-build/EnexToMd.js
|
npm test tests-build/EnexToMd.js
|
||||||
npm test tests-build/HtmlToMd.js
|
npm test tests-build/HtmlToMd.js
|
||||||
npm test tests-build/markdownUtils.js
|
npm test tests-build/markdownUtils.js
|
||||||
@ -32,5 +31,7 @@ npm test tests-build/models_Tag.js
|
|||||||
npm test tests-build/models_Setting.js
|
npm test tests-build/models_Setting.js
|
||||||
npm test tests-build/services_InteropService.js
|
npm test tests-build/services_InteropService.js
|
||||||
npm test tests-build/services_ResourceService.js
|
npm test tests-build/services_ResourceService.js
|
||||||
npm test tests-build/synchronizer.js
|
|
||||||
npm test tests-build/urlUtils.js
|
npm test tests-build/urlUtils.js
|
||||||
|
npm test tests-build/encryption.js
|
||||||
|
npm test tests-build/services_rest_Api.js
|
||||||
|
npm test tests-build/synchronizer.js
|
@ -6,6 +6,7 @@ const markdownUtils = require('lib/markdownUtils.js');
|
|||||||
const Api = require('lib/services/rest/Api');
|
const Api = require('lib/services/rest/Api');
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
|
const Tag = require('lib/models/Tag');
|
||||||
const Resource = require('lib/models/Resource');
|
const Resource = require('lib/models/Resource');
|
||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
|
||||||
@ -45,6 +46,60 @@ describe('services_rest_Api', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update folders', async (done) => {
|
||||||
|
let f1 = await Folder.save({ title: "mon carnet" });
|
||||||
|
const response = await api.route('PUT', 'folders/' + f1.id, null, JSON.stringify({
|
||||||
|
title: 'modifié',
|
||||||
|
}));
|
||||||
|
|
||||||
|
let f1b = await Folder.load(f1.id);
|
||||||
|
expect(f1b.title).toBe('modifié');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete folders', async (done) => {
|
||||||
|
let f1 = await Folder.save({ title: "mon carnet" });
|
||||||
|
await api.route('DELETE', 'folders/' + f1.id);
|
||||||
|
|
||||||
|
let f1b = await Folder.load(f1.id);
|
||||||
|
expect(!f1b).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create folders', async (done) => {
|
||||||
|
const response = await api.route('POST', 'folders', null, JSON.stringify({
|
||||||
|
title: 'from api',
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(!!response.id).toBe(true);
|
||||||
|
|
||||||
|
let f = await Folder.all();
|
||||||
|
expect(f.length).toBe(1);
|
||||||
|
expect(f[0].title).toBe('from api');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get one folder', async (done) => {
|
||||||
|
let f1 = await Folder.save({ title: "mon carnet" });
|
||||||
|
const response = await api.route('GET', 'folders/' + f1.id);
|
||||||
|
expect(response.id).toBe(f1.id);
|
||||||
|
|
||||||
|
const hasThrown = await checkThrowAsync(async () => await api.route('GET', 'folders/doesntexist'));
|
||||||
|
expect(hasThrown).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail on invalid paths', async (done) => {
|
||||||
|
const hasThrown = await checkThrowAsync(async () => await api.route('GET', 'schtroumpf'));
|
||||||
|
expect(hasThrown).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('should get notes', async (done) => {
|
it('should get notes', async (done) => {
|
||||||
let response = null;
|
let response = null;
|
||||||
const f1 = await Folder.save({ title: "mon carnet" });
|
const f1 = await Folder.save({ title: "mon carnet" });
|
||||||
@ -159,4 +214,44 @@ describe('services_rest_Api', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add tags to notes', async (done) => {
|
||||||
|
const tag = await Tag.save({ title: "mon étiquette" });
|
||||||
|
const note = await Note.save({ title: "ma note" });
|
||||||
|
|
||||||
|
const response = await api.route('POST', 'tags/' + tag.id + '/notes', null, JSON.stringify({
|
||||||
|
id: note.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const noteIds = await Tag.noteIds(tag.id);
|
||||||
|
expect(noteIds[0]).toBe(note.id);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove tags from notes', async (done) => {
|
||||||
|
const tag = await Tag.save({ title: "mon étiquette" });
|
||||||
|
const note = await Note.save({ title: "ma note" });
|
||||||
|
await Tag.addNote(tag.id, note.id);
|
||||||
|
|
||||||
|
const response = await api.route('DELETE', 'tags/' + tag.id + '/notes/' + note.id);
|
||||||
|
|
||||||
|
const noteIds = await Tag.noteIds(tag.id);
|
||||||
|
expect(noteIds.length).toBe(0);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list all tag notes', async (done) => {
|
||||||
|
const tag = await Tag.save({ title: "mon étiquette" });
|
||||||
|
const note1 = await Note.save({ title: "ma note un" });
|
||||||
|
const note2 = await Note.save({ title: "ma note deux" });
|
||||||
|
await Tag.addNote(tag.id, note1.id);
|
||||||
|
await Tag.addNote(tag.id, note2.id);
|
||||||
|
|
||||||
|
const response = await api.route('GET', 'tags/' + tag.id + '/notes');
|
||||||
|
expect(response.length).toBe(2);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
File diff suppressed because one or more lines are too long
119
ElectronClient/app/package-lock.json
generated
119
ElectronClient/app/package-lock.json
generated
@ -40,17 +40,30 @@
|
|||||||
"integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
|
"integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
"version": "5.7.2",
|
"version": "5.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
|
||||||
"integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw=="
|
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
|
||||||
},
|
},
|
||||||
"acorn-globals": {
|
"acorn-globals": {
|
||||||
"version": "4.1.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz",
|
||||||
"integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==",
|
"integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"acorn": "^5.0.0"
|
"acorn": "^6.0.1",
|
||||||
|
"acorn-walk": "^6.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg=="
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acorn-walk": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg=="
|
||||||
},
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
@ -914,9 +927,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browser-process-hrtime": {
|
"browser-process-hrtime": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
|
||||||
"integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44="
|
"integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw=="
|
||||||
},
|
},
|
||||||
"buffer-from": {
|
"buffer-from": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@ -1783,6 +1796,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
},
|
},
|
||||||
|
"depd": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||||
|
},
|
||||||
"detect-indent": {
|
"detect-indent": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
|
||||||
@ -3519,6 +3537,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
|
||||||
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
|
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
|
||||||
},
|
},
|
||||||
|
"http-errors": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==",
|
||||||
|
"requires": {
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"inherits": "2.0.3",
|
||||||
|
"setprototypeof": "1.1.0",
|
||||||
|
"statuses": ">= 1.5.0 < 2",
|
||||||
|
"toidentifier": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"http-signature": {
|
"http-signature": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
@ -3909,9 +3939,9 @@
|
|||||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||||
},
|
},
|
||||||
"joplin-turndown": {
|
"joplin-turndown": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.9.tgz",
|
||||||
"integrity": "sha512-RPZJSZEplVPL3UiJNkaKsFAG8bCGofsKIiH24s8/4qcy1xYnEufvg++rHm7rxi/0VCtpSkRBlWHSs1/srJZvoA==",
|
"integrity": "sha512-8MOxX4t5Ai22muHhXPMGNoKc/AB7gSo0eUvNh6dyd6b3vcSiMIRZE8UHpMjS9ruJQ+8e+8TtJXc0nfbexeHwrA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsdom": "^11.9.0"
|
"jsdom": "^11.9.0"
|
||||||
}
|
}
|
||||||
@ -4483,6 +4513,32 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
},
|
},
|
||||||
|
"multiparty": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-AvESCnNoQlZiOfP9R4mxN8M9csy2L16EIbWIkt3l4FuGti9kXBS8QVzlfyg4HEnarJhrzZilgNFlZtqmoiAIIA==",
|
||||||
|
"requires": {
|
||||||
|
"fd-slicer": "1.1.0",
|
||||||
|
"http-errors": "~1.7.0",
|
||||||
|
"safe-buffer": "5.1.2",
|
||||||
|
"uid-safe": "2.1.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fd-slicer": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
|
||||||
|
"requires": {
|
||||||
|
"pend": "~1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
|
||||||
@ -4888,8 +4944,7 @@
|
|||||||
"pend": {
|
"pend": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
|
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"performance-now": {
|
"performance-now": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@ -5059,6 +5114,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
|
||||||
"integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw=="
|
"integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw=="
|
||||||
},
|
},
|
||||||
|
"random-bytes": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
|
||||||
|
},
|
||||||
"randomatic": {
|
"randomatic": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz",
|
||||||
@ -5539,6 +5599,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||||
},
|
},
|
||||||
|
"setprototypeof": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
@ -6538,6 +6603,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"statuses": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||||
|
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||||
|
},
|
||||||
"stealthy-require": {
|
"stealthy-require": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||||
@ -6816,6 +6886,11 @@
|
|||||||
"repeat-string": "^1.6.1"
|
"repeat-string": "^1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"toidentifier": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||||
|
},
|
||||||
"tough-cookie": {
|
"tough-cookie": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
||||||
@ -6898,6 +6973,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz",
|
||||||
"integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg=="
|
"integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg=="
|
||||||
},
|
},
|
||||||
|
"uid-safe": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||||
|
"requires": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"union-value": {
|
"union-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
||||||
@ -7182,9 +7265,9 @@
|
|||||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
||||||
},
|
},
|
||||||
"whatwg-mimetype": {
|
"whatwg-mimetype": {
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz",
|
||||||
"integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew=="
|
"integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw=="
|
||||||
},
|
},
|
||||||
"whatwg-url": {
|
"whatwg-url": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
"mermaid": "^8.0.0-rc.8",
|
"mermaid": "^8.0.0-rc.8",
|
||||||
"mime": "^2.3.1",
|
"mime": "^2.3.1",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
|
"multiparty": "^4.2.1",
|
||||||
"node-fetch": "^1.7.3",
|
"node-fetch": "^1.7.3",
|
||||||
"node-notifier": "^5.2.1",
|
"node-notifier": "^5.2.1",
|
||||||
"promise": "^8.0.1",
|
"promise": "^8.0.1",
|
||||||
|
@ -494,6 +494,12 @@ class BaseApplication {
|
|||||||
setLocale(Setting.value('locale'));
|
setLocale(Setting.value('locale'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Setting.value('api.token')) {
|
||||||
|
EncryptionService.instance().randomHexString(64).then((token) => {
|
||||||
|
Setting.setValue('api.token', token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
time.setDateFormat(Setting.value('dateFormat'));
|
time.setDateFormat(Setting.value('dateFormat'));
|
||||||
time.setTimeFormat(Setting.value('timeFormat'));
|
time.setTimeFormat(Setting.value('timeFormat'));
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ const { Logger } = require('lib/logger.js');
|
|||||||
const randomClipperPort = require('lib/randomClipperPort');
|
const randomClipperPort = require('lib/randomClipperPort');
|
||||||
const enableServerDestroy = require('server-destroy');
|
const enableServerDestroy = require('server-destroy');
|
||||||
const Api = require('lib/services/rest/Api');
|
const Api = require('lib/services/rest/Api');
|
||||||
|
const multiparty = require('multiparty');
|
||||||
|
|
||||||
class ClipperServer {
|
class ClipperServer {
|
||||||
|
|
||||||
@ -13,7 +14,9 @@ class ClipperServer {
|
|||||||
this.startState_ = 'idle';
|
this.startState_ = 'idle';
|
||||||
this.server_ = null;
|
this.server_ = null;
|
||||||
this.port_ = null;
|
this.port_ = null;
|
||||||
this.api_ = new Api();
|
this.api_ = new Api(() => {
|
||||||
|
return Setting.value('api.token');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static instance() {
|
static instance() {
|
||||||
@ -122,19 +125,36 @@ class ClipperServer {
|
|||||||
|
|
||||||
const url = urlParser.parse(request.url, true);
|
const url = urlParser.parse(request.url, true);
|
||||||
|
|
||||||
const execRequest = async (request, body = '') => {
|
const execRequest = async (request, body = '', files = []) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.api_.route(request.method, url.pathname, url.query, body);
|
const response = await this.api_.route(request.method, url.pathname, url.query, body, files);
|
||||||
writeResponse(200, response);
|
writeResponse(200, response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
|
||||||
writeResponse(error.httpCode ? error.httpCode : 500, error.message);
|
writeResponse(error.httpCode ? error.httpCode : 500, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contentType = request.headers['content-type'] ? request.headers['content-type'] : '';
|
||||||
|
|
||||||
if (request.method === 'OPTIONS') {
|
if (request.method === 'OPTIONS') {
|
||||||
writeCorsHeaders(200);
|
writeCorsHeaders(200);
|
||||||
response.end();
|
response.end();
|
||||||
|
} else {
|
||||||
|
if (contentType.indexOf('multipart/form-data') === 0) {
|
||||||
|
const form = new multiparty.Form();
|
||||||
|
|
||||||
|
form.parse(request, function(error, fields, files) {
|
||||||
|
if (error) {
|
||||||
|
writeResponse(error.httpCode ? error.httpCode : 500, error.message);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
execRequest(
|
||||||
|
request,
|
||||||
|
fields && fields.props && fields.props.length ? fields.props[0] : '',
|
||||||
|
files && files.data ? files.data : []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (request.method === 'POST') {
|
if (request.method === 'POST') {
|
||||||
let body = '';
|
let body = '';
|
||||||
@ -150,6 +170,7 @@ class ClipperServer {
|
|||||||
execRequest(request);
|
execRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
enableServerDestroy(this.server_);
|
enableServerDestroy(this.server_);
|
||||||
|
@ -154,6 +154,8 @@ class Setting extends BaseModel {
|
|||||||
|
|
||||||
'net.customCertificates': { value: '', type: Setting.TYPE_STRING, show: (settings) => { return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav')].indexOf(settings['sync.target']) >= 0 }, public: true, appTypes: ['desktop', 'cli'], label: () => _('Custom TLS certificates'), description: () => _('Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on "Check synchronisation configuration".') },
|
'net.customCertificates': { value: '', type: Setting.TYPE_STRING, show: (settings) => { return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav')].indexOf(settings['sync.target']) >= 0 }, public: true, appTypes: ['desktop', 'cli'], label: () => _('Custom TLS certificates'), description: () => _('Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on "Check synchronisation configuration".') },
|
||||||
'net.ignoreTlsErrors': { value: false, type: Setting.TYPE_BOOL, show: (settings) => { return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav')].indexOf(settings['sync.target']) >= 0 }, public: true, appTypes: ['desktop', 'cli'], label: () => _('Ignore TLS certificate errors') },
|
'net.ignoreTlsErrors': { value: false, type: Setting.TYPE_BOOL, show: (settings) => { return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav')].indexOf(settings['sync.target']) >= 0 }, public: true, appTypes: ['desktop', 'cli'], label: () => _('Ignore TLS certificate errors') },
|
||||||
|
|
||||||
|
'api.token': { value: null, type: Setting.TYPE_STRING, public: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.metadata_;
|
return this.metadata_;
|
||||||
|
@ -213,6 +213,11 @@ class EncryptionService {
|
|||||||
sjcl.random.addEntropy(hexSeed, 1024, 'shim.randomBytes');
|
sjcl.random.addEntropy(hexSeed, 1024, 'shim.randomBytes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async randomHexString(byteCount) {
|
||||||
|
const bytes = await shim.randomBytes(byteCount);
|
||||||
|
return bytes.map((a) => { return hexPad(a.toString(16), 2); }).join('');
|
||||||
|
}
|
||||||
|
|
||||||
async generateMasterKey(password) {
|
async generateMasterKey(password) {
|
||||||
const bytes = await shim.randomBytes(256);
|
const bytes = await shim.randomBytes(256);
|
||||||
const hexaBytes = bytes.map((a) => { return hexPad(a.toString(16), 2); }).join('');
|
const hexaBytes = bytes.map((a) => { return hexPad(a.toString(16), 2); }).join('');
|
||||||
|
@ -2,6 +2,8 @@ const { ltrimSlashes } = require('lib/path-utils.js');
|
|||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const Tag = require('lib/models/Tag');
|
const Tag = require('lib/models/Tag');
|
||||||
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
|
const BaseModel = require('lib/BaseModel');
|
||||||
const Setting = require('lib/models/Setting');
|
const Setting = require('lib/models/Setting');
|
||||||
const markdownUtils = require('lib/markdownUtils');
|
const markdownUtils = require('lib/markdownUtils');
|
||||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
const mimeUtils = require('lib/mime-utils.js').mime;
|
||||||
@ -48,6 +50,14 @@ class ErrorForbidden extends ApiError {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ErrorBadRequest extends ApiError {
|
||||||
|
|
||||||
|
constructor(message = 'Bad Request') {
|
||||||
|
super(message, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
|
|
||||||
constructor(token = null) {
|
constructor(token = null) {
|
||||||
@ -56,25 +66,68 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get token() {
|
get token() {
|
||||||
return this.token_;
|
return typeof this.token_ === 'function' ? this.token_() : this.token_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async route(method, path, query = null, body = null) {
|
parsePath(path) {
|
||||||
path = ltrimSlashes(path);
|
path = ltrimSlashes(path);
|
||||||
if (!path) throw new ErrorNotFound(); // Nothing at the root yet
|
if (!path) return { callName: '', params: [] };
|
||||||
|
|
||||||
const pathParts = path.split('/');
|
const pathParts = path.split('/');
|
||||||
const callSuffix = pathParts.splice(0,1)[0];
|
const callSuffix = pathParts.splice(0,1)[0];
|
||||||
const callName = 'action_' + callSuffix;
|
let callName = 'action_' + callSuffix;
|
||||||
if (!this[callName]) throw new ErrorNotFound();
|
return {
|
||||||
|
callName: callName,
|
||||||
|
params: pathParts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
async route(method, path, query = null, body = null, files = null) {
|
||||||
return this[callName]({
|
if (!files) files = [];
|
||||||
|
|
||||||
|
const parsedPath = this.parsePath(path);
|
||||||
|
if (!parsedPath.callName) throw new ErrorNotFound(); // Nothing at the root yet
|
||||||
|
|
||||||
|
const request = {
|
||||||
method: method,
|
method: method,
|
||||||
|
path: ltrimSlashes(path),
|
||||||
query: query ? query : {},
|
query: query ? query : {},
|
||||||
body: body,
|
body: body,
|
||||||
params: pathParts,
|
bodyJson_: null,
|
||||||
});
|
bodyJson: function(disallowedProperties = null) {
|
||||||
|
if (!this.bodyJson_) this.bodyJson_ = JSON.parse(this.body);
|
||||||
|
|
||||||
|
if (disallowedProperties) {
|
||||||
|
const filteredBody = Object.assign({}, this.bodyJson_);
|
||||||
|
for (let i = 0; i < disallowedProperties.length; i++) {
|
||||||
|
const n = disallowedProperties[i];
|
||||||
|
delete filteredBody[n];
|
||||||
|
}
|
||||||
|
return filteredBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.bodyJson_;
|
||||||
|
},
|
||||||
|
files: files,
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = null;
|
||||||
|
let link = null;
|
||||||
|
let params = parsedPath.params;
|
||||||
|
|
||||||
|
if (params.length >= 1) {
|
||||||
|
id = params[0];
|
||||||
|
params.splice(0, 1);
|
||||||
|
if (params.length >= 1) {
|
||||||
|
link = params[0];
|
||||||
|
params.splice(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.params = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this[parsedPath.callName](request, id, link);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!error.httpCode) error.httpCode = 500;
|
if (!error.httpCode) error.httpCode = 500;
|
||||||
throw error;
|
throw error;
|
||||||
@ -89,6 +142,10 @@ class Api {
|
|||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get readonlyProperties() {
|
||||||
|
return ['id', 'created_time', 'updated_time', 'encryption_blob_encrypted', 'encryption_applied', 'encryption_cipher_text'];
|
||||||
|
}
|
||||||
|
|
||||||
fields_(request, defaultFields) {
|
fields_(request, defaultFields) {
|
||||||
const query = request.query;
|
const query = request.query;
|
||||||
if (!query || !query.fields) return defaultFields;
|
if (!query || !query.fields) return defaultFields;
|
||||||
@ -97,39 +154,139 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkToken_(request) {
|
checkToken_(request) {
|
||||||
|
// For now, whitelist some calls to allow the web clipper to work
|
||||||
|
// without an extra auth step
|
||||||
|
const whiteList = [
|
||||||
|
[ 'GET', 'ping' ],
|
||||||
|
[ 'GET', 'tags' ],
|
||||||
|
[ 'GET', 'folders' ],
|
||||||
|
[ 'POST', 'notes' ],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < whiteList.length; i++) {
|
||||||
|
if (whiteList[i][0] === request.method && whiteList[i][1] === request.path) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.token) return;
|
if (!this.token) return;
|
||||||
if (!request.query || !request.query.token) throw new ErrorForbidden('Missing "token" parameter');
|
if (!request.query || !request.query.token) throw new ErrorForbidden('Missing "token" parameter');
|
||||||
if (request.query.token !== this.token) throw new ErrorForbidden('Invalid "token" parameter');
|
if (request.query.token !== this.token) throw new ErrorForbidden('Invalid "token" parameter');
|
||||||
}
|
}
|
||||||
|
|
||||||
async action_ping(request) {
|
async defaultAction_(modelType, request, id = null, link = null) {
|
||||||
|
this.checkToken_(request);
|
||||||
|
|
||||||
|
if (link) throw new ErrorNotFound(); // Default action doesn't support links at all for now
|
||||||
|
|
||||||
|
const ModelClass = BaseItem.getClassByItemType(modelType);
|
||||||
|
|
||||||
|
const getOneModel = async () => {
|
||||||
|
const model = await ModelClass.load(id);
|
||||||
|
if (!model) throw new ErrorNotFound();
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === 'GET') {
|
||||||
|
if (id) {
|
||||||
|
return getOneModel();
|
||||||
|
} else {
|
||||||
|
const options = {};
|
||||||
|
const fields = this.fields_(request, []);
|
||||||
|
if (fields.length) options.fields = fields;
|
||||||
|
return await ModelClass.all(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === 'PUT' && id) {
|
||||||
|
const model = await getOneModel();
|
||||||
|
let newModel = Object.assign({}, model, request.bodyJson(this.readonlyProperties));
|
||||||
|
newModel = await ModelClass.save(newModel, { userSideValidation: true });
|
||||||
|
return newModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === 'DELETE' && id) {
|
||||||
|
const model = await getOneModel();
|
||||||
|
await ModelClass.delete(model.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === 'POST') {
|
||||||
|
const model = request.bodyJson(this.readonlyProperties);
|
||||||
|
const result = await ModelClass.save(model, { userSideValidation: true });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ErrorMethodNotAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
async action_ping(request, id = null, link = null) {
|
||||||
if (request.method === 'GET') {
|
if (request.method === 'GET') {
|
||||||
return 'JoplinClipperServer';
|
return 'JoplinClipperServer';
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ErrorMethodNotAllowed();
|
throw new ErrorMethodNotAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
async action_folders(request) {
|
async action_folders(request, id = null, link = null) {
|
||||||
if (request.method === 'GET') {
|
if (request.method === 'GET' && !id) {
|
||||||
return await Folder.allAsTree({ fields: this.fields_(request, ['id', 'parent_id', 'title']) });
|
return await Folder.allAsTree({ fields: this.fields_(request, ['id', 'parent_id', 'title']) });
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ErrorMethodNotAllowed();
|
return this.defaultAction_(BaseModel.TYPE_FOLDER, request, id, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
async action_tags(request, id = null, link = null) {
|
||||||
|
if (link === 'notes') {
|
||||||
|
const tag = await Tag.load(id);
|
||||||
|
if (!tag) throw new ErrorNotFound();
|
||||||
|
|
||||||
|
if (request.method === 'POST') {
|
||||||
|
const note = request.bodyJson();
|
||||||
|
if (!note || !note.id) throw new ErrorBadRequest('Missing note ID');
|
||||||
|
return await Tag.addNote(tag.id, note.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === 'DELETE') {
|
||||||
|
const noteId = request.params.length ? request.params[0] : null;
|
||||||
|
if (!noteId) throw new ErrorBadRequest('Missing note ID');
|
||||||
|
await Tag.removeNote(tag.id, noteId);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async action_tags(request) {
|
|
||||||
if (request.method === 'GET') {
|
if (request.method === 'GET') {
|
||||||
return await Tag.all({ fields: this.fields_(request, ['id', 'title']) })
|
return await Tag.noteIds(tag.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ErrorMethodNotAllowed();
|
return this.defaultAction_(BaseModel.TYPE_TAG, request, id, link);
|
||||||
}
|
}
|
||||||
|
|
||||||
async action_notes(request) {
|
async action_master_keys(request, id = null, link = null) {
|
||||||
|
return this.defaultAction_(BaseModel.TYPE_MASTER_KEY, request, id, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
async action_resources(request, id = null, link = null) {
|
||||||
|
// fieldName: "data"
|
||||||
|
// headers: Object
|
||||||
|
// originalFilename: "test.jpg"
|
||||||
|
// path: "C:\Users\Laurent\AppData\Local\Temp\BW77wkpP23iIGUstd0kDuXXC.jpg"
|
||||||
|
// size: 164394
|
||||||
|
|
||||||
|
if (request.method === 'POST') {
|
||||||
|
if (!request.files.length) throw new ErrorBadRequest('Resource cannot be created without a file');
|
||||||
|
const filePath = request.files[0].path;
|
||||||
|
const resource = await shim.createResourceFromPath(filePath);
|
||||||
|
const newResource = Object.assign({}, resource, request.bodyJson(this.readonlyProperties));
|
||||||
|
return await Resource.save(newResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.defaultAction_(BaseModel.TYPE_RESOURCE, request, id, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
async action_notes(request, id = null, link = null) {
|
||||||
if (request.method === 'GET') {
|
if (request.method === 'GET') {
|
||||||
this.checkToken_(request);
|
this.checkToken_(request);
|
||||||
|
|
||||||
const noteId = request.params.length ? request.params[0] : null;
|
const noteId = id;
|
||||||
const parentId = request.query.parent_id ? request.query.parent_id : null;
|
const parentId = request.query.parent_id ? request.query.parent_id : null;
|
||||||
const fields = this.fields_(request, []); // previews() already returns default fields
|
const fields = this.fields_(request, []); // previews() already returns default fields
|
||||||
const options = {};
|
const options = {};
|
||||||
@ -177,7 +334,7 @@ class Api {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ErrorMethodNotAllowed();
|
return this.defaultAction_(BaseModel.TYPE_NOTE, request, id, link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,9 +143,7 @@ function shimInit() {
|
|||||||
await fs.copy(filePath, targetPath, { overwrite: true });
|
await fs.copy(filePath, targetPath, { overwrite: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
await Resource.save(resource, { isNew: true });
|
return await Resource.save(resource, { isNew: true });
|
||||||
|
|
||||||
return resource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shim.attachFileToNote = async function(note, filePath, position = null) {
|
shim.attachFileToNote = async function(note, filePath, position = null) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user