2017-06-24 20:06:28 +02:00
|
|
|
import { Log } from 'lib/log.js';
|
|
|
|
import { Database } from 'lib/database.js';
|
|
|
|
import { uuid } from 'lib/uuid.js';
|
|
|
|
import { time } from 'lib/time-utils.js';
|
2017-05-10 21:51:43 +02:00
|
|
|
|
2017-05-08 00:20:34 +02:00
|
|
|
class BaseModel {
|
|
|
|
|
2017-06-17 20:40:08 +02:00
|
|
|
static addModelMd(model) {
|
|
|
|
if (!model) return model;
|
|
|
|
|
|
|
|
if (Array.isArray(model)) {
|
|
|
|
let output = [];
|
|
|
|
for (let i = 0; i < model.length; i++) {
|
|
|
|
output.push(this.addModelMd(model[i]));
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
} else {
|
|
|
|
model = Object.assign({}, model);
|
|
|
|
model.type_ = this.itemType();
|
|
|
|
return model;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-25 12:41:03 +02:00
|
|
|
static logger() {
|
|
|
|
return this.db().logger();
|
|
|
|
}
|
|
|
|
|
2017-05-10 21:51:43 +02:00
|
|
|
static tableName() {
|
|
|
|
throw new Error('Must be overriden');
|
|
|
|
}
|
|
|
|
|
2017-05-12 21:54:06 +02:00
|
|
|
static useUuid() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-18 21:58:01 +02:00
|
|
|
static itemType() {
|
|
|
|
throw new Error('Must be overriden');
|
|
|
|
}
|
|
|
|
|
|
|
|
static trackChanges() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-06-20 00:18:24 +02:00
|
|
|
static trackDeleted() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-15 21:10:00 +02:00
|
|
|
static byId(items, id) {
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
|
|
if (items[i].id == id) return items[i];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-05-19 21:32:49 +02:00
|
|
|
static hasField(name) {
|
|
|
|
let fields = this.fieldNames();
|
|
|
|
return fields.indexOf(name) >= 0;
|
|
|
|
}
|
|
|
|
|
2017-05-18 21:58:01 +02:00
|
|
|
static fieldNames() {
|
|
|
|
return this.db().tableFieldNames(this.tableName());
|
|
|
|
}
|
|
|
|
|
2017-06-15 20:18:48 +02:00
|
|
|
static fieldType(name) {
|
|
|
|
let fields = this.fields();
|
|
|
|
for (let i = 0; i < fields.length; i++) {
|
|
|
|
if (fields[i].name == name) return fields[i].type;
|
|
|
|
}
|
|
|
|
throw new Error('Unknown field: ' + name);
|
|
|
|
}
|
|
|
|
|
2017-05-20 00:16:50 +02:00
|
|
|
static fields() {
|
|
|
|
return this.db().tableFields(this.tableName());
|
|
|
|
}
|
|
|
|
|
2017-06-15 01:14:15 +02:00
|
|
|
static identifyItemType(item) {
|
2017-06-15 20:18:48 +02:00
|
|
|
if (!item) throw new Error('Cannot identify undefined item');
|
2017-06-19 21:26:27 +02:00
|
|
|
if ('body' in item || ('parent_id' in item && !!item.parent_id)) return BaseModel.MODEL_TYPE_NOTE;
|
|
|
|
if ('sync_time' in item) return BaseModel.MODEL_TYPE_FOLDER;
|
2017-06-15 01:14:15 +02:00
|
|
|
throw new Error('Cannot identify item: ' + JSON.stringify(item));
|
|
|
|
}
|
|
|
|
|
2017-05-20 00:16:50 +02:00
|
|
|
static new() {
|
|
|
|
let fields = this.fields();
|
|
|
|
let output = {};
|
|
|
|
for (let i = 0; i < fields.length; i++) {
|
|
|
|
let f = fields[i];
|
|
|
|
output[f.name] = f.default;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2017-05-18 21:58:01 +02:00
|
|
|
static fromApiResult(apiResult) {
|
|
|
|
let fieldNames = this.fieldNames();
|
|
|
|
let output = {};
|
|
|
|
for (let i = 0; i < fieldNames.length; i++) {
|
|
|
|
let f = fieldNames[i];
|
|
|
|
output[f] = f in apiResult ? apiResult[f] : null;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2017-05-18 22:31:40 +02:00
|
|
|
static modOptions(options) {
|
|
|
|
if (!options) {
|
|
|
|
options = {};
|
|
|
|
} else {
|
|
|
|
options = Object.assign({}, options);
|
|
|
|
}
|
|
|
|
if (!('trackChanges' in options)) options.trackChanges = true;
|
2017-06-20 21:18:19 +02:00
|
|
|
if (!('trackDeleted' in options)) options.trackDeleted = null;
|
2017-05-18 22:31:40 +02:00
|
|
|
if (!('isNew' in options)) options.isNew = 'auto';
|
2017-06-18 01:49:52 +02:00
|
|
|
if (!('autoTimestamp' in options)) options.autoTimestamp = true;
|
2017-06-25 01:19:11 +02:00
|
|
|
if (!('transactionNextQueries' in options)) options.transactionNextQueries = [];
|
2017-05-18 22:31:40 +02:00
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2017-06-24 19:40:03 +02:00
|
|
|
static count() {
|
|
|
|
return this.db().selectOne('SELECT count(*) as total FROM `' + this.tableName() + '`').then((r) => {
|
|
|
|
return r ? r['total'] : 0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-05-19 21:12:09 +02:00
|
|
|
static load(id) {
|
2017-06-11 23:11:14 +02:00
|
|
|
return this.loadByField('id', id);
|
|
|
|
}
|
|
|
|
|
2017-06-25 11:00:54 +02:00
|
|
|
static applySqlOptions(options, sql, params = null) {
|
|
|
|
if (!options) options = {};
|
|
|
|
|
|
|
|
if (options.orderBy) {
|
|
|
|
sql += ' ORDER BY ' + options.orderBy;
|
|
|
|
if (options.caseInsensitive === true) sql += ' COLLATE NOCASE';
|
|
|
|
if (options.orderByDir) sql += ' ' + options.orderByDir;
|
|
|
|
}
|
|
|
|
if (options.limit) sql += ' LIMIT ' + options.limit;
|
2017-06-25 14:49:46 +02:00
|
|
|
//if (options.fields && options.fields.length) sql = sql.replace('SELECT *', 'SELECT ' + this.db().escapeFields(options.fields).join(','));
|
2017-06-25 11:00:54 +02:00
|
|
|
|
|
|
|
return { sql: sql, params: params };
|
|
|
|
}
|
|
|
|
|
|
|
|
static async all(options = null) {
|
|
|
|
let q = this.applySqlOptions(options, 'SELECT * FROM `' + this.tableName() + '`');
|
2017-06-25 17:17:40 +02:00
|
|
|
return this.modelSelectAll(q.sql);
|
2017-06-25 11:00:54 +02:00
|
|
|
}
|
|
|
|
|
2017-06-17 20:12:09 +02:00
|
|
|
static modelSelectOne(sql, params = null) {
|
|
|
|
if (params === null) params = [];
|
2017-06-17 20:40:08 +02:00
|
|
|
return this.db().selectOne(sql, params).then((model) => {
|
2017-06-24 19:40:03 +02:00
|
|
|
return this.filter(this.addModelMd(model));
|
2017-06-17 20:40:08 +02:00
|
|
|
});
|
2017-06-17 20:12:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static modelSelectAll(sql, params = null) {
|
|
|
|
if (params === null) params = [];
|
2017-06-17 20:40:08 +02:00
|
|
|
return this.db().selectAll(sql, params).then((models) => {
|
2017-06-24 19:40:03 +02:00
|
|
|
return this.filterArray(this.addModelMd(models));
|
2017-06-17 20:40:08 +02:00
|
|
|
});
|
2017-06-17 20:12:09 +02:00
|
|
|
}
|
|
|
|
|
2017-06-25 13:39:42 +02:00
|
|
|
static loadByField(fieldName, fieldValue) {
|
2017-06-24 19:40:03 +02:00
|
|
|
return this.modelSelectOne('SELECT * FROM `' + this.tableName() + '` WHERE `' + fieldName + '` = ?', [fieldValue]);
|
2017-05-19 21:12:09 +02:00
|
|
|
}
|
|
|
|
|
2017-05-19 21:32:49 +02:00
|
|
|
static applyPatch(model, patch) {
|
|
|
|
model = Object.assign({}, model);
|
|
|
|
for (let n in patch) {
|
|
|
|
if (!patch.hasOwnProperty(n)) continue;
|
|
|
|
model[n] = patch[n];
|
|
|
|
}
|
|
|
|
return model;
|
|
|
|
}
|
|
|
|
|
2017-05-23 22:36:19 +02:00
|
|
|
static diffObjects(oldModel, newModel) {
|
|
|
|
let output = {};
|
2017-06-21 00:16:41 +02:00
|
|
|
let type = null;
|
2017-05-23 22:36:19 +02:00
|
|
|
for (let n in newModel) {
|
2017-06-21 00:16:41 +02:00
|
|
|
if (n == 'type_') {
|
|
|
|
type = n;
|
|
|
|
continue;
|
|
|
|
}
|
2017-05-23 22:36:19 +02:00
|
|
|
if (!newModel.hasOwnProperty(n)) continue;
|
|
|
|
if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
|
|
|
|
output[n] = newModel[n];
|
|
|
|
}
|
|
|
|
}
|
2017-06-21 00:16:41 +02:00
|
|
|
if (type !== null) output.type_ = type;
|
2017-05-23 22:36:19 +02:00
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2017-06-18 01:49:52 +02:00
|
|
|
static saveQuery(o, options) {
|
2017-05-20 00:16:50 +02:00
|
|
|
let temp = {}
|
|
|
|
let fieldNames = this.fieldNames();
|
|
|
|
for (let i = 0; i < fieldNames.length; i++) {
|
|
|
|
let n = fieldNames[i];
|
|
|
|
if (n in o) temp[n] = o[n];
|
|
|
|
}
|
|
|
|
o = temp;
|
|
|
|
|
2017-06-18 01:49:52 +02:00
|
|
|
let query = {};
|
2017-06-25 01:19:11 +02:00
|
|
|
let modelId = o.id;
|
2017-05-12 21:54:06 +02:00
|
|
|
|
2017-06-18 01:49:52 +02:00
|
|
|
if (options.autoTimestamp && this.hasField('updated_time')) {
|
2017-06-19 00:06:10 +02:00
|
|
|
o.updated_time = time.unixMs();
|
2017-05-19 21:32:49 +02:00
|
|
|
}
|
|
|
|
|
2017-06-18 01:49:52 +02:00
|
|
|
if (options.isNew) {
|
2017-05-19 21:12:09 +02:00
|
|
|
if (this.useUuid() && !o.id) {
|
2017-06-25 01:19:11 +02:00
|
|
|
modelId = uuid.create();
|
|
|
|
o.id = modelId;
|
2017-05-18 21:58:01 +02:00
|
|
|
}
|
2017-05-19 21:32:49 +02:00
|
|
|
|
|
|
|
if (!o.created_time && this.hasField('created_time')) {
|
2017-06-19 00:06:10 +02:00
|
|
|
o.created_time = time.unixMs();
|
2017-05-19 21:32:49 +02:00
|
|
|
}
|
|
|
|
|
2017-05-12 21:54:06 +02:00
|
|
|
query = Database.insertQuery(this.tableName(), o);
|
2017-05-11 22:14:01 +02:00
|
|
|
} else {
|
2017-05-12 21:54:06 +02:00
|
|
|
let where = { id: o.id };
|
|
|
|
let temp = Object.assign({}, o);
|
|
|
|
delete temp.id;
|
|
|
|
query = Database.updateQuery(this.tableName(), temp, where);
|
2017-05-11 22:14:01 +02:00
|
|
|
}
|
2017-05-12 21:54:06 +02:00
|
|
|
|
2017-06-25 01:19:11 +02:00
|
|
|
query.id = modelId;
|
2017-05-18 21:58:01 +02:00
|
|
|
|
|
|
|
return query;
|
|
|
|
}
|
|
|
|
|
2017-05-18 22:31:40 +02:00
|
|
|
static save(o, options = null) {
|
|
|
|
options = this.modOptions(options);
|
2017-06-29 22:52:52 +02:00
|
|
|
options.isNew = this.isNew(o, options);
|
2017-05-18 21:58:01 +02:00
|
|
|
|
2017-06-24 19:40:03 +02:00
|
|
|
o = this.filter(o);
|
|
|
|
|
2017-06-11 23:11:14 +02:00
|
|
|
let queries = [];
|
2017-06-18 01:49:52 +02:00
|
|
|
let saveQuery = this.saveQuery(o, options);
|
2017-06-25 01:19:11 +02:00
|
|
|
let modelId = saveQuery.id;
|
2017-05-18 21:58:01 +02:00
|
|
|
|
2017-06-11 23:11:14 +02:00
|
|
|
queries.push(saveQuery);
|
|
|
|
|
2017-06-25 01:19:11 +02:00
|
|
|
for (let i = 0; i < options.transactionNextQueries.length; i++) {
|
|
|
|
queries.push(options.transactionNextQueries[i]);
|
|
|
|
}
|
2017-06-11 23:11:14 +02:00
|
|
|
|
|
|
|
return this.db().transactionExecBatch(queries).then(() => {
|
2017-05-18 21:58:01 +02:00
|
|
|
o = Object.assign({}, o);
|
2017-06-25 01:19:11 +02:00
|
|
|
o.id = modelId;
|
2017-06-17 20:40:08 +02:00
|
|
|
o = this.addModelMd(o);
|
2017-06-24 19:40:03 +02:00
|
|
|
return this.filter(o);
|
2017-05-21 21:55:01 +02:00
|
|
|
}).catch((error) => {
|
|
|
|
Log.error('Cannot save model', error);
|
2017-05-18 21:58:01 +02:00
|
|
|
});
|
2017-05-10 21:51:43 +02:00
|
|
|
}
|
|
|
|
|
2017-06-29 22:52:52 +02:00
|
|
|
static isNew(object, options) {
|
|
|
|
if (options && ('isNew' in options)) {
|
|
|
|
// options.isNew can be "auto" too
|
|
|
|
if (options.isNew === true) return true;
|
|
|
|
if (options.isNew === false) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !object.id;
|
|
|
|
}
|
|
|
|
|
2017-06-20 00:18:24 +02:00
|
|
|
static deletedItems() {
|
|
|
|
return this.db().selectAll('SELECT * FROM deleted_items');
|
|
|
|
}
|
|
|
|
|
2017-06-20 21:18:19 +02:00
|
|
|
static remoteDeletedItem(itemId) {
|
|
|
|
return this.db().exec('DELETE FROM deleted_items WHERE item_id = ?', [itemId]);
|
|
|
|
}
|
|
|
|
|
2017-06-24 19:40:03 +02:00
|
|
|
static filterArray(models) {
|
|
|
|
let output = [];
|
|
|
|
for (let i = 0; i < models.length; i++) {
|
|
|
|
output.push(this.filter(models[i]));
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
static filter(model) {
|
2017-06-27 01:20:01 +02:00
|
|
|
if (!model) return model;
|
|
|
|
|
|
|
|
let output = Object.assign({}, model);
|
|
|
|
for (let n in output) {
|
|
|
|
if (!output.hasOwnProperty(n)) continue;
|
|
|
|
// The SQLite database doesn't have booleans so cast everything to int
|
|
|
|
if (output[n] === true) output[n] = 1;
|
|
|
|
if (output[n] === false) output[n] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
2017-06-24 19:40:03 +02:00
|
|
|
}
|
|
|
|
|
2017-05-18 22:31:40 +02:00
|
|
|
static delete(id, options = null) {
|
|
|
|
options = this.modOptions(options);
|
|
|
|
|
2017-06-25 09:52:25 +02:00
|
|
|
if (!id) throw new Error('Cannot delete object without an ID');
|
2017-05-16 22:25:19 +02:00
|
|
|
|
2017-05-18 22:31:40 +02:00
|
|
|
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => {
|
2017-06-20 21:18:19 +02:00
|
|
|
let trackDeleted = this.trackDeleted();
|
|
|
|
if (options.trackDeleted !== null) trackDeleted = options.trackDeleted;
|
|
|
|
if (trackDeleted) {
|
2017-06-20 00:18:24 +02:00
|
|
|
return this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.itemType(), id, time.unixMs()]);
|
|
|
|
}
|
2017-05-18 22:31:40 +02:00
|
|
|
});
|
2017-05-16 22:25:19 +02:00
|
|
|
}
|
|
|
|
|
2017-05-10 21:51:43 +02:00
|
|
|
static db() {
|
2017-05-20 00:16:50 +02:00
|
|
|
if (!this.db_) throw new Error('Accessing database before it has been initialised');
|
|
|
|
return this.db_;
|
2017-05-10 21:51:43 +02:00
|
|
|
}
|
|
|
|
|
2017-05-08 00:20:34 +02:00
|
|
|
}
|
|
|
|
|
2017-06-19 21:26:27 +02:00
|
|
|
BaseModel.MODEL_TYPE_NOTE = 1;
|
|
|
|
BaseModel.MODEL_TYPE_FOLDER = 2;
|
|
|
|
BaseModel.MODEL_TYPE_SETTING = 3;
|
2017-06-24 20:51:43 +02:00
|
|
|
BaseModel.MODEL_TYPE_RESOURCE = 4;
|
2017-06-06 22:01:43 +02:00
|
|
|
BaseModel.tableInfo_ = null;
|
|
|
|
BaseModel.tableKeys_ = null;
|
|
|
|
BaseModel.db_ = null;
|
2017-06-11 23:11:14 +02:00
|
|
|
BaseModel.dispatch = function(o) {};
|
2017-06-06 22:01:43 +02:00
|
|
|
|
2017-05-08 00:20:34 +02:00
|
|
|
export { BaseModel };
|