2014-10-20 05:30:44 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// ENiGMA½
|
2020-06-08 03:21:31 +00:00
|
|
|
const conf = require('./config');
|
2018-06-23 03:26:46 +00:00
|
|
|
|
|
|
|
// deps
|
2022-06-05 20:04:25 +00:00
|
|
|
const sqlite3 = require('sqlite3');
|
|
|
|
const sqlite3Trans = require('sqlite3-trans');
|
|
|
|
const paths = require('path');
|
|
|
|
const async = require('async');
|
|
|
|
const _ = require('lodash');
|
|
|
|
const assert = require('assert');
|
|
|
|
const moment = require('moment');
|
2018-06-23 03:26:46 +00:00
|
|
|
|
|
|
|
// database handles
|
2017-09-16 23:13:11 +00:00
|
|
|
const dbs = {};
|
2014-10-21 04:47:13 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
exports.getTransactionDatabase = getTransactionDatabase;
|
|
|
|
exports.getModDatabasePath = getModDatabasePath;
|
|
|
|
exports.loadDatabaseForMod = loadDatabaseForMod;
|
|
|
|
exports.getISOTimestampString = getISOTimestampString;
|
|
|
|
exports.sanitizeString = sanitizeString;
|
|
|
|
exports.initializeDatabases = initializeDatabases;
|
2023-08-24 18:58:44 +00:00
|
|
|
exports.scheduledEventOptimizeDatabases = scheduledEventOptimizeDatabases;
|
2014-10-20 05:30:44 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
exports.dbs = dbs;
|
2014-10-20 05:30:44 +00:00
|
|
|
|
2017-09-16 23:13:11 +00:00
|
|
|
function getTransactionDatabase(db) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return sqlite3Trans.wrap(db);
|
2017-09-16 23:13:11 +00:00
|
|
|
}
|
|
|
|
|
2014-10-20 05:30:44 +00:00
|
|
|
function getDatabasePath(name) {
|
2020-06-08 03:21:31 +00:00
|
|
|
const Config = conf.get();
|
|
|
|
return paths.join(Config.paths.db, `${name}.sqlite3`);
|
2016-06-26 04:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getModDatabasePath(moduleInfo, suffix) {
|
2018-06-22 05:15:04 +00:00
|
|
|
//
|
2018-06-23 03:26:46 +00:00
|
|
|
// 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.
|
2018-06-22 05:15:04 +00:00
|
|
|
//
|
2022-06-05 20:04:25 +00:00
|
|
|
const HOST_RE =
|
2023-01-08 20:18:50 +00:00
|
|
|
/^(([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])$/;
|
2018-06-22 05:15:04 +00:00
|
|
|
|
|
|
|
assert(_.isObject(moduleInfo));
|
|
|
|
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
|
|
|
|
|
|
|
let full = moduleInfo.packageName;
|
2022-06-05 20:04:25 +00:00
|
|
|
if (suffix) {
|
2018-06-22 05:15:04 +00:00
|
|
|
full += `.${suffix}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(
|
2022-06-05 20:04:25 +00:00
|
|
|
full.split('.').length > 1 && HOST_RE.test(full),
|
|
|
|
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation'
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
|
2020-06-08 03:21:31 +00:00
|
|
|
const Config = conf.get();
|
|
|
|
return paths.join(Config.paths.modsDb, `${full}.sqlite3`);
|
2014-10-20 05:30:44 +00:00
|
|
|
}
|
|
|
|
|
2018-07-21 20:32:06 +00:00
|
|
|
function loadDatabaseForMod(modInfo, cb) {
|
2022-06-05 20:04:25 +00:00
|
|
|
const db = getTransactionDatabase(
|
|
|
|
new sqlite3.Database(getModDatabasePath(modInfo), err => {
|
2018-07-21 20:32:06 +00:00
|
|
|
return cb(err, db);
|
2022-06-05 20:04:25 +00:00
|
|
|
})
|
|
|
|
);
|
2018-07-21 20:32:06 +00:00
|
|
|
}
|
|
|
|
|
2016-10-07 03:03:04 +00:00
|
|
|
function getISOTimestampString(ts) {
|
2018-06-22 05:15:04 +00:00
|
|
|
ts = ts || moment();
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!moment.isMoment(ts)) {
|
|
|
|
if (_.isString(ts)) {
|
2018-11-23 21:47:18 +00:00
|
|
|
ts = ts.replace(/\//g, '-');
|
|
|
|
}
|
|
|
|
ts = moment(ts);
|
|
|
|
}
|
2018-11-26 03:31:25 +00:00
|
|
|
return ts.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
2016-10-07 03:03:04 +00:00
|
|
|
}
|
|
|
|
|
2018-12-16 06:42:19 +00:00
|
|
|
function sanitizeString(s) {
|
2022-06-05 20:04:25 +00:00
|
|
|
return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => {
|
2023-01-08 20:18:50 +00:00
|
|
|
// eslint-disable-line no-control-regex
|
2018-06-22 05:15:04 +00:00
|
|
|
switch (c) {
|
2022-06-05 20:04:25 +00:00
|
|
|
case '\0':
|
|
|
|
return '\\0';
|
|
|
|
case '\x08':
|
|
|
|
return '\\b';
|
|
|
|
case '\x09':
|
|
|
|
return '\\t';
|
|
|
|
case '\x1a':
|
|
|
|
return '\\z';
|
|
|
|
case '\n':
|
|
|
|
return '\\n';
|
|
|
|
case '\r':
|
|
|
|
return '\\r';
|
|
|
|
|
|
|
|
case '"':
|
2023-01-08 20:18:50 +00:00
|
|
|
case "'":
|
2018-06-22 05:15:04 +00:00
|
|
|
return `${c}${c}`;
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
case '\\':
|
|
|
|
case '%':
|
2018-06-22 05:15:04 +00:00
|
|
|
return `\\${c}`;
|
|
|
|
}
|
|
|
|
});
|
2018-01-27 04:34:32 +00:00
|
|
|
}
|
|
|
|
|
2015-11-07 01:25:07 +00:00
|
|
|
function initializeDatabases(cb) {
|
2022-06-05 20:04:25 +00:00
|
|
|
async.eachSeries(
|
2023-01-13 01:26:44 +00:00
|
|
|
['system', 'user', 'message', 'file', 'activitypub'],
|
2022-06-05 20:04:25 +00:00
|
|
|
(dbName, next) => {
|
|
|
|
dbs[dbName] = sqlite3Trans.wrap(
|
|
|
|
new sqlite3.Database(getDatabasePath(dbName), err => {
|
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
dbs[dbName].serialize(() => {
|
|
|
|
DB_INIT_TABLE[dbName](() => {
|
|
|
|
return next(null);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
2015-05-11 03:39:39 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 02:00:36 +00:00
|
|
|
function enableForeignKeys(db) {
|
2018-06-22 05:15:04 +00:00
|
|
|
db.run('PRAGMA foreign_keys = ON;');
|
2017-07-10 02:00:36 +00:00
|
|
|
}
|
|
|
|
|
2016-09-20 03:28:21 +00:00
|
|
|
const DB_INIT_TABLE = {
|
2022-06-05 20:04:25 +00:00
|
|
|
system: cb => {
|
2018-06-22 05:15:04 +00:00
|
|
|
enableForeignKeys(dbs.system);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// Various stat/event logging - see stat_log.js
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.system.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS system_stat (
|
2018-06-23 03:26:46 +00:00
|
|
|
stat_name VARCHAR PRIMARY KEY NOT NULL,
|
|
|
|
stat_value VARCHAR NOT NULL
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.system.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS system_event_log (
|
2018-06-23 03:26:46 +00:00
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
timestamp DATETIME NOT NULL,
|
|
|
|
log_name VARCHAR NOT NULL,
|
|
|
|
log_value VARCHAR NOT NULL,
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
UNIQUE(timestamp, log_name)
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.system.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS user_event_log (
|
2018-06-23 03:26:46 +00:00
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
timestamp DATETIME NOT NULL,
|
|
|
|
user_id INTEGER NOT NULL,
|
2018-07-21 20:32:06 +00:00
|
|
|
session_id VARCHAR NOT NULL,
|
2018-06-23 03:26:46 +00:00
|
|
|
log_name VARCHAR NOT NULL,
|
|
|
|
log_value VARCHAR NOT NULL,
|
|
|
|
|
2018-07-21 20:32:06 +00:00
|
|
|
UNIQUE(timestamp, user_id, session_id, log_name)
|
2018-06-23 03:26:46 +00:00
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2017-02-13 02:49:56 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(null);
|
|
|
|
},
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
user: cb => {
|
2018-06-22 05:15:04 +00:00
|
|
|
enableForeignKeys(dbs.user);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.user.run(
|
2020-06-08 03:21:31 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS user (
|
2018-06-23 03:26:46 +00:00
|
|
|
id INTEGER PRIMARY KEY,
|
2020-06-08 03:21:31 +00:00
|
|
|
user_name VARCHAR NOT NULL,
|
2018-06-23 03:26:46 +00:00
|
|
|
UNIQUE(user_name)
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// :TODO: create FK on delete/etc.
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.user.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS user_property (
|
2018-06-23 03:26:46 +00:00
|
|
|
user_id INTEGER NOT NULL,
|
|
|
|
prop_name VARCHAR NOT NULL,
|
|
|
|
prop_value VARCHAR,
|
|
|
|
UNIQUE(user_id, prop_name),
|
2020-06-08 03:21:31 +00:00
|
|
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
2018-06-23 03:26:46 +00:00
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2020-07-14 04:40:49 +00:00
|
|
|
dbs.user.run(
|
|
|
|
`CREATE INDEX IF NOT EXISTS user_property_id_and_name_index0
|
|
|
|
ON user_property (user_id, prop_name);`
|
|
|
|
);
|
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.user.run(
|
2020-06-08 03:21:31 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS user_group_member (
|
|
|
|
group_name VARCHAR NOT NULL,
|
2018-06-23 03:26:46 +00:00
|
|
|
user_id INTEGER NOT NULL,
|
|
|
|
UNIQUE(group_name, user_id)
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2019-01-03 05:13:42 +00:00
|
|
|
dbs.user.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS user_achievement (
|
|
|
|
user_id INTEGER NOT NULL,
|
|
|
|
achievement_tag VARCHAR NOT NULL,
|
|
|
|
timestamp DATETIME NOT NULL,
|
2019-01-14 01:19:00 +00:00
|
|
|
match VARCHAR NOT NULL,
|
2019-01-21 04:58:00 +00:00
|
|
|
title VARCHAR NOT NULL,
|
|
|
|
text VARCHAR NOT NULL,
|
|
|
|
points INTEGER NOT NULL,
|
2019-01-14 01:19:00 +00:00
|
|
|
UNIQUE(user_id, achievement_tag, match),
|
2019-01-03 05:13:42 +00:00
|
|
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
|
|
|
);`
|
|
|
|
);
|
|
|
|
|
2019-06-12 00:25:24 +00:00
|
|
|
//
|
|
|
|
// Table for temporary tokens, generally used for e.g. 'outside'
|
|
|
|
// access such as email links.
|
|
|
|
// Examples: PW reset, enabling of 2FA/OTP, etc.
|
|
|
|
//
|
|
|
|
dbs.user.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS user_temporary_token (
|
|
|
|
user_id INTEGER NOT NULL,
|
|
|
|
token VARCHAR NOT NULL,
|
2019-06-12 01:54:57 +00:00
|
|
|
token_type VARCHAR NOT NULL,
|
2019-06-12 00:25:24 +00:00
|
|
|
timestamp DATETIME NOT NULL,
|
2019-06-12 01:54:57 +00:00
|
|
|
UNIQUE(user_id, token_type),
|
2019-06-12 00:25:24 +00:00
|
|
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
|
|
|
);`
|
|
|
|
);
|
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(null);
|
|
|
|
},
|
2022-06-05 20:04:25 +00:00
|
|
|
message: cb => {
|
2018-06-22 05:15:04 +00:00
|
|
|
enableForeignKeys(dbs.message);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS message (
|
2020-06-08 03:21:31 +00:00
|
|
|
message_id INTEGER PRIMARY KEY,
|
2018-06-23 03:26:46 +00:00
|
|
|
area_tag VARCHAR NOT NULL,
|
2020-06-08 03:21:31 +00:00
|
|
|
message_uuid VARCHAR(36) NOT NULL,
|
2018-06-23 03:26:46 +00:00
|
|
|
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,
|
2020-06-08 03:21:31 +00:00
|
|
|
UNIQUE(message_uuid)
|
2018-06-23 03:26:46 +00:00
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE INDEX IF NOT EXISTS message_by_area_tag_index
|
2018-06-23 03:26:46 +00:00
|
|
|
ON message (area_tag);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts4 (
|
2018-06-23 03:26:46 +00:00
|
|
|
content="message",
|
|
|
|
subject,
|
|
|
|
message
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
DELETE FROM message_fts WHERE docid=old.rowid;
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2018-01-15 19:22:11 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
DELETE FROM message_fts WHERE docid=old.rowid;
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS message_meta (
|
2018-06-23 03:26:46 +00:00
|
|
|
message_id INTEGER NOT NULL,
|
|
|
|
meta_category INTEGER NOT NULL,
|
|
|
|
meta_name VARCHAR NOT NULL,
|
|
|
|
meta_value VARCHAR NOT NULL,
|
2020-06-08 03:21:31 +00:00
|
|
|
UNIQUE(message_id, meta_category, meta_name, meta_value),
|
2018-06-23 03:26:46 +00:00
|
|
|
FOREIGN KEY(message_id) REFERENCES message(message_id) ON DELETE CASCADE
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// :TODO: need SQL to ensure cleaned up if delete from message?
|
2018-06-22 05:15:04 +00:00
|
|
|
/*
|
2023-01-07 20:48:12 +00:00
|
|
|
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,
|
|
|
|
);`
|
|
|
|
);
|
|
|
|
*/
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS user_message_area_last_read (
|
2018-06-23 03:26:46 +00:00
|
|
|
user_id INTEGER NOT NULL,
|
|
|
|
area_tag VARCHAR NOT NULL,
|
|
|
|
message_id INTEGER NOT NULL,
|
|
|
|
UNIQUE(user_id, area_tag)
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2018-01-15 19:22:11 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.message.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
2018-06-23 03:26:46 +00:00
|
|
|
scan_toss VARCHAR NOT NULL,
|
|
|
|
area_tag VARCHAR NOT NULL,
|
|
|
|
message_id INTEGER NOT NULL,
|
|
|
|
UNIQUE(scan_toss, area_tag)
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2017-02-13 02:49:56 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(null);
|
|
|
|
},
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
file: cb => {
|
2018-06-22 05:15:04 +00:00
|
|
|
enableForeignKeys(dbs.file);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
2018-06-23 03:26:46 +00:00
|
|
|
// :TODO: should any of this be unique -- file_sha256 unless dupes are allowed on the system
|
2018-06-22 05:15:04 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS file (
|
2018-06-23 03:26:46 +00:00
|
|
|
file_id INTEGER PRIMARY KEY,
|
|
|
|
area_tag VARCHAR NOT NULL,
|
|
|
|
file_sha256 VARCHAR NOT NULL,
|
|
|
|
file_name, /* FTS @ file_fts */
|
|
|
|
storage_tag VARCHAR NOT NULL,
|
|
|
|
desc, /* FTS @ file_fts */
|
2020-06-08 03:21:31 +00:00
|
|
|
desc_long, /* FTS @ file_fts */
|
2018-06-23 03:26:46 +00:00
|
|
|
upload_timestamp DATETIME NOT NULL
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE INDEX IF NOT EXISTS file_by_area_tag_index
|
2018-06-23 03:26:46 +00:00
|
|
|
ON file (area_tag);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE INDEX IF NOT EXISTS file_by_sha256_index
|
2018-06-23 03:26:46 +00:00
|
|
|
ON file (file_sha256);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2017-03-09 05:37:02 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts4 (
|
2018-06-23 03:26:46 +00:00
|
|
|
content="file",
|
|
|
|
file_name,
|
|
|
|
desc,
|
|
|
|
desc_long
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
DELETE FROM file_fts WHERE docid=old.rowid;
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-29 03:54:25 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
DELETE FROM file_fts WHERE docid=old.rowid;
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TRIGGER IF NOT EXISTS file_after_insert AFTER INSERT ON file BEGIN
|
2018-06-23 03:26:46 +00:00
|
|
|
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
|
|
|
END;`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS file_meta (
|
2018-06-23 03:26:46 +00:00
|
|
|
file_id INTEGER NOT NULL,
|
|
|
|
meta_name VARCHAR NOT NULL,
|
|
|
|
meta_value VARCHAR NOT NULL,
|
2020-06-08 03:21:31 +00:00
|
|
|
UNIQUE(file_id, meta_name, meta_value),
|
2018-06-23 03:26:46 +00:00
|
|
|
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS hash_tag (
|
2018-06-23 03:26:46 +00:00
|
|
|
hash_tag_id INTEGER PRIMARY KEY,
|
|
|
|
hash_tag VARCHAR NOT NULL,
|
2020-06-08 03:21:31 +00:00
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
UNIQUE(hash_tag)
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-09-20 03:28:21 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS file_hash_tag (
|
2018-06-23 03:26:46 +00:00
|
|
|
hash_tag_id INTEGER NOT NULL,
|
|
|
|
file_id INTEGER NOT NULL,
|
2020-06-08 03:21:31 +00:00
|
|
|
|
2020-11-29 23:24:51 +00:00
|
|
|
UNIQUE(hash_tag_id, file_id),
|
|
|
|
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
2018-06-23 03:26:46 +00:00
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2016-10-25 03:49:45 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS file_user_rating (
|
2018-06-23 03:26:46 +00:00
|
|
|
file_id INTEGER NOT NULL,
|
|
|
|
user_id INTEGER NOT NULL,
|
|
|
|
rating INTEGER NOT NULL,
|
2017-02-08 03:20:10 +00:00
|
|
|
|
2020-11-29 23:24:51 +00:00
|
|
|
UNIQUE(file_id, user_id),
|
|
|
|
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
|
|
|
-- Note that we cannot CASCADE if user_id is removed from user.db
|
|
|
|
-- See processing in oputil's removeUser()
|
2018-06-23 03:26:46 +00:00
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2017-02-08 03:20:10 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
2018-06-23 03:26:46 +00:00
|
|
|
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
|
|
|
expire_timestamp DATETIME NOT NULL
|
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2017-02-13 02:49:56 +00:00
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
dbs.file.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS file_web_serve_batch (
|
2018-06-23 03:26:46 +00:00
|
|
|
hash_id VARCHAR NOT NULL,
|
|
|
|
file_id INTEGER NOT NULL,
|
2017-09-26 16:39:23 +00:00
|
|
|
|
2020-11-29 23:24:51 +00:00
|
|
|
UNIQUE(hash_id, file_id),
|
|
|
|
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
2018-06-23 03:26:46 +00:00
|
|
|
);`
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
2017-09-26 16:39:23 +00:00
|
|
|
|
2023-01-13 01:26:44 +00:00
|
|
|
return cb(null);
|
|
|
|
},
|
|
|
|
activitypub: cb => {
|
2023-01-28 19:59:12 +00:00
|
|
|
enableForeignKeys(dbs.activitypub);
|
|
|
|
|
2023-01-23 21:45:56 +00:00
|
|
|
// ActivityPub Collections of various types such as followers, following, likes, ...
|
2023-01-14 04:27:02 +00:00
|
|
|
dbs.activitypub.run(
|
2023-01-21 08:19:19 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS collection (
|
2023-02-05 17:42:30 +00:00
|
|
|
collection_id VARCHAR NOT NULL, -- ie: http://somewhere.com/_enig/ap/users/NuSkooler/followers
|
2023-01-20 23:03:27 +00:00
|
|
|
name VARCHAR NOT NULL, -- examples: followers, follows, ...
|
|
|
|
timestamp DATETIME NOT NULL, -- Timestamp in which this entry was created
|
2023-01-28 18:55:31 +00:00
|
|
|
owner_actor_id VARCHAR NOT NULL, -- Local, owning Actor ID, or the #Public magic collection ID
|
|
|
|
object_id VARCHAR NOT NULL, -- Object ID from obj_json.id
|
|
|
|
object_json VARCHAR NOT NULL, -- Object varies by collection (obj_json.type)
|
|
|
|
is_private INTEGER NOT NULL, -- Is this object private to |owner_actor_id|?
|
2023-01-21 08:19:19 +00:00
|
|
|
|
2023-01-28 18:55:31 +00:00
|
|
|
UNIQUE(name, collection_id, object_id)
|
2023-01-14 04:27:02 +00:00
|
|
|
);`
|
|
|
|
);
|
|
|
|
|
2023-01-20 23:03:27 +00:00
|
|
|
dbs.activitypub.run(
|
2023-01-28 18:55:31 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS collection_entry_by_name_actor_id_index0
|
|
|
|
ON collection (name, owner_actor_id);`
|
|
|
|
);
|
|
|
|
|
|
|
|
dbs.activitypub.run(
|
|
|
|
`CREATE INDEX IF NOT EXISTS collection_entry_by_name_collection_id_index0
|
|
|
|
ON collection (name, collection_id);`
|
2023-01-20 23:03:27 +00:00
|
|
|
);
|
|
|
|
|
2023-02-20 23:01:16 +00:00
|
|
|
// Collection meta contains 0:N additional metadata records for a object_id in a collection
|
|
|
|
dbs.activitypub.run(
|
|
|
|
`CREATE TABLE IF NOT EXISTS collection_object_meta (
|
|
|
|
collection_id VARCHAR NOT NULL,
|
|
|
|
name VARCHAR NOT NULL,
|
|
|
|
object_id VARCHAR NOT NULL,
|
|
|
|
meta_name VARCHAR NOT NULL,
|
|
|
|
meta_value VARCHAR NOT NULL,
|
|
|
|
|
|
|
|
UNIQUE(collection_id, object_id, meta_name),
|
|
|
|
FOREIGN KEY(name, collection_id, object_id) REFERENCES collection(name, collection_id, object_id) ON DELETE CASCADE
|
|
|
|
);`
|
|
|
|
);
|
|
|
|
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(null);
|
2022-06-05 20:04:25 +00:00
|
|
|
},
|
|
|
|
};
|
2023-08-24 18:58:44 +00:00
|
|
|
|
|
|
|
function scheduledEventOptimizeDatabases(args, cb) {
|
|
|
|
async.forEachSeries(
|
|
|
|
Object.keys(dbs),
|
|
|
|
(db, nextDb) => {
|
|
|
|
return db.run('PRAGMA OPTIMIZE', nextDb);
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|