add database code
This commit is contained in:
parent
8471e5177c
commit
9544bc70d6
@ -31,14 +31,11 @@ class APIMethods {
|
|||||||
info (con, req, cb) {
|
info (con, req, cb) {
|
||||||
if (!req.fields) req.fields = "*";
|
if (!req.fields) req.fields = "*";
|
||||||
if (!req.fieldsExtSource) req.fieldsExtSource = "*";
|
if (!req.fieldsExtSource) req.fieldsExtSource = "*";
|
||||||
const cache = this.info.cache[
|
const cacheKey = `${req.fields}\u0000${req.fieldsExtSource}`;
|
||||||
JSON.stringify({
|
|
||||||
f: req.fields, e: req.fieldsExtSource
|
const cache = this.info.cache[cacheKey];
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
if (cache) {
|
if (cache) {
|
||||||
console.debug("CACHE!");
|
|
||||||
return cb({ result: cache, trace_id: req.trace_id, ended: true });
|
return cb({ result: cache, trace_id: req.trace_id, ended: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,11 +52,7 @@ class APIMethods {
|
|||||||
if (!fields[k]) serverInfo.extSource[k] = undefined;
|
if (!fields[k]) serverInfo.extSource[k] = undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.info.cache[
|
this.info.cache[cacheKey] = serverInfo;
|
||||||
JSON.stringify({
|
|
||||||
f: req.fields, e: req.fieldsExtSource
|
|
||||||
})
|
|
||||||
] = serverInfo;
|
|
||||||
|
|
||||||
cb({ result: serverInfo, trace_id: req.trace_id, ended: true });
|
cb({ result: serverInfo, trace_id: req.trace_id, ended: true });
|
||||||
}
|
}
|
||||||
@ -104,6 +97,35 @@ class APIMethods {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async characters (con, req, cb) {
|
||||||
|
const authedMethod = this.authed;
|
||||||
|
const isAuthed = await (new Promise((rs, rj) => {
|
||||||
|
authedMethod(con, req, ({ result, ended }) => {
|
||||||
|
if (ended)
|
||||||
|
return rs(result);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!isAuthed) {
|
||||||
|
return cb({
|
||||||
|
error: "require authorize",
|
||||||
|
trace_id: req.trace_id,
|
||||||
|
ended: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// getting characters
|
||||||
|
const user = global.authed.get(con);
|
||||||
|
const result = (await user.getCharacters())
|
||||||
|
.map(character => {
|
||||||
|
return character;
|
||||||
|
});
|
||||||
|
|
||||||
|
cb({
|
||||||
|
result, trace_id: req.trace_id, ended: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*setSession (isEncrypted, address, { key, counter }, cb) {
|
/*setSession (isEncrypted, address, { key, counter }, cb) {
|
||||||
if (!global.config.server.secureMode)
|
if (!global.config.server.secureMode)
|
||||||
return cb({ error: "Encryption is off by configuration file into server" });
|
return cb({ error: "Encryption is off by configuration file into server" });
|
||||||
|
@ -13,7 +13,7 @@ module.exports.registerToken = function (user) {
|
|||||||
token = module.exports();
|
token = module.exports();
|
||||||
} while (global.authed.tokens[token] !== undefined);
|
} while (global.authed.tokens[token] !== undefined);
|
||||||
global.authed.tokens[token] = user;
|
global.authed.tokens[token] = user;
|
||||||
// (!) Потом добавить регистрацию в БД!
|
// TODO: Потом добавить регистрацию в БД!
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
@ -9,26 +9,46 @@
|
|||||||
* * 5 - root
|
* * 5 - root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class User {
|
const ACCESS_LEVELS = {
|
||||||
constructor(name, accessLevel) {
|
"-1": "banned",
|
||||||
if (
|
0: "user",
|
||||||
typeof accessLevel !== "number" ||
|
1: "helper",
|
||||||
{
|
2: "moderator",
|
||||||
"-1": true,
|
3: "admin",
|
||||||
0: true,
|
4: "superadmin",
|
||||||
1: true,
|
5: "root",
|
||||||
2: true,
|
};
|
||||||
3: true,
|
|
||||||
4: true,
|
class AccessLevel {
|
||||||
5: true,
|
static validate (value) {
|
||||||
}["" + accessLevel] !== true
|
const access = Object.fromEntries(Object.keys(ACCESS_LEVELS)
|
||||||
) {
|
.map(level => [level, true]));
|
||||||
|
return (
|
||||||
|
Number.isInteger(value) &&
|
||||||
|
access["" + value] === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (accessLevel) {
|
||||||
|
if (!this.__proto__.constructor.validate(accessLevel))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid access level: ${accessLevel} (must be ranged from -1 to 5)`,
|
`Invalid access level: ${accessLevel}`,
|
||||||
);
|
);
|
||||||
}
|
this.code = accessLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
get accessName () {
|
||||||
|
return ACCESS_LEVELS[""+this.code]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
constructor (name, accessLevel) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.accessLevel = accessLevel;
|
if (accessLevel instanceof AccessLevel)
|
||||||
|
this.accessLevel = accessLevel;
|
||||||
|
else
|
||||||
|
this.accessLevel = new AccessLevel(accessLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserByName(name) {
|
static async getUserByName(name) {
|
||||||
@ -41,8 +61,15 @@ class User {
|
|||||||
User.cache[name] = user;
|
User.cache[name] = user;
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
// Потом добавить чтение из БД
|
// TODO: Потом добавить чтение из БД
|
||||||
return null;
|
//return null;
|
||||||
|
|
||||||
|
const userdata = await global.database.getUserRowByName(name);
|
||||||
|
if (!userdata)
|
||||||
|
return null;
|
||||||
|
const user = new User(userdata.username, userdata.accessLevel);
|
||||||
|
User.cache[name] = user;
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getByToken(token) {
|
static getByToken(token) {
|
||||||
@ -50,13 +77,27 @@ class User {
|
|||||||
return user ?? null;
|
return user ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserForAPI(getFullInfo = false) {
|
getUserForAPI(getFullInfo = false, fields = "*") {
|
||||||
return {
|
const getted = {
|
||||||
username: this.name,
|
username: this.name,
|
||||||
accessLevel: this.accessLevel,
|
accessLevel: this.accessLevel.code,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (fields !== "*") {
|
||||||
|
const selected = Object.fromEntries(fields.split(/\s*,\s*/g).map(field => [field, true]));
|
||||||
|
for (let field in getted) {
|
||||||
|
if (!selected[field])
|
||||||
|
getted[field] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getted;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCharacters () {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
User.cache = {};
|
User.cache = {};
|
||||||
|
|
||||||
module.exports = { User };
|
module.exports = { User, AccessLevel };
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
},
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"path": "./data"
|
"storage": "./data.db",
|
||||||
|
"logging": false
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"apiType": "kobold"
|
"apiType": "kobold"
|
||||||
|
124
server/database/dbModel.js
Normal file
124
server/database/dbModel.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
const { DataTypes } = require("sequelize");
|
||||||
|
const { AccessLevel } = require("../api/utils/user");
|
||||||
|
const logger = require("../logger");
|
||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
|
||||||
|
/*const SALT_ROUNDS = Math.round(Math.r) + 6;
|
||||||
|
const BCRYPT_SALT = bcrypt.genSaltSync(SALT_ROUNDS);*/
|
||||||
|
function getSalt () {
|
||||||
|
//return bcrypt.genSaltSync(10);
|
||||||
|
return bcrypt.genSaltSync(Math.round(Math.random() * 4) + 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function (sequelize, dbObj) {
|
||||||
|
const dbModel = {
|
||||||
|
users: sequelize.define("users", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
comment: "ID of user",
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
comment: "Username of user",
|
||||||
|
validate: {
|
||||||
|
not: /null/iu,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accessLevel: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false,
|
||||||
|
comment: "access level of user",
|
||||||
|
get: function () {
|
||||||
|
return new AccessLevel(this.getDataValue("accessLevel"));
|
||||||
|
},
|
||||||
|
set: function (value) {
|
||||||
|
if (value instanceof AccessLevel)
|
||||||
|
return this.setDataValue("accessLevel", value.code);
|
||||||
|
if (Number.isInteger(value)) {
|
||||||
|
if (AccessLevel.validate(value))
|
||||||
|
return this.setDataValue("accessLevel", value);
|
||||||
|
}
|
||||||
|
throw new TypeError("accessLevel must be AccessLevel object or integer");
|
||||||
|
//this.setDataValue('inventory', JSON.stringify(value));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: false,
|
||||||
|
comment: "Hash code of password",
|
||||||
|
set: function (value) {
|
||||||
|
const hash = bcrypt.hashSync(value, getSalt());
|
||||||
|
this.setDataValue('password', hash);
|
||||||
|
},
|
||||||
|
get: function () {
|
||||||
|
const checkHash = this.getDataValue("password");
|
||||||
|
return async function (password) {
|
||||||
|
return await new Promise((rs, rj) => {
|
||||||
|
bcrypt.compare(password, checkHash, (err, res) => {
|
||||||
|
if (err)
|
||||||
|
return rs(false);
|
||||||
|
logger.debug("isRes is", res);
|
||||||
|
rs(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isActivate: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: true,
|
||||||
|
comment: "Is user activated",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
userCharacters: sequelize.define("userCharacters", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
comment: "ID of character",
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
comment: "Name of character",
|
||||||
|
},
|
||||||
|
ownerId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
comment: "ID of character's owner",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
dbModel.userCharacters.belongsTo(dbModel.users, {
|
||||||
|
foreignKey: "ownerId", // field in userCharacters
|
||||||
|
targetKey: "id", // field in users
|
||||||
|
onDelete: "CASCADE", // If delete user, characters will be deleted
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
});
|
||||||
|
dbModel.users.hasMany(dbModel.userCharacters, {
|
||||||
|
foreignKey: "ownerId", // field ownerId
|
||||||
|
sourceKey: "id",
|
||||||
|
});
|
||||||
|
|
||||||
|
await sequelize.authenticate();
|
||||||
|
const initPromise = Object.entries(dbModel).map(async ([tableName, table]) => {
|
||||||
|
logger.log(`* init table: ${tableName}`);
|
||||||
|
await table.sync({ alter: true });
|
||||||
|
const rows = await table.findAll();
|
||||||
|
await dbObj.cache[tableName]?.(rows, table);
|
||||||
|
logger.log(`* inited: ${tableName}`);
|
||||||
|
});
|
||||||
|
await Promise.all(initPromise);
|
||||||
|
};
|
89
server/database/index.js
Normal file
89
server/database/index.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
const { Sequelize } = require("sequelize");
|
||||||
|
const dbModel = require("./dbModel");
|
||||||
|
const logger = require("../logger");
|
||||||
|
|
||||||
|
class DatabaseCache {
|
||||||
|
async users (rows, table) {
|
||||||
|
// Add root user only where root user is activated
|
||||||
|
if (global.config.rootuser.activated) {
|
||||||
|
const isRootHasOnDB = rows.find(x => x.username === global.config.rootuser.login);
|
||||||
|
const rootLevel = 5; // TODO: replace it to AccessLevel.root in future
|
||||||
|
|
||||||
|
if (!isRootHasOnDB) {
|
||||||
|
await table.create({
|
||||||
|
username: global.config.rootuser.login,
|
||||||
|
accessLevel: rootLevel,
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
return await this.users((
|
||||||
|
await table.findAll()
|
||||||
|
), table);
|
||||||
|
} else if (!rows.find(x => x.username === global.config.rootuser.login && x.accessLevel.code === rootLevel)) {
|
||||||
|
throw new Error(`Username ${
|
||||||
|
global.config.rootuser.login
|
||||||
|
} is busy`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.users.rows = rows;
|
||||||
|
this.users.byUsername = new Object();
|
||||||
|
this.users.byId = new Object();
|
||||||
|
|
||||||
|
for (let row of rows) {
|
||||||
|
this.users.byUsername[row.username] = row;
|
||||||
|
this.users.byId[row.id] = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user info
|
||||||
|
this.users.add = async (row, isCreate = false) => {
|
||||||
|
if (isCreate)
|
||||||
|
await table.create(row);
|
||||||
|
|
||||||
|
let addedData = null;
|
||||||
|
if (row.username)
|
||||||
|
addedData = await table.findAll({
|
||||||
|
where: {
|
||||||
|
username: row.username
|
||||||
|
}
|
||||||
|
})[0];
|
||||||
|
else if (row.id)
|
||||||
|
addedData = await table.findAll({
|
||||||
|
where: {
|
||||||
|
id: row.id
|
||||||
|
}
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if (!addedData) throw new Error("Invalid row information");
|
||||||
|
this.users.rows.push(addedData);
|
||||||
|
// By keys
|
||||||
|
this.users.byUsername[addedData.username] = addedData;
|
||||||
|
this.users.byId[addedData.id] = addedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Database {
|
||||||
|
constructor(sequlizeData) {
|
||||||
|
this.sequlize = new Sequelize(sequlizeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(fromHandler = false) {
|
||||||
|
global.server.toggleLock(true);
|
||||||
|
this.cache = new DatabaseCache();
|
||||||
|
await dbModel(this.sequlize, this);
|
||||||
|
global.server.toggleLock(false);
|
||||||
|
if (!fromHandler)
|
||||||
|
global.server.on("config.update", async ({
|
||||||
|
oldConfig, newConfig
|
||||||
|
}) => {
|
||||||
|
if (JSON.stringify(oldConfig.database) !== JSON.stringify(newConfig.database))
|
||||||
|
await this.init(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return ROW of 'users' table by username
|
||||||
|
async getUserRowByName (name) {
|
||||||
|
return this.cache.users.byUsername[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Database;
|
@ -4,6 +4,8 @@ const parser = require("./packet-parser");
|
|||||||
const frontend = require("./frontend");
|
const frontend = require("./frontend");
|
||||||
const websocket = require("./ws-handler");
|
const websocket = require("./ws-handler");
|
||||||
const crypt = require("./crypt");
|
const crypt = require("./crypt");
|
||||||
|
const Database = require("./database");
|
||||||
|
|
||||||
const EventEmitter = require("events");
|
const EventEmitter = require("events");
|
||||||
const net = require("net");
|
const net = require("net");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@ -11,10 +13,29 @@ const path = require("path");
|
|||||||
|
|
||||||
const { API } = require("./api");
|
const { API } = require("./api");
|
||||||
|
|
||||||
|
// So.. what part of the WeakMap/Server class made you think
|
||||||
|
// Oh! Developer of this AI client agrees that
|
||||||
|
// [Object Server].isLocked can be writeable?
|
||||||
|
const isLockedServer = new WeakMap();
|
||||||
|
|
||||||
class Server extends EventEmitter {
|
class Server extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.on("config.update", ({ oldConfig, newConfig }) => {});
|
this.on("config.update", ({ oldConfig, newConfig }) => {});
|
||||||
|
//this.isLocked = false;
|
||||||
|
isLockedServer.set(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
set isLocked (_) {
|
||||||
|
throw new Error("isLocked not writeable");
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLocked () {
|
||||||
|
return isLockedServer.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLock (mode = true) {
|
||||||
|
isLockedServer.set(this, !!mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
run(address, port) {
|
run(address, port) {
|
||||||
@ -185,7 +206,8 @@ const defaultConfigData = {
|
|||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
dialect: "sqlite",
|
dialect: "sqlite",
|
||||||
path: "./data",
|
storage: "./data.db",
|
||||||
|
logging: false
|
||||||
},
|
},
|
||||||
ai: {
|
ai: {
|
||||||
apiType: "kobold",
|
apiType: "kobold",
|
||||||
@ -199,63 +221,12 @@ const defaultConfigData = {
|
|||||||
|
|
||||||
global.server = new Server();
|
global.server = new Server();
|
||||||
cfgHandle(global.server, defaultConfigData, logger);
|
cfgHandle(global.server, defaultConfigData, logger);
|
||||||
|
global.database = new Database(global.config.database);
|
||||||
logger.log("Current config:", global.config);
|
global.database.init()
|
||||||
|
.then(() => {
|
||||||
global.server.run(global.config.server.address, global.config.server.port);
|
logger.log("Current config:", global.config);
|
||||||
|
global.server.run(global.config.server.address, global.config.server.port);
|
||||||
/*
|
})
|
||||||
if (global.config.server.secureMode) {
|
.catch((err) => {
|
||||||
async function initEncryption () {
|
throw err;
|
||||||
const datadir = global.config.server["ssl-path"] ?? path.join(__dirname, "/encryption");
|
|
||||||
|
|
||||||
function generate () {
|
|
||||||
logger.log("Generate new keys...");
|
|
||||||
global.openSSH = crypt.generateOpenSSH({
|
|
||||||
commonName: global.config.server.info.name
|
|
||||||
}, {
|
|
||||||
days: 365,
|
|
||||||
pkcs7: true,
|
|
||||||
clientCertificate: true,
|
|
||||||
clientCertificateCN: "AI Adventure Labs",
|
|
||||||
algorithm: 'sha256',
|
|
||||||
keySize: 2048,
|
|
||||||
});
|
|
||||||
fs.writeFileSync(path.join(datadir, "public.pem"), global.openSSH.public_key.bytes);
|
|
||||||
fs.writeFileSync(path.join(datadir, "private.pem"), global.openSSH.private_key.bytes);
|
|
||||||
fs.writeFileSync(path.join(datadir, "cert.crt"), global.openSSH.cert);
|
|
||||||
}
|
|
||||||
|
|
||||||
const readFilePromise = new Promise((resolve, reject) => {
|
|
||||||
fs.readFile(path.join(datadir, "public.pem"), (err, public_key) => {
|
|
||||||
if (err) {
|
|
||||||
return resolve(generate());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
public_key = new crypt.CryptKey(public_key);
|
|
||||||
const private_key = new crypt.CryptKey(fs.readFileSync(path.join(datadir, "private.pem")));
|
|
||||||
const cert = fs.readFileSync(path.join(datadir, "cert.crt"));
|
|
||||||
|
|
||||||
global.openSSH = new crypt.OpenSSH({
|
|
||||||
public_key, private_key, cert
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
} catch (_) {
|
|
||||||
resolve(generate());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await readFilePromise;
|
|
||||||
|
|
||||||
logger.log(`loaded from ${datadir}`);
|
|
||||||
};
|
|
||||||
initEncryption().then(() => {
|
|
||||||
server.run(global.config.server.address, global.config.server.port);
|
|
||||||
}).catch(e => {
|
|
||||||
throw e;
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
else
|
|
||||||
server.run(global.config.server.address, global.config.server.port);
|
|
||||||
*/
|
|
||||||
|
Loading…
Reference in New Issue
Block a user