UNPKG

dotweb

Version:

ready to use web server components

1,421 lines (1,397 loc) 82.9 kB
"use strict"; const fs = require("fs"); const crypto = require("crypto"); const path = require("path"); const mongoose = require("mongoose"); const checksum = require("checksum"); const redis = require("redis"); const jwt = require("jsonwebtoken"); const http = require("http"); const https = require("https"); const express = require("express"); const cookieParser = require("cookie-parser"); const cors = require("cors"); const multer = require("multer"); const socket = require("socket.io"); const admin = require("firebase-admin"); const { populate } = require("mongoose/lib/utils"); const cacheState = { null: 0, private: 1, public: 2, }; const accessLevel = { null: 0, public: 1, private: 2, full: 3, }; const Console = class Console { colors = { reset: "\x1b[0m", bright: "\x1b[1m", dim: "\x1b[2m", underscore: "\x1b[4m", blink: "\x1b[5m", reverse: "\x1b[7m", hidden: "\x1b[8m", black: "\x1b[30m", red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", white: "\x1b[37m", fgblack: "\x1b[30m", fgred: "\x1b[31m", fggreen: "\x1b[32m", fgyellow: "\x1b[33m", fgblue: "\x1b[34m", fgmagenta: "\x1b[35m", fgcyan: "\x1b[36m", fgwhite: "\x1b[37m", bgblack: "\x1b[40m", bgred: "\x1b[41m", bggreen: "\x1b[42m", bgyellow: "\x1b[43m", bgblue: "\x1b[44m", bgmagenta: "\x1b[45m", bgcyan: "\x1b[46m", bgwhite: "\x1b[47m", }; pid_length = 6; files = []; constructor(...filenames) { this.threadId = process.pid.toString(); if (this.threadId.length > this.pid_length) this.threadId = this.threadId.substr(this.threadId.length - this.pid_length, this.pid_length); else if (this.threadId.length < this.pid_length) this.threadId = "0".repeat(this.pid_length - this.threadId.length) + this.threadId; this.create = this.create.bind(this); this.destroy = this.destroy.bind(this); this.formatConsoleDateTime = this.formatConsoleDateTime.bind(this); this.formatConsoleTime = this.formatConsoleTime.bind(this); this.log = this.log.bind(this); this.create(...filenames); } create(...filenames) { this.files = filenames.map(filename => fs.createWriteStream(path.resolve(filename), { flags: "a", }) ); this.filenames = filenames; } destroy() { this.files.map((ws, index) => { ws.end(); }); this.files = []; } formatConsoleDateTime(date) { let year = date.getFullYear(); let month = date.getMonth() + 1; let day = date.getDate(); let hour = date.getHours(); let minutes = date.getMinutes(); let seconds = date.getSeconds(); return ( "[" + year + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day) + " " + (hour < 10 ? "0" + hour : hour) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + "] " ); } formatConsoleTime(date) { let hour = date.getHours(); let minutes = date.getMinutes(); let seconds = date.getSeconds(); let milliseconds = date.getMilliseconds(); return ( "[" + (hour < 10 ? "0" + hour : hour) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + (milliseconds < 10 ? "00" + milliseconds : milliseconds < 100 ? "0" + milliseconds : milliseconds) + "] " ); } log(text, visual = [], ...more) { let affect = "", bgEndSpace = ""; if (typeof visual === "string") visual = [visual]; if (Array.isArray(visual) && visual.length > 0) { for (let color of visual.map(x => x.toString().toLowerCase())) { if (typeof this.colors[color] === "string") { if (!bgEndSpace && color.substr(0, 2) === "bg") bgEndSpace = " "; affect = this.colors[color] + affect; } } } let sText = this.threadId + " " + this.formatConsoleTime(new Date()) + text; let lText = this.threadId + " " + this.formatConsoleDateTime(new Date()) + text; if (affect) { sText = affect + sText + bgEndSpace + this.colors.reset; lText = affect + lText + bgEndSpace + this.colors.reset; } console.log(sText); if (this.files.length > 0) this.files[0].write(lText + "\n"); for (const index in more) { if (more[index] && typeof this.files[index + 1] !== "undefined") { this.files[index + 1].write(lText + "\n"); } } } }; const FileWatcher = class FileWatcher { cache = {}; over = false; #called = false; #password = ""; #iv64 = "s7epMK3v"; watcher_interval = 0; file_has_eof = false; encoding = "base64"; constructor(application, { watcher_interval, file_has_eof, encoding } = {}) { this.log = (text, visual, more) => application.log(`require ::: ${text}`, visual, more); const argv = 3 > process.argv.length ? ["", "", ""] : process.argv.slice(process.argv.length - 1, process.argv.length); this.#called = argv[0].length === 32; this.#password = argv[0]; this.create = this.create.bind(this); this.destroy = this.destroy.bind(this); this.encode = this.encode.bind(this); this.decode = this.decode.bind(this); this.processBuffer = this.processBuffer.bind(this); this.parser = this.parser.bind(this); this.validate = this.validate.bind(this); this.timer = this.timer.bind(this); this.watcher = this.watcher.bind(this); this.remove = this.remove.bind(this); this.loader = this.loader.bind(this); this.require = this.require.bind(this); this.create({ watcher_interval, file_has_eof, encoding, }); } do = (event, ...args) => (typeof this[event] === "function" ? this[event](...args) : () => {}); create({ watcher_interval, file_has_eof, encoding } = {}) { if (watcher_interval) this.watcher_interval = watcher_interval; if (file_has_eof) this.file_has_eof = file_has_eof; if (encoding) this.encoding = encoding; this.do("open"); this.do("activate"); } destroy() { this.over = true; for (const filename in this.cache) this.remove(filename, true); this.log("destroyed", "fgRed"); } encode(plaintext, iv64, encoding) { const cipher = crypto.createCipheriv("aes-256-ctr", this.#password, Buffer.from(iv64, "base64")); let encrypted = cipher.update(plaintext, "utf8", encoding); encrypted += cipher.final(encoding); return encrypted; } decode(encrypted, iv64, encoding) { const decipher = crypto.createDecipheriv("aes-256-ctr", this.#password, Buffer.from(iv64, "base64")); let plaintext = decipher.update(encrypted, encoding, "utf8"); plaintext += decipher.final("utf8"); return plaintext; } processBuffer(buffer) { if (typeof buffer === "string" && buffer.length === 38) { try { return { cached: true, iv64: eval(buffer.replace("require", "String")), }; } catch {} } return { cached: false, iv64: crypto.randomBytes(16).toString("base64"), }; } parser(reload, buffer, filename, construct, args) { const _module = new module.constructor(); if (reload) { this.log(`reload dynamic module ${path.relative("./", filename)}`, "fgYellow"); } else { this.log(`${this.watcher_interval ? "dynamic module" : "module"} ${path.relative("./", filename)}`, "fgCyan"); } _module.paths = module.paths; _module._compile(buffer, filename); return construct ? new _module.exports(...args) : args.length === 0 ? _module.exports : _module.exports(...args); } validate(data) { let output = data .toString() .split(/\n|\r|\n\r|\r\n"/g) .filter(text => typeof text !== "undefined" && text.length !== 0); return output.length > 0 && output.pop().replace(/ /g, "") === "//eof"; } timer(filename, construct, args, hash) { setTimeout(() => { if (!this.over) { if (fs.existsSync(filename)) { checksum.file(filename, (err, checksum) => { if (!err && checksum !== hash) { this.loader(filename, construct, args, checksum, true); } else { this.timer(filename, construct, args, hash); } }); } else { this.remove(filename); this.timer(filename, construct, args, hash); } } }, this.watcher_interval * 1000); } watcher(filename, construct, args, hash) { if (!this.over && this.watcher_interval > 0) { if (hash) { this.timer(filename, construct, args, hash); } else { checksum.file(filename, (err, checksum) => { if (err) { setTimeout(() => { this.watcher(filename, construct, args, hash); }, this.watcher_interval * 1000); } else { this.timer(filename, construct, args, checksum); } }); } } } remove(filename, silence = false) { if (this.cache[filename]) { try { if (!silence) this.log(`removed dynamic module ${path.relative("./", filename)}`, "fgRed"); if (typeof this.cache[filename].destroy === "function") this.cache[filename].destroy(); delete this.cache[filename]; delete require.cache[require.resolve(filename)]; } catch (e) { // this.log(e, ["bgRed","fgBlack"]); } } } loader(filename, construct, args, hash, reload = false) { let scriptBuffer = fs.readFileSync(filename).toString(); if (this.#called) { const { cached, iv64 } = this.processBuffer(scriptBuffer); const encrypted = path.join(path.dirname(filename), ".", this.encode(path.basename(filename, ".js"), this.#iv64, "hex")); if (cached && iv64 && fs.existsSync(encrypted)) { scriptBuffer = this.decode(fs.readFileSync(encrypted), iv64); this.watcher(filename, construct, args, hash); } else if (iv64 && !cached) { fs.writeFile(encrypted, this.encode(scriptBuffer, iv64), err => { if (!err) { fs.writeFileSync(filename, `require("${iv64}");\r\n`); this.watcher(filename, construct, args, hash); } }); } else { return false; } } else { this.watcher(filename, construct, args, hash); } let valid = !this.file_has_eof || this.validate(scriptBuffer); if (!valid) { this.log(`no "//eof" for ${path.relative("./", filename)}`, "fgRed"); } if (!this.over && (!reload || valid)) { this.remove(filename, true); this.cache[filename] = this.parser(reload, scriptBuffer, filename, construct, args); return this.cache[filename]; } } require(filename, construct = false, ...args) { let resolved = path.resolve(filename); if (fs.existsSync(resolved + ".js")) resolved += ".js"; // else if (fs.existsSync(resolved + "/index.js")) resolved += "/index.js"; if (fs.existsSync(resolved)) { return this.loader(resolved, construct, args, false, false); } else { return require(filename); } } }; const ModuleObject = class ModuleObject { _override() { return false; } override() { return false; } constructor(initializer, name) { this.initializer = initializer; this.name = name; this._override = this._override.bind(this); this.override = this.override.bind(this); const _properties = this._override(); if (typeof _properties === "object") { for (const name in _properties) { this[name] = _properties[name]; } } const properties = this.override(); if (typeof properties === "object") { for (const name in properties) { this[name] = properties[name]; } } } register() { this.initializer[this.name] = this; } asignment() { return this.initializer[this.name]; } }; const Configuration = class Configuration extends ModuleObject { constructor(initializer, name) { super(initializer, name); if (typeof initializer[name] === "undefined" && typeof initializer.build === "function") { initializer.log("build configuration"); initializer.build(this); } if (typeof initializer[name] !== "undefined" && typeof initializer.rebuild === "function") { initializer.log("rebuild configuration"); initializer.rebuild(this, initializer[name]); } this.register(); } }; const Utilities = class Utilities extends ModuleObject { constructor(initializer, name) { super(initializer, name); this.register(); } }; const Initializer = class Initializer { log_files = []; watcher_interval = 2; file_has_eof = false; constructor(name, { log_files, watcher_interval, file_has_eof } = {}) { if (log_files) this.log_files = log_files; if (watcher_interval) this.watcher_interval = watcher_interval; if (file_has_eof) this.file_has_eof = file_has_eof; this.name = name; this.reload = this.reload.bind(this); this.destroy = this.destroy.bind(this); this.import = this.import.bind(this); this.require = this.require.bind(this); this.console = new Console(...this.log_files); this.log = (text, visual, more) => this.console.log(`${name} ${text}`, visual, more); this.fileWatcher = new FileWatcher(this, { watcher_interval: this.watcher_interval, file_has_eof: this.file_has_eof, }); this.do("open"); this.do("activate"); } reload({ log_files, watcher_interval, file_has_eof }) { if (log_files) this.log_files = log_files; if (watcher_interval) this.watcher_interval = watcher_interval; if (file_has_eof) this.file_has_eof = file_has_eof; this.console.create(...this.log_files); this.fileWatcher.create({ watcher_interval: this.watcher_interval, file_has_eof: this.file_has_eof, }); this.do("activate"); } do = (event, ...args) => (typeof this[event] === "function" ? this[event](...args) : () => {}); destroy() { this.do("close"); if (typeof this.console !== "undefined" && typeof this.console.destroy === "function") this.console.destroy(); if (typeof this.fileWatcher !== "undefined" && typeof this.fileWatcher.destroy === "function") this.fileWatcher.destroy(); this.log("watcher ::: destroyed", "fgRed"); } config = {}; import(filename, name = false) { const _module = this.require(filename, true, this, name || filename); if (_module instanceof ModuleObject) { return _module.asignment(); } return _module; } require(filename, constract = false, ...args) { return this.fileWatcher.require(filename, constract, ...args); } }; const Model = class Model { async fromCache(name, def) { const value = await this.cache(name); return typeof value !== "undefined" ? value : def; } async cache(name, data, duration) { if (typeof this.application.services.cache === "undefined") { return undefined; } return this.application.services.cache.apply(`${this.application.name}#Model#${this.name}#${name}`, data, duration); } // library level _override() { return false; } // developer level override() { return false; } constructor(application, name) { this.log = (text, visual, more) => application.log(`model[${name}] ::: ${text}`, visual, more); this.warn = (text, more) => application.log(`model[${name}] ::: ${text}`, ["bgRed", "fgWhite"], more); this.application = application; this.name = name; this._override = this._override.bind(this); const _properties = this._override(); if (typeof _properties === "object") { for (const name in _properties) { this[name] = _properties[name]; } } this.override = this.override.bind(this); const properties = this.override(); if (typeof properties === "object") { for (const name in properties) { this[name] = properties[name]; } } this.fromCache = this.fromCache.bind(this); this.fromCache = this.fromCache.bind(this); this.cache = this.cache.bind(this); this.register = this.register.bind(this); this.destroy = this.destroy.bind(this); this.create = this.create.bind(this); this.create(); this.do("open"); this.register(); } do = (event, ...args) => (typeof this[event] === "function" ? this[event](...args) : () => {}); register() { this.application.models[this.name] = this; this.log("registered"); this.do("activate"); } destroy() { this.do("close"); delete this.application.models[this.name]; this.log("destroyed", "fgRed"); } create() { this.model = {}; } }; const ModelMongoose = class ModelMongoose extends Model { constructor(application, name) { super(application, name); this.close = this.close.bind(this); this.schema = this.schema.bind(this); this.make = this.make.bind(this); } close() { for (const name in this.application.mongooseModels) { delete this.application.mongooseModels[name][this.name]; } this.application.service(ServiceMongoose, service => delete service.connection.models[this.name]); } schema() { return new mongoose.Schema({}); } make(service, name) { if (service instanceof ServiceMongoose && this.mongooseSchema) { if (typeof this.application.mongooseModels !== "object") { this.application.mongooseModels = {}; } if (typeof this.application.mongooseModels[name] !== "object") { this.application.mongooseModels[name] = {}; } this.application.mongooseModels[name][this.name] = service.connection.model(this.name, this.mongooseSchema); } } create() { try { this.mongooseSchema = this.schema(); for (const name in this.application.services) { this.make(this.application.services[name], name); } // this.model = mongoose.model(this.name, schema); } catch (err) { this.log(`create ${err}`, ["bgYellow", "fgBlack"]); } } }; const Controller = class Controller { async fromCache(name, def) { const value = await this.cache(name); return typeof value !== "undefined" ? value : def; } async cache(name, data, duration) { if (typeof this.application.services.cache === "undefined") { return undefined; } if (this.action) { return this.application.services.cache.apply(`${this.application.name}#Controller#${this.item}#${this.action}#${name}`, data, duration); } else { return this.application.services.cache.apply(`${this.application.name}#Controller#${this.item}#${name}`, data, duration); } } cacheMethod = false; cacheState = cacheState.null; cacheExpire = 10; role = false; messages = {}; showDelay = false; __override() { return false; } _override() { return false; } override() { return false; } constructor(application, item, action) { this.application = application; this.item = item; this.action = action || false; if (this.action) { this.log = (text, visual, more) => application.log(`controller[${item}/${action}] ::: ${text}`, visual, more); this.warn = (text, more) => application.log(`controller[${item}/${action}] ::: ${text}`, ["bgYellow", "fgBlack"], more); } else { this.log = (text, more) => application.log(`controller[${item}] ::: ${text}`, visual, more); this.warn = (text, more) => application.log(`controller[${item}] ::: ${text}`, ["bgYellow", "fgBlack"], more); } this.__override = this.__override.bind(this); const __properties = this.__override(); if (typeof __properties === "object") { for (const __name in __properties) { this[__name] = __properties[__name]; } } this._override = this._override.bind(this); const _properties = this._override(); if (typeof _properties === "object") { for (const _name in _properties) { if (_name === "pre" && Array.isArray(this[_name]) && Array.isArray(properties[_name])) { this[_name] = [...this[_name], ...properties[_name]]; } else { this[_name] = _properties[_name]; } } } this.override = this.override.bind(this); const properties = this.override(); if (typeof properties === "object") { for (const name in properties) { if (name === "payload" && Array.isArray(this[name]) && Array.isArray(properties[name])) { this[name] = [...this[name], ...properties[name]]; } else { this[name] = properties[name]; } } } this.fromCache = this.fromCache.bind(this); this.cache = this.cache.bind(this); this.state = this.state.bind(this); this.developer = this.developer.bind(this); this.cookie = this.cookie.bind(this); this.print = this.print.bind(this); this.interface = this.interface.bind(this); this.cacheControl = this.cacheControl.bind(this); this.register(); this.do("open"); } do = (event, ...args) => (typeof this[event] === "function" ? this[event](...args) : () => {}); model(name) { return this.model.service(name); } service(subClass, filter) { return this.application.service(subClass, filter); } controller(item, action) { return this.application.controller(item, action); } execute(extras, callback, reject) { return this.application.execute(extras, callback, reject); } register() { this.do("beforeRegister"); if (this.action) { if (typeof this.application.controllers[this.item] !== "object") this.application.controllers[this.item] = {}; this.application.controllers[this.item][this.action] = this; } else { this.application.controllers[this.item] = this; } this.log("registered"); this.do("activate"); } destroy() { this.do("close"); if (this.action) { delete this.application.controllers[this.item][this.action]; if (Object.keys(this.application.controllers[this.item]).length === 0) { delete this.application.controllers[this.item]; } } else { delete this.application.controllers[this.item]; } this.log("destroyed", "fgRed"); } /** * * developer leave a note for UI developers * * @param {Object} note string or any object to describes any situation * @param {Object} requestId request.id */ state(requestId, code) { if (requestId) { this.application.serverStorage.state[requestId] = code; } } developer(requestId, note) { if (requestId) { if (typeof this.application.serverStorage.developer[requestId] === "undefined") { this.application.serverStorage.developer[requestId] = []; } this.application.serverStorage.developer[requestId].push(note); } } cookie(requestId, ...args) { if (requestId) { if (typeof this.application.serverStorage.cookie[requestId] === "undefined") { this.application.serverStorage.cookie[requestId] = []; } this.application.serverStorage.cookie[requestId].push(args); } } /** * * cacheControl specifies an cacheId to determine the cache flows for any requests * * @param {Object} query passed arguments from client * @param {Object} request request information * @param {Number} level authentication level specifies by authenticator method * @return {String} a cache Id to determine the duplicate requests */ cacheControl(query, request, level) { let allowed = !this.cacheMethod; if (!allowed && Array.isArray(this.cacheMethod)) allowed = this.cacheMethod.includes(request.method); if (!allowed && typeof this.cacheMethod === "string") allowed = this.cacheMethod === request.method; if (allowed) { if (this.cacheState !== cacheState.null) { if (this.cacheState === cacheState.public || level === accessLevel.public) { if (request.method && typeof this[request.method] === "function") { return `#method=${request.method}#params=${request.params}#query=${JSON.stringify(query)}`; } else { return `#params=${request.params}#query=${JSON.stringify(query)}`; } } else if (request.deviceId) { if (request.method && typeof this[request.method] === "function") { return `#method=${request.method}#params=${request.params}#query=${JSON.stringify(query)}#deviceId=${request.deviceId}`; } else { return `#params=${request.params}#query=${JSON.stringify(query)}#deviceId=${request.deviceId}`; } } } } return false; } /** * * authenticator specifies that any user with specific acl has right to access this control * * @param {Object} query passed arguments from client * @param {Object} request request information * @return {String} authentication level (denied,public,private,grant) */ authenticator(query, request) { if (!this.role || request.engine) { return accessLevel.public; } else if (this.role === request.role) { return accessLevel.private; } else if (Array.isArray(this.role) && this.role.includes(request.role)) { return accessLevel.private; } return accessLevel.null; } /** * * handler will handle/process the request * * @param {Object} query passed arguments from client * @param {Object} request request information * @param {Number} level authentication level specifies by authenticator method * @param {String} cacheId cache Id determine the duplicate requests specifies by CacheControl method * @return {Object} return the controller outputs ofter processing */ // handler(query, request, level, cacheId) { // return query; // } print(request, text, visual, ...more) { if (this.showDelay && request && request.start) { this.log(`#${request.id} ${text} @${new Date().getTime() - request.start.getTime()}`, visual, ...more); } else if (request) { this.log(`#${request.id} ${text}`, visual, ...more); } else { this.log(`{NO REQUEST} ${text}`, visual, ...more); } } middleware(name, extras) { return new (this.service(Service, name).middleware)(this.application, extras); } async payload(extras = {}) { if (typeof this.__pre === "function") { await this.__pre(extras); } if (typeof this._pre === "function") { await this._pre(extras); } if (typeof this.pre === "function") { await this.pre(extras); } } async interface(query = {}, extras = {}, callback, reject) { extras.params = extras.params.slice(this.action ? 2 : 1); extras.log = (text, visual, ...more) => this.print(extras, text, visual, ...more); extras.warn = (text, ...more) => this.print(extras, text, ["fgBlack", "bgYellow"], ...more); extras.cookie = (name, value) => this.cookie(extras.id, name, value); extras.developer = note => this.developer(extras.id, note); extras.state = code => this.state(extras.id, code); let handler = false; let handlerName = extras.method; if (extras.method && typeof this[extras.method] === "function") handler = this[extras.method]; else if (typeof this.handler === "function") { handler = this.handler; handlerName = "handler"; } if (typeof handler === "function") { let cacheId = false, payload = false; try { let level = await this.authenticator(query, extras); if (level !== accessLevel.null) { this.print(extras, `${handlerName} {${extras.role || "NONE"}} access granted`); try { if (this.cacheExpire) cacheId = await this.cacheControl(query, extras, level); if (cacheId) payload = await this.cache(cacheId); } catch (err) { this.print(extras, `${handlerName} cache ${err}`, ["fgBlack", "bgYellow"]); } finally { if (payload) { const state = this.cacheState === cacheState.public || level === accessLevel.public ? "public" : "private"; this.print(extras, `${handlerName} responds from ${state} cache`, "fgGreen"); callback(0, payload, 200); } else { try { await this.payload(extras); if (this.model && typeof extras.collection === "function") { extras.model = await extras.collection(); } payload = await handler.bind(this)(query, extras, level, cacheId); if (this.cacheExpire && cacheId) { const state = this.cacheState === cacheState.public || level === accessLevel.public ? "public" : "private"; this.print(extras, `${handlerName} method successfully respond & ${state} cached until ${this.cacheExpire} seconds`); this.cache(cacheId, payload, this.cacheExpire); } else { this.print(extras, `${handlerName} method successfully respond`); } callback(0, payload, 200); } catch (code) { if (+code != code) { console.log(code); extras.warn(`${handlerName} handle ${code}`); extras.developer(code); callback(21, false, 400); } else { callback(code, false, 400); } } } } } else { extras.warn(`${handlerName} access denied`); callback(14, false, 403); } } catch (code) { if (+code != code) { extras.warn(`${handlerName} access ${code}`); extras.developer(code); callback(21, false, 400); } else { callback(code, false, 400); } } } else if (typeof reject === "function") { this.print(extras, `${handlerName} has no ${extras.method ? `${extras.method}() or handler()` : "handler()"} method to respond`, "fgYellow"); reject(); } } }; const ControllerMongoose = class ControllerMongoose extends Controller { __override() { this.__pre = this.__pre.bind(this); return { model: "none", messages: { 100: "inputs have not proper values", 101: "Collection is not found", 102: "Could not found", 103: "Document is not found", }, }; } __pre(extras) { extras.database = dbname => { return this.application.database(dbname); }; extras.collection = (name, dbname) => { const database = this.application.database(dbname); if (!name && typeof database[this.model] !== "undefined") { return database[this.model]; } else if (name && typeof database[name] !== "undefined") { return database[name]; } return false; }; extras.pre = async document => document; extras.make = async filter => filter; extras.populate = async data => data; extras.select = async data => data; extras.post = async document => document; extras.product = async (output, action) => output; extras.end = async (data, single, action) => { await extras.populate(data); await extras.select(data); let output = action === "insert" ? await data.save() : await data.exec(); if (single && output) { output = await extras.post(output.toObject()); } else if (Array.isArray(output)) { for (const index in output) { output[index] = await extras.post(output[index].toObject()); } } return await extras.product(output, action); }; extras.options = async name => { const settings = this.application.enum[this.model]; if (!settings) throw 101; return settings[name]; }; extras.insert = async (inputs = {}) => { const { warn, developer, collection, pre } = extras; const model = await collection(); if (!model) throw 101; const values = await pre(inputs); try { const data = model(values); if (data) return await extras.end(data, true, "insert"); } catch (err) { warn(err); developer(err); throw 100; } }; extras.update = async (_id, inputs = {}) => { const { warn, developer, collection, pre } = extras; const model = await collection(); if (!model) throw 101; const document = await model.findOne({ _id }); if (document) { const values = await pre({ ...inputs, _id }); try { const data = model.findOneAndUpdate({ _id: document._id }, { $set: values }, { new: true }); if (data) return await extras.end(data, true, "update"); } catch (err) { warn(err); developer(err); throw 100; } } else throw 103; }; extras.upsert = async (inputs = {}) => { return inputs._id ? await extras.update(inputs._id, inputs) : await extras.insert(inputs); }; extras.read = async (filter = {}, sort = []) => { const { params, make, collection, end } = extras; const model = await collection(); const [_id] = params; let data = false; if (_id) { data = model.find({ _id }); } else { data = model.find(await make(filter)); } if (data) { if (sort) { if (Array.isArray(sort)) { for (const name of sort) { if (Array.isArray(sort[name])) { data.sort([sort[name]]); } else if (typeof sort === "string") { data.sort([[sort[name], 1]]); } } } else if (typeof sort === "string") { data.sort([[sort, 1]]); } } return await end(data, false, "read"); } else throw 21; }; extras.remove = async _id => { const { collection, end } = extras; const model = await collection(); if (!model) throw 101; if (_id) { const data = await end(model.findOne({ _id }), true, "remove"); if (data) { const affected = await model.deleteOne({ _id }); if (affected) return data; } else throw 103; } else throw 102; }; } }; const ControllerCrud = class ControllerCrud extends ControllerMongoose { _override() { return { role: false, cacheMethod: "get", cacheState: cacheState.public, cacheExpire: 10, model: "none", messages: { 100: "inputs have not proper values", 101: "Collection is not found", 102: "Could not found", 103: "Document is not found", }, }; } async options({}, { params, options }) { const [name] = params; return await options(name); } async get({ filter = {}, sort = [] }, { read }) { return await read(filter, sort); } async post(query, { params, upsert }) { const [_id] = params; if (_id) query._id = _id; return await upsert(query); } async delete({}, { params, remove }) { const [_id] = params; return await remove(_id); } }; const Service = class Service { async fromCache(name, def) { const value = await this.cache(name); return typeof value !== "undefined" ? value : def; } async cache(name, data, duration) { if (typeof this.application.services.cache === "undefined") { return undefined; } return this.application.services.cache.apply(`${this.application.name}#Service#${this.name}#${name}`, data, duration); } // service type level __override() { return false; } // library level _override() { return false; } // developer level override() { return false; } name = "none"; args = []; constructor(application, ...args) { this.bit = application.serviceBit++; this.queueTime = new Date(); this.application = application; this.args = args; this.log = (text, visual, more) => application.log(`service#${this.bit}[${this.name}] ::: ${text}`, visual, more); this.warn = (text, more) => application.log(`service#${this.bit}[${this.name}] ::: ${text}`, "fgRed", more); this.print = (id, text, visual, more) => application.log(`service#${this.bit}[${this.name}#${id}] ::: ${text}`, visual, more); this.__override = this.__override.bind(this); const __properties = this.__override(); if (typeof __properties === "object") { for (const name in __properties) { this[name] = __properties[name]; } } this._override = this._override.bind(this); const _properties = this._override(); if (typeof _properties === "object") { for (const name in _properties) { this[name] = _properties[name]; } } this.override = this.override.bind(this); const properties = this.override(); if (typeof properties === "object") { for (const name in properties) { this[name] = properties[name]; } } this.log("start queue"); this.fromCache = this.fromCache.bind(this); this.cache = this.cache.bind(this); this.service = this.service.bind(this); this.destroy = this.destroy.bind(this); this.register = this.register.bind(this); this.rebuild = this.rebuild.bind(this); this.build = this.build.bind(this); this.do("open"); this.register(); } do = (event, ...args) => (typeof this[event] === "function" ? this[event](...args) : () => {}); model(name) { return this.model.service(name); } service(subClass, filter) { return this.application.service(subClass, filter); } controller(item, action) { return this.application.controller(item, action); } execute(extras, callback, reject) { return this.application.execute(extras, callback, reject); } destroy() { this.do("close"); delete this.application.services[this.name]; this.log("destroyed", "fgRed"); } async register(rebuild = false) { await new Promise(resolve => { let intervalId = setInterval(() => { if (this.bit === this.application.serviceStat) { clearInterval(intervalId); resolve(); } }, 100); }); this.startTime = new Date(); if (rebuild) this.destroy(); this.log("registered #" + this.bit); try { let message = await this.build(...this.args); if (!message) message = "already up now"; this.runTime = new Date(); this.log(`${message} @${this.runTime.getTime() - this.startTime.getTime()}`, ["bgCyan", "fgBlack"]); } catch (err) { this.runTime = new Date(); this.log(`${err} @${this.runTime.getTime() - this.startTime.getTime()}`, ["bgYellow", "fgWhite"]); } finally { this.application.serviceStat++; if (!this.application.running && this.application.serviceStat === this.application.serviceBit) { this.application.running = true; this.application.runTime = new Date(); this.application.log(`application ::: already up now @${this.application.runTime.getTime() - this.application.startTime.getTime()}`); } this.application.services[this.name] = this; this.do("activate"); } } rebuild(...args) { if (this.bit < this.application.serviceBit) this.bit = this.application.serviceBit++; if (args.length > 0) this.args = args; this.register(true); } build(...args) {} }; const ServiceDatabase = class ServiceDatabase extends Service { __override() { return { //override the superclass properties module: "none", name: "database", }; } build() { throw `it is a superclass`; } }; const ServiceMongoose = class ServiceMongoose extends ServiceDatabase { _override() { this.close = this.close.bind(this); return { //override the superclass properties module: "mongoose", }; } async build(mongoURL, name) { if (this.service(ServiceMongoose)) this.name = name; this.connection = await mongoose.createConnection(mongoURL, { useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, }); for (const model in this.application.models) { this.application.models[model].make(this, this.name); } this.log("use mongoose", "fgMagenta"); return `connected to ${mongoURL}`; } close() { mongoose.connection.close(); } }; const ServiceMessaging = class ServiceMessaging extends Service { __override() { this.send = this.send.bind(this); return { //override the superclass properties module: "none", name: "messaging", }; } build() { throw `it is a superclass`; } send(deviceIds, data) {} }; const ServiceFirebase = class ServiceFirebase extends ServiceMessaging { _override() { this.close = this.close.bind(this); this.token = this.token.bind(this); this.update = this.update.bind(this); return { //override the superclass properties module: "firebase", }; } build(credential, databaseURL, config) { this.config = config; this.app = admin.initializeApp({ credential: admin.credential.cert(credential), databaseURL: databaseURL, }); this.messaging = this.app.messaging(); this.log("use firebase", "fgMagenta"); return `linked to ${databaseURL}`; } close() { this.app.delete(); } async send(deviceIds, data) { if (!Array.isArray(deviceIds)) deviceIds = [deviceIds]; const tokens = []; for (const deviceId of deviceIds) { const token = await this.token(deviceId); if (token) tokens.push(token); } this.messaging .sendMulticast(this.payload(tokens, data)) .then(response => { if (response && Array.isArray(response.responses)) { if (typeof callback === "function") { callback(response.responses.map(x => x.messageId)); } } else { if (typeof reject === "function") reject(); } }) .catch(error => { this.log(error); if (typeof reject === "function") reject(); }); } payload(tokens, content) { return Object.assign( { android: { priority: "normal", }, apns: { headers: { "apns-priority": "5", }, }, webpush: { headers: { Urgency: "high", }, }, tokens, }, content ); } async token(deviceId) { return await this.cache(`#token#${deviceId}`); } update(deviceId, token) { this.cache(`#token#${deviceId}`, token); } }; const ServiceBroker = class ServiceBroker extends Service { __override() { this.agent = this.agent.bind(this); this.parse = this.parse.bind(this); return { //override the superclass properties module: "none", name: "broker", }; } agent() { return false; } build(build = 1, messages = {}) { throw `it is a superclass`; } parse(extras, code, payload) { const { start, id, item, action } = extras; const route = `${item}/${action}`; const delay = new Date().getTime() - start.getTime(); let controllers = this.application.controllers, messages = {}; if (typeof controllers[item] === "object" && controllers[item][action] instanceof Controller) { messages = controllers[item][action].messages; } else if (typeof controllers[item] instanceof Controller) { messages = controllers[item].messages; } const visual = ["bgWhite", "fgBlack"]; if (Array.isArray(payload)) { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <array>(rows: ${payload.length}) @${delay}`, visual); } else if (typeof payload === "object" && Array.isArray(payload.data)) { if (payload === null) { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <object>NULL @${delay}`, visual); } else if (payload.count) { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <object>(rows: ${payload.data.length}, total: ${payload.count}) @${delay}`, [ "fgCyan", ]); } else { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <object>(rows: ${payload.data.length}) @${delay}`, visual); } } else if (typeof payload !== "undefined") { if (typeof payload === "object") { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <object>(Mixed) @${delay}`, visual); } else if (typeof payload === "string" && payload.length > 40) { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <string>(long) @${delay}`, visual); } else { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <${typeof payload}>${JSON.stringify(payload)} @${delay}`, visual); } } else { this.log(`#${id} > ${route} response ${code ? "!" + code : "OK"} <undefined> @${delay}`, visual); } let message = messages[code] || (this.messages ? this.messages[code] || this.messages[11] : undefined); return { code, payload, message, }; } }; const ServiceExpress = class ServiceExpress extends ServiceBroker { _override() { this.answer = this.answer.bind(this); this.information = this.information.bind(this); this.authorization = this.authorization.bind(this); this.static = this.static.bind(this); this.crud = this.crud.bind(this); return { //override the superclass properties module: "express", name: "web", }; } agent() { return this.app; } build(build = 1, messages = {}, limit = "50mb") { this.build = build; this.messages = messages; const app = (this.app = express()); this.upload = multer(); app.use( express.urlencoded({ limit, extended: true, }) ); app.use( express.json({ limit, }) ); // app.use(express.urlencoded({ extended: true })); // app.use(express.json()); app.use(cookieParser()); if (this.cors) app.use(cors()); app.use(this.upload.single("attachment")); app.use("*", this.information); app.use("*", this.authorization); app.use(["/:item/:action", "/:item"], this.crud); app.use("*", this.static); this.log("use express", "fgMagenta"); if (this.service(ServiceListener)) this.service(ServiceListener).rebuild(); } answer(res, extras, status = 501, code = 22, payload = false) { const { id, developer } = extras; const response = this.parse(extras, code, payload, status); if (typeof this.application.serverStorage.cookie[id] === "object") { const cookies = this.application.serverStorage.cookie[id]; for (const args of cookies) { this.log(`#${id} {${args[0]}} cookie sent`); res.cookie(...args); } delete this.application.serverStorage.cookie[id]; } if (typeof this.build !== "undefined") { response.build = typeof this.build === "function" ? this.build() : this.build; } if (typeof this.application.serverStorage.state[id] === "number") { status = this.application.serverStorage.state[id]; delete this.application.serverStorage.state[id]; } if (developer && typeof this.application.serverStorage.developer[id] !== "undefined") { response.developer = this.application.serverStorage.developer[id]; res.status(status).json(response); delete this.application.serverStorage.developer[id]; } else { res.status(status).json(response); } } async information(req, res, next) { let extras = {}; try { extras.start = new Date(); extras.protocol = req.protocol; extras.domain = (req.headers.host || req.hostname).toString(); extras.api = (extras.domain.split(".").length < 3 ? ["www"] : extras.domain.split("."))[0].toLowerCase(); extras.method = req.method.toLowerCase(); extras.ip = (req.headers["x-forwarded-for"] || req.connection.remoteAddress).toString().split(":").pop(); extras.authorization = req.headers.authorization || req.cookies.auth; extras.query = (extras.method === "post" ? (typeof req.body === "undefined" ? req.body : req.body) : req.query) || {}; extras.developer = req.headers.developer; extras.url = (req.baseUrl || req.url || req.path).toString(); extras.params = extras.url.split("/").filter(param => param.length > 0); extras.path = (req.originalUrl |