initial
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
class App{
|
||||
constructor(){
|
||||
this.root = path.resolve(dirname(fileURLToPath(import.meta.url)) + '/../../');
|
||||
}
|
||||
|
||||
plugins = [];
|
||||
|
||||
async use(plugin){
|
||||
this[plugin.name] = plugin;
|
||||
plugin.app = this;
|
||||
this.plugins.push(plugin);
|
||||
}
|
||||
|
||||
async init(){
|
||||
for (let p of this.plugins){
|
||||
console.debug('Initializing', p.name)
|
||||
if(p.init) await p.init(this);
|
||||
}
|
||||
}
|
||||
|
||||
async importModules(modules){
|
||||
const mods = {};
|
||||
for (let m of modules){
|
||||
mods[m.name] = (await import(`../${m.path}`))[m.name];
|
||||
}
|
||||
|
||||
for (let m of modules){
|
||||
this.use(new mods[m.name]())
|
||||
}
|
||||
}
|
||||
|
||||
async replace(p){
|
||||
let old = this[p.name];
|
||||
this.plugins[this.plugins.indexOf(old)] = p;
|
||||
this[p.name] = p;
|
||||
p.app = this;
|
||||
if(p.init) await p.init(this);
|
||||
}
|
||||
|
||||
async start(){
|
||||
for (let p of this.plugins){
|
||||
console.debug('Starting', p.name)
|
||||
if(p.start) await p.start(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,41 @@
|
||||
import path from 'path'
|
||||
class Config{
|
||||
name = 'config';
|
||||
|
||||
fs = {
|
||||
repo: null
|
||||
}
|
||||
db = {
|
||||
name:'pronature',
|
||||
url: "mongodb://127.0.0.1:27017/pronature"
|
||||
}
|
||||
site = {
|
||||
host:'https://localhost:5173',
|
||||
ssl: true,
|
||||
port: 3000,
|
||||
certificate: {
|
||||
key: './.cert/dev-key.pem',
|
||||
cert: './.cert/dev-cert.pem',
|
||||
passphrase: 'parola'
|
||||
}
|
||||
}
|
||||
am = {
|
||||
salt : 'P@ssSal7y!!',
|
||||
cookie: {
|
||||
secret: 'S3cret4C00k!ie$',
|
||||
maxAge: 1000 * 60 * 60 * 24 * 7
|
||||
}
|
||||
}
|
||||
|
||||
async init(app){
|
||||
if (!this.fs.repo){
|
||||
this.fs.repo = path.resolve(`${app.root}/repo`) + '/';
|
||||
}
|
||||
}
|
||||
|
||||
async start(app){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export { Config };
|
||||
@@ -0,0 +1,186 @@
|
||||
import { ObjectId, MongoClient } from 'mongodb';
|
||||
|
||||
let db;
|
||||
let dbo;
|
||||
|
||||
class Db {
|
||||
name = 'db';
|
||||
init(app){
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
async start(app){
|
||||
db = await MongoClient.connect(app.config.db.url, {maxPoolSize: 256});
|
||||
try {
|
||||
dbo = db.db(app.config.db.name);
|
||||
this.instance = dbo;
|
||||
for (let c of ['users', 'user_sessions', 'history', 'log', 'assets']){
|
||||
try {
|
||||
await dbo.createCollection(c);
|
||||
}catch(err){}
|
||||
}
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
async create(collection, value){
|
||||
try {
|
||||
delete value._id;
|
||||
return await dbo.collection(collection).insertOne(value);
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
async get(collection, key, projection){
|
||||
try {
|
||||
let res = await dbo.collection(collection).findOne(key, projection ? {projection} : undefined)
|
||||
return res;
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
async list(collection, query){
|
||||
try {
|
||||
let cursor = dbo.collection(collection).find(query.query, query.project ? {projection: query.project} : undefined);
|
||||
query.sort && cursor.sort(query.sort);
|
||||
query.skip && cursor.skip(query.skip);
|
||||
query.limit && cursor.limit(query.limit);
|
||||
let count = await dbo.collection(collection).countDocuments(query.query);
|
||||
let result = await cursor.toArray();
|
||||
return {data: result, count:count};
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
async aggregate(collection, specs){
|
||||
try {
|
||||
let cursor = dbo.collection(collection);
|
||||
let aggCursor = cursor.aggregate(specs);
|
||||
let result = await aggCursor.toArray();
|
||||
return result.length == 1 ? result[0] : result;
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
async distinct(collection, key, query){
|
||||
try {
|
||||
return await dbo.collection(collection).distinct(key, query);
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
async update(collection, key, value){
|
||||
let r;
|
||||
try {
|
||||
delete value._id;
|
||||
r = await dbo.collection(collection).replaceOne(key, value, {upsert:true});
|
||||
}finally{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
async updateSet(collection, key, value){
|
||||
let r;
|
||||
try {
|
||||
r = await dbo.collection(collection).updateMany(key, value);
|
||||
}finally{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
async remove(collection, key){
|
||||
try {
|
||||
await dbo.collection(collection).deleteMany(key);
|
||||
}finally{
|
||||
}
|
||||
}
|
||||
|
||||
convertToObjectId(object, key, recursive, result){
|
||||
if (object && object[key]){
|
||||
if (Array.isArray(object[key])){
|
||||
object[key].forEach((v, i, a)=>{
|
||||
a[i] = this.ObjectId(v);
|
||||
result && result.push(a[i]);
|
||||
})
|
||||
}else{
|
||||
let oid = this.ObjectId(object[key])
|
||||
object[key] = oid;
|
||||
result && result.push(oid);
|
||||
}
|
||||
}
|
||||
if (recursive){
|
||||
for (var k in object){
|
||||
if (typeof(object[k]) == 'object'){
|
||||
this.convertToObjectId(object[k], key, recursive, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sanitizeQuery(q){
|
||||
if (!q) return;
|
||||
Object.getOwnPropertyNames(q).forEach(n=>{
|
||||
if (n.startsWith('$')){
|
||||
//sanitize $
|
||||
console.warn('Deleting suspicious query key', n)
|
||||
delete q[n];
|
||||
}
|
||||
if (typeof(q[n]) == 'object'){
|
||||
this.sanitizeQuery(q[n]);
|
||||
}
|
||||
//prepare for DB
|
||||
if (n.startsWith('*')){
|
||||
let n1 = '$' + n.slice(1);
|
||||
q[n1] = q[n];
|
||||
delete q[n];
|
||||
n = n1;
|
||||
}
|
||||
if (['$where', '$group', '$merge', '$lookup', '$accumulator', '$function'].includes(n)){
|
||||
delete q[n];
|
||||
console.warn('Deleting suspicious query key', n);
|
||||
}
|
||||
})
|
||||
if (q.$oid){
|
||||
q.$oid.forEach(o=>{
|
||||
q[o] = this.ObjectId(q[o]);
|
||||
})
|
||||
delete q.$oid;
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
sanitizeProjection(p){
|
||||
if (!p) return { _id: 1 };
|
||||
let props = Object.getOwnPropertyNames(p);
|
||||
|
||||
props.forEach(n=>{
|
||||
if (typeof p[n] == 'object'){
|
||||
console.warn('Deleting suspicious projection key, object', n)
|
||||
delete p[n]
|
||||
}else if (!n.match(/^[0-9a-zA-Z\._\$\#\-\:]*$/)){
|
||||
console.warn('Deleting suspicious projection key', n)
|
||||
delete p[n]
|
||||
}else if (typeof p[n] !== 'number' && !p[n].match(/^[0-9a-zA-Z\._\$\#\-]*$/)){
|
||||
console.warn('Deleting suspicious projection value', n, p[n])
|
||||
delete p[n]
|
||||
}
|
||||
})
|
||||
|
||||
props = Object.getOwnPropertyNames(p);
|
||||
if (props.length == 0){
|
||||
return { _id: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
checkLimit(q){
|
||||
if (!q) return;
|
||||
if (!q.limit) q.limit = 100;
|
||||
if (q.limit == 'all') delete q.limit;
|
||||
}
|
||||
|
||||
ObjectId(id){
|
||||
return new ObjectId(id);
|
||||
}
|
||||
}
|
||||
|
||||
export { Db };
|
||||
@@ -0,0 +1,111 @@
|
||||
import express from 'express'
|
||||
import session from 'express-session';
|
||||
import compression from 'compression';
|
||||
import MongoDBStore from 'connect-mongodb-session';
|
||||
import https from 'https';
|
||||
import fs from 'fs';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import helmet from 'helmet';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
class WebServer {
|
||||
name = 'webServer';
|
||||
|
||||
async init(app) {
|
||||
const xapp = express();
|
||||
this.xapp = xapp;
|
||||
|
||||
xapp.disable('x-powered-by');
|
||||
|
||||
xapp.use(compression());
|
||||
xapp.use(cookieParser());
|
||||
|
||||
const store = new MongoDBStore(session)({
|
||||
uri: app.config.db.url,
|
||||
databaseName: app.config.db.name,
|
||||
collection: 'user_sessions'
|
||||
});
|
||||
|
||||
store.on('error', function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
xapp.use(session({
|
||||
secret: app.config.am.cookie.secret,
|
||||
cookie: {
|
||||
maxAge: app.config.am.cookie.maxAge || 1000 * 60 * 60 * 24 * 7,
|
||||
secure: !!app.config.am.cookie.secure,
|
||||
httpOnly: true,
|
||||
sameSite: app.config.am.cookie.sameSite
|
||||
},
|
||||
store,
|
||||
resave: false,
|
||||
saveUninitialized: (app.config.am?.session?.saveUninitialized === undefined) ? true : app.config.am.session.saveUninitialized,
|
||||
proxy: true
|
||||
}));
|
||||
|
||||
// xapp.use((req, res, next) => {
|
||||
// let l = app.config.langs.find(l => l.code == (req.query?.lang || req.cookies.lang || 'bg'));
|
||||
// req.lang = l || app.config.langs[0];
|
||||
// next();
|
||||
// })
|
||||
|
||||
xapp.use(express.json({ limit: '150mb' }));
|
||||
xapp.use(express.urlencoded({ extended: false, limit: '150mb' }));
|
||||
|
||||
xapp.use((req, res, next) => {
|
||||
res.locals.cspNonce = uuidv4();
|
||||
next();
|
||||
});
|
||||
|
||||
app.config.am.helmet && xapp.use(helmet(app.config.am.helmet));
|
||||
}
|
||||
|
||||
async start(app) {
|
||||
let indexFile = app.root + '/index.html';
|
||||
|
||||
let indexFileContent = fs.readFileSync(indexFile, { encoding: 'utf-8' });
|
||||
|
||||
function index(req, res) {
|
||||
//res.sendFile(indexFile);
|
||||
res.send(indexFileContent.replace(/\#NONCE\#/g, res.locals.cspNonce));
|
||||
}
|
||||
|
||||
this.xapp.get('/', index);
|
||||
|
||||
// app.config.langs.forEach(l => {
|
||||
// this.xapp.use('/' + l.code, index);
|
||||
// })
|
||||
|
||||
this.xapp.use(express.static(`${this.app.root}/dist/`));
|
||||
|
||||
this.xapp.use((req, res, next) => {
|
||||
if (req.method == 'GET' || req.method == 'POST') {
|
||||
return res.status(404).end();
|
||||
} else next();
|
||||
});
|
||||
|
||||
///error handler!
|
||||
this.xapp.use((err, req, res, next) => {
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
})
|
||||
|
||||
let started = () => {
|
||||
console.log(`app started on port ${app.config.site.port}. SSL is ${app.config.site.ssl ? 'enabled' : 'disabled'}.`)
|
||||
}
|
||||
|
||||
if (app.config.site.ssl) {
|
||||
this.server = https.createServer({
|
||||
key: fs.readFileSync(app.config.site.certificate.key),
|
||||
cert: fs.readFileSync(app.config.site.certificate.cert),
|
||||
passphrase: app.config.site.certificate.passphrase
|
||||
}, this.xapp).listen(app.config.site.port, started);
|
||||
} else {
|
||||
this.server = this.xapp.listen(app.config.site.port, app.config.site.hostAddress || '127.0.0.1', started);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { WebServer };
|
||||
@@ -0,0 +1,25 @@
|
||||
import express from 'express';
|
||||
import fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import multipart from 'connect-multiparty';
|
||||
|
||||
const multipartMiddleware = multipart();
|
||||
class GameObjectsApi{
|
||||
|
||||
name = 'gameObjectsApi'
|
||||
route = '/api/game-object'
|
||||
|
||||
init(app){
|
||||
const { am, config } = app;
|
||||
const router = express.Router();
|
||||
router.put('/', multipartMiddleware, async (req, res)=>{
|
||||
try{
|
||||
}catch(err){
|
||||
}
|
||||
});
|
||||
|
||||
app.webServer.xapp.use(this.route, router);
|
||||
}
|
||||
}
|
||||
|
||||
export {GameObjectsApi}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
console.debug = function(){
|
||||
if (process.env.debug){
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
import App from './app/App.js';
|
||||
|
||||
const modules = [
|
||||
{name:'Config', path:'app/Config.js'},
|
||||
{name:'Db', path:'app/Db.js'},
|
||||
{name:'WebServer', path:'app/WebServer.js'},
|
||||
{name:'GameObjectsApi', path:'controllers/api/GameObjects.js'},
|
||||
]
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
console.error(err, 'Uncaught Exception thrown');
|
||||
}).on('unhandledRejection', (reason, p) => {
|
||||
console.error(reason, 'Unhandled Rejection at Promise', p);
|
||||
});
|
||||
|
||||
const app = new App();
|
||||
|
||||
await app.importModules(modules);
|
||||
|
||||
await app.init();
|
||||
|
||||
console.log(`Starting...`);
|
||||
app.start();
|
||||
Reference in New Issue
Block a user