converted all timestamps to string representation

This commit is contained in:
azivner 2017-12-10 12:56:59 -05:00
parent e3b708c322
commit 021f02bd8c
22 changed files with 304 additions and 96 deletions

View File

@ -0,0 +1,156 @@
DROP TABLE migrations;
-- Sync
CREATE TABLE `sync_mig` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`entity_name` TEXT NOT NULL,
`entity_id` TEXT NOT NULL,
`source_id` TEXT NOT NULL,
`sync_date` TEXT NOT NULL);
INSERT INTO sync_mig (id, entity_name, entity_id, source_id, sync_date)
SELECT id, entity_name, entity_id, source_id, datetime(sync_date, 'unixepoch') || '.000' FROM sync;
DROP TABLE sync;
ALTER TABLE sync_mig RENAME TO sync;
CREATE UNIQUE INDEX `IDX_sync_entity_name_id` ON `sync` (
`entity_name`,
`entity_id`
);
CREATE INDEX `IDX_sync_sync_date` ON `sync` (
`sync_date`
);
-- Options
UPDATE options SET opt_value = datetime(opt_value, 'unixepoch') || '.000' WHERE opt_name IN ('last_backup_date');
UPDATE options SET date_modified = datetime(date_modified, 'unixepoch') || '.000';
-- Event log
CREATE TABLE `event_log_mig` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`note_id` TEXT,
`comment` TEXT,
`date_added` TEXT NOT NULL
);
INSERT INTO event_log_mig (id, note_id, comment, date_added)
SELECT id, note_id, comment, datetime(date_added, 'unixepoch') || '.000' FROM event_log;
DROP TABLE event_log;
ALTER TABLE event_log_mig RENAME TO event_log;
CREATE INDEX `IDX_event_log_date_added` ON `event_log` (
`date_added`
);
-- Notes
CREATE TABLE IF NOT EXISTS "notes_mig" (
`note_id` TEXT NOT NULL,
`note_title` TEXT,
`note_text` TEXT,
`is_protected` INT NOT NULL DEFAULT 0,
`is_deleted` INT NOT NULL DEFAULT 0,
`date_created` TEXT NOT NULL,
`date_modified` TEXT NOT NULL,
PRIMARY KEY(`note_id`)
);
INSERT INTO notes_mig (note_id, note_title, note_text, is_protected, is_deleted, date_created, date_modified)
SELECT note_id, note_title, note_text, is_protected, is_deleted,
datetime(date_created, 'unixepoch') || '.000',
datetime(date_modified, 'unixepoch') || '.000'
FROM notes;
DROP TABLE notes;
ALTER TABLE notes_mig RENAME TO notes;
CREATE INDEX `IDX_notes_is_deleted` ON `notes` (
`is_deleted`
);
-- note history
CREATE TABLE IF NOT EXISTS "notes_history_mig" (
`note_history_id` TEXT NOT NULL PRIMARY KEY,
`note_id` TEXT NOT NULL,
`note_title` TEXT,
`note_text` TEXT,
`is_protected` INT NOT NULL DEFAULT 0,
`date_modified_from` TEXT NOT NULL,
`date_modified_to` TEXT NOT NULL
);
INSERT INTO notes_history_mig (note_history_id, note_id, note_title, note_text, is_protected, date_modified_from, date_modified_to)
SELECT note_history_id, note_id, note_title, note_text, is_protected,
datetime(date_modified_from, 'unixepoch') || '.000',
datetime(date_modified_to, 'unixepoch') || '.000'
FROM notes_history;
DROP TABLE notes_history;
ALTER TABLE notes_history_mig RENAME TO notes_history;
CREATE INDEX `IDX_notes_history_note_id` ON `notes_history` (
`note_id`
);
CREATE INDEX `IDX_notes_history_note_date_modified_from` ON `notes_history` (
`date_modified_from`
);
CREATE INDEX `IDX_notes_history_note_date_modified_to` ON `notes_history` (
`date_modified_to`
);
-- Source IDs
DROP TABLE source_ids;
CREATE TABLE `source_ids` (
`source_id` TEXT NOT NULL,
`date_created` TEXT NOT NULL,
PRIMARY KEY(`source_id`)
);
-- Recent notes
DROP TABLE recent_notes;
CREATE TABLE `recent_notes` (
'note_tree_id'TEXT NOT NULL PRIMARY KEY,
`note_path` TEXT NOT NULL,
`date_accessed` TEXT NOT NULL,
is_deleted INT
);
-- Notes tree
CREATE TABLE IF NOT EXISTS "notes_tree_mig" (
`note_tree_id` TEXT NOT NULL,
`note_id` TEXT NOT NULL,
`note_pid` TEXT NOT NULL,
`note_pos` INTEGER NOT NULL,
`prefix` TEXT,
`is_expanded` BOOLEAN,
`is_deleted` INTEGER NOT NULL DEFAULT 0,
`date_modified` TEXT NOT NULL,
PRIMARY KEY(`note_tree_id`)
);
INSERT INTO notes_tree_mig (note_tree_id, note_id, note_pid, note_pos, prefix, is_expanded, is_deleted, date_modified)
SELECT note_tree_id, note_id, note_pid, note_pos, prefix, is_expanded, is_deleted,
datetime(date_modified, 'unixepoch') || '.000'
FROM notes_tree;
DROP TABLE notes_tree;
ALTER TABLE notes_tree_mig RENAME TO notes_tree;
CREATE INDEX `IDX_notes_tree_note_tree_id` ON `notes_tree` (
`note_tree_id`
);
CREATE INDEX `IDX_notes_tree_note_id_note_pid` ON `notes_tree` (
`note_id`,
`note_pid`
);

View File

@ -100,7 +100,7 @@ const noteEditor = (function() {
}
async function loadNoteToEditor(noteId) {
currentNote = await server.get('notes/' + noteId);
currentNote = await loadNote(noteId);
if (isNewNoteCreated) {
isNewNoteCreated = false;

View File

@ -67,6 +67,8 @@ async function importNotes(dir, parentNoteId) {
const noteId = utils.newNoteId();
const noteTreeId = utils.newNoteHistoryId();
const now = utils.nowDate();
await sql.insert('notes_tree', {
note_tree_id: noteTreeId,
note_id: noteId,
@ -74,7 +76,7 @@ async function importNotes(dir, parentNoteId) {
note_pos: notePos,
is_expanded: 0,
is_deleted: 0,
date_modified: utils.nowTimestamp()
date_modified: now
});
await sync_table.addNoteTreeSync(noteTreeId);
@ -85,8 +87,8 @@ async function importNotes(dir, parentNoteId) {
note_text: noteText,
is_deleted: 0,
is_protected: 0,
date_created: utils.nowTimestamp(),
date_modified: utils.nowTimestamp()
date_created: now,
date_modified: now
});
await sync_table.addNoteSync(noteId);

View File

@ -4,7 +4,6 @@ const express = require('express');
const router = express.Router();
const options = require('../../services/options');
const utils = require('../../services/utils');
const migration = require('../../services/migration');
const source_id = require('../../services/source_id');
const auth = require('../../services/auth');
const password_encryption = require('../../services/password_encryption');

View File

@ -30,8 +30,7 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => {
}
res.send({
detail: detail,
loadTime: utils.nowTimestamp()
detail: detail
});
});

View File

@ -14,7 +14,7 @@ router.put('/:noteTreeId/move-to/:parentNoteId', auth.checkApiAuth, async (req,
const maxNotePos = await sql.getSingleValue('SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0', [parentNoteId]);
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 1;
const now = utils.nowTimestamp();
const now = utils.nowDate();
await sql.doInTransaction(async () => {
await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
@ -40,7 +40,7 @@ router.put('/:noteTreeId/move-before/:beforeNoteTreeId', async (req, res, next)
await sync_table.addNoteReorderingSync(beforeNote.note_pid);
const now = utils.nowTimestamp();
const now = utils.nowDate();
await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[beforeNote.note_pid, beforeNote.note_pos, now, noteTreeId]);
@ -70,7 +70,7 @@ router.put('/:noteTreeId/move-after/:afterNoteTreeId', async (req, res, next) =>
await sync_table.addNoteReorderingSync(afterNote.note_pid);
await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[afterNote.note_pid, afterNote.note_pos + 1, utils.nowTimestamp(), noteTreeId]);
[afterNote.note_pid, afterNote.note_pos + 1, utils.nowDate(), noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
});
@ -107,13 +107,13 @@ router.put('/:childNoteId/clone-to/:parentNoteId', auth.checkApiAuth, async (req
await sql.doInTransaction(async () => {
const noteTree = {
'note_tree_id': utils.newNoteTreeId(),
'note_id': childNoteId,
'note_pid': parentNoteId,
'note_pos': newNotePos,
'is_expanded': 0,
'date_modified': utils.nowTimestamp(),
'is_deleted': 0
note_tree_id: utils.newNoteTreeId(),
note_id: childNoteId,
note_pid: parentNoteId,
note_pos: newNotePos,
is_expanded: 0,
date_modified: utils.nowDate(),
is_deleted: 0
};
await sql.replace("notes_tree", noteTree);
@ -160,13 +160,13 @@ router.put('/:noteId/clone-after/:afterNoteTreeId', async (req, res, next) => {
await sync_table.addNoteReorderingSync(afterNote.note_pid);
const noteTree = {
'note_tree_id': utils.newNoteTreeId(),
'note_id': noteId,
'note_pid': afterNote.note_pid,
'note_pos': afterNote.note_pos + 1,
'is_expanded': 0,
'date_modified': utils.nowTimestamp(),
'is_deleted': 0
note_tree_id: utils.newNoteTreeId(),
note_id: noteId,
note_pid: afterNote.note_pid,
note_pos: afterNote.note_pos + 1,
is_expanded: 0,
date_modified: utils.nowDate(),
is_deleted: 0
};
await sql.replace("notes_tree", noteTree);

View File

@ -20,7 +20,7 @@ router.put('/:noteTreeId/:notePath', auth.checkApiAuth, async (req, res, next) =
await sql.replace('recent_notes', {
note_tree_id: noteTreeId,
note_path: notePath,
date_accessed: utils.nowTimestamp(),
date_accessed: utils.nowDate(),
is_deleted: 0
});
@ -39,7 +39,7 @@ async function getRecentNotes() {
}
async function deleteOld() {
const cutoffDateAccessed = utils.nowTimestamp() - 24 * 60 * 60;
const cutoffDateAccessed = utils.dateStr(new Date(Date.now() - 24 * 60 * 60 * 1000));
await sql.doInTransaction(async () => {
await sql.execute("DELETE FROM recent_notes WHERE date_accessed < ?", [cutoffDateAccessed]);

View File

@ -52,7 +52,7 @@ router.put('/:noteTreeId/set-prefix', auth.checkApiAuth, async (req, res, next)
const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
await sql.doInTransaction(async () => {
await sql.execute("UPDATE notes_tree SET prefix = ?, date_modified = ? WHERE note_tree_id = ?", [prefix, utils.nowTimestamp(), noteTreeId]);
await sql.execute("UPDATE notes_tree SET prefix = ?, date_modified = ? WHERE note_tree_id = ?", [prefix, utils.nowDate(), noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
});

View File

@ -68,15 +68,23 @@ CREATE TABLE `source_ids` (
`date_created` INTEGER NOT NULL,
PRIMARY KEY(`source_id`)
);
CREATE TABLE `recent_notes` (
'note_tree_id'TEXT NOT NULL PRIMARY KEY,
`note_path` TEXT NOT NULL,
`date_accessed` INTEGER NOT NULL ,
is_deleted INT
);
CREATE TABLE IF NOT EXISTS "notes_tree" (
[note_tree_id] VARCHAR(30) PRIMARY KEY NOT NULL,
[note_id] VARCHAR(30) NOT NULL,
[note_pid] VARCHAR(30) NOT NULL,
[note_pos] INTEGER NOT NULL,
[is_expanded] BOOLEAN NULL ,
date_modified INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0
, `prefix` TEXT);
`note_tree_id` VARCHAR ( 30 ) NOT NULL,
`note_id` VARCHAR ( 30 ) NOT NULL,
`note_pid` VARCHAR ( 30 ) NOT NULL,
`note_pos` INTEGER NOT NULL,
`is_expanded` BOOLEAN,
`date_modified` TEXT NOT NULL DEFAULT 0,
`is_deleted` INTEGER NOT NULL DEFAULT 0,
`prefix` TEXT,
PRIMARY KEY(`note_tree_id`)
);
CREATE INDEX `IDX_notes_tree_note_tree_id` ON `notes_tree` (
`note_tree_id`
);
@ -84,9 +92,3 @@ CREATE INDEX `IDX_notes_tree_note_id_note_pid` ON `notes_tree` (
`note_id`,
`note_pid`
);
CREATE TABLE `recent_notes` (
'note_tree_id'TEXT NOT NULL PRIMARY KEY,
`note_path` TEXT NOT NULL,
`date_accessed` INTEGER NOT NULL ,
is_deleted INT
);

View File

@ -3,7 +3,7 @@
const build = require('./build');
const packageJson = require('../package');
const APP_DB_VERSION = 49;
const APP_DB_VERSION = 50;
module.exports = {
app_version: packageJson.version,

View File

@ -1,6 +1,7 @@
"use strict";
const migration = require('./migration');
const sql = require('./sql');
const utils = require('./utils');
const options = require('./options');
@ -13,7 +14,7 @@ async function checkAuth(req, res, next) {
else if (!req.session.loggedIn && !utils.isElectron()) {
res.redirect("login");
}
else if (!await migration.isDbUpToDate()) {
else if (!await sql.isDbUpToDate()) {
res.redirect("migration");
}
else {
@ -34,7 +35,7 @@ async function checkApiAuth(req, res, next) {
if (!req.session.loggedIn) {
res.status(401).send("Not authorized");
}
else if (await migration.isDbUpToDate()) {
else if (await sql.isDbUpToDate()) {
next();
}
else {

View File

@ -8,10 +8,12 @@ const log = require('./log');
const sql = require('./sql');
async function regularBackup() {
const now = utils.nowTimestamp();
const last_backup_date = parseInt(await options.getOption('last_backup_date'));
const now = new Date();
const lastBackupDate = utils.parseDate(await options.getOption('last_backup_date'));
if (now - last_backup_date > 43200) {
console.log(lastBackupDate);
if (now.getTime() - lastBackupDate.getTime() > 43200 * 1000) {
await backupNow();
}
@ -19,7 +21,7 @@ async function regularBackup() {
}
async function backupNow() {
const now = utils.nowTimestamp();
const now = utils.nowDate();
const date_str = new Date().toISOString().substr(0, 19).replace(/:/g, '');

View File

@ -10,7 +10,7 @@ async function addNoteEvent(noteId, comment) {
await sql.insert('event_log', {
note_id : noteId,
comment: comment,
date_added: utils.nowTimestamp()
date_added: utils.nowDate()
});
log.info("Event log for " + noteId + ": " + comment);

View File

@ -70,6 +70,8 @@ async function migrate() {
await options.setOption("db_version", mig.dbVersion);
});
sql.setDbReadyAsResolved();
log.info("Migration to version " + mig.dbVersion + " has been successful.");
mig['success'] = true;
@ -87,13 +89,6 @@ async function migrate() {
return migrations;
}
async function isDbUpToDate() {
const dbVersion = parseInt(await options.getOption('db_version'));
return dbVersion >= app_info.db_version;
}
module.exports = {
migrate,
isDbUpToDate
migrate
};

View File

@ -23,7 +23,7 @@ async function createNewNote(parentNoteId, note) {
newNotePos = afterNote.note_pos + 1;
await sql.execute('UPDATE notes_tree SET note_pos = note_pos + 1, date_modified = ? WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0',
[utils.nowTimestamp(), parentNoteId, afterNote.note_pos]);
[utils.nowDate(), parentNoteId, afterNote.note_pos]);
await sync_table.addNoteReorderingSync(parentNoteId);
}
@ -31,28 +31,27 @@ async function createNewNote(parentNoteId, note) {
throw new Error('Unknown target: ' + note.target);
}
const now = utils.nowTimestamp();
const now = utils.nowDate();
await sql.insert("notes", {
'note_id': noteId,
'note_title': note.note_title,
'note_text': '',
'date_created': now,
'date_modified': now,
'is_protected': note.is_protected
note_id: noteId,
note_title: note.note_title,
note_text: '',
date_created: now,
date_modified: now,
is_protected: note.is_protected
});
await sync_table.addNoteSync(noteId);
await sql.insert("notes_tree", {
'note_tree_id': noteTreeId,
'note_id': noteId,
'note_pid': parentNoteId,
'note_pos': newNotePos,
'is_expanded': 0,
'date_modified': now,
'is_deleted': 0
note_tree_id: noteTreeId,
note_id: noteId,
note_pid: parentNoteId,
note_pos: newNotePos,
is_expanded: 0,
date_modified: now,
is_deleted: 0
});
await sync_table.addNoteTreeSync(noteTreeId);
@ -142,16 +141,19 @@ async function updateNote(noteId, newNote, ctx) {
await encryptNote(newNote, ctx);
}
const now = utils.nowTimestamp();
const now = new Date();
const historySnapshotTimeInterval = parseInt(await options.getOption('history_snapshot_time_interval'));
const historyCutoff = now - historySnapshotTimeInterval;
const historyCutoff = utils.dateStr(new Date(now.getTime() - historySnapshotTimeInterval * 1000));
const existingNoteHistoryId = await sql.getSingleValue("SELECT note_history_id FROM notes_history WHERE note_id = ? AND date_modified_to >= ?", [noteId, historyCutoff]);
const existingNoteHistoryId = await sql.getSingleValue(
"SELECT note_history_id FROM notes_history WHERE note_id = ? AND date_modified_to >= ?", [noteId, historyCutoff]);
await sql.doInTransaction(async () => {
if (!existingNoteHistoryId && (now - newNote.detail.date_created) >= historySnapshotTimeInterval) {
const msSinceDateCreated = now.getTime() - utils.parseDate(newNote.detail.date_created).getTime();
if (!existingNoteHistoryId && msSinceDateCreated >= historySnapshotTimeInterval * 1000) {
const oldNote = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]);
if (oldNote.is_protected) {
@ -188,7 +190,7 @@ async function updateNote(noteId, newNote, ctx) {
}
async function deleteNote(noteTreeId) {
const now = utils.nowTimestamp();
const now = utils.nowDate();
await sql.execute("UPDATE notes_tree SET is_deleted = 1, date_modified = ? WHERE note_tree_id = ?", [now, noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);

View File

@ -24,7 +24,7 @@ async function setOption(optName, optValue) {
await sql.replace("options", {
opt_name: optName,
opt_value: optValue,
date_modified: utils.nowTimestamp()
date_modified: utils.nowDate()
});
}
@ -42,7 +42,7 @@ async function initOptions(startNotePath) {
await setOption('start_note_path', startNotePath);
await setOption('protected_session_timeout', 600);
await setOption('history_snapshot_time_interval', 600);
await setOption('last_backup_date', utils.nowTimestamp());
await setOption('last_backup_date', utils.nowDate());
await setOption('db_version', app_info.db_version);
await setOption('last_synced_pull', app_info.db_version);

View File

@ -5,12 +5,12 @@ const messaging = require('./messaging');
const options = require('./options');
const sync = require('./sync');
let startTime = utils.nowTimestamp();
let startTime = utils.nowDate();
let sentSyncId = [];
async function sendPing() {
const syncs = await sql.getResults("SELECT * FROM sync WHERE sync_date >= ? AND source_id != ?", [startTime, source_id.currentSourceId]);
startTime = utils.nowTimestamp();
startTime = utils.nowDate();
const data = {};
const syncIds = [];

View File

@ -13,7 +13,7 @@ sql.dbReady.then(async () => {
await sql.doInTransaction(async () => {
await sql.insert("source_ids", {
source_id: currentSourceId,
date_created: utils.nowTimestamp()
date_created: utils.nowDate()
});
});

View File

@ -5,6 +5,7 @@ const dataDir = require('./data_dir');
const fs = require('fs');
const sqlite = require('sqlite');
const utils = require('./utils');
const app_info = require('./app_info');
async function createConnection() {
return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise});
@ -15,7 +16,11 @@ const dbConnected = createConnection();
let dbReadyResolve = null;
const dbReady = new Promise((resolve, reject) => {
dbConnected.then(async db => {
dbReadyResolve = () => resolve(db);
dbReadyResolve = () => {
log.info("DB ready.");
resolve(db);
};
const tableResults = await getResults("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'");
if (tableResults.length !== 1) {
@ -27,6 +32,7 @@ const dbReady = new Promise((resolve, reject) => {
await executeScript(schema);
const noteId = utils.newNoteId();
const now = utils.nowDate();
await insert('notes_tree', {
note_tree_id: utils.newNoteTreeId(),
@ -34,7 +40,7 @@ const dbReady = new Promise((resolve, reject) => {
note_pid: 'root',
note_pos: 1,
is_deleted: 0,
date_modified: utils.nowTimestamp()
date_modified: now
});
await insert('notes', {
@ -43,8 +49,8 @@ const dbReady = new Promise((resolve, reject) => {
note_text: 'Text',
is_protected: 0,
is_deleted: 0,
date_created: utils.nowTimestamp(),
date_modified: utils.nowTimestamp()
date_created: now,
date_modified: now
});
await require('./options').initOptions(noteId);
@ -56,9 +62,17 @@ const dbReady = new Promise((resolve, reject) => {
else {
const username = await getSingleValue("SELECT opt_value FROM options WHERE opt_name = 'username'");
if (username) {
resolve(db);
if (!username) {
log.info("Login/password not initialized. DB not ready.");
return;
}
if (!await isDbUpToDate()) {
return;
}
resolve(db);
}
})
.catch(e => {
@ -202,9 +216,8 @@ async function doInTransaction(func) {
await rollback();
transactionActive = false;
resolve();
throw e;
reject(e);
}
});
@ -213,6 +226,18 @@ async function doInTransaction(func) {
}
}
async function isDbUpToDate() {
const dbVersion = parseInt(await getSingleValue("SELECT opt_value FROM options WHERE opt_name = 'db_version'"));
const upToDate = dbVersion >= app_info.db_version;
if (!upToDate) {
log.info("App db version is " + app_info.db_version + ", while db version is " + dbVersion + ". Migration needed.");
}
return upToDate;
}
module.exports = {
dbReady,
insert,
@ -226,5 +251,6 @@ module.exports = {
execute,
executeScript,
doInTransaction,
setDbReadyAsResolved
setDbReadyAsResolved,
isDbUpToDate
};

View File

@ -37,9 +37,7 @@ async function sync() {
syncInProgress = true;
try {
if (!await migration.isDbUpToDate()) {
log.info("DB not up to date");
if (!await sql.isDbUpToDate()) {
return {
success: false,
message: "DB not up to date"

View File

@ -30,7 +30,7 @@ async function addEntitySync(entityName, entityId, sourceId) {
await sql.replace("sync", {
entity_name: entityName,
entity_id: entityId,
sync_date: utils.nowTimestamp(),
sync_date: utils.nowDate(),
source_id: sourceId || source_id.currentSourceId
});
}

View File

@ -27,6 +27,29 @@ function nowTimestamp() {
return Math.floor(Date.now() / 1000);
}
function nowDate() {
return dateStr(new Date());
}
function dateStr(date) {
return date.toISOString().replace("T", " ").replace("Z", "");
}
/**
* @param str - needs to be in the "YYYY-MM-DD HH:MM:SS.sss" format as outputted by dateStr().
* also is assumed to be GMT time, *not* local time
*/
function parseDate(str) {
try {
const isoDate = str.replace(" ", "T") + "Z";
return new Date(Date.parse(isoDate));
}
catch (e) {
throw new Error("Can't parse date from " + str + ": " + e.stack);
}
}
function toBase64(plainText) {
return Buffer.from(plainText).toString('base64');
}
@ -68,6 +91,9 @@ module.exports = {
randomSecureToken,
randomString,
nowTimestamp,
nowDate,
dateStr,
parseDate,
newNoteId,
newNoteTreeId,
newNoteHistoryId,