251 lines
6.9 KiB
JavaScript
251 lines
6.9 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
var conf = require('./config.js');
|
|
|
|
var sqlite3 = require('sqlite3');
|
|
var paths = require('path');
|
|
var async = require('async');
|
|
|
|
const _ = require('lodash');
|
|
const assert = require('assert');
|
|
|
|
// database handles
|
|
var dbs = {};
|
|
|
|
exports.getModDatabasePath = getModDatabasePath;
|
|
exports.initializeDatabases = initializeDatabases;
|
|
|
|
exports.dbs = dbs;
|
|
|
|
function getDatabasePath(name) {
|
|
return paths.join(conf.config.paths.db, `${name}.sqlite3`);
|
|
}
|
|
|
|
function getModDatabasePath(moduleInfo, suffix) {
|
|
//
|
|
// Mods that use a database are stored in Config.paths.modsDb (e.g. enigma-bbs/db/mods)
|
|
// We expect that moduleInfo defines packageName which will be the base of the modules
|
|
// filename. An optional suffix may be supplied as well.
|
|
//
|
|
const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
|
|
|
|
assert(_.isObject(moduleInfo));
|
|
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
|
|
|
let full = moduleInfo.packageName;
|
|
if(suffix) {
|
|
full += `.${suffix}`;
|
|
}
|
|
|
|
assert(
|
|
(full.split('.').length > 1 && HOST_RE.test(full)),
|
|
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation');
|
|
|
|
return paths.join(conf.config.paths.modsDb, `${full}.sqlite3`);
|
|
}
|
|
|
|
function initializeDatabases(cb) {
|
|
// :TODO: this will need to change if more DB's are added ... why?
|
|
async.series(
|
|
[
|
|
function systemDb(callback) {
|
|
dbs.system = new sqlite3.Database(getDatabasePath('system'), function dbCreated(err) {
|
|
if(err) {
|
|
callback(err);
|
|
} else {
|
|
dbs.system.serialize(function serialized() {
|
|
createSystemTables();
|
|
});
|
|
callback(null);
|
|
}
|
|
});
|
|
},
|
|
function userDb(callback) {
|
|
dbs.user = new sqlite3.Database(getDatabasePath('user'), function dbCreated(err) {
|
|
if(err) {
|
|
callback(err);
|
|
} else {
|
|
dbs.user.serialize(function serialized() {
|
|
createUserTables();
|
|
createInitialUserValues();
|
|
});
|
|
callback(null);
|
|
}
|
|
});
|
|
},
|
|
function messageDb(callback) {
|
|
dbs.message = new sqlite3.Database(getDatabasePath('message'), function dbCreated(err) {
|
|
if(err) {
|
|
callback(err);
|
|
} else {
|
|
dbs.message.serialize(function serialized() {
|
|
createMessageBaseTables();
|
|
createInitialMessageValues();
|
|
});
|
|
callback(null);
|
|
}
|
|
});
|
|
}
|
|
],
|
|
function complete(err) {
|
|
cb(err);
|
|
}
|
|
);
|
|
}
|
|
|
|
function createSystemTables() {
|
|
dbs.system.run(
|
|
'CREATE TABLE IF NOT EXISTS system_property (' +
|
|
' prop_name VARCHAR PRIMARY KEY NOT NULL,' +
|
|
' prop_value VARCHAR NOT NULL' +
|
|
');'
|
|
);
|
|
|
|
//
|
|
// system_log can round log_timestamp for daily, monthly, etc.
|
|
// statistics as well as unique entries.
|
|
//
|
|
/*
|
|
dbs.system.run(
|
|
'CREATE TABLE IF NOT EXISTS system_log (' +
|
|
' log_timestamp DATETIME PRIMARY KEY NOT NULL ( ' +
|
|
' log_name VARCHARNOT NULL,' +
|
|
' log_value VARCHAR NOT NULL,' +
|
|
' UNIQUE(log_timestamp, log_name)' +
|
|
');'
|
|
);*/
|
|
}
|
|
|
|
function createUserTables() {
|
|
dbs.user.run(
|
|
'CREATE TABLE IF NOT EXISTS user (' +
|
|
' id INTEGER PRIMARY KEY,' +
|
|
' user_name VARCHAR NOT NULL,' +
|
|
' UNIQUE(user_name)' +
|
|
');'
|
|
);
|
|
|
|
// :TODO: create FK on delete/etc.
|
|
|
|
dbs.user.run(
|
|
'CREATE TABLE IF NOT EXISTS user_property (' +
|
|
' user_id INTEGER NOT NULL,' +
|
|
' prop_name VARCHAR NOT NULL,' +
|
|
' prop_value VARCHAR,' +
|
|
' UNIQUE(user_id, prop_name),' +
|
|
' FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE' +
|
|
');'
|
|
);
|
|
|
|
dbs.user.run(
|
|
'CREATE TABLE IF NOT EXISTS user_group_member (' +
|
|
' group_name VARCHAR NOT NULL,' +
|
|
' user_id INTEGER NOT NULL,' +
|
|
' UNIQUE(group_name, user_id)' +
|
|
');'
|
|
);
|
|
|
|
dbs.user.run(
|
|
'CREATE TABLE IF NOT EXISTS user_login_history (' +
|
|
' user_id INTEGER NOT NULL,' +
|
|
' user_name VARCHAR NOT NULL,' +
|
|
' timestamp DATETIME NOT NULL' +
|
|
');'
|
|
);
|
|
}
|
|
|
|
function createMessageBaseTables() {
|
|
dbs.message.run(
|
|
'CREATE TABLE IF NOT EXISTS message (' +
|
|
' message_id INTEGER PRIMARY KEY,' +
|
|
' area_tag VARCHAR NOT NULL,' +
|
|
' message_uuid VARCHAR(36) NOT NULL,' +
|
|
' reply_to_message_id INTEGER,' +
|
|
' to_user_name VARCHAR NOT NULL,' +
|
|
' from_user_name VARCHAR NOT NULL,' +
|
|
' subject,' + // FTS @ message_fts
|
|
' message,' + // FTS @ message_fts
|
|
' modified_timestamp DATETIME NOT NULL,' +
|
|
' view_count INTEGER NOT NULL DEFAULT 0,' +
|
|
' UNIQUE(message_uuid)' +
|
|
');'
|
|
);
|
|
|
|
dbs.message.run(
|
|
'CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts4 (' +
|
|
' content="message",' +
|
|
' subject,' +
|
|
' message' +
|
|
');'
|
|
);
|
|
|
|
dbs.message.run(
|
|
'CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN' +
|
|
' DELETE FROM message_fts WHERE docid=old.rowid;' +
|
|
'END;' +
|
|
'CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN' +
|
|
' DELETE FROM message_fts WHERE docid=old.rowid;' +
|
|
'END;' +
|
|
'' +
|
|
'CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN' +
|
|
' INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);' +
|
|
'END;' +
|
|
'CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN' +
|
|
' INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);' +
|
|
'END;'
|
|
);
|
|
|
|
// :TODO: need SQL to ensure cleaned up if delete from message?
|
|
dbs.message.run(
|
|
'CREATE TABLE IF NOT EXISTS message_meta (' +
|
|
' message_id INTEGER NOT NULL,' +
|
|
' meta_category INTEGER NOT NULL,' +
|
|
' meta_name VARCHAR NOT NULL,' +
|
|
' meta_value VARCHAR NOT NULL,' +
|
|
' UNIQUE(message_id, meta_category, meta_name, meta_value),' + // :TODO:why unique here?
|
|
' FOREIGN KEY(message_id) REFERENCES message(message_id)' +
|
|
');'
|
|
);
|
|
|
|
// :TODO: need SQL to ensure cleaned up if delete from message?
|
|
dbs.message.run(
|
|
'CREATE TABLE IF NOT EXISTS hash_tag (' +
|
|
' hash_tag_id INTEGER PRIMARY KEY,' +
|
|
' hash_tag_name VARCHAR NOT NULL,' +
|
|
' UNIQUE(hash_tag_name)' +
|
|
');'
|
|
);
|
|
|
|
// :TODO: need SQL to ensure cleaned up if delete from message?
|
|
dbs.message.run(
|
|
'CREATE TABLE IF NOT EXISTS message_hash_tag (' +
|
|
' hash_tag_id INTEGER NOT NULL,' +
|
|
' message_id INTEGER NOT NULL' +
|
|
');'
|
|
);
|
|
|
|
dbs.message.run(
|
|
'CREATE TABLE IF NOT EXISTS user_message_area_last_read (' +
|
|
' user_id INTEGER NOT NULL,' +
|
|
' area_tag VARCHAR NOT NULL,' +
|
|
' message_id INTEGER NOT NULL,' +
|
|
' UNIQUE(user_id, area_tag)' +
|
|
');'
|
|
);
|
|
|
|
dbs.message.run(
|
|
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
|
scan_toss VARCHAR NOT NULL,
|
|
area_tag VARCHAR NOT NULL,
|
|
message_id INTEGER NOT NULL,
|
|
UNIQUE(scan_toss, area_tag)
|
|
);`
|
|
);
|
|
}
|
|
|
|
function createInitialMessageValues() {
|
|
}
|
|
|
|
function createInitialUserValues() {
|
|
} |