add more db infra
This commit is contained in:
parent
8f00e688e0
commit
3159fc9c0a
14 changed files with 221 additions and 20 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
server/node_modules
|
||||
server/config.json
|
||||
server/build
|
||||
server/logs
|
|
@ -5,6 +5,10 @@
|
|||
"port": 3306,
|
||||
"username": "user",
|
||||
"password": "password",
|
||||
"name": "MultiProbe"
|
||||
"name": "MultiProbe",
|
||||
"pbkdf2": {
|
||||
"itterations": 10000,
|
||||
"keylength": 64
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@ import FunkyArray from "./objects/FunkyArray";
|
|||
import RemoteUser from "./objects/RemoteUser";
|
||||
import { MessageType } from "./enums/MessageType";
|
||||
import Database from "./objects/Database";
|
||||
import { Console } from "hsconsole";
|
||||
|
||||
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
||||
|
||||
const users = new FunkyArray<string, RemoteUser>();
|
||||
|
||||
|
@ -12,7 +15,7 @@ new Database(Config.database.address, Config.database.port, Config.database.user
|
|||
|
||||
const server = new WebSocketServer({
|
||||
port: Config.port
|
||||
}, () => console.log(`Server listening at ${Config.port}`));
|
||||
}, () => Console.printInfo(`Server listening at ${Config.port}`));
|
||||
|
||||
function sendToAllButSelf(user:RemoteUser, data:Buffer) {
|
||||
users.forEach(otherUser => {
|
||||
|
|
|
@ -15,5 +15,11 @@ interface ConfigDatabase {
|
|||
port: number,
|
||||
username: string,
|
||||
password: string,
|
||||
name: string
|
||||
name: string,
|
||||
pbkdf2: DatabasePbkdf2
|
||||
}
|
||||
|
||||
interface DatabasePbkdf2 {
|
||||
itterations: number,
|
||||
keylength: number
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { Console } from "hsconsole";
|
||||
import { createPool, Pool, RowDataPacket } from "mysql2";
|
||||
|
||||
export type DBInDataType = string | number | null | undefined;
|
||||
|
@ -20,7 +21,7 @@ export default class Database {
|
|||
database: databaseName
|
||||
});
|
||||
|
||||
console.log(`DB connection pool created. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
|
||||
Console.printInfo(`DB connection pool created. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
|
||||
|
||||
Database.Instance = this;
|
||||
}
|
||||
|
|
34
server/objects/Party.ts
Normal file
34
server/objects/Party.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
export default class Party {
|
||||
public Id:number;
|
||||
public PartyRef:string;
|
||||
public Name:string;
|
||||
public CreatedByUserId:number;
|
||||
public CreatedDatetime:Date;
|
||||
public LastModifiedByUserId?:number;
|
||||
public LastModifiedDatetime?:Date;
|
||||
public DeletedByUserId?:number;
|
||||
public DeletedDatetime?:Date;
|
||||
public IsDeleted:boolean;
|
||||
|
||||
public constructor(id?:number, partyRef?:string, name?:string, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) {
|
||||
if (typeof(id) == "number" && typeof(partyRef) == "string" && typeof(name) == "string" && typeof(createdByUserId) == "number" && createdDateTime instanceof Date && typeof(lastModifiedByUserId) == "number" && lastModifiedDatetime instanceof Date && typeof(deletedByUserId) == "number" && deletedDatetime instanceof Date && typeof(isDeleted) == "boolean") {
|
||||
this.Id = id;
|
||||
this.PartyRef = partyRef;
|
||||
this.Name = name;
|
||||
this.CreatedByUserId = createdByUserId;
|
||||
this.CreatedDatetime = createdDateTime;
|
||||
this.LastModifiedByUserId = lastModifiedByUserId;
|
||||
this.LastModifiedDatetime = lastModifiedDatetime;
|
||||
this.DeletedByUserId = deletedByUserId;
|
||||
this.DeletedDatetime = deletedDatetime;
|
||||
this.IsDeleted = isDeleted;
|
||||
} else {
|
||||
this.Id = Number.MIN_VALUE;
|
||||
this.PartyRef = "";
|
||||
this.Name = "";
|
||||
this.CreatedByUserId = Number.MIN_VALUE;
|
||||
this.CreatedDatetime = new Date(0);
|
||||
this.IsDeleted = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,10 @@ export default class User {
|
|||
public PasswordHash:string;
|
||||
public CreatedByUserId:number;
|
||||
public CreatedDatetime:Date;
|
||||
public LastModifiedByUserId:number;
|
||||
public LastModifiedDatetime:Date;
|
||||
public DeletedByUserId:number;
|
||||
public DeletedDatetime:Date;
|
||||
public LastModifiedByUserId?:number;
|
||||
public LastModifiedDatetime?:Date;
|
||||
public DeletedByUserId?:number;
|
||||
public DeletedDatetime?:Date;
|
||||
public IsDeleted:boolean;
|
||||
|
||||
public constructor(id?:number, username?:string, passwordSalt?:string, passwordHash?:string, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) {
|
||||
|
@ -31,10 +31,6 @@ export default class User {
|
|||
this.PasswordSalt = "";
|
||||
this.CreatedByUserId = Number.MIN_VALUE;
|
||||
this.CreatedDatetime = new Date(0);
|
||||
this.LastModifiedByUserId = Number.MIN_VALUE;
|
||||
this.LastModifiedDatetime = new Date(0);
|
||||
this.DeletedByUserId = Number.MIN_VALUE;
|
||||
this.DeletedDatetime = new Date(0);
|
||||
this.IsDeleted = false;
|
||||
}
|
||||
}
|
||||
|
|
14
server/package-lock.json
generated
14
server/package-lock.json
generated
|
@ -10,6 +10,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bufferstuff": "^1.5.1",
|
||||
"hsconsole": "^1.0.2",
|
||||
"mysql2": "^3.9.7",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
|
@ -557,6 +558,11 @@
|
|||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dyetty": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dyetty/-/dyetty-1.0.1.tgz",
|
||||
"integrity": "sha512-MQEccirDXkAQf5U1gIwcIz46+vMMEEyAl33nCqOJ7TeCRKgcHTZdG013gmWRWw3Q9wivnJqcJ04ohZnyF8nRew=="
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
|
@ -946,6 +952,14 @@
|
|||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/hsconsole": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hsconsole/-/hsconsole-1.0.2.tgz",
|
||||
"integrity": "sha512-st+jaSpNw3uoIhE5vl2lVN8Op8yQF2FyLRdBG68s8vqjduJdKUGtoEXd8Zxe6du1zzpFHHRcU3zJbAq8BOmYQA==",
|
||||
"dependencies": {
|
||||
"dyetty": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bufferstuff": "^1.5.1",
|
||||
"hsconsole": "^1.0.2",
|
||||
"mysql2": "^3.9.7",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
|
|
53
server/repos/PartyRepo.ts
Normal file
53
server/repos/PartyRepo.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import Database from "../objects/Database";
|
||||
import Party from "../objects/Party";
|
||||
import User from "../objects/User";
|
||||
import RepoBase from "./RepoBase";
|
||||
|
||||
export default class PartyRepo {
|
||||
public static async selectById(id:number) {
|
||||
const dbParty = await Database.Instance.query("SELECT * FROM Party WHERE Id = ? LIMIT 1", [id]);
|
||||
if (dbParty == null || dbParty.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
const party = new Party();
|
||||
populatePartyFromDB(party, dbParty[0]);
|
||||
return party;
|
||||
}
|
||||
}
|
||||
|
||||
public static async selectByPartyRef(partyRef:string) {
|
||||
const dbParty = await Database.Instance.query("SELECT * FROM Party WHERE PartyRef = ? LIMIT 1", [partyRef]);
|
||||
if (dbParty == null || dbParty.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
const party = new Party();
|
||||
populatePartyFromDB(party, dbParty[0]);
|
||||
return party;
|
||||
}
|
||||
}
|
||||
|
||||
public static async insertUpdate(user:User) {
|
||||
if (user.Id === Number.MIN_VALUE) {
|
||||
await Database.Instance.query("INSERT Party (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted)
|
||||
]);
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE Party SET Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted), user.Id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function populatePartyFromDB(party:Party, dbParty:any) {
|
||||
party.Id = dbParty.Id;
|
||||
party.PartyRef = dbParty.PartyRef;
|
||||
party.Name = dbParty.Name;
|
||||
party.CreatedByUserId = dbParty.CreatedByUserId;
|
||||
party.CreatedDatetime = dbParty.CreatedDatetime;
|
||||
party.LastModifiedByUserId = dbParty.LastModifiedByUserId;
|
||||
party.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbParty.LastModifiedDatetime);
|
||||
party.DeletedByUserId = dbParty.DeletedByUserId;
|
||||
party.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbParty.DeletedDatetime);
|
||||
party.IsDeleted = dbParty.IsDeleted;
|
||||
}
|
5
server/repos/RepoBase.ts
Normal file
5
server/repos/RepoBase.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default class RepoBase {
|
||||
public static convertNullableDatetimeIntToDate(dateTimeInt?:number) {
|
||||
return dateTimeInt ? new Date(dateTimeInt) : undefined;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import Database from "../objects/Database";
|
||||
import User from "../objects/User";
|
||||
import RepoBase from "./RepoBase";
|
||||
|
||||
export default class UsersRepo {
|
||||
export default class UserRepo {
|
||||
public static async selectById(id:number) {
|
||||
const dbUser = await Database.Instance.query("SELECT * FROM User WHERE Id = ? LIMIT 1", [id]);
|
||||
if (dbUser == null || dbUser.length === 0) {
|
||||
|
@ -26,12 +27,12 @@ export default class UsersRepo {
|
|||
|
||||
public static async insertUpdate(user:User) {
|
||||
if (user.Id === Number.MIN_VALUE) {
|
||||
await Database.Instance.query("INSERT users (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime.getTime(), user.DeletedByUserId, user.DeletedDatetime.getTime(), Number(user.IsDeleted)
|
||||
await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted)
|
||||
]);
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE users SET Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime.getTime(), user.DeletedByUserId, user.DeletedDatetime.getTime(), Number(user.IsDeleted), user.Id
|
||||
await Database.Instance.query(`UPDATE User SET Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted), user.Id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -45,8 +46,8 @@ function populateUserFromDB(user:User, dbUser:any) {
|
|||
user.CreatedByUserId = dbUser.CreatedByUserId;
|
||||
user.CreatedDatetime = dbUser.CreatedDatetime;
|
||||
user.LastModifiedByUserId = dbUser.LastModifiedByUserId;
|
||||
user.LastModifiedDatetime = dbUser.LastModifiedDatetime;
|
||||
user.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.LastModifiedDatetime);
|
||||
user.DeletedByUserId = dbUser.DeletedByUserId;
|
||||
user.DeletedDatetime = dbUser.DeletedDatetime;
|
||||
user.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.DeletedDatetime);
|
||||
user.IsDeleted = dbUser.IsDeleted;
|
||||
}
|
46
server/services/UserService.ts
Normal file
46
server/services/UserService.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Console } from "hsconsole";
|
||||
import User from "../objects/User";
|
||||
import PartyRepo from "../repos/PartyRepo";
|
||||
import UserRepo from "../repos/UserRepo";
|
||||
import PasswordUtility from "../utilities/PasswordUtility";
|
||||
|
||||
export default class UserService {
|
||||
public static async GetUser(id:number) {
|
||||
try {
|
||||
return await UserRepo.selectById(id);
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public static async GetParty(id:number) {
|
||||
try {
|
||||
return await PartyRepo.selectById(id);
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public static async GetPartyByPartyRef(partyRef:string) {
|
||||
try {
|
||||
return await PartyRepo.selectByPartyRef(partyRef);
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public static async CreateUser(currentUserId:number, username:string, password:string) {
|
||||
try {
|
||||
const user = new User();
|
||||
user.Username = username;
|
||||
user.PasswordSalt = PasswordUtility.GenerateSalt();
|
||||
user.PasswordHash = await PasswordUtility.HashPassword(user.PasswordSalt, password);
|
||||
user.CreatedByUserId = currentUserId;
|
||||
user.CreatedDatetime = new Date();
|
||||
|
||||
await UserRepo.insertUpdate(user);
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
}
|
||||
}
|
||||
}
|
36
server/utilities/PasswordUtility.ts
Normal file
36
server/utilities/PasswordUtility.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { pbkdf2, randomBytes } from "crypto";
|
||||
import Config from "../objects/Config";
|
||||
|
||||
export default abstract class PasswordUtility {
|
||||
public static ValidatePassword(hash:string, salt:string, password:string) {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
pbkdf2(password, salt, Config.database.pbkdf2.itterations, Config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else {
|
||||
if (derivedKey.toString("hex") !== hash) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
return resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static HashPassword(salt:string, password:string) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
pbkdf2(password, salt, Config.database.pbkdf2.itterations, Config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else {
|
||||
return resolve(derivedKey.toString("hex"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static GenerateSalt() {
|
||||
return randomBytes(Config.database.pbkdf2.keylength).toString("hex");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue