Shinobi/libs/sql.js

527 lines
22 KiB
JavaScript

var fs = require('fs');
var async = require("async");
module.exports = function(s,config){
s.onBeforeDatabaseLoadExtensions.forEach(function(extender){
extender(config)
})
//sql/database connection with knex
s.databaseOptions = {
client: config.databaseType,
connection: config.db,
}
if(s.databaseOptions.client.indexOf('sqlite')>-1){
s.databaseOptions.client = 'sqlite3'
s.databaseOptions.useNullAsDefault = true
try{
require('sqlite3')
}catch(err){
console.log('Installing SQlite3 Module...')
require('child_process').execSync('npm install sqlite3 --unsafe-perm')
}
}
if(s.databaseOptions.client === 'sqlite3' && s.databaseOptions.connection.filename === undefined){
s.databaseOptions.connection.filename = s.mainDirectory+"/shinobi.sqlite"
}
s.mergeQueryValues = function(query,values){
if(!values){values=[]}
var valuesNotFunction = true;
if(typeof values === 'function'){
var values = [];
valuesNotFunction = false;
}
if(values&&valuesNotFunction){
var splitQuery = query.split('?')
var newQuery = ''
splitQuery.forEach(function(v,n){
newQuery += v
var value = values[n]
if(value){
if(isNaN(value) || value instanceof Date){
newQuery += "'"+value+"'"
}else{
newQuery += value
}
}
})
}else{
newQuery = query
}
return newQuery
}
s.getUnixDate = function(value){
newValue = new Date(value).valueOf()
return newValue
}
s.stringToSqlTime = function(value){
newValue = new Date(value.replace('T',' '))
return newValue
}
var runQuery = async.queue(function(data, callback) {
s.databaseEngine
.raw(data.query,data.values)
.asCallback(callback)
}, 4);
const processWhereCondition = (dbQuery,where,didOne) => {
var whereIsArray = where instanceof Array;
if(where[0] && where[0] instanceof Array){
dbQuery.where(function() {
var _this = this
var didOneInsideGroup = false
where.forEach((whereInsideGroup) => {
console.log('LINE',whereInsideGroup)
processWhereCondition(_this,whereInsideGroup,didOneInsideGroup)
})
})
}else if(!didOne){
didOne = true
whereIsArray ? dbQuery.where(...where) : dbQuery.where(where)
}else if(where.length === 4){
const separator = where[0] + ''
where.shift()
switch(separator){
case'and':
whereIsArray ? dbQuery.andWhere(...where) : dbQuery.andWhere(where)
break;
case'or':
whereIsArray ? dbQuery.orWhere(...where) : dbQuery.orWhere(where)
break;
}
}else{
whereIsArray ? dbQuery.andWhere(...where) : dbQuery.andWhere(where)
}
}
const knexQuery = (options,callback) => {
if(!s.databaseEngine)return// console.log('Database Not Set');
if(config.debugLogVerbose && config.debugLog === true){
s.debugLog('s.knexQuery QUERY',options)
}
// options = {
// action: "",
// columns: "",
// table: ""
// }
var dbQuery
switch(options.action){
case'select':
options.columns = options.columns.indexOf(',') === -1 ? [options.columns] : options.columns.split(',');
dbQuery = s.databaseEngine.select(...options.columns).from(options.table)
break;
case'update':
dbQuery = s.databaseEngine(options.table).update(options.update)
break;
case'delete':
dbQuery = s.databaseEngine(options.table).del()
break;
case'insert':
dbQuery = s.databaseEngine(options.table).insert(options.insert)
break;
}
if(options.where){
var didOne = false;
options.where.forEach((where) => {
processWhereCondition(dbQuery,where,didOne)
})
}
if(options.orderBy){
dbQuery.orderBy(...options.orderBy)
}
if(options.limit){
dbQuery.limit(options.limit)
}
if(config.debugLog === true){
console.log(dbQuery.toString())
}
if(callback || options.update || options.insert){
dbQuery.asCallback(function(err,r) {
if(err)console.log(err)
if(callback)callback(err,r)
})
}
return dbQuery
}
const getDatabaseRows = function(options,callback){
//current cant handle `end` time
var whereQuery = [
['ke','=',options.groupKey],
]
const monitorRestrictions = options.monitorRestrictions
var frameLimit = parseInt(options.limit) || 500
const chosenDate = options.date
const startDate = options.startDate ? s.stringToSqlTime(options.startDate) : null
const endDate = options.endDate ? s.stringToSqlTime(options.endDate) : null
const startOperator = options.startOperator || '>='
const endOperator = options.endOperator || '<='
if(chosenDate){
if(chosenDate.indexOf('-') === -1 && !isNaN(chosenDate)){
chosenDate = parseInt(chosenDate)
}
var selectedDate = chosenDate
if(typeof chosenDate === 'string' && chosenDate.indexOf('.') > -1){
selectedDate = chosenDate.split('.')[0]
}
selectedDate = new Date(selectedDate)
var utcSelectedDate = new Date(selectedDate.getTime() + selectedDate.getTimezoneOffset() * 60000)
startDate = moment(utcSelectedDate).format('YYYY-MM-DD HH:mm:ss')
var dayAfter = utcSelectedDate
dayAfter.setDate(dayAfter.getDate() + 1)
endDate = moment(dayAfter).format('YYYY-MM-DD HH:mm:ss')
}
if(startDate){
if(endDate){
whereQuery.push(['time',startOperator,startDate])
whereQuery.push(['time',endOperator,endDate])
}else{
whereQuery.push(['time',startOperator,startDate])
}
}
if(monitorRestrictions && monitorRestrictions.length > 0){
whereQuery.push(monitorRestrictions)
}
if(options.archived){
whereQuery.push(['details','LIKE',`%"archived":"1"%`])
}
if(options.filename){
whereQuery.push(['filename','=',options.filename])
frameLimit = "1";
}
s.knexQuery({
action: "select",
columns: options.columns || "*",
table: options.table,
where: whereQuery,
orderBy: options.orderBy || ['time','desc'],
limit: frameLimit || '500'
},(err,r) => {
if(err){
callback({
ok: false,
total: 0,
limit: frameLimit,
[options.rowType || 'rows']: []
})
}else{
r.forEach(function(file){
file.details = s.parseJSON(file.details)
})
callback({
ok: true,
total: r.length,
limit: frameLimit,
[options.rowType || 'rows']: r
})
}
})
}
s.knexQuery = knexQuery
s.getDatabaseRows = getDatabaseRows
s.sqlQuery = function(query,values,onMoveOn,hideLog){
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(!onMoveOn){onMoveOn=function(){}}
// if(s.databaseOptions.client === 'pg'){
// query = query
// .replace(/ NOT LIKE /g," NOT ILIKE ")
// .replace(/ LIKE /g," ILIKE ")
// }
if(config.debugLog === true){
var mergedQuery = s.mergeQueryValues(query,values)
s.debugLog('s.sqlQuery QUERY',mergedQuery)
}
if(!s.databaseEngine || !s.databaseEngine.raw){
s.connectDatabase()
}
return runQuery.push({
query: query,
values: values
},function(err,r){
if(err && !hideLog){
console.log('s.sqlQuery QUERY ERRORED',query)
console.log('s.sqlQuery ERROR',err)
}
if(onMoveOn && typeof onMoveOn === 'function'){
switch(s.databaseOptions.client){
case'sqlite3':
if(!r)r=[]
break;
default:
if(r)r=r[0]
break;
}
onMoveOn(err,r)
}
})
}
s.connectDatabase = function(){
s.databaseEngine = require('knex')(s.databaseOptions)
}
s.preQueries = function(){
var knex = s.databaseEngine
var mySQLtail = ''
if(config.databaseType === 'mysql'){
mySQLtail = ' ENGINE=InnoDB DEFAULT CHARSET=utf8'
//add Presets table and modernize
var createPresetsTableQuery = 'CREATE TABLE IF NOT EXISTS `Presets` ( `ke` varchar(50) DEFAULT NULL, `name` text, `details` text, `type` varchar(50) DEFAULT NULL)'
s.sqlQuery( createPresetsTableQuery + mySQLtail + ';',[],function(err){
if(err)console.error(err)
if(config.databaseType === 'sqlite3'){
var aQuery = "ALTER TABLE Presets RENAME TO _Presets_old;"
aQuery += createPresetsTableQuery
aQuery += "INSERT INTO Presets (`ke`, `name`, `details`, `type`) SELECT `ke`, `name`, `details`, `type` FROM _Presets_old;COMMIT;DROP TABLE _Presets_old;"
}else{
s.sqlQuery('ALTER TABLE `Presets` CHANGE COLUMN `type` `type` VARCHAR(50) NULL DEFAULT NULL AFTER `details`;',[],function(err){
if(err)console.error(err)
},true)
}
},true)
//add Schedules table, will remove in future
s.sqlQuery("CREATE TABLE IF NOT EXISTS `Schedules` (`ke` varchar(50) DEFAULT NULL,`name` text,`details` text,`start` varchar(10) DEFAULT NULL,`end` varchar(10) DEFAULT NULL,`enabled` int(1) NOT NULL DEFAULT '1')" + mySQLtail + ';',[],function(err){
if(err)console.error(err)
},true)
//add Timelapses and Timelapse Frames tables, will remove in future
s.sqlQuery("CREATE TABLE IF NOT EXISTS `Timelapses` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`details` longtext,`date` date NOT NULL,`time` timestamp NOT NULL,`end` timestamp NOT NULL,`size` int(11)NOT NULL)" + mySQLtail + ';',[],function(err){
if(err)console.error(err)
},true)
s.sqlQuery("CREATE TABLE IF NOT EXISTS `Timelapse Frames` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`details` longtext,`filename` varchar(50) NOT NULL,`time` timestamp NULL DEFAULT NULL,`size` int(11) NOT NULL)" + mySQLtail + ';',[],function(err){
if(err)console.error(err)
},true)
//Add index to Videos table
s.sqlQuery('CREATE INDEX `videos_index` ON Videos(`time`);',[],function(err){
if(err && err.code !== 'ER_DUP_KEYNAME'){
console.error(err)
}
},true)
//Add index to Events table
s.sqlQuery('CREATE INDEX `events_index` ON Events(`ke`, `mid`, `time`);',[],function(err){
if(err && err.code !== 'ER_DUP_KEYNAME'){
console.error(err)
}
},true)
//Add index to Logs table
s.sqlQuery('CREATE INDEX `logs_index` ON Logs(`ke`, `mid`, `time`);',[],function(err){
if(err && err.code !== 'ER_DUP_KEYNAME'){
console.error(err)
}
},true)
//Add index to Monitors table
s.sqlQuery('CREATE INDEX `monitors_index` ON Monitors(`ke`, `mode`, `type`, `ext`);',[],function(err){
if(err && err.code !== 'ER_DUP_KEYNAME'){
console.error(err)
}
},true)
//Add index to Timelapse Frames table
s.sqlQuery('CREATE INDEX `timelapseframes_index` ON `Timelapse Frames`(`ke`, `mid`, `time`);',[],function(err){
if(err && err.code !== 'ER_DUP_KEYNAME'){
console.error(err)
}
},true)
//add Cloud Videos table, will remove in future
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Videos` (`mid` varchar(50) NOT NULL,`ke` varchar(50) DEFAULT NULL,`href` text NOT NULL,`size` float DEFAULT NULL,`time` timestamp NULL DEFAULT NULL,`end` timestamp NULL DEFAULT NULL,`status` int(1) DEFAULT \'0\',`details` text)' + mySQLtail + ';',[],function(err){
if(err)console.error(err)
},true)
//add Events Counts table, will remove in future
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Events Counts` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`details` longtext NOT NULL,`time` timestamp NOT NULL DEFAULT current_timestamp(),`end` timestamp NOT NULL DEFAULT current_timestamp(),`count` int(10) NOT NULL DEFAULT 1,`tag` varchar(30) DEFAULT NULL)' + mySQLtail + ';',[],function(err){
if(err && err.code !== 'ER_TABLE_EXISTS_ERROR'){
console.error(err)
}
s.sqlQuery('ALTER TABLE `Events Counts` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `details`;',[],function(err){
// console.error(err)
},true)
},true)
//add Cloud Timelapse Frames table, will remove in future
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Timelapse Frames` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`href` text NOT NULL,`details` longtext,`filename` varchar(50) NOT NULL,`time` timestamp NULL DEFAULT NULL,`size` int(11) NOT NULL)' + mySQLtail + ';',[],function(err){
if(err)console.error(err)
},true)
//create Files table
var createFilesTableQuery = "CREATE TABLE IF NOT EXISTS `Files` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`name` tinytext NOT NULL,`size` float NOT NULL DEFAULT '0',`details` text NOT NULL,`status` int(1) NOT NULL DEFAULT '0',`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP)"
s.sqlQuery(createFilesTableQuery + mySQLtail + ';',[],function(err){
if(err)console.error(err)
//add time column to Files table
if(config.databaseType === 'sqlite3'){
var aQuery = "ALTER TABLE Files RENAME TO _Files_old;"
aQuery += createPresetsTableQuery
aQuery += "INSERT INTO Files (`ke`, `mid`, `name`, `details`, `size`, `status`, `time`) SELECT `ke`, `mid`, `name`, `details`, `size`, `status`, `time` FROM _Files_old;COMMIT;DROP TABLE _Files_old;"
}else{
s.sqlQuery('ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;',[],function(err){
if(err && err.sqlMessage && err.sqlMessage.indexOf('Duplicate') === -1)console.error(err)
},true)
}
},true)
}
delete(s.preQueries)
}
s.sqlQueryBetweenTimesWithPermissions = (options,callback) => {
// options = {
// table: 'Events Counts',
// user: user,
// monitorId: req.params.id,
// startTime: req.query.start,
// endTime: req.query.end,
// startTimeOperator: req.query.startOperator,
// endTimeOperator: req.query.endOperator,
// limit: req.query.limit,
// archived: req.query.archived,
// endIsStartTo: !!req.query.endIsStartTo,
// parseRowDetails: true,
// rowName: 'counts'
// }
const user = options.user
const groupKey = options.groupKey
const monitorId = options.monitorId
const limit = options.limit
const archived = options.archived
const theTableSelected = options.table
const endIsStartTo = options.endIsStartTo
const userDetails = user.details
const rowName = options.rowName || 'rows'
const preliminaryValidationFailed = options.preliminaryValidationFailed || false
var endTime = options.endTime
var startTimeOperator = options.startTimeOperator
var endTimeOperator = options.endTimeOperator
var startTime = options.startTime
if(preliminaryValidationFailed){
if(options.noFormat){
callback([]);
}else{
callback({
ok: true,
[rowName]: [],
})
}
return
}
var queryString = 'SELECT * FROM `' + theTableSelected + '` WHERE ke=?'
var queryValues = [groupKey]
var queryStringCount = 'SELECT COUNT(*) FROM `' + theTableSelected + '` WHERE ke=?'
var queryCountValues = [groupKey]
if(archived === '1'){
queryString += ` AND details LIKE '%"archived":"1"'`
queryStringCount += ` AND details LIKE '%"archived":"1"'`
}
if(!monitorId){
if(
userDetails.sub &&
userDetails.monitors &&
userDetails.allmonitors !== '1'
){
try{
userDetails.monitors = JSON.parse(userDetails.monitors)
}catch(er){}
var queryWheres = []
userDetails.monitors.forEach(function(v,n){
queryWheres.push('mid=?')
queryValues.push(v)
})
queryString += ' AND ('+queryWheres.join(' OR ')+')'
queryStringCount += ' AND ('+queryWheres.join(' OR ')+')'
}
}else{
if(
!userDetails.sub ||
userDetails.allmonitors !== '0' ||
userDetails.monitors.indexOf(monitorId) >- 1
){
queryString += ' and mid=?'
queryValues.push(monitorId)
queryStringCount += ' and mid=?'
queryCountValues.push(monitorId)
}else{
res.end('[]');
return;
}
}
if(startTime || endTime){
if(startTime && startTime !== ''){
startTime = s.stringToSqlTime(startTime)
}
if(endTime && endTime !== ''){
endTime = s.stringToSqlTime(endTime)
}
if(!startTimeOperator || startTimeOperator==''){
startTimeOperator = startTimeOperator || '>='
}
if(!endTimeOperator || endTimeOperator==''){
endTimeOperator = endTimeOperator || '<='
}
var theEndParameter = '`end`'
if(endIsStartTo){
theEndParameter = '`time`'
}
switch(true){
case(startTime && startTime !== '' && endTime && endTime !== ''):
queryString += ' AND `time` '+startTimeOperator+' ? AND '+theEndParameter+' '+endTimeOperator+' ?';
queryStringCount += ' AND `time` '+startTimeOperator+' ? AND '+theEndParameter+' '+endTimeOperator+' ?';
queryValues.push(startTime)
queryValues.push(endTime)
queryCountValues.push(startTime)
queryCountValues.push(endTime)
break;
case(startTime && startTime !== ''):
queryString += ' AND `time` '+startTimeOperator+' ?';
queryStringCount += ' AND `time` '+startTimeOperator+' ?';
queryValues.push(startTime)
queryCountValues.push(startTime)
break;
case(endTime && endTime !== ''):
queryString += ' AND '+theEndParameter+' '+endTimeOperator+' ?';
queryStringCount += ' AND '+theEndParameter+' '+endTimeOperator+' ?';
queryValues.push(endTime)
queryCountValues.push(endTime)
break;
}
}
queryString += ' ORDER BY `time` DESC';
var rowLimit = limit || '100'
if(rowLimit !== '0'){
queryString += ' LIMIT ' + rowLimit
}
s.sqlQuery(queryString,queryValues,function(err,r){
if(!r){
callback({
total: 0,
limit: rowLimit,
skip: 0,
[rowName]: []
});
return
}
if(options.parseRowDetails){
r.forEach((row) => {
row.details = JSON.parse(row.details)
})
}
if(options.noCount){
if(options.noFormat){
callback(r)
}else{
callback({
ok: true,
[rowName]: r,
endIsStartTo: endIsStartTo
})
}
}else{
s.sqlQuery(queryStringCount,queryCountValues,function(err,count){
var skipOver = 0
if(rowLimit.indexOf(',') > -1){
skipOver = parseInt(rowLimit.split(',')[0])
rowLimit = parseInt(rowLimit.split(',')[1])
}else{
rowLimit = parseInt(rowLimit)
}
callback({
total: count[0]['COUNT(*)'],
limit: rowLimit,
skip: skipOver,
[rowName]: r,
endIsStartTo: endIsStartTo
})
})
}
})
}
}