mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
ETAPI auth, spec improvements etc.
This commit is contained in:
parent
2d2641dbd7
commit
91dec23d5e
3
TODO
3
TODO
@ -1,3 +0,0 @@
|
|||||||
- new icon
|
|
||||||
- polish becca entities API
|
|
||||||
- separate private and public APIs in becca entities
|
|
@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "mig_api_tokens"
|
|
||||||
(
|
|
||||||
apiTokenId TEXT PRIMARY KEY NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
token TEXT NOT NULL,
|
|
||||||
utcDateCreated TEXT NOT NULL,
|
|
||||||
isDeleted INT NOT NULL DEFAULT 0);
|
|
||||||
|
|
||||||
INSERT INTO mig_api_tokens (apiTokenId, name, token, utcDateCreated, isDeleted)
|
|
||||||
SELECT apiTokenId, 'Trilium Sender', token, utcDateCreated, isDeleted FROM api_tokens;
|
|
||||||
|
|
||||||
DROP TABLE api_tokens;
|
|
||||||
|
|
||||||
ALTER TABLE mig_api_tokens RENAME TO api_tokens;
|
|
13
db/migrations/0190__change_to_etapi_tokens.sql
Normal file
13
db/migrations/0190__change_to_etapi_tokens.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "etapi_tokens"
|
||||||
|
(
|
||||||
|
etapiTokenId TEXT PRIMARY KEY NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
tokenHash TEXT NOT NULL,
|
||||||
|
utcDateCreated TEXT NOT NULL,
|
||||||
|
utcDateModified TEXT NOT NULL,
|
||||||
|
isDeleted INT NOT NULL DEFAULT 0);
|
||||||
|
|
||||||
|
INSERT INTO etapi_tokens (etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified, isDeleted)
|
||||||
|
SELECT apiTokenId, 'Trilium Sender', token, utcDateCreated, utcDateCreated, isDeleted FROM api_tokens;
|
||||||
|
|
||||||
|
DROP TABLE api_tokens;
|
10
db/migrations/0191__hash_tokens.js
Normal file
10
db/migrations/0191__hash_tokens.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = () => {
|
||||||
|
const sql = require('../../src/services/sql');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
for (const {etapiTokenId, token} of sql.getRows("SELECT etapiTokenId, tokenHash AS token FROM etapi_tokens")) {
|
||||||
|
const tokenHash = crypto.createHash('sha256').update(token).digest('base64');
|
||||||
|
|
||||||
|
sql.execute(`UPDATE etapi_tokens SET tokenHash = ? WHERE etapiTokenId = ?`, [tokenHash, etapiTokenId]);
|
||||||
|
}
|
||||||
|
};
|
@ -10,11 +10,11 @@ CREATE TABLE IF NOT EXISTS "entity_changes" (
|
|||||||
`isSynced` INTEGER NOT NULL,
|
`isSynced` INTEGER NOT NULL,
|
||||||
`utcDateChanged` TEXT NOT NULL
|
`utcDateChanged` TEXT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS "api_tokens"
|
CREATE TABLE IF NOT EXISTS "etapi_tokens"
|
||||||
(
|
(
|
||||||
apiTokenId TEXT PRIMARY KEY NOT NULL,
|
etapiTokenId TEXT PRIMARY KEY NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
token TEXT NOT NULL,
|
tokenHash TEXT NOT NULL,
|
||||||
utcDateCreated TEXT NOT NULL,
|
utcDateCreated TEXT NOT NULL,
|
||||||
isDeleted INT NOT NULL DEFAULT 0);
|
isDeleted INT NOT NULL DEFAULT 0);
|
||||||
CREATE TABLE IF NOT EXISTS "branches" (
|
CREATE TABLE IF NOT EXISTS "branches" (
|
||||||
|
11462
package-lock.json
generated
11462
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
const lex = require('../../src/services/search/services/lex.js');
|
const lex = require('../../src/services/search/services/lex');
|
||||||
|
|
||||||
describe("Lexer fulltext", () => {
|
describe("Lexer fulltext", () => {
|
||||||
it("simple lexing", () => {
|
it("simple lexing", () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const Note = require('../../src/becca/entities/note.js');
|
const Note = require('../../src/becca/entities/note');
|
||||||
const Branch = require('../../src/becca/entities/branch.js');
|
const Branch = require('../../src/becca/entities/branch');
|
||||||
const Attribute = require('../../src/becca/entities/attribute.js');
|
const Attribute = require('../../src/becca/entities/attribute');
|
||||||
const becca = require('../../src/becca/becca.js');
|
const becca = require('../../src/becca/becca');
|
||||||
const randtoken = require('rand-token').generator({source: 'crypto'});
|
const randtoken = require('rand-token').generator({source: 'crypto'});
|
||||||
|
|
||||||
/** @returns {Note} */
|
/** @returns {Note} */
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const handleParens = require('../../src/services/search/services/handle_parens.js');
|
const handleParens = require('../../src/services/search/services/handle_parens');
|
||||||
|
|
||||||
describe("Parens handler", () => {
|
describe("Parens handler", () => {
|
||||||
it("handles parens", () => {
|
it("handles parens", () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const SearchContext = require("../../src/services/search/search_context.js");
|
const SearchContext = require("../../src/services/search/search_context");
|
||||||
const parse = require('../../src/services/search/services/parse.js');
|
const parse = require('../../src/services/search/services/parse');
|
||||||
|
|
||||||
function tokens(toks, cur = 0) {
|
function tokens(toks, cur = 0) {
|
||||||
return toks.map(arg => {
|
return toks.map(arg => {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
const searchService = require('../../src/services/search/services/search.js');
|
const searchService = require('../../src/services/search/services/search');
|
||||||
const Note = require('../../src/becca/entities/note.js');
|
const Note = require('../../src/becca/entities/note');
|
||||||
const Branch = require('../../src/becca/entities/branch.js');
|
const Branch = require('../../src/becca/entities/branch');
|
||||||
const SearchContext = require('../../src/services/search/search_context.js');
|
const SearchContext = require('../../src/services/search/search_context');
|
||||||
const dateUtils = require('../../src/services/date_utils.js');
|
const dateUtils = require('../../src/services/date_utils');
|
||||||
const becca = require('../../src/becca/becca.js');
|
const becca = require('../../src/becca/becca');
|
||||||
const {NoteBuilder, findNoteByTitle, note} = require('./note_cache_mocking.js');
|
const {NoteBuilder, findNoteByTitle, note} = require('./note_cache_mocking');
|
||||||
|
|
||||||
describe("Search", () => {
|
describe("Search", () => {
|
||||||
let rootNote;
|
let rootNote;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const {note} = require('./note_cache_mocking.js');
|
const {note} = require('./note_cache_mocking');
|
||||||
const ValueExtractor = require('../../src/services/search/value_extractor.js');
|
const ValueExtractor = require('../../src/services/search/value_extractor');
|
||||||
const becca = require('../../src/becca/becca.js');
|
const becca = require('../../src/becca/becca');
|
||||||
const SearchContext = require("../../src/services/search/search_context.js");
|
const SearchContext = require("../../src/services/search/search_context");
|
||||||
|
|
||||||
const dsc = new SearchContext();
|
const dsc = new SearchContext();
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ const sessionSecret = require('./services/session_secret');
|
|||||||
const dataDir = require('./services/data_dir');
|
const dataDir = require('./services/data_dir');
|
||||||
const utils = require('./services/utils');
|
const utils = require('./services/utils');
|
||||||
require('./services/handlers');
|
require('./services/handlers');
|
||||||
require('./becca/becca_loader.js');
|
require('./becca/becca_loader');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require("../services/sql.js");
|
const sql = require("../services/sql");
|
||||||
const NoteSet = require("../services/search/note_set");
|
const NoteSet = require("../services/search/note_set");
|
||||||
|
const EtapiToken = require("./entities/etapi_token");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca.
|
* Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca.
|
||||||
@ -24,6 +25,8 @@ class Becca {
|
|||||||
this.attributeIndex = {};
|
this.attributeIndex = {};
|
||||||
/** @type {Object.<String, Option>} */
|
/** @type {Object.<String, Option>} */
|
||||||
this.options = {};
|
this.options = {};
|
||||||
|
/** @type {Object.<String, EtapiToken>} */
|
||||||
|
this.etapiTokens = {};
|
||||||
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
}
|
}
|
||||||
@ -64,10 +67,12 @@ class Becca {
|
|||||||
this.dirtyNoteSetCache();
|
this.dirtyNoteSetCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Note|null} */
|
||||||
getNote(noteId) {
|
getNote(noteId) {
|
||||||
return this.notes[noteId];
|
return this.notes[noteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Note[]} */
|
||||||
getNotes(noteIds, ignoreMissing = false) {
|
getNotes(noteIds, ignoreMissing = false) {
|
||||||
const filteredNotes = [];
|
const filteredNotes = [];
|
||||||
|
|
||||||
@ -88,29 +93,44 @@ class Becca {
|
|||||||
return filteredNotes;
|
return filteredNotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Branch|null} */
|
||||||
getBranch(branchId) {
|
getBranch(branchId) {
|
||||||
return this.branches[branchId];
|
return this.branches[branchId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Attribute|null} */
|
||||||
getAttribute(attributeId) {
|
getAttribute(attributeId) {
|
||||||
return this.attributes[attributeId];
|
return this.attributes[attributeId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Branch|null} */
|
||||||
getBranchFromChildAndParent(childNoteId, parentNoteId) {
|
getBranchFromChildAndParent(childNoteId, parentNoteId) {
|
||||||
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
|
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {NoteRevision|null} */
|
||||||
getNoteRevision(noteRevisionId) {
|
getNoteRevision(noteRevisionId) {
|
||||||
const row = sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
|
const row = sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
|
||||||
|
|
||||||
const NoteRevision = require("./entities/note_revision.js"); // avoiding circular dependency problems
|
const NoteRevision = require("./entities/note_revision"); // avoiding circular dependency problems
|
||||||
return row ? new NoteRevision(row) : null;
|
return row ? new NoteRevision(row) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Option|null} */
|
||||||
getOption(name) {
|
getOption(name) {
|
||||||
return this.options[name];
|
return this.options[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {EtapiToken[]} */
|
||||||
|
getEtapiTokens() {
|
||||||
|
return Object.values(this.etapiTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {EtapiToken|null} */
|
||||||
|
getEtapiToken(etapiTokenId) {
|
||||||
|
return this.etapiTokens[etapiTokenId];
|
||||||
|
}
|
||||||
|
|
||||||
getEntity(entityName, entityId) {
|
getEntity(entityName, entityId) {
|
||||||
if (!entityName || !entityId) {
|
if (!entityName || !entityId) {
|
||||||
return null;
|
return null;
|
||||||
@ -130,17 +150,19 @@ class Becca {
|
|||||||
return this[camelCaseEntityName][entityId];
|
return this[camelCaseEntityName][entityId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {RecentNote[]} */
|
||||||
getRecentNotesFromQuery(query, params = []) {
|
getRecentNotesFromQuery(query, params = []) {
|
||||||
const rows = sql.getRows(query, params);
|
const rows = sql.getRows(query, params);
|
||||||
|
|
||||||
const RecentNote = require("./entities/recent_note.js"); // avoiding circular dependency problems
|
const RecentNote = require("./entities/recent_note"); // avoiding circular dependency problems
|
||||||
return rows.map(row => new RecentNote(row));
|
return rows.map(row => new RecentNote(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {NoteRevision[]} */
|
||||||
getNoteRevisionsFromQuery(query, params = []) {
|
getNoteRevisionsFromQuery(query, params = []) {
|
||||||
const rows = sql.getRows(query, params);
|
const rows = sql.getRows(query, params);
|
||||||
|
|
||||||
const NoteRevision = require("./entities/note_revision.js"); // avoiding circular dependency problems
|
const NoteRevision = require("./entities/note_revision"); // avoiding circular dependency problems
|
||||||
return rows.map(row => new NoteRevision(row));
|
return rows.map(row => new NoteRevision(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ const Note = require('./entities/note');
|
|||||||
const Branch = require('./entities/branch');
|
const Branch = require('./entities/branch');
|
||||||
const Attribute = require('./entities/attribute');
|
const Attribute = require('./entities/attribute');
|
||||||
const Option = require('./entities/option');
|
const Option = require('./entities/option');
|
||||||
|
const EtapiToken = require("./entities/etapi_token");
|
||||||
const cls = require("../services/cls");
|
const cls = require("../services/cls");
|
||||||
const entityConstructor = require("../becca/entity_constructor");
|
const entityConstructor = require("../becca/entity_constructor");
|
||||||
|
|
||||||
@ -45,6 +46,10 @@ function load() {
|
|||||||
new Option(row);
|
new Option(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const row of sql.getRows(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
|
||||||
|
new EtapiToken(row);
|
||||||
|
}
|
||||||
|
|
||||||
for (const noteId in becca.notes) {
|
for (const noteId in becca.notes) {
|
||||||
becca.notes[noteId].sortParents();
|
becca.notes[noteId].sortParents();
|
||||||
}
|
}
|
||||||
@ -75,7 +80,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["notes", "branches", "attributes"].includes(entityName)) {
|
if (["notes", "branches", "attributes", "etapi_tokens"].includes(entityName)) {
|
||||||
const EntityClass = entityConstructor.getEntityFromEntityName(entityName);
|
const EntityClass = entityConstructor.getEntityFromEntityName(entityName);
|
||||||
const primaryKeyName = EntityClass.primaryKeyName;
|
const primaryKeyName = EntityClass.primaryKeyName;
|
||||||
|
|
||||||
@ -112,6 +117,8 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENT
|
|||||||
branchDeleted(entityId);
|
branchDeleted(entityId);
|
||||||
} else if (entityName === 'attributes') {
|
} else if (entityName === 'attributes') {
|
||||||
attributeDeleted(entityId);
|
attributeDeleted(entityId);
|
||||||
|
} else if (entityName === 'etapi_tokens') {
|
||||||
|
etapiTokenDeleted(entityId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -220,6 +227,10 @@ function noteReorderingUpdated(branchIdList) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function etapiTokenDeleted(etapiTokenId) {
|
||||||
|
delete becca.etapiTokens[etapiTokenId];
|
||||||
|
}
|
||||||
|
|
||||||
eventService.subscribeBeccaLoader(eventService.ENTER_PROTECTED_SESSION, () => {
|
eventService.subscribeBeccaLoader(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||||
try {
|
try {
|
||||||
becca.decryptProtectedNotes();
|
becca.decryptProtectedNotes();
|
||||||
|
@ -40,7 +40,7 @@ class AbstractEntity {
|
|||||||
|
|
||||||
get becca() {
|
get becca() {
|
||||||
if (!becca) {
|
if (!becca) {
|
||||||
becca = require('../becca.js');
|
becca = require('../becca');
|
||||||
}
|
}
|
||||||
|
|
||||||
return becca;
|
return becca;
|
||||||
@ -116,6 +116,19 @@ class AbstractEntity {
|
|||||||
|
|
||||||
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
|
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsDeletedSimple() {
|
||||||
|
const entityId = this[this.constructor.primaryKeyName];
|
||||||
|
const entityName = this.constructor.entityName;
|
||||||
|
|
||||||
|
sql.execute(`UPDATE ${entityName} SET isDeleted = 1, utcDateModified = ?
|
||||||
|
WHERE ${this.constructor.primaryKeyName} = ?`,
|
||||||
|
[dateUtils.utcNowDateTime(), entityId]);
|
||||||
|
|
||||||
|
this.addEntityChange(true);
|
||||||
|
|
||||||
|
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AbstractEntity;
|
module.exports = AbstractEntity;
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const dateUtils = require('../../services/date_utils.js');
|
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ApiToken is an entity representing token used to authenticate against Trilium API from client applications.
|
|
||||||
* Used by:
|
|
||||||
* - Trilium Sender
|
|
||||||
* - ETAPI clients
|
|
||||||
*/
|
|
||||||
class ApiToken extends AbstractEntity {
|
|
||||||
static get entityName() { return "api_tokens"; }
|
|
||||||
static get primaryKeyName() { return "apiTokenId"; }
|
|
||||||
static get hashedProperties() { return ["apiTokenId", "name", "token", "utcDateCreated"]; }
|
|
||||||
|
|
||||||
constructor(row) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
this.apiTokenId = row.apiTokenId;
|
|
||||||
/** @type {string} */
|
|
||||||
this.name = row.name;
|
|
||||||
/** @type {string} */
|
|
||||||
this.token = row.token;
|
|
||||||
/** @type {string} */
|
|
||||||
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
getPojo() {
|
|
||||||
return {
|
|
||||||
apiTokenId: this.apiTokenId,
|
|
||||||
name: this.name,
|
|
||||||
token: this.token,
|
|
||||||
utcDateCreated: this.utcDateCreated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ApiToken;
|
|
@ -1,9 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Note = require('./note.js');
|
const Note = require('./note');
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
const AbstractEntity = require("./abstract_entity");
|
||||||
const sql = require("../../services/sql.js");
|
const sql = require("../../services/sql");
|
||||||
const dateUtils = require("../../services/date_utils.js");
|
const dateUtils = require("../../services/date_utils");
|
||||||
const promotedAttributeDefinitionParser = require("../../services/promoted_attribute_definition_parser");
|
const promotedAttributeDefinitionParser = require("../../services/promoted_attribute_definition_parser");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Note = require('./note.js');
|
const Note = require('./note');
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
const AbstractEntity = require("./abstract_entity");
|
||||||
const sql = require("../../services/sql.js");
|
const sql = require("../../services/sql");
|
||||||
const dateUtils = require("../../services/date_utils.js");
|
const dateUtils = require("../../services/date_utils");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
|
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
|
||||||
|
72
src/becca/entities/etapi_token.js
Normal file
72
src/becca/entities/etapi_token.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const dateUtils = require('../../services/date_utils');
|
||||||
|
const AbstractEntity = require("./abstract_entity");
|
||||||
|
const sql = require("../../services/sql.js");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications.
|
||||||
|
* Used by:
|
||||||
|
* - Trilium Sender
|
||||||
|
* - ETAPI clients
|
||||||
|
*/
|
||||||
|
class EtapiToken extends AbstractEntity {
|
||||||
|
static get entityName() { return "etapi_tokens"; }
|
||||||
|
static get primaryKeyName() { return "etapiTokenId"; }
|
||||||
|
static get hashedProperties() { return ["etapiTokenId", "name", "tokenHash", "utcDateCreated", "utcDateModified", "isDeleted"]; }
|
||||||
|
|
||||||
|
constructor(row) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateFromRow(row);
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromRow(row) {
|
||||||
|
/** @type {string} */
|
||||||
|
this.etapiTokenId = row.etapiTokenId;
|
||||||
|
/** @type {string} */
|
||||||
|
this.name = row.name;
|
||||||
|
/** @type {string} */
|
||||||
|
this.tokenHash = row.tokenHash;
|
||||||
|
/** @type {string} */
|
||||||
|
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
|
||||||
|
/** @type {string} */
|
||||||
|
this.utcDateModified = row.utcDateModified || this.utcDateCreated;
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.isDeleted = !!row.isDeleted;
|
||||||
|
|
||||||
|
this.becca.etapiTokens[this.etapiTokenId] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.etapiTokenId) {
|
||||||
|
this.becca.etapiTokens[this.etapiTokenId] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPojo() {
|
||||||
|
return {
|
||||||
|
etapiTokenId: this.etapiTokenId,
|
||||||
|
name: this.name,
|
||||||
|
tokenHash: this.tokenHash,
|
||||||
|
utcDateCreated: this.utcDateCreated,
|
||||||
|
utcDateModified: this.utcDateModified,
|
||||||
|
isDeleted: this.isDeleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeSaving() {
|
||||||
|
this.utcDateModified = dateUtils.utcNowDateTime();
|
||||||
|
|
||||||
|
super.beforeSaving();
|
||||||
|
|
||||||
|
this.becca.etapiTokens[this.etapiTokenId] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = EtapiToken;
|
@ -6,8 +6,8 @@ const sql = require('../../services/sql');
|
|||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const dateUtils = require('../../services/date_utils');
|
const dateUtils = require('../../services/date_utils');
|
||||||
const entityChangesService = require('../../services/entity_changes');
|
const entityChangesService = require('../../services/entity_changes');
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
const AbstractEntity = require("./abstract_entity");
|
||||||
const NoteRevision = require("./note_revision.js");
|
const NoteRevision = require("./note_revision");
|
||||||
|
|
||||||
const LABEL = 'label';
|
const LABEL = 'label';
|
||||||
const RELATION = 'relation';
|
const RELATION = 'relation';
|
||||||
@ -984,7 +984,7 @@ class Note extends AbstractEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const Attribute = require("./attribute.js");
|
const Attribute = require("./attribute");
|
||||||
|
|
||||||
new Attribute({
|
new Attribute({
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
@ -1016,7 +1016,7 @@ class Note extends AbstractEntity {
|
|||||||
* @return {Attribute}
|
* @return {Attribute}
|
||||||
*/
|
*/
|
||||||
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
|
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
|
||||||
const Attribute = require("./attribute.js");
|
const Attribute = require("./attribute");
|
||||||
|
|
||||||
return new Attribute({
|
return new Attribute({
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
|
@ -4,9 +4,9 @@ const protectedSessionService = require('../../services/protected_session');
|
|||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const dateUtils = require('../../services/date_utils');
|
const dateUtils = require('../../services/date_utils');
|
||||||
const becca = require('../becca.js');
|
const becca = require('../becca');
|
||||||
const entityChangesService = require('../../services/entity_changes');
|
const entityChangesService = require('../../services/entity_changes');
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
const AbstractEntity = require("./abstract_entity");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NoteRevision represents snapshot of note's title and content at some point in the past.
|
* NoteRevision represents snapshot of note's title and content at some point in the past.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const dateUtils = require('../../services/date_utils.js');
|
const dateUtils = require('../../services/date_utils');
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
const AbstractEntity = require("./abstract_entity");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option represents name-value pair, either directly configurable by the user or some system property.
|
* Option represents name-value pair, either directly configurable by the user or some system property.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const dateUtils = require('../../services/date_utils.js');
|
const dateUtils = require('../../services/date_utils');
|
||||||
const AbstractEntity = require("./abstract_entity.js");
|
const AbstractEntity = require("./abstract_entity");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RecentNote represents recently visited note.
|
* RecentNote represents recently visited note.
|
||||||
|
@ -3,7 +3,7 @@ const NoteRevision = require('./entities/note_revision');
|
|||||||
const Branch = require('./entities/branch');
|
const Branch = require('./entities/branch');
|
||||||
const Attribute = require('./entities/attribute');
|
const Attribute = require('./entities/attribute');
|
||||||
const RecentNote = require('./entities/recent_note');
|
const RecentNote = require('./entities/recent_note');
|
||||||
const ApiToken = require('./entities/api_token');
|
const EtapiToken = require('./entities/etapi_token');
|
||||||
const Option = require('./entities/option');
|
const Option = require('./entities/option');
|
||||||
|
|
||||||
const ENTITY_NAME_TO_ENTITY = {
|
const ENTITY_NAME_TO_ENTITY = {
|
||||||
@ -14,7 +14,7 @@ const ENTITY_NAME_TO_ENTITY = {
|
|||||||
"note_revisions": NoteRevision,
|
"note_revisions": NoteRevision,
|
||||||
"note_revision_contents": NoteRevision,
|
"note_revision_contents": NoteRevision,
|
||||||
"recent_notes": RecentNote,
|
"recent_notes": RecentNote,
|
||||||
"api_tokens": ApiToken,
|
"etapi_tokens": EtapiToken,
|
||||||
"options": Option
|
"options": Option
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const becca = require('./becca.js');
|
const becca = require('./becca');
|
||||||
const log = require('../services/log');
|
const log = require('../services/log');
|
||||||
const beccaService = require('./becca_service.js');
|
const beccaService = require('./becca_service.js');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
const becca = require("../becca/becca");
|
const becca = require("../becca/becca");
|
||||||
const ru = require("./route_utils");
|
const eu = require("./etapi_utils");
|
||||||
const mappers = require("./mappers");
|
const mappers = require("./mappers");
|
||||||
const attributeService = require("../services/attributes");
|
const attributeService = require("../services/attributes");
|
||||||
const validators = require("./validators.js");
|
const validators = require("./validators");
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
ru.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => {
|
||||||
const attribute = ru.getAndCheckAttribute(req.params.attributeId);
|
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
|
||||||
|
|
||||||
res.json(mappers.mapAttributeToPojo(attribute));
|
res.json(mappers.mapAttributeToPojo(attribute));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'post' ,'/etapi/attributes', (req, res, next) => {
|
eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => {
|
||||||
const params = req.body;
|
const params = req.body;
|
||||||
|
|
||||||
ru.getAndCheckNote(params.noteId);
|
eu.getAndCheckNote(params.noteId);
|
||||||
|
|
||||||
if (params.type === 'relation') {
|
if (params.type === 'relation') {
|
||||||
ru.getAndCheckNote(params.value);
|
eu.getAndCheckNote(params.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.type !== 'relation' && params.type !== 'label') {
|
if (params.type !== 'relation' && params.type !== 'label') {
|
||||||
throw new ru.EtapiError(400, ru.GENERIC_CODE, `Only "relation" and "label" are supported attribute types, "${params.type}" given.`);
|
throw new eu.EtapiError(400, eu.GENERIC_CODE, `Only "relation" and "label" are supported attribute types, "${params.type}" given.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -30,7 +30,7 @@ function register(router) {
|
|||||||
res.json(mappers.mapAttributeToPojo(attr));
|
res.json(mappers.mapAttributeToPojo(attr));
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
throw new ru.EtapiError(400, ru.GENERIC_CODE, e.message);
|
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,15 +38,15 @@ function register(router) {
|
|||||||
'value': validators.isString
|
'value': validators.isString
|
||||||
};
|
};
|
||||||
|
|
||||||
ru.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
||||||
const attribute = ru.getAndCheckAttribute(req.params.attributeId);
|
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
|
||||||
|
|
||||||
ru.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
||||||
|
|
||||||
res.json(mappers.mapAttributeToPojo(attribute));
|
res.json(mappers.mapAttributeToPojo(attribute));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
||||||
const attribute = becca.getAttribute(req.params.attributeId);
|
const attribute = becca.getAttribute(req.params.attributeId);
|
||||||
|
|
||||||
if (!attribute || attribute.isDeleted) {
|
if (!attribute || attribute.isDeleted) {
|
||||||
|
43
src/etapi/auth.js
Normal file
43
src/etapi/auth.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const becca = require("../becca/becca");
|
||||||
|
const eu = require("./etapi_utils");
|
||||||
|
const passwordEncryptionService = require("../services/password_encryption.js");
|
||||||
|
const etapiTokenService = require("../services/etapi_tokens.js");
|
||||||
|
|
||||||
|
function register(router) {
|
||||||
|
eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', (req, res, next) => {
|
||||||
|
const {password, tokenName} = req.body;
|
||||||
|
|
||||||
|
if (!passwordEncryptionService.verifyPassword(password)) {
|
||||||
|
throw new eu.EtapiError(401, "WRONG_PASSWORD", "Wrong password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const {authToken} = etapiTokenService.createToken(tokenName || "ETAPI login");
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
authToken
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
eu.route(router, 'post', '/etapi/auth/logout', (req, res, next) => {
|
||||||
|
const parsed = etapiTokenService.parseAuthToken(req.headers.authorization);
|
||||||
|
|
||||||
|
if (!parsed || !parsed.etapiTokenId) {
|
||||||
|
throw new eu.EtapiError(400, eu.GENERIC_CODE, "Cannot logout this token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const etapiToken = becca.getEtapiToken(parsed.etapiTokenId);
|
||||||
|
|
||||||
|
if (!etapiToken) {
|
||||||
|
// shouldn't happen since this already passed auth validation
|
||||||
|
throw new Error(`Cannot find the token ${parsed.etapiTokenId}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
etapiToken.markAsDeletedSimple();
|
||||||
|
|
||||||
|
res.sendStatus(204);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
register
|
||||||
|
}
|
@ -1,24 +1,24 @@
|
|||||||
const becca = require("../becca/becca.js");
|
const becca = require("../becca/becca");
|
||||||
const ru = require("./route_utils");
|
const eu = require("./etapi_utils");
|
||||||
const mappers = require("./mappers");
|
const mappers = require("./mappers");
|
||||||
const Branch = require("../becca/entities/branch");
|
const Branch = require("../becca/entities/branch");
|
||||||
const noteService = require("../services/notes");
|
const noteService = require("../services/notes");
|
||||||
const TaskContext = require("../services/task_context");
|
const TaskContext = require("../services/task_context");
|
||||||
const entityChangesService = require("../services/entity_changes");
|
const entityChangesService = require("../services/entity_changes");
|
||||||
const validators = require("./validators.js");
|
const validators = require("./validators");
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
ru.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => {
|
||||||
const branch = ru.getAndCheckBranch(req.params.branchId);
|
const branch = eu.getAndCheckBranch(req.params.branchId);
|
||||||
|
|
||||||
res.json(mappers.mapBranchToPojo(branch));
|
res.json(mappers.mapBranchToPojo(branch));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'post' ,'/etapi/branches', (req, res, next) => {
|
eu.route(router, 'post' ,'/etapi/branches', (req, res, next) => {
|
||||||
const params = req.body;
|
const params = req.body;
|
||||||
|
|
||||||
ru.getAndCheckNote(params.noteId);
|
eu.getAndCheckNote(params.noteId);
|
||||||
ru.getAndCheckNote(params.parentNoteId);
|
eu.getAndCheckNote(params.parentNoteId);
|
||||||
|
|
||||||
const existing = becca.getBranchFromChildAndParent(params.noteId, params.parentNoteId);
|
const existing = becca.getBranchFromChildAndParent(params.noteId, params.parentNoteId);
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ function register(router) {
|
|||||||
res.json(mappers.mapBranchToPojo(branch));
|
res.json(mappers.mapBranchToPojo(branch));
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
throw new ru.EtapiError(400, ru.GENERIC_CODE, e.message);
|
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,15 +46,15 @@ function register(router) {
|
|||||||
'isExpanded': validators.isBoolean
|
'isExpanded': validators.isBoolean
|
||||||
};
|
};
|
||||||
|
|
||||||
ru.route(router, 'patch' ,'/etapi/branches/:branchId', (req, res, next) => {
|
eu.route(router, 'patch' ,'/etapi/branches/:branchId', (req, res, next) => {
|
||||||
const branch = ru.getAndCheckBranch(req.params.branchId);
|
const branch = eu.getAndCheckBranch(req.params.branchId);
|
||||||
|
|
||||||
ru.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
eu.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
||||||
|
|
||||||
res.json(mappers.mapBranchToPojo(branch));
|
res.json(mappers.mapBranchToPojo(branch));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => {
|
eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => {
|
||||||
const branch = becca.getBranch(req.params.branchId);
|
const branch = becca.getBranch(req.params.branchId);
|
||||||
|
|
||||||
if (!branch || branch.isDeleted) {
|
if (!branch || branch.isDeleted) {
|
||||||
@ -66,8 +66,8 @@ function register(router) {
|
|||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'post' ,'/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
|
eu.route(router, 'post' ,'/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
|
||||||
ru.getAndCheckNote(req.params.parentNoteId);
|
eu.getAndCheckNote(req.params.parentNoteId);
|
||||||
|
|
||||||
entityChangesService.addNoteReorderingEntityChange(req.params.parentNoteId, "etapi");
|
entityChangesService.addNoteReorderingEntityChange(req.params.parentNoteId, "etapi");
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ info:
|
|||||||
servers:
|
servers:
|
||||||
- url: http://localhost:37740/etapi
|
- url: http://localhost:37740/etapi
|
||||||
- url: http://localhost:8080/etapi
|
- url: http://localhost:8080/etapi
|
||||||
|
security:
|
||||||
|
- EtapiTokenAuth: []
|
||||||
paths:
|
paths:
|
||||||
/create-note:
|
/create-note:
|
||||||
post:
|
post:
|
||||||
@ -43,16 +45,142 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
/notes/{noteId}:
|
/notes:
|
||||||
get:
|
get:
|
||||||
description: Returns a note identified by its ID
|
description: Search notes
|
||||||
operationId: getNoteById
|
operationId: searchNotes
|
||||||
|
parameters:
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: search query string as described in https://github.com/zadam/trilium/wiki/Search
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
examples:
|
||||||
|
fulltext:
|
||||||
|
summary: Fulltext search for keywords (not exact match)
|
||||||
|
value: 'towers tolkien'
|
||||||
|
fulltextExactMatch:
|
||||||
|
summary: Fulltext search for exact match (notice the double quotes)
|
||||||
|
value: '"Two Towers"'
|
||||||
|
fulltextWithLabel:
|
||||||
|
summary: Fulltext search for keyword AND matching label
|
||||||
|
value: 'towers #book'
|
||||||
|
- name: fastSearch
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: enable fast search (fulltext doesn't look into content)
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: includeArchivedNotes
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: search by default ignores archived notes. Set to 'true' to includes archived notes into search results.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: ancestorNoteId
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: search only in a subtree identified by the subtree noteId. By default whole tree is searched.
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/EntityId'
|
||||||
|
- name: ancestorDepth
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: define how deep in the tree should the notes be searched
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
examples:
|
||||||
|
directChildren:
|
||||||
|
summary: depth of exactly 1 (direct children) to the ancestor (root if not set)
|
||||||
|
value: eq1
|
||||||
|
grandGrandChildren:
|
||||||
|
summary: depth of exactly 3 to the ancestor (root if not set)
|
||||||
|
value: eq3
|
||||||
|
lessThan4:
|
||||||
|
summary: depth less than 4 (so 1, 2, 3) to the ancestor (root if not set)
|
||||||
|
value: lt4
|
||||||
|
greaterThan2:
|
||||||
|
summary: depth greater than 2 (so 3, 4, 5, 6...) to the ancestor (root if not set)
|
||||||
|
value: gt4
|
||||||
|
- name: orderBy
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: name of the property/label to order search results by
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
- title
|
||||||
|
- '#publicationDate'
|
||||||
|
- isProtected
|
||||||
|
- isArchived
|
||||||
|
- dateCreated
|
||||||
|
- dateModified
|
||||||
|
- utcDateCreated
|
||||||
|
- utcDateModified
|
||||||
|
- parentCount
|
||||||
|
- childrenCount
|
||||||
|
- attributeCount
|
||||||
|
- labelCount
|
||||||
|
- ownedLabelCount
|
||||||
|
- relationCount
|
||||||
|
- ownedRelationCount
|
||||||
|
- relationCountIncludingLinks
|
||||||
|
- ownedRelationCountIncludingLinks
|
||||||
|
- targetRelationCount
|
||||||
|
- targetRelationCountIncludingLinks
|
||||||
|
- contentSize
|
||||||
|
- noteSize
|
||||||
|
- revisionCount
|
||||||
|
- name: orderDirection
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: order direction, ascending or descending
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: asc
|
||||||
|
enum:
|
||||||
|
- asc
|
||||||
|
- desc
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: limit the number of results you want to receive
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 10
|
||||||
|
- name: debug
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: set to true to get debug information in the response (search query parsing)
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: search response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SearchResponse'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/notes/{noteId}:
|
||||||
parameters:
|
parameters:
|
||||||
- name: noteId
|
- name: noteId
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/EntityId'
|
$ref: '#/components/schemas/EntityId'
|
||||||
|
get:
|
||||||
|
description: Returns a note identified by its ID
|
||||||
|
operationId: getNoteById
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: note response
|
description: note response
|
||||||
@ -69,12 +197,6 @@ paths:
|
|||||||
patch:
|
patch:
|
||||||
description: patch a note identified by the noteId with changes in the body
|
description: patch a note identified by the noteId with changes in the body
|
||||||
operationId: patchNoteById
|
operationId: patchNoteById
|
||||||
parameters:
|
|
||||||
- name: noteId
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@ -97,13 +219,6 @@ paths:
|
|||||||
delete:
|
delete:
|
||||||
description: deletes a single note based on the noteId supplied
|
description: deletes a single note based on the noteId supplied
|
||||||
operationId: deleteNoteById
|
operationId: deleteNoteById
|
||||||
parameters:
|
|
||||||
- name: noteId
|
|
||||||
in: path
|
|
||||||
description: noteId of note to delete
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: note deleted
|
description: note deleted
|
||||||
@ -114,15 +229,15 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
/branches/{branchId}:
|
/branches/{branchId}:
|
||||||
get:
|
|
||||||
description: Returns a branch identified by its ID
|
|
||||||
operationId: getBranchById
|
|
||||||
parameters:
|
parameters:
|
||||||
- name: branchId
|
- name: branchId
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/EntityId'
|
$ref: '#/components/schemas/EntityId'
|
||||||
|
get:
|
||||||
|
description: Returns a branch identified by its ID
|
||||||
|
operationId: getBranchById
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: branch response
|
description: branch response
|
||||||
@ -161,12 +276,6 @@ paths:
|
|||||||
patch:
|
patch:
|
||||||
description: patch a branch identified by the branchId with changes in the body
|
description: patch a branch identified by the branchId with changes in the body
|
||||||
operationId: patchBranchById
|
operationId: patchBranchById
|
||||||
parameters:
|
|
||||||
- name: branchId
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@ -187,15 +296,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
delete:
|
delete:
|
||||||
description: deletes a branch based on the branchId supplied. If this is the last branch of the (child) note, then the note is deleted as well.
|
description: >
|
||||||
|
deletes a branch based on the branchId supplied. If this is the last branch of the (child) note,
|
||||||
|
then the note is deleted as well.
|
||||||
operationId: deleteBranchById
|
operationId: deleteBranchById
|
||||||
parameters:
|
|
||||||
- name: branchId
|
|
||||||
in: path
|
|
||||||
description: branchId of note to delete
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: branch deleted
|
description: branch deleted
|
||||||
@ -206,15 +310,15 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
/attributes/{attributeId}:
|
/attributes/{attributeId}:
|
||||||
get:
|
|
||||||
description: Returns an attribute identified by its ID
|
|
||||||
operationId: getAttributeById
|
|
||||||
parameters:
|
parameters:
|
||||||
- name: attributeId
|
- name: attributeId
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/EntityId'
|
$ref: '#/components/schemas/EntityId'
|
||||||
|
get:
|
||||||
|
description: Returns an attribute identified by its ID
|
||||||
|
operationId: getAttributeById
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: attribute response
|
description: attribute response
|
||||||
@ -253,12 +357,6 @@ paths:
|
|||||||
patch:
|
patch:
|
||||||
description: patch a attribute identified by the attributeId with changes in the body
|
description: patch a attribute identified by the attributeId with changes in the body
|
||||||
operationId: patchAttributeById
|
operationId: patchAttributeById
|
||||||
parameters:
|
|
||||||
- name: attributeId
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@ -281,13 +379,6 @@ paths:
|
|||||||
delete:
|
delete:
|
||||||
description: deletes a attribute based on the attributeId supplied.
|
description: deletes a attribute based on the attributeId supplied.
|
||||||
operationId: deleteAttributeById
|
operationId: deleteAttributeById
|
||||||
parameters:
|
|
||||||
- name: attributeId
|
|
||||||
in: path
|
|
||||||
description: attributeId of attribute to delete
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: attribute deleted
|
description: attribute deleted
|
||||||
@ -298,8 +389,17 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
/refresh-note-ordering/{parentNoteId}:
|
/refresh-note-ordering/{parentNoteId}:
|
||||||
|
parameters:
|
||||||
|
- name: parentNoteId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/EntityId'
|
||||||
post:
|
post:
|
||||||
description: notePositions in branches are not automatically pushed to connected clients and need a specific instruction. If you want your changes to be in effect immediately, call this service after setting branches' notePosition. Note that you need to supply "parentNoteId" of branch(es) with changed positions.
|
description: >
|
||||||
|
notePositions in branches are not automatically pushed to connected clients and need a specific instruction.
|
||||||
|
If you want your changes to be in effect immediately, call this service after setting branches' notePosition.
|
||||||
|
Note that you need to supply "parentNoteId" of branch(es) with changed positions.
|
||||||
operationId: postRefreshNoteOrdering
|
operationId: postRefreshNoteOrdering
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
@ -310,29 +410,230 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
|
/inbox/{date}:
|
||||||
|
get:
|
||||||
|
description: >
|
||||||
|
returns an "inbox" note, into which note can be created. Date will be used depending on whether the inbox
|
||||||
|
is a fixed note (identified with #inbox label) or a day note in a journal.
|
||||||
|
operationId: getInboxNote
|
||||||
|
parameters:
|
||||||
|
- name: date
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
example: 2022-02-22
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: inbox note
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/calendar/days/{date}:
|
||||||
|
get:
|
||||||
|
description: returns a day note for a given date. Gets created if doesn't exist.
|
||||||
|
operationId: getDayNote
|
||||||
|
parameters:
|
||||||
|
- name: date
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
example: 2022-02-22
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: day note
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/calendar/weeks/{date}:
|
||||||
|
get:
|
||||||
|
description: returns a week note for a given date. Gets created if doesn't exist.
|
||||||
|
operationId: getWeekNote
|
||||||
|
parameters:
|
||||||
|
- name: date
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
example: 2022-02-22
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: week note
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/calendar/months/{month}:
|
||||||
|
get:
|
||||||
|
description: returns a week note for a given date. Gets created if doesn't exist.
|
||||||
|
operationId: getMonthNote
|
||||||
|
parameters:
|
||||||
|
- name: month
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '[0-9]{4}-[0-9]{2}'
|
||||||
|
example: 2022-02
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: month note
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/calendar/years/{year}:
|
||||||
|
get:
|
||||||
|
description: returns a week note for a given date. Gets created if doesn't exist.
|
||||||
|
operationId: getYearNote
|
||||||
|
parameters:
|
||||||
|
- name: year
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '[0-9]{4}-[0-9]{2}'
|
||||||
|
example: 2022-02
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: year note
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/auth/login:
|
||||||
|
post:
|
||||||
|
description: get an ETAPI token based on password for further use with ETAPI
|
||||||
|
operationId: login
|
||||||
|
security: [] # no token based auth for login endpoint
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: user's password used to e.g. login to Trilium server and/or protect notes
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: auth token
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
authToken:
|
||||||
|
type: string
|
||||||
|
example: Bc4bFn0Ffiok_4NpbVCDnFz7B2WU+pdhW8B5Ne3DiR5wXrEyqdjgRIsk=
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/auth/logout:
|
||||||
|
post:
|
||||||
|
description: logout (delete/deactivate) an ETAPI token
|
||||||
|
operationId: logout
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: logout successful
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
components:
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
EtapiTokenAuth:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
schemas:
|
schemas:
|
||||||
CreateNoteDef:
|
CreateNoteDef:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- parentNoteId
|
- parentNoteId
|
||||||
|
- type
|
||||||
- title
|
- title
|
||||||
- content
|
- content
|
||||||
properties:
|
properties:
|
||||||
noteId:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
description: Leave this out unless you want to force a specific noteId
|
|
||||||
branchId:
|
|
||||||
$ref: '#/components/schemas/EntityId'
|
|
||||||
description: Leave this out unless you want to force a specific branchId
|
|
||||||
parentNoteId:
|
parentNoteId:
|
||||||
$ref: '#/components/schemas/EntityId'
|
$ref: '#/components/schemas/EntityId'
|
||||||
description: Note ID of the parent note in the tree
|
description: Note ID of the parent note in the tree
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- text
|
||||||
|
- code
|
||||||
|
- file
|
||||||
|
- image
|
||||||
|
- search
|
||||||
|
- book
|
||||||
|
- relation-map
|
||||||
|
- render
|
||||||
|
mime:
|
||||||
|
type: string
|
||||||
|
description: this needs to be specified only for note types 'code', 'file', 'image'.
|
||||||
|
example: application/json
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
content:
|
content:
|
||||||
type: string
|
type: string
|
||||||
|
notePosition:
|
||||||
|
type: integer
|
||||||
|
description: >
|
||||||
|
Position of the note in the parent. Normal ordering is 10, 20, 30 ...
|
||||||
|
So if you want to create a note on the first position, use e.g. 5, for second position 15, for last e.g. 1000000
|
||||||
|
prefix:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Prefix is branch (placement) specific title prefix for the note.
|
||||||
|
Let's say you have your note placed into two different places in the tree,
|
||||||
|
but you want to change the title a bit in one of the placements. For this you can use prefix.
|
||||||
|
noteId:
|
||||||
|
$ref: '#/components/schemas/EntityId'
|
||||||
|
description: DON'T specify unless you want to force a specific noteId
|
||||||
|
branchId:
|
||||||
|
$ref: '#/components/schemas/EntityId'
|
||||||
|
description: DON'T specify unless you want to force a specific branchId
|
||||||
Note:
|
Note:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -438,6 +739,18 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Attribute'
|
$ref: '#/components/schemas/Attribute'
|
||||||
|
SearchResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- results
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
debugInfo:
|
||||||
|
type: object
|
||||||
|
description: debugging info on parsing the search query enabled with &debug=true parameter
|
||||||
EntityId:
|
EntityId:
|
||||||
type: string
|
type: string
|
||||||
pattern: '[a-zA-Z0-9]{4,12}'
|
pattern: '[a-zA-Z0-9]{4,12}'
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
const cls = require("../services/cls.js");
|
const cls = require("../services/cls");
|
||||||
const sql = require("../services/sql.js");
|
const sql = require("../services/sql");
|
||||||
const log = require("../services/log.js");
|
const log = require("../services/log");
|
||||||
const becca = require("../becca/becca.js");
|
const becca = require("../becca/becca");
|
||||||
|
const etapiTokenService = require("../services/etapi_tokens.js");
|
||||||
|
const config = require("../services/config.js");
|
||||||
const GENERIC_CODE = "GENERIC";
|
const GENERIC_CODE = "GENERIC";
|
||||||
|
|
||||||
|
const noAuthentication = config.General && config.General.noAuthentication === true;
|
||||||
|
|
||||||
class EtapiError extends Error {
|
class EtapiError extends Error {
|
||||||
constructor(statusCode, code, message) {
|
constructor(statusCode, code, message) {
|
||||||
super();
|
super();
|
||||||
@ -26,16 +30,15 @@ function sendError(res, statusCode, code, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkEtapiAuth(req, res, next) {
|
function checkEtapiAuth(req, res, next) {
|
||||||
if (false) {
|
if (noAuthentication || etapiTokenService.isValidAuthHeader(req.headers.authorization)) {
|
||||||
sendError(res, 401, "NOT_AUTHENTICATED", "Not authenticated");
|
next();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
next();
|
sendError(res, 401, "NOT_AUTHENTICATED", "Not authenticated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function route(router, method, path, routeHandler) {
|
function processRequest(req, res, routeHandler, next, method, path) {
|
||||||
router[method](path, checkEtapiAuth, (req, res, next) => {
|
|
||||||
try {
|
try {
|
||||||
cls.namespace.bindEmitter(req);
|
cls.namespace.bindEmitter(req);
|
||||||
cls.namespace.bindEmitter(res);
|
cls.namespace.bindEmitter(res);
|
||||||
@ -48,18 +51,23 @@ function route(router, method, path, routeHandler) {
|
|||||||
|
|
||||||
return sql.transactional(cb);
|
return sql.transactional(cb);
|
||||||
});
|
});
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
log.error(`${method} ${path} threw exception ${e.message} with stacktrace: ${e.stack}`);
|
log.error(`${method} ${path} threw exception ${e.message} with stacktrace: ${e.stack}`);
|
||||||
|
|
||||||
if (e instanceof EtapiError) {
|
if (e instanceof EtapiError) {
|
||||||
sendError(res, e.statusCode, e.code, e.message);
|
sendError(res, e.statusCode, e.code, e.message);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
sendError(res, 500, GENERIC_CODE, e.message);
|
sendError(res, 500, GENERIC_CODE, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function route(router, method, path, routeHandler) {
|
||||||
|
router[method](path, checkEtapiAuth, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
function NOT_AUTHENTICATED_ROUTE(router, method, path, routeHandler) {
|
||||||
|
router[method](path, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAndCheckNote(noteId) {
|
function getAndCheckNote(noteId) {
|
||||||
@ -121,12 +129,11 @@ function validateAndPatch(entity, props, allowedProperties) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
EtapiError,
|
EtapiError,
|
||||||
sendError,
|
sendError,
|
||||||
checkEtapiAuth,
|
|
||||||
route,
|
route,
|
||||||
|
NOT_AUTHENTICATED_ROUTE,
|
||||||
GENERIC_CODE,
|
GENERIC_CODE,
|
||||||
validateAndPatch,
|
validateAndPatch,
|
||||||
getAndCheckNote,
|
getAndCheckNote,
|
||||||
getAndCheckBranch,
|
getAndCheckBranch,
|
||||||
getAndCheckAttribute,
|
getAndCheckAttribute
|
||||||
getNotAllowedPatchPropertyError: (propertyName, allowedProperties) => new EtapiError(400, "PROPERTY_NOT_ALLOWED_FOR_PATCH", `Property '${propertyName}' is not allowed to be patched, allowed properties are ${allowedProperties}.`),
|
|
||||||
}
|
}
|
@ -1,36 +1,48 @@
|
|||||||
const becca = require("../becca/becca");
|
const becca = require("../becca/becca");
|
||||||
const utils = require("../services/utils");
|
const utils = require("../services/utils");
|
||||||
const ru = require("./route_utils");
|
const eu = require("./etapi_utils");
|
||||||
const mappers = require("./mappers");
|
const mappers = require("./mappers");
|
||||||
const noteService = require("../services/notes");
|
const noteService = require("../services/notes");
|
||||||
const TaskContext = require("../services/task_context");
|
const TaskContext = require("../services/task_context");
|
||||||
const validators = require("./validators");
|
const validators = require("./validators");
|
||||||
const searchService = require("../services/search/services/search");
|
const searchService = require("../services/search/services/search");
|
||||||
|
const SearchContext = require("../services/search/search_context");
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
ru.route(router, 'get', '/etapi/notes', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
|
||||||
const {search} = req.query;
|
const {search} = req.query;
|
||||||
|
|
||||||
if (!search?.trim()) {
|
if (!search?.trim()) {
|
||||||
throw new ru.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory");
|
throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory");
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = parseSearchParams(req);
|
const searchParams = parseSearchParams(req);
|
||||||
|
const searchContext = new SearchContext(searchParams);
|
||||||
|
|
||||||
const foundNotes = searchService.searchNotes(search, searchParams);
|
const searchResults = searchService.findResultsWithQuery(search, searchContext);
|
||||||
|
const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]);
|
||||||
|
|
||||||
res.json(foundNotes.map(note => mappers.mapNoteToPojo(note)));
|
const resp = {
|
||||||
|
results: foundNotes.map(note => mappers.mapNoteToPojo(note))
|
||||||
|
};
|
||||||
|
|
||||||
|
if (searchContext.debugInfo) {
|
||||||
|
resp.debugInfo = searchContext.debugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(resp);
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'get', '/etapi/notes/:noteId', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/notes/:noteId', (req, res, next) => {
|
||||||
const note = ru.getAndCheckNote(req.params.noteId);
|
const note = eu.getAndCheckNote(req.params.noteId);
|
||||||
|
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'post' ,'/etapi/create-note', (req, res, next) => {
|
eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => {
|
||||||
const params = req.body;
|
const params = req.body;
|
||||||
|
|
||||||
ru.getAndCheckNote(params.parentNoteId);
|
eu.getAndCheckNote(params.parentNoteId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = noteService.createNewNote(params);
|
const resp = noteService.createNewNote(params);
|
||||||
@ -41,7 +53,7 @@ function register(router) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
return ru.sendError(res, 400, ru.GENERIC_CODE, e.message);
|
return eu.sendError(res, 400, eu.GENERIC_CODE, e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,19 +63,19 @@ function register(router) {
|
|||||||
'mime': validators.isString
|
'mime': validators.isString
|
||||||
};
|
};
|
||||||
|
|
||||||
ru.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {
|
eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {
|
||||||
const note = ru.getAndCheckNote(req.params.noteId)
|
const note = eu.getAndCheckNote(req.params.noteId)
|
||||||
|
|
||||||
if (note.isProtected) {
|
if (note.isProtected) {
|
||||||
throw new ru.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI`);
|
throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI`);
|
||||||
}
|
}
|
||||||
|
|
||||||
ru.validateAndPatch(note, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
eu.validateAndPatch(note, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
||||||
|
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'delete' ,'/etapi/notes/:noteId', (req, res, next) => {
|
eu.route(router, 'delete' ,'/etapi/notes/:noteId', (req, res, next) => {
|
||||||
const {noteId} = req.params;
|
const {noteId} = req.params;
|
||||||
|
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
@ -77,8 +89,8 @@ function register(router) {
|
|||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'get', '/etapi/notes/:noteId/content', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/notes/:noteId/content', (req, res, next) => {
|
||||||
const note = ru.getAndCheckNote(req.params.noteId);
|
const note = eu.getAndCheckNote(req.params.noteId);
|
||||||
|
|
||||||
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
||||||
|
|
||||||
@ -90,8 +102,8 @@ function register(router) {
|
|||||||
res.send(note.getContent());
|
res.send(note.getContent());
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'put', '/etapi/notes/:noteId/content', (req, res, next) => {
|
eu.route(router, 'put', '/etapi/notes/:noteId/content', (req, res, next) => {
|
||||||
const note = ru.getAndCheckNote(req.params.noteId);
|
const note = eu.getAndCheckNote(req.params.noteId);
|
||||||
|
|
||||||
note.setContent(req.body);
|
note.setContent(req.body);
|
||||||
|
|
||||||
@ -130,7 +142,7 @@ function parseBoolean(obj, name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!['true', 'false'].includes(obj[name])) {
|
if (!['true', 'false'].includes(obj[name])) {
|
||||||
throw new ru.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'`);
|
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj[name] === 'true';
|
return obj[name] === 'true';
|
||||||
@ -144,7 +156,7 @@ function parseInteger(obj, name) {
|
|||||||
const integer = parseInt(obj[name]);
|
const integer = parseInt(obj[name]);
|
||||||
|
|
||||||
if (!['asc', 'desc'].includes(obj[name])) {
|
if (!['asc', 'desc'].includes(obj[name])) {
|
||||||
throw new ru.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'`);
|
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return integer;
|
return integer;
|
||||||
@ -158,7 +170,7 @@ function parseOrderDirection(obj, name) {
|
|||||||
const integer = parseInt(obj[name]);
|
const integer = parseInt(obj[name]);
|
||||||
|
|
||||||
if (Number.isNaN(integer)) {
|
if (Number.isNaN(integer)) {
|
||||||
throw new ru.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}`);
|
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return integer;
|
return integer;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
const specialNotesService = require("../services/special_notes");
|
const specialNotesService = require("../services/special_notes");
|
||||||
const dateNotesService = require("../services/date_notes");
|
const dateNotesService = require("../services/date_notes");
|
||||||
const ru = require("./route_utils");
|
const eu = require("./etapi_utils");
|
||||||
const mappers = require("./mappers");
|
const mappers = require("./mappers");
|
||||||
|
|
||||||
const getDateInvalidError = date => new ru.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
|
const getDateInvalidError = date => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
|
||||||
const getMonthInvalidError = month => new ru.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
|
const getMonthInvalidError = month => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
|
||||||
const getYearInvalidError = year => new ru.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
|
const getYearInvalidError = year => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
|
||||||
|
|
||||||
function isValidDate(date) {
|
function isValidDate(date) {
|
||||||
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
|
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
|
||||||
@ -16,7 +16,7 @@ function isValidDate(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
ru.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => {
|
||||||
const {date} = req.params;
|
const {date} = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
@ -27,18 +27,18 @@ function register(router) {
|
|||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'get', '/etapi/date/:date', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/days/:date', (req, res, next) => {
|
||||||
const {date} = req.params;
|
const {date} = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
throw getDateInvalidError(res, date);
|
throw getDateInvalidError(res, date);
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = dateNotesService.getDateNote(date);
|
const note = dateNotesService.getDayNote(date);
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'get', '/etapi/week/:date', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/weeks/:date', (req, res, next) => {
|
||||||
const {date} = req.params;
|
const {date} = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
@ -49,7 +49,7 @@ function register(router) {
|
|||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'get', '/etapi/month/:month', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/months/:month', (req, res, next) => {
|
||||||
const {month} = req.params;
|
const {month} = req.params;
|
||||||
|
|
||||||
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
|
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
|
||||||
@ -60,7 +60,7 @@ function register(router) {
|
|||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
ru.route(router, 'get', '/etapi/year/:year', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/years/:year', (req, res, next) => {
|
||||||
const {year} = req.params;
|
const {year} = req.params;
|
||||||
|
|
||||||
if (!/[0-9]{4}/.test(year)) {
|
if (!/[0-9]{4}/.test(year)) {
|
||||||
|
@ -15,6 +15,7 @@ export async function showDialog(openTab) {
|
|||||||
import('./options/shortcuts.js'),
|
import('./options/shortcuts.js'),
|
||||||
import('./options/code_notes.js'),
|
import('./options/code_notes.js'),
|
||||||
import('./options/password.js'),
|
import('./options/password.js'),
|
||||||
|
import('./options/etapi.js'),
|
||||||
import('./options/backup.js'),
|
import('./options/backup.js'),
|
||||||
import('./options/sync.js'),
|
import('./options/sync.js'),
|
||||||
import('./options/other.js'),
|
import('./options/other.js'),
|
||||||
|
128
src/public/app/dialogs/options/etapi.js
Normal file
128
src/public/app/dialogs/options/etapi.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import server from "../../services/server.js";
|
||||||
|
import utils from "../../services/utils.js";
|
||||||
|
|
||||||
|
const TPL = `
|
||||||
|
<h4>ETAPI</h4>
|
||||||
|
|
||||||
|
<p>ETAPI is a REST API used to access Trilium instance programmatically, without UI. <br/>
|
||||||
|
See more details on <a href="https://github.com/zadam/trilium/wiki/ETAPI">wiki</a> and <a onclick="window.open('etapi/etapi.openapi.yaml')" href="etapi/etapi.openapi.yaml">ETAPI OpenAPI spec</a>.</p>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-sm" id="create-etapi-token">Create new ETAPI token</button>
|
||||||
|
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
<h5>Existing tokens</h5>
|
||||||
|
|
||||||
|
<div id="no-tokens-yet">There are no tokens yet. Click on the button above to create one.</div>
|
||||||
|
|
||||||
|
<div style="overflow: auto; height: 500px;">
|
||||||
|
<table id="tokens-table" class="table table-stripped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Token name</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.token-table-button {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px;
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: large;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-table-button:hover {
|
||||||
|
border: 1px solid var(--main-border-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default class EtapiOptions {
|
||||||
|
constructor() {
|
||||||
|
$("#options-etapi").html(TPL);
|
||||||
|
|
||||||
|
$("#create-etapi-token").on("click", async () => {
|
||||||
|
const promptDialog = await import('../../dialogs/prompt.js');
|
||||||
|
const tokenName = await promptDialog.ask({
|
||||||
|
title: "New ETAPI token",
|
||||||
|
message: "Please enter new token's name",
|
||||||
|
defaultValue: "new token"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenName.trim()) {
|
||||||
|
alert("Token name can't be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {token} = await server.post('etapi-tokens', {tokenName});
|
||||||
|
|
||||||
|
await promptDialog.ask({
|
||||||
|
title: "ETAPI token created",
|
||||||
|
message: 'Copy the created token into clipboard. Trilium stores the token hashed and this is the last time you see it.',
|
||||||
|
defaultValue: token
|
||||||
|
});
|
||||||
|
|
||||||
|
this.refreshTokens();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.refreshTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshTokens() {
|
||||||
|
const $noTokensYet = $("#no-tokens-yet");
|
||||||
|
const $tokensTable = $("#tokens-table");
|
||||||
|
|
||||||
|
const tokens = await server.get('etapi-tokens');
|
||||||
|
|
||||||
|
$noTokensYet.toggle(tokens.length === 0);
|
||||||
|
$tokensTable.toggle(tokens.length > 0);
|
||||||
|
|
||||||
|
const $tokensTableBody = $tokensTable.find("tbody");
|
||||||
|
$tokensTableBody.empty();
|
||||||
|
|
||||||
|
for (const token of tokens) {
|
||||||
|
$tokensTableBody.append(
|
||||||
|
$("<tr>")
|
||||||
|
.append($("<td>").text(token.name))
|
||||||
|
.append($("<td>").text(token.utcDateCreated))
|
||||||
|
.append($("<td>").append(
|
||||||
|
$('<span class="bx bx-pen token-table-button" title="Rename this token"></span>')
|
||||||
|
.on("click", () => this.renameToken(token.etapiTokenId, token.name)),
|
||||||
|
$('<span class="bx bx-trash token-table-button" title="Delete / deactive this token"></span>')
|
||||||
|
.on("click", () => this.deleteToken(token.etapiTokenId, token.name))
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async renameToken(etapiTokenId, oldName) {
|
||||||
|
const promptDialog = await import('../../dialogs/prompt.js');
|
||||||
|
const tokenName = await promptDialog.ask({
|
||||||
|
title: "Rename token",
|
||||||
|
message: "Please enter new token's name",
|
||||||
|
defaultValue: oldName
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.patch(`etapi-tokens/${etapiTokenId}`, {name: tokenName});
|
||||||
|
|
||||||
|
this.refreshTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteToken(etapiTokenId, name) {
|
||||||
|
if (!confirm(`Are you sure you want to delete ETAPI token "${name}"?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.remove(`etapi-tokens/${etapiTokenId}`);
|
||||||
|
|
||||||
|
this.refreshTokens();
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,11 @@ const $form = $("#prompt-dialog-form");
|
|||||||
let resolve;
|
let resolve;
|
||||||
let shownCb;
|
let shownCb;
|
||||||
|
|
||||||
export function ask({ message, defaultValue, shown }) {
|
export function ask({ title, message, defaultValue, shown }) {
|
||||||
shownCb = shown;
|
shownCb = shown;
|
||||||
|
|
||||||
|
$("#prompt-title").text(title || "Prompt");
|
||||||
|
|
||||||
$question = $("<label>")
|
$question = $("<label>")
|
||||||
.prop("for", "prompt-dialog-answer")
|
.prop("for", "prompt-dialog-answer")
|
||||||
.text(message);
|
.text(message);
|
||||||
@ -30,7 +32,7 @@ export function ask({ message, defaultValue, shown }) {
|
|||||||
.append($question)
|
.append($question)
|
||||||
.append($answer));
|
.append($answer));
|
||||||
|
|
||||||
utils.openDialog($dialog);
|
utils.openDialog($dialog, false);
|
||||||
|
|
||||||
return new Promise((res, rej) => { resolve = res; });
|
return new Promise((res, rej) => { resolve = res; });
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ class AppContext extends Component {
|
|||||||
const appContext = new AppContext(window.glob.isMainWindow);
|
const appContext = new AppContext(window.glob.isMainWindow);
|
||||||
|
|
||||||
// we should save all outstanding changes before the page/app is closed
|
// we should save all outstanding changes before the page/app is closed
|
||||||
$(window).on('beforeunload', () => {
|
$(window).on('beforeunload', () => {return "SSS";
|
||||||
let allSaved = true;
|
let allSaved = true;
|
||||||
|
|
||||||
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter(wr => !!wr.deref());
|
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter(wr => !!wr.deref());
|
||||||
|
@ -11,12 +11,12 @@ async function getInboxNote() {
|
|||||||
|
|
||||||
/** @returns {NoteShort} */
|
/** @returns {NoteShort} */
|
||||||
async function getTodayNote() {
|
async function getTodayNote() {
|
||||||
return await getDateNote(dayjs().format("YYYY-MM-DD"));
|
return await getDayNote(dayjs().format("YYYY-MM-DD"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {NoteShort} */
|
/** @returns {NoteShort} */
|
||||||
async function getDateNote(date) {
|
async function getDayNote(date) {
|
||||||
const note = await server.get('special-notes/date/' + date, "date-note");
|
const note = await server.get('special-notes/days/' + date, "date-note");
|
||||||
|
|
||||||
await ws.waitForMaxKnownEntityChangeId();
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ async function getDateNote(date) {
|
|||||||
|
|
||||||
/** @returns {NoteShort} */
|
/** @returns {NoteShort} */
|
||||||
async function getWeekNote(date) {
|
async function getWeekNote(date) {
|
||||||
const note = await server.get('special-notes/week/' + date, "date-note");
|
const note = await server.get('special-notes/weeks/' + date, "date-note");
|
||||||
|
|
||||||
await ws.waitForMaxKnownEntityChangeId();
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ async function getWeekNote(date) {
|
|||||||
|
|
||||||
/** @returns {NoteShort} */
|
/** @returns {NoteShort} */
|
||||||
async function getMonthNote(month) {
|
async function getMonthNote(month) {
|
||||||
const note = await server.get('special-notes/month/' + month, "date-note");
|
const note = await server.get('special-notes/months/' + month, "date-note");
|
||||||
|
|
||||||
await ws.waitForMaxKnownEntityChangeId();
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ async function getMonthNote(month) {
|
|||||||
|
|
||||||
/** @returns {NoteShort} */
|
/** @returns {NoteShort} */
|
||||||
async function getYearNote(year) {
|
async function getYearNote(year) {
|
||||||
const note = await server.get('special-notes/year/' + year, "date-note");
|
const note = await server.get('special-notes/years/' + year, "date-note");
|
||||||
|
|
||||||
await ws.waitForMaxKnownEntityChangeId();
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ async function createSearchNote(opts = {}) {
|
|||||||
export default {
|
export default {
|
||||||
getInboxNote,
|
getInboxNote,
|
||||||
getTodayNote,
|
getTodayNote,
|
||||||
getDateNote,
|
getDayNote,
|
||||||
getWeekNote,
|
getWeekNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
||||||
getYearNote,
|
getYearNote,
|
||||||
|
@ -36,6 +36,9 @@ async function processEntityChanges(entityChanges) {
|
|||||||
|
|
||||||
loadResults.addOption(ec.entity.name);
|
loadResults.addOption(ec.entity.name);
|
||||||
}
|
}
|
||||||
|
else if (ec.entityName === 'etapi_tokens') {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error(`Unknown entityName ${ec.entityName}`);
|
throw new Error(`Unknown entityName ${ec.entityName}`);
|
||||||
}
|
}
|
||||||
|
@ -389,16 +389,26 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
|||||||
this.getTodayNote = dateNotesService.getTodayNote;
|
this.getTodayNote = dateNotesService.getTodayNote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns date-note. If it doesn't exist, it is automatically created.
|
* Returns day note for a given date. If it doesn't exist, it is automatically created.
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @param {string} date - e.g. "2019-04-29"
|
||||||
|
* @return {Promise<NoteShort>}
|
||||||
|
* @deprecated use getDayNote instead
|
||||||
|
*/
|
||||||
|
this.getDateNote = dateNotesService.getDayNote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns day note for a given date. If it doesn't exist, it is automatically created.
|
||||||
*
|
*
|
||||||
* @method
|
* @method
|
||||||
* @param {string} date - e.g. "2019-04-29"
|
* @param {string} date - e.g. "2019-04-29"
|
||||||
* @return {Promise<NoteShort>}
|
* @return {Promise<NoteShort>}
|
||||||
*/
|
*/
|
||||||
this.getDateNote = dateNotesService.getDateNote;
|
this.getDayNote = dateNotesService.getDayNote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns date-note for the first date of the week of the given date. If it doesn't exist, it is automatically created.
|
* Returns day note for the first date of the week of the given date. If it doesn't exist, it is automatically created.
|
||||||
*
|
*
|
||||||
* @method
|
* @method
|
||||||
* @param {string} date - e.g. "2019-04-29"
|
* @param {string} date - e.g. "2019-04-29"
|
||||||
|
@ -41,6 +41,10 @@ async function put(url, data, componentId) {
|
|||||||
return await call('PUT', url, data, {'trilium-component-id': componentId});
|
return await call('PUT', url, data, {'trilium-component-id': componentId});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function patch(url, data, componentId) {
|
||||||
|
return await call('PATCH', url, data, {'trilium-component-id': componentId});
|
||||||
|
}
|
||||||
|
|
||||||
async function remove(url, componentId) {
|
async function remove(url, componentId) {
|
||||||
return await call('DELETE', url, null, {'trilium-component-id': componentId});
|
return await call('DELETE', url, null, {'trilium-component-id': componentId});
|
||||||
}
|
}
|
||||||
@ -185,6 +189,7 @@ export default {
|
|||||||
get,
|
get,
|
||||||
post,
|
post,
|
||||||
put,
|
put,
|
||||||
|
patch,
|
||||||
remove,
|
remove,
|
||||||
ajax,
|
ajax,
|
||||||
// don't remove, used from CKEditor image upload!
|
// don't remove, used from CKEditor image upload!
|
||||||
|
@ -245,10 +245,11 @@ function focusSavedElement() {
|
|||||||
$lastFocusedElement = null;
|
$lastFocusedElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDialog($dialog) {
|
async function openDialog($dialog, closeActDialog = true) {
|
||||||
|
if (closeActDialog) {
|
||||||
closeActiveDialog();
|
closeActiveDialog();
|
||||||
|
|
||||||
glob.activeDialog = $dialog;
|
glob.activeDialog = $dialog;
|
||||||
|
}
|
||||||
|
|
||||||
saveFocusedElement();
|
saveFocusedElement();
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
this.$dropdownContent.on('click', '.calendar-date', async ev => {
|
this.$dropdownContent.on('click', '.calendar-date', async ev => {
|
||||||
const date = $(ev.target).closest('.calendar-date').attr('data-calendar-date');
|
const date = $(ev.target).closest('.calendar-date').attr('data-calendar-date');
|
||||||
|
|
||||||
const note = await dateNoteService.getDateNote(date);
|
const note = await dateNoteService.getDayNote(date);
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
||||||
|
@ -219,6 +219,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
|||||||
else if (command === "editTitle") {
|
else if (command === "editTitle") {
|
||||||
const promptDialog = await import("../../dialogs/prompt.js");
|
const promptDialog = await import("../../dialogs/prompt.js");
|
||||||
const title = await promptDialog.ask({
|
const title = await promptDialog.ask({
|
||||||
|
title: "Rename note",
|
||||||
message: "Enter new note title:",
|
message: "Enter new note title:",
|
||||||
defaultValue: $title.text()
|
defaultValue: $title.text()
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,7 @@ function getClipperInboxNote() {
|
|||||||
let clipperInbox = attributeService.getNoteWithLabel('clipperInbox');
|
let clipperInbox = attributeService.getNoteWithLabel('clipperInbox');
|
||||||
|
|
||||||
if (!clipperInbox) {
|
if (!clipperInbox) {
|
||||||
clipperInbox = dateNoteService.getDateNote(dateUtils.localNowDate());
|
clipperInbox = dateNoteService.getDayNote(dateUtils.localNowDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
return clipperInbox;
|
return clipperInbox;
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
const becca = require("../../becca/becca");
|
|
||||||
const utils = require("../../services/utils");
|
|
||||||
const noteService = require("../../services/notes");
|
|
||||||
const attributeService = require("../../services/attributes");
|
|
||||||
const Branch = require("../../becca/entities/branch");
|
|
||||||
const specialNotesService = require("../../services/special_notes");
|
|
||||||
const dateNotesService = require("../../services/date_notes");
|
|
||||||
const entityChangesService = require("../../services/entity_changes.js");
|
|
||||||
const TaskContext = require("../../services/task_context.js");
|
|
||||||
|
|
||||||
function register(router) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
register
|
|
||||||
}
|
|
30
src/routes/api/etapi_tokens.js
Normal file
30
src/routes/api/etapi_tokens.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const etapiTokenService = require("../../services/etapi_tokens");
|
||||||
|
|
||||||
|
function getTokens() {
|
||||||
|
const tokens = etapiTokenService.getTokens();
|
||||||
|
|
||||||
|
tokens.sort((a, b) => a.utcDateCreated < b.utcDateCreated ? -1 : 1);
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToken(req) {
|
||||||
|
return {
|
||||||
|
authToken: etapiTokenService.createToken(req.body.tokenName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchToken(req) {
|
||||||
|
etapiTokenService.renameToken(req.params.etapiTokenId, req.body.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteToken(req) {
|
||||||
|
etapiTokenService.deleteToken(req.params.etapiTokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getTokens,
|
||||||
|
createToken,
|
||||||
|
patchToken,
|
||||||
|
deleteToken
|
||||||
|
};
|
@ -10,8 +10,8 @@ const appInfo = require('../../services/app_info');
|
|||||||
const eventService = require('../../services/events');
|
const eventService = require('../../services/events');
|
||||||
const sqlInit = require('../../services/sql_init');
|
const sqlInit = require('../../services/sql_init');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const ApiToken = require('../../becca/entities/api_token');
|
|
||||||
const ws = require("../../services/ws");
|
const ws = require("../../services/ws");
|
||||||
|
const etapiTokenService = require("../../services/etapi_tokens");
|
||||||
|
|
||||||
function loginSync(req) {
|
function loginSync(req) {
|
||||||
if (!sqlInit.schemaExists()) {
|
if (!sqlInit.schemaExists()) {
|
||||||
@ -90,15 +90,12 @@ function token(req) {
|
|||||||
return [401, "Incorrect password"];
|
return [401, "Incorrect password"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiToken = new ApiToken({
|
|
||||||
// for backwards compatibility with Sender which does not send the name
|
// for backwards compatibility with Sender which does not send the name
|
||||||
name: req.body.tokenName || "Trilium Sender",
|
const tokenName = req.body.tokenName || "Trilium Sender / Web Clipper";
|
||||||
token: utils.randomSecureToken()
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
return {
|
const {authToken} = etapiTokenService.createToken(tokenName);
|
||||||
token: apiToken.token
|
|
||||||
};
|
return { token: authToken };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const passwordService = require('../../services/password.js');
|
const passwordService = require('../../services/password');
|
||||||
|
|
||||||
function changePassword(req) {
|
function changePassword(req) {
|
||||||
if (passwordService.isPasswordSet()) {
|
if (passwordService.isPasswordSet()) {
|
||||||
|
@ -15,7 +15,7 @@ function uploadImage(req) {
|
|||||||
|
|
||||||
const originalName = "Sender image." + imageType(file.buffer).ext;
|
const originalName = "Sender image." + imageType(file.buffer).ext;
|
||||||
|
|
||||||
const parentNote = dateNoteService.getDateNote(req.headers['x-local-date']);
|
const parentNote = dateNoteService.getDayNote(req.headers['x-local-date']);
|
||||||
|
|
||||||
const {note, noteId} = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
|
const {note, noteId} = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ function uploadImage(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveNote(req) {
|
function saveNote(req) {
|
||||||
const parentNote = dateNoteService.getDateNote(req.headers['x-local-date']);
|
const parentNote = dateNoteService.getDayNote(req.headers['x-local-date']);
|
||||||
|
|
||||||
const {note, branch} = noteService.createNewNote({
|
const {note, branch} = noteService.createNewNote({
|
||||||
parentNoteId: parentNote.noteId,
|
parentNoteId: parentNote.noteId,
|
||||||
|
@ -10,8 +10,8 @@ function getInboxNote(req) {
|
|||||||
return specialNotesService.getInboxNote(req.params.date);
|
return specialNotesService.getInboxNote(req.params.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateNote(req) {
|
function getDayNote(req) {
|
||||||
return dateNoteService.getDateNote(req.params.date);
|
return dateNoteService.getDayNote(req.params.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWeekNote(req) {
|
function getWeekNote(req) {
|
||||||
@ -26,7 +26,7 @@ function getYearNote(req) {
|
|||||||
return dateNoteService.getYearNote(req.params.year);
|
return dateNoteService.getYearNote(req.params.year);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateNotesForMonth(req) {
|
function getDayNotesForMonth(req) {
|
||||||
const month = req.params.month;
|
const month = req.params.month;
|
||||||
|
|
||||||
return sql.getMap(`
|
return sql.getMap(`
|
||||||
@ -68,11 +68,11 @@ function getHoistedNote() {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getInboxNote,
|
getInboxNote,
|
||||||
getDateNote,
|
getDayNote,
|
||||||
getWeekNote,
|
getWeekNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
||||||
getYearNote,
|
getYearNote,
|
||||||
getDateNotesForMonth,
|
getDayNotesForMonth,
|
||||||
createSqlConsole,
|
createSqlConsole,
|
||||||
saveSqlConsole,
|
saveSqlConsole,
|
||||||
createSearchNote,
|
createSearchNote,
|
||||||
|
@ -4,7 +4,7 @@ const utils = require('../services/utils');
|
|||||||
const optionService = require('../services/options');
|
const optionService = require('../services/options');
|
||||||
const myScryptService = require('../services/my_scrypt');
|
const myScryptService = require('../services/my_scrypt');
|
||||||
const log = require('../services/log');
|
const log = require('../services/log');
|
||||||
const passwordService = require("../services/password.js");
|
const passwordService = require("../services/password");
|
||||||
|
|
||||||
function loginPage(req, res) {
|
function loginPage(req, res) {
|
||||||
res.render('login', { failedAuth: false });
|
res.render('login', { failedAuth: false });
|
||||||
|
@ -31,15 +31,17 @@ const scriptRoute = require('./api/script');
|
|||||||
const senderRoute = require('./api/sender');
|
const senderRoute = require('./api/sender');
|
||||||
const filesRoute = require('./api/files');
|
const filesRoute = require('./api/files');
|
||||||
const searchRoute = require('./api/search');
|
const searchRoute = require('./api/search');
|
||||||
const specialNotesRoute = require('./api/special_notes.js');
|
const specialNotesRoute = require('./api/special_notes');
|
||||||
const noteMapRoute = require('./api/note_map.js');
|
const noteMapRoute = require('./api/note_map');
|
||||||
const clipperRoute = require('./api/clipper');
|
const clipperRoute = require('./api/clipper');
|
||||||
const similarNotesRoute = require('./api/similar_notes');
|
const similarNotesRoute = require('./api/similar_notes');
|
||||||
const keysRoute = require('./api/keys');
|
const keysRoute = require('./api/keys');
|
||||||
const backendLogRoute = require('./api/backend_log');
|
const backendLogRoute = require('./api/backend_log');
|
||||||
const statsRoute = require('./api/stats');
|
const statsRoute = require('./api/stats');
|
||||||
const fontsRoute = require('./api/fonts');
|
const fontsRoute = require('./api/fonts');
|
||||||
|
const etapiTokensApiRoutes = require('./api/etapi_tokens');
|
||||||
const shareRoutes = require('../share/routes');
|
const shareRoutes = require('../share/routes');
|
||||||
|
const etapiAuthRoutes = require('../etapi/auth');
|
||||||
const etapiAttributeRoutes = require('../etapi/attributes');
|
const etapiAttributeRoutes = require('../etapi/attributes');
|
||||||
const etapiBranchRoutes = require('../etapi/branches');
|
const etapiBranchRoutes = require('../etapi/branches');
|
||||||
const etapiNoteRoutes = require('../etapi/notes');
|
const etapiNoteRoutes = require('../etapi/notes');
|
||||||
@ -56,7 +58,7 @@ const entityChangesService = require('../services/entity_changes');
|
|||||||
const csurf = require('csurf');
|
const csurf = require('csurf');
|
||||||
const {createPartialContentHandler} = require("express-partial-content");
|
const {createPartialContentHandler} = require("express-partial-content");
|
||||||
const rateLimit = require("express-rate-limit");
|
const rateLimit = require("express-rate-limit");
|
||||||
const AbstractEntity = require("../becca/entities/abstract_entity.js");
|
const AbstractEntity = require("../becca/entities/abstract_entity");
|
||||||
|
|
||||||
const csrfMiddleware = csurf({
|
const csrfMiddleware = csurf({
|
||||||
cookie: true,
|
cookie: true,
|
||||||
@ -182,7 +184,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const GET = 'get', POST = 'post', PUT = 'put', DELETE = 'delete';
|
const GET = 'get', POST = 'post', PUT = 'put', PATCH = 'patch', DELETE = 'delete';
|
||||||
const uploadMiddleware = multer.single('upload');
|
const uploadMiddleware = multer.single('upload');
|
||||||
|
|
||||||
function register(app) {
|
function register(app) {
|
||||||
@ -272,11 +274,11 @@ function register(app) {
|
|||||||
apiRoute(GET, '/api/note-map/:noteId/backlinks', noteMapRoute.getBacklinks);
|
apiRoute(GET, '/api/note-map/:noteId/backlinks', noteMapRoute.getBacklinks);
|
||||||
|
|
||||||
apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote);
|
apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote);
|
||||||
apiRoute(GET, '/api/special-notes/date/:date', specialNotesRoute.getDateNote);
|
apiRoute(GET, '/api/special-notes/days/:date', specialNotesRoute.getDayNote);
|
||||||
apiRoute(GET, '/api/special-notes/week/:date', specialNotesRoute.getWeekNote);
|
apiRoute(GET, '/api/special-notes/weeks/:date', specialNotesRoute.getWeekNote);
|
||||||
apiRoute(GET, '/api/special-notes/month/:month', specialNotesRoute.getMonthNote);
|
apiRoute(GET, '/api/special-notes/months/:month', specialNotesRoute.getMonthNote);
|
||||||
apiRoute(GET, '/api/special-notes/year/:year', specialNotesRoute.getYearNote);
|
apiRoute(GET, '/api/special-notes/years/:year', specialNotesRoute.getYearNote);
|
||||||
apiRoute(GET, '/api/special-notes/notes-for-month/:month', specialNotesRoute.getDateNotesForMonth);
|
apiRoute(GET, '/api/special-notes/notes-for-month/:month', specialNotesRoute.getDayNotesForMonth);
|
||||||
apiRoute(POST, '/api/special-notes/sql-console', specialNotesRoute.createSqlConsole);
|
apiRoute(POST, '/api/special-notes/sql-console', specialNotesRoute.createSqlConsole);
|
||||||
apiRoute(POST, '/api/special-notes/save-sql-console', specialNotesRoute.saveSqlConsole);
|
apiRoute(POST, '/api/special-notes/save-sql-console', specialNotesRoute.saveSqlConsole);
|
||||||
apiRoute(POST, '/api/special-notes/search-note', specialNotesRoute.createSearchNote);
|
apiRoute(POST, '/api/special-notes/search-note', specialNotesRoute.createSearchNote);
|
||||||
@ -341,8 +343,8 @@ function register(app) {
|
|||||||
|
|
||||||
// no CSRF since this is called from android app
|
// no CSRF since this is called from android app
|
||||||
route(POST, '/api/sender/login', [], loginApiRoute.token, apiResultHandler);
|
route(POST, '/api/sender/login', [], loginApiRoute.token, apiResultHandler);
|
||||||
route(POST, '/api/sender/image', [auth.checkToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
|
route(POST, '/api/sender/image', [auth.checkEtapiToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
|
||||||
route(POST, '/api/sender/note', [auth.checkToken], senderRoute.saveNote, apiResultHandler);
|
route(POST, '/api/sender/note', [auth.checkEtapiToken], senderRoute.saveNote, apiResultHandler);
|
||||||
|
|
||||||
apiRoute(GET, '/api/quick-search/:searchString', searchRoute.quickSearch);
|
apiRoute(GET, '/api/quick-search/:searchString', searchRoute.quickSearch);
|
||||||
apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote);
|
apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote);
|
||||||
@ -358,7 +360,7 @@ function register(app) {
|
|||||||
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
|
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
|
||||||
|
|
||||||
// in case of local electron, local calls are allowed unauthenticated, for server they need auth
|
// in case of local electron, local calls are allowed unauthenticated, for server they need auth
|
||||||
const clipperMiddleware = utils.isElectron() ? [] : [auth.checkToken];
|
const clipperMiddleware = utils.isElectron() ? [] : [auth.checkEtapiToken];
|
||||||
|
|
||||||
route(GET, '/api/clipper/handshake', clipperMiddleware, clipperRoute.handshake, apiResultHandler);
|
route(GET, '/api/clipper/handshake', clipperMiddleware, clipperRoute.handshake, apiResultHandler);
|
||||||
route(POST, '/api/clipper/clippings', clipperMiddleware, clipperRoute.addClipping, apiResultHandler);
|
route(POST, '/api/clipper/clippings', clipperMiddleware, clipperRoute.addClipping, apiResultHandler);
|
||||||
@ -379,7 +381,14 @@ function register(app) {
|
|||||||
|
|
||||||
route(GET, '/api/fonts', [auth.checkApiAuthOrElectron], fontsRoute.getFontCss);
|
route(GET, '/api/fonts', [auth.checkApiAuthOrElectron], fontsRoute.getFontCss);
|
||||||
|
|
||||||
|
apiRoute(GET, '/api/etapi-tokens', etapiTokensApiRoutes.getTokens);
|
||||||
|
apiRoute(POST, '/api/etapi-tokens', etapiTokensApiRoutes.createToken);
|
||||||
|
apiRoute(PATCH, '/api/etapi-tokens/:etapiTokenId', etapiTokensApiRoutes.patchToken);
|
||||||
|
apiRoute(DELETE, '/api/etapi-tokens/:etapiTokenId', etapiTokensApiRoutes.deleteToken);
|
||||||
|
|
||||||
shareRoutes.register(router);
|
shareRoutes.register(router);
|
||||||
|
|
||||||
|
etapiAuthRoutes.register(router);
|
||||||
etapiAttributeRoutes.register(router);
|
etapiAttributeRoutes.register(router);
|
||||||
etapiBranchRoutes.register(router);
|
etapiBranchRoutes.register(router);
|
||||||
etapiNoteRoutes.register(router);
|
etapiNoteRoutes.register(router);
|
||||||
|
@ -4,7 +4,7 @@ const build = require('./build');
|
|||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||||
|
|
||||||
const APP_DB_VERSION = 192;
|
const APP_DB_VERSION = 194;
|
||||||
const SYNC_VERSION = 25;
|
const SYNC_VERSION = 25;
|
||||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
const etapiTokenService = require("./etapi_tokens");
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const sqlInit = require('./sql_init');
|
const sqlInit = require('./sql_init');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const passwordEncryptionService = require('./password_encryption');
|
const passwordEncryptionService = require('./password_encryption');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const passwordService = require("./password.js");
|
const passwordService = require("./password");
|
||||||
|
|
||||||
const noAuthentication = config.General && config.General.noAuthentication === true;
|
const noAuthentication = config.General && config.General.noAuthentication === true;
|
||||||
|
|
||||||
@ -72,15 +72,12 @@ function checkAppNotInitialized(req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkToken(req, res, next) {
|
function checkEtapiToken(req, res, next) {
|
||||||
const token = req.headers.authorization;
|
if (etapiTokenService.isValidAuthHeader(req.headers.authorization)) {
|
||||||
|
next();
|
||||||
// TODO: put all tokens into becca memory to avoid these requests
|
|
||||||
if (sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) {
|
|
||||||
reject(req, res, "Token not found");
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
next();
|
reject(req, res, "Token not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +113,6 @@ module.exports = {
|
|||||||
checkPasswordSet,
|
checkPasswordSet,
|
||||||
checkAppNotInitialized,
|
checkAppNotInitialized,
|
||||||
checkApiAuthOrElectron,
|
checkApiAuthOrElectron,
|
||||||
checkToken,
|
checkEtapiToken,
|
||||||
checkCredentials
|
checkCredentials
|
||||||
};
|
};
|
||||||
|
@ -309,8 +309,18 @@ function BackendScriptApi(currentNote, apiParams) {
|
|||||||
* @method
|
* @method
|
||||||
* @param {string} date in YYYY-MM-DD format
|
* @param {string} date in YYYY-MM-DD format
|
||||||
* @returns {Note|null}
|
* @returns {Note|null}
|
||||||
|
* @deprecated use getDayNote instead
|
||||||
*/
|
*/
|
||||||
this.getDateNote = dateNoteService.getDateNote;
|
this.getDateNote = dateNoteService.getDayNote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns day note for given date. If such note doesn't exist, it is created.
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @param {string} date in YYYY-MM-DD format
|
||||||
|
* @returns {Note|null}
|
||||||
|
*/
|
||||||
|
this.getDayNote = dateNoteService.getDayNote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns today's day note. If such note doesn't exist, it is created.
|
* Returns today's day note. If such note doesn't exist, it is created.
|
||||||
|
@ -76,7 +76,7 @@ async function anonymize() {
|
|||||||
|
|
||||||
const db = new Database(anonymizedFile);
|
const db = new Database(anonymizedFile);
|
||||||
|
|
||||||
db.prepare("UPDATE api_tokens SET token = 'API token value'").run();
|
db.prepare("UPDATE etapi_tokens SET tokenHash = 'API token hash value'").run();
|
||||||
db.prepare("UPDATE notes SET title = 'title'").run();
|
db.prepare("UPDATE notes SET title = 'title'").run();
|
||||||
db.prepare("UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL").run();
|
db.prepare("UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL").run();
|
||||||
db.prepare("UPDATE note_revisions SET title = 'title'").run();
|
db.prepare("UPDATE note_revisions SET title = 'title'").run();
|
||||||
|
@ -567,7 +567,7 @@ class ConsistencyChecks {
|
|||||||
this.runEntityChangeChecks("note_revisions", "noteRevisionId");
|
this.runEntityChangeChecks("note_revisions", "noteRevisionId");
|
||||||
this.runEntityChangeChecks("branches", "branchId");
|
this.runEntityChangeChecks("branches", "branchId");
|
||||||
this.runEntityChangeChecks("attributes", "attributeId");
|
this.runEntityChangeChecks("attributes", "attributeId");
|
||||||
this.runEntityChangeChecks("api_tokens", "apiTokenId");
|
this.runEntityChangeChecks("etapi_tokens", "etapiTokenId");
|
||||||
this.runEntityChangeChecks("options", "name");
|
this.runEntityChangeChecks("options", "name");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,7 +660,7 @@ class ConsistencyChecks {
|
|||||||
return `${tableName}: ${count}`;
|
return `${tableName}: ${count}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tables = [ "notes", "note_revisions", "branches", "attributes", "api_tokens" ];
|
const tables = [ "notes", "note_revisions", "branches", "attributes", "etapi_tokens" ];
|
||||||
|
|
||||||
log.info("Table counts: " + tables.map(tableName => getTableRowCount(tableName)).join(", "));
|
log.info("Table counts: " + tables.map(tableName => getTableRowCount(tableName)).join(", "));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const attributeService = require('./attributes');
|
const attributeService = require('./attributes');
|
||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
const becca = require('../becca/becca');
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const protectedSessionService = require('./protected_session');
|
const protectedSessionService = require('./protected_session');
|
||||||
|
|
||||||
@ -124,7 +123,7 @@ function getMonthNote(dateStr, rootNote = null) {
|
|||||||
return monthNote;
|
return monthNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateNoteTitle(rootNote, dayNumber, dateObj) {
|
function getDayNoteTitle(rootNote, dayNumber, dateObj) {
|
||||||
const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
|
const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
|
||||||
const weekDay = DAYS[dateObj.getDay()];
|
const weekDay = DAYS[dateObj.getDay()];
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ function getDateNoteTitle(rootNote, dayNumber, dateObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Note} */
|
/** @returns {Note} */
|
||||||
function getDateNote(dateStr) {
|
function getDayNote(dateStr) {
|
||||||
dateStr = dateStr.trim().substr(0, 10);
|
dateStr = dateStr.trim().substr(0, 10);
|
||||||
|
|
||||||
let dateNote = attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
|
let dateNote = attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
|
||||||
@ -152,7 +151,7 @@ function getDateNote(dateStr) {
|
|||||||
|
|
||||||
const dateObj = dateUtils.parseLocalDate(dateStr);
|
const dateObj = dateUtils.parseLocalDate(dateStr);
|
||||||
|
|
||||||
const noteTitle = getDateNoteTitle(rootNote, dayNumber, dateObj);
|
const noteTitle = getDayNoteTitle(rootNote, dayNumber, dateObj);
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
dateNote = createNote(monthNote, noteTitle);
|
dateNote = createNote(monthNote, noteTitle);
|
||||||
@ -170,7 +169,7 @@ function getDateNote(dateStr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTodayNote() {
|
function getTodayNote() {
|
||||||
return getDateNote(dateUtils.localNowDate());
|
return getDayNote(dateUtils.localNowDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStartOfTheWeek(date, startOfTheWeek) {
|
function getStartOfTheWeek(date, startOfTheWeek) {
|
||||||
@ -197,7 +196,7 @@ function getWeekNote(dateStr, options = {}) {
|
|||||||
|
|
||||||
dateStr = dateUtils.utcDateTimeStr(dateObj);
|
dateStr = dateUtils.utcDateTimeStr(dateObj);
|
||||||
|
|
||||||
return getDateNote(dateStr);
|
return getDayNote(dateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -205,6 +204,6 @@ module.exports = {
|
|||||||
getYearNote,
|
getYearNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
||||||
getWeekNote,
|
getWeekNote,
|
||||||
getDateNote,
|
getDayNote,
|
||||||
getTodayNote
|
getTodayNote
|
||||||
};
|
};
|
||||||
|
@ -137,7 +137,7 @@ function fillAllEntityChanges() {
|
|||||||
fillEntityChanges("note_revision_contents", "noteRevisionId");
|
fillEntityChanges("note_revision_contents", "noteRevisionId");
|
||||||
fillEntityChanges("recent_notes", "noteId");
|
fillEntityChanges("recent_notes", "noteId");
|
||||||
fillEntityChanges("attributes", "attributeId");
|
fillEntityChanges("attributes", "attributeId");
|
||||||
fillEntityChanges("api_tokens", "apiTokenId");
|
fillEntityChanges("etapi_tokens", "etapiTokenId");
|
||||||
fillEntityChanges("options", "name", 'isSynced = 1');
|
fillEntityChanges("options", "name", 'isSynced = 1');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
107
src/services/etapi_tokens.js
Normal file
107
src/services/etapi_tokens.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
const becca = require("../becca/becca");
|
||||||
|
const utils = require("./utils");
|
||||||
|
const EtapiToken = require("../becca/entities/etapi_token");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
function getTokens() {
|
||||||
|
return becca.getEtapiTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokenHash(token) {
|
||||||
|
return crypto.createHash('sha256').update(token).digest('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToken(tokenName) {
|
||||||
|
const token = utils.randomSecureToken();
|
||||||
|
const tokenHash = getTokenHash(token);
|
||||||
|
|
||||||
|
const etapiToken = new EtapiToken({
|
||||||
|
name: tokenName,
|
||||||
|
tokenHash
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return {
|
||||||
|
authToken: `${etapiToken.etapiTokenId}_${token}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAuthToken(auth) {
|
||||||
|
if (!auth) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = auth.split("_");
|
||||||
|
|
||||||
|
if (chunks.length === 1) {
|
||||||
|
return { token: auth }; // legacy format without etapiTokenId
|
||||||
|
}
|
||||||
|
else if (chunks.length === 2) {
|
||||||
|
return {
|
||||||
|
etapiTokenId: chunks[0],
|
||||||
|
token: chunks[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null; // wrong format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidAuthHeader(auth) {
|
||||||
|
const parsed = parseAuthToken(auth);
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authTokenHash = getTokenHash(parsed.token);
|
||||||
|
|
||||||
|
if (parsed.etapiTokenId) {
|
||||||
|
const etapiToken = becca.getEtapiToken(parsed.etapiTokenId);
|
||||||
|
|
||||||
|
if (!etapiToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return etapiToken.tokenHash === authTokenHash;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const etapiToken of becca.getEtapiTokens()) {
|
||||||
|
if (etapiToken.tokenHash === authTokenHash) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameToken(etapiTokenId, newName) {
|
||||||
|
const etapiToken = becca.getEtapiToken(etapiTokenId);
|
||||||
|
|
||||||
|
if (!etapiToken) {
|
||||||
|
throw new Error(`Token ${etapiTokenId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
etapiToken.name = newName;
|
||||||
|
etapiToken.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteToken(etapiTokenId) {
|
||||||
|
const etapiToken = becca.getEtapiToken(etapiTokenId);
|
||||||
|
|
||||||
|
if (!etapiToken) {
|
||||||
|
return; // ok, already deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
etapiToken.isDeleted = true;
|
||||||
|
etapiToken.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getTokens,
|
||||||
|
createToken,
|
||||||
|
renameToken,
|
||||||
|
deleteToken,
|
||||||
|
parseAuthToken,
|
||||||
|
isValidAuthHeader
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const sql = require("./sql.js");
|
const sql = require("./sql");
|
||||||
|
|
||||||
function getOption(name) {
|
function getOption(name) {
|
||||||
let option;
|
let option;
|
||||||
|
@ -18,6 +18,7 @@ class SearchContext {
|
|||||||
this.orderDirection = params.orderDirection;
|
this.orderDirection = params.orderDirection;
|
||||||
this.limit = params.limit;
|
this.limit = params.limit;
|
||||||
this.debug = params.debug;
|
this.debug = params.debug;
|
||||||
|
this.debugInfo = null;
|
||||||
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
|
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
|
||||||
this.highlightedTokens = [];
|
this.highlightedTokens = [];
|
||||||
this.originalQuery = "";
|
this.originalQuery = "";
|
||||||
|
@ -11,7 +11,7 @@ const RelationWhereExp = require('../expressions/relation_where');
|
|||||||
const PropertyComparisonExp = require('../expressions/property_comparison');
|
const PropertyComparisonExp = require('../expressions/property_comparison');
|
||||||
const AttributeExistsExp = require('../expressions/attribute_exists');
|
const AttributeExistsExp = require('../expressions/attribute_exists');
|
||||||
const LabelComparisonExp = require('../expressions/label_comparison');
|
const LabelComparisonExp = require('../expressions/label_comparison');
|
||||||
const NoteFlatTextExp = require('../expressions/note_flat_text.js');
|
const NoteFlatTextExp = require('../expressions/note_flat_text');
|
||||||
const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext');
|
const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext');
|
||||||
const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext');
|
const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext');
|
||||||
const OrderByAndLimitExp = require('../expressions/order_by_and_limit');
|
const OrderByAndLimitExp = require('../expressions/order_by_and_limit');
|
||||||
|
@ -124,9 +124,13 @@ function parseQueryToExpression(query, searchContext) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (searchContext.debug) {
|
if (searchContext.debug) {
|
||||||
log.info(`Fulltext tokens: ` + JSON.stringify(fulltextTokens));
|
searchContext.debugInfo = {
|
||||||
log.info(`Expression tokens: ` + JSON.stringify(structuredExpressionTokens, null, 4));
|
fulltextTokens,
|
||||||
log.info("Expression tree: " + JSON.stringify(expression, null, 4));
|
structuredExpressionTokens,
|
||||||
|
expression
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info("Search debug: " + JSON.stringify(searchContext.debugInfo, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression;
|
return expression;
|
||||||
|
@ -23,7 +23,7 @@ function getInboxNote(date) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
inbox = attributeService.getNoteWithLabel('inbox')
|
inbox = attributeService.getNoteWithLabel('inbox')
|
||||||
|| dateNoteService.getDateNote(date);
|
|| dateNoteService.getDayNote(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
return inbox;
|
return inbox;
|
||||||
@ -137,7 +137,7 @@ function saveSqlConsole(sqlConsoleNoteId) {
|
|||||||
|
|
||||||
const sqlConsoleHome =
|
const sqlConsoleHome =
|
||||||
attributeService.getNoteWithLabel('sqlConsoleHome')
|
attributeService.getNoteWithLabel('sqlConsoleHome')
|
||||||
|| dateNoteService.getDateNote(today);
|
|| dateNoteService.getDayNote(today);
|
||||||
|
|
||||||
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
|
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ function getSearchHome() {
|
|||||||
const today = dateUtils.localNowDate();
|
const today = dateUtils.localNowDate();
|
||||||
|
|
||||||
return hoistedNote.searchNoteInSubtree('#searchHome')
|
return hoistedNote.searchNoteInSubtree('#searchHome')
|
||||||
|| dateNoteService.getDateNote(today);
|
|| dateNoteService.getDayNote(today);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
const { Menu, Tray } = require('electron');
|
const { Menu, Tray } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const windowService = require("./window.js");
|
const windowService = require("./window");
|
||||||
const {getMainWindow} = require("./window.js");
|
|
||||||
|
|
||||||
const UPDATE_TRAY_EVENTS = [
|
const UPDATE_TRAY_EVENTS = [
|
||||||
'minimize', 'maximize', 'show', 'hide'
|
'minimize', 'maximize', 'show', 'hide'
|
||||||
@ -81,7 +80,7 @@ const updateTrayMenu = () => {
|
|||||||
tray?.setContextMenu(contextMenu);
|
tray?.setContextMenu(contextMenu);
|
||||||
}
|
}
|
||||||
const changeVisibility = () => {
|
const changeVisibility = () => {
|
||||||
const window = getMainWindow();
|
const window = windowService.getMainWindow();
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
window.hide();
|
window.hide();
|
||||||
|
@ -7,7 +7,7 @@ const config = require('./config');
|
|||||||
const syncMutexService = require('./sync_mutex');
|
const syncMutexService = require('./sync_mutex');
|
||||||
const protectedSessionService = require('./protected_session');
|
const protectedSessionService = require('./protected_session');
|
||||||
const becca = require("../becca/becca");
|
const becca = require("../becca/becca");
|
||||||
const AbstractEntity = require("../becca/entities/abstract_entity.js");
|
const AbstractEntity = require("../becca/entities/abstract_entity");
|
||||||
|
|
||||||
let webSocketServer;
|
let webSocketServer;
|
||||||
let lastSyncedPush = null;
|
let lastSyncedPush = null;
|
||||||
@ -139,7 +139,7 @@ function fillInAdditionalProperties(entityChange) {
|
|||||||
|
|
||||||
// entities with higher number can reference the entities with lower number
|
// entities with higher number can reference the entities with lower number
|
||||||
const ORDERING = {
|
const ORDERING = {
|
||||||
"api_tokens": 0,
|
"etapi_tokens": 0,
|
||||||
"attributes": 1,
|
"attributes": 1,
|
||||||
"branches": 1,
|
"branches": 1,
|
||||||
"note_contents": 1,
|
"note_contents": 1,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const shaca = require("./shaca/shaca");
|
const shaca = require("./shaca/shaca");
|
||||||
const shacaLoader = require("./shaca/shaca_loader");
|
const shacaLoader = require("./shaca/shaca_loader");
|
||||||
const shareRoot = require("./share_root");
|
const shareRoot = require("./share_root");
|
||||||
const contentRenderer = require("./content_renderer.js");
|
const contentRenderer = require("./content_renderer");
|
||||||
|
|
||||||
function getSharedSubTreeRoot(note) {
|
function getSharedSubTreeRoot(note) {
|
||||||
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('../sql');
|
const sql = require('../sql');
|
||||||
const shaca = require('./shaca.js');
|
const shaca = require('./shaca');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const Note = require('./entities/note');
|
const Note = require('./entities/note');
|
||||||
const Branch = require('./entities/branch');
|
const Branch = require('./entities/branch');
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" data-toggle="tab" href="#options-password">Password</a>
|
<a class="nav-link" data-toggle="tab" href="#options-password">Password</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-toggle="tab" href="#options-etapi">ETAPI</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" data-toggle="tab" href="#options-backup">Backup</a>
|
<a class="nav-link" data-toggle="tab" href="#options-backup">Backup</a>
|
||||||
</li>
|
</li>
|
||||||
@ -41,6 +44,7 @@
|
|||||||
<div id="options-shortcuts" class="tab-pane"></div>
|
<div id="options-shortcuts" class="tab-pane"></div>
|
||||||
<div id="options-code-notes" class="tab-pane"></div>
|
<div id="options-code-notes" class="tab-pane"></div>
|
||||||
<div id="options-password" class="tab-pane"></div>
|
<div id="options-password" class="tab-pane"></div>
|
||||||
|
<div id="options-etapi" class="tab-pane"></div>
|
||||||
<div id="options-backup" class="tab-pane"></div>
|
<div id="options-backup" class="tab-pane"></div>
|
||||||
<div id="options-sync-setup" class="tab-pane"></div>
|
<div id="options-sync-setup" class="tab-pane"></div>
|
||||||
<div id="options-other" class="tab-pane"></div>
|
<div id="options-other" class="tab-pane"></div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form id="prompt-dialog-form">
|
<form id="prompt-dialog-form">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title mr-auto">Prompt</h5>
|
<h5 class="modal-title mr-auto" id="prompt-title">Prompt</h5>
|
||||||
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
|
2
src/www
2
src/www
@ -23,7 +23,7 @@ const https = require('https');
|
|||||||
const config = require('./services/config');
|
const config = require('./services/config');
|
||||||
const log = require('./services/log');
|
const log = require('./services/log');
|
||||||
const appInfo = require('./services/app_info');
|
const appInfo = require('./services/app_info');
|
||||||
const ws = require('./services/ws.js');
|
const ws = require('./services/ws');
|
||||||
const utils = require('./services/utils');
|
const utils = require('./services/utils');
|
||||||
const sqlInit = require('./services/sql_init');
|
const sqlInit = require('./services/sql_init');
|
||||||
const port = require('./services/port');
|
const port = require('./services/port');
|
||||||
|
12
test-etapi/_login.http
Normal file
12
test-etapi/_login.http
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
POST {{triliumHost}}/etapi/auth/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "1234"
|
||||||
|
}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
|
||||||
|
client.global.set("authToken", response.body.authToken);
|
||||||
|
%}
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -24,6 +25,7 @@ Content-Type: application/json
|
|||||||
### Clone to another location
|
### Clone to another location
|
||||||
|
|
||||||
POST {{triliumHost}}/etapi/branches
|
POST {{triliumHost}}/etapi/branches
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -45,6 +47,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -60,6 +63,7 @@ GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -71,6 +75,7 @@ GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -83,6 +88,7 @@ GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -96,6 +102,7 @@ GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
|||||||
|
|
||||||
POST {{triliumHost}}/etapi/attributes
|
POST {{triliumHost}}/etapi/attributes
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"noteId": "{{createdNoteId}}",
|
"noteId": "{{createdNoteId}}",
|
||||||
@ -118,6 +125,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -16,6 +17,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
POST {{triliumHost}}/etapi/attributes
|
POST {{triliumHost}}/etapi/attributes
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -31,30 +33,35 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
DELETE {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
DELETE {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
||||||
|
|
||||||
### repeat the DELETE request to test the idempotency
|
### repeat the DELETE request to test the idempotency
|
||||||
|
|
||||||
DELETE {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
DELETE {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 404, "Response status is not 404");
|
client.assert(response.status === 404, "Response status is not 404");
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -16,6 +17,7 @@ Content-Type: application/json
|
|||||||
### Clone to another location
|
### Clone to another location
|
||||||
|
|
||||||
POST {{triliumHost}}/etapi/branches
|
POST {{triliumHost}}/etapi/branches
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -28,36 +30,42 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
DELETE {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
DELETE {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
||||||
|
|
||||||
### repeat the DELETE request to test the idempotency
|
### repeat the DELETE request to test the idempotency
|
||||||
|
|
||||||
DELETE {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
DELETE {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 404, "Response status is not 404");
|
client.assert(response.status === 404, "Response status is not 404");
|
||||||
@ -67,11 +75,13 @@ GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -16,6 +17,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
POST {{triliumHost}}/etapi/attributes
|
POST {{triliumHost}}/etapi/attributes
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -31,6 +33,7 @@ Content-Type: application/json
|
|||||||
### Clone to another location
|
### Clone to another location
|
||||||
|
|
||||||
POST {{triliumHost}}/etapi/branches
|
POST {{triliumHost}}/etapi/branches
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -43,42 +46,49 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
> {% client.assert(response.status === 200, "Response status is not 200"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
DELETE {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
DELETE {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
||||||
|
|
||||||
### repeat the DELETE request to test the idempotency
|
### repeat the DELETE request to test the idempotency
|
||||||
|
|
||||||
DELETE {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
DELETE {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
> {% client.assert(response.status === 204, "Response status is not 204"); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 404, "Response status is not 404");
|
client.assert(response.status === 404, "Response status is not 404");
|
||||||
@ -88,6 +98,7 @@ GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 404, "Response status is not 404");
|
client.assert(response.status === 404, "Response status is not 404");
|
||||||
@ -97,6 +108,7 @@ GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 404, "Response status is not 404");
|
client.assert(response.status === 404, "Response status is not 404");
|
||||||
@ -106,6 +118,7 @@ GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 404, "Response status is not 404");
|
client.assert(response.status === 404, "Response status is not 404");
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
GET {{triliumHost}}/etapi/inbox/2022-01-01
|
GET {{triliumHost}}/etapi/inbox/2022-01-01
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -8,7 +9,8 @@ GET {{triliumHost}}/etapi/inbox/2022-01-01
|
|||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/date/2022-01-01
|
GET {{triliumHost}}/etapi/calendar/days/2022-01-01
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -18,18 +20,20 @@ GET {{triliumHost}}/etapi/date/2022-01-01
|
|||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/date/2022-1
|
GET {{triliumHost}}/etapi/calendar/days/2022-1
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Correct error handling", function() {
|
client.test("Correct error handling", function() {
|
||||||
client.assert(response.status === 400, "Response status is not 400");
|
client.assert(response.status === 400, "Response status is not 400");
|
||||||
client.assert(response.body.code == "DATE_INVALID");
|
client.assert(response.body.code === "DATE_INVALID");
|
||||||
});
|
});
|
||||||
%}
|
%}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/week/2022-01-01
|
GET {{triliumHost}}/etapi/calendar/weeks/2022-01-01
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -39,18 +43,20 @@ GET {{triliumHost}}/etapi/week/2022-01-01
|
|||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/week/2022-1
|
GET {{triliumHost}}/etapi/calendar/weeks/2022-1
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Correct error handling", function() {
|
client.test("Correct error handling", function() {
|
||||||
client.assert(response.status === 400, "Response status is not 400");
|
client.assert(response.status === 400, "Response status is not 400");
|
||||||
client.assert(response.body.code == "DATE_INVALID");
|
client.assert(response.body.code === "DATE_INVALID");
|
||||||
});
|
});
|
||||||
%}
|
%}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/month/2022-01
|
GET {{triliumHost}}/etapi/calendar/months/2022-01
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -60,18 +66,20 @@ GET {{triliumHost}}/etapi/month/2022-01
|
|||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/month/2022-1
|
GET {{triliumHost}}/etapi/calendar/months/2022-1
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Correct error handling", function() {
|
client.test("Correct error handling", function() {
|
||||||
client.assert(response.status === 400, "Response status is not 400");
|
client.assert(response.status === 400, "Response status is not 400");
|
||||||
client.assert(response.body.code == "MONTH_INVALID");
|
client.assert(response.body.code === "MONTH_INVALID");
|
||||||
});
|
});
|
||||||
%}
|
%}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/year/2022
|
GET {{triliumHost}}/etapi/calendar/years/2022
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
@ -81,12 +89,13 @@ GET {{triliumHost}}/etapi/year/2022
|
|||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/year/202
|
GET {{triliumHost}}/etapi/calendar/years/202
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Correct error handling", function() {
|
client.test("Correct error handling", function() {
|
||||||
client.assert(response.status === 400, "Response status is not 400");
|
client.assert(response.status === 400, "Response status is not 400");
|
||||||
client.assert(response.body.code == "YEAR_INVALID");
|
client.assert(response.body.code === "YEAR_INVALID");
|
||||||
});
|
});
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
34
test-etapi/logout.http
Normal file
34
test-etapi/logout.http
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
POST {{triliumHost}}/etapi/auth/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "1234"
|
||||||
|
}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.assert(response.status === 200);
|
||||||
|
|
||||||
|
client.global.set("testAuthToken", response.body.authToken);
|
||||||
|
%}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/notes/root
|
||||||
|
Authorization: {{testAuthToken}}
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 200); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST {{triliumHost}}/etapi/auth/logout
|
||||||
|
Authorization: {{testAuthToken}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 204); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/notes/root
|
||||||
|
Authorization: {{testAuthToken}}
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
103
test-etapi/no-token.http
Normal file
103
test-etapi/no-token.http
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
GET {{triliumHost}}/etapi/notes?search=aaa
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/notes/root
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
PATCH {{triliumHost}}/etapi/notes/root
|
||||||
|
Authorization: fakeauth
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
DELETE {{triliumHost}}/etapi/notes/root
|
||||||
|
Authorization: fakeauth
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/branches/root
|
||||||
|
Authorization: fakeauth
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
PATCH {{triliumHost}}/etapi/branches/root
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
DELETE {{triliumHost}}/etapi/branches/root
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/attributes/000
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
PATCH {{triliumHost}}/etapi/attributes/000
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
DELETE {{triliumHost}}/etapi/attributes/000
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/inbox/2022-02-22
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/calendar/days/2022-02-22
|
||||||
|
Authorization: fakeauth
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/calendar/weeks/2022-02-22
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/calendar/months/2022-02
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/calendar/years/2022
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 401); %}
|
||||||
|
|
||||||
|
### Fake URL will get a 404 even without token
|
||||||
|
|
||||||
|
GET {{triliumHost}}/etapi/zzzzzz
|
||||||
|
|
||||||
|
> {% client.assert(response.status === 404); %}
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/refresh-note-ordering/root
|
POST {{triliumHost}}/etapi/refresh-note-ordering/root
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -16,6 +17,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
POST {{triliumHost}}/etapi/attributes
|
POST {{triliumHost}}/etapi/attributes
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -31,6 +33,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -40,6 +43,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.body.value === "CHANGED");
|
client.assert(response.body.value === "CHANGED");
|
||||||
@ -48,6 +52,7 @@ client.assert(response.body.value === "CHANGED");
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -62,6 +67,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -13,6 +14,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -24,6 +26,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 200);
|
client.assert(response.status === 200);
|
||||||
@ -35,6 +38,7 @@ client.assert(response.body.isExpanded === true);
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -49,6 +53,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -14,6 +15,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 200);
|
client.assert(response.status === 200);
|
||||||
@ -25,6 +27,7 @@ client.assert(response.body.mime === 'application/json');
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -36,6 +39,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 200);
|
client.assert(response.status === 200);
|
||||||
@ -47,6 +51,7 @@ client.assert(response.body.mime === 'text/html');
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -61,6 +66,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -14,6 +15,7 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
PUT {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
PUT {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: text/plain
|
Content-Type: text/plain
|
||||||
|
|
||||||
Changed content
|
Changed content
|
||||||
@ -21,5 +23,6 @@ Changed content
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.assert(response.body === "Changed content"); %}
|
> {% client.assert(response.body === "Changed content"); %}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
POST {{triliumHost}}/etapi/create-note
|
POST {{triliumHost}}/etapi/create-note
|
||||||
|
Authorization: {{authToken}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -13,23 +14,26 @@ Content-Type: application/json
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {% client.global.set("content", response.body); %}
|
> {% client.global.set("content", response.body); %}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes?search={{content}}
|
GET {{triliumHost}}/etapi/notes?search={{content}}&debug=true
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 200);
|
client.assert(response.status === 200);
|
||||||
client.assert(response.body.length === 1);
|
client.assert(response.body.results.length === 1);
|
||||||
%}
|
%}
|
||||||
|
|
||||||
### Same but with fast search which doesn't look in the content so 0 notes should be found
|
### Same but with fast search which doesn't look in the content so 0 notes should be found
|
||||||
|
|
||||||
GET {{triliumHost}}/etapi/notes?search={{content}}&fastSearch=true
|
GET {{triliumHost}}/etapi/notes?search={{content}}&fastSearch=true
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
client.assert(response.status === 200);
|
client.assert(response.status === 200);
|
||||||
client.assert(response.body.length === 0);
|
client.assert(response.body.results.length === 0);
|
||||||
%}
|
%}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user