Big update progress on encapsulating models
This commit is contained in:
31
src/classes/AccountStatsUpdater.ts
Normal file
31
src/classes/AccountStatsUpdater.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {Ruleset} from "../rulesets";
|
||||||
|
import {AccountStats, OutcomeType, PlayerGameResults} from "../models/Stats";
|
||||||
|
import {UpdateError} from "../errors";
|
||||||
|
import StatsUpdater from "./StatsUpdater";
|
||||||
|
|
||||||
|
export class AccountStatsUpdater {
|
||||||
|
private data?: AccountStats;
|
||||||
|
private readonly updater: StatsUpdater;
|
||||||
|
constructor(data?: AccountStats) {
|
||||||
|
if (data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
this.updater = new StatsUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
use(data: AccountStats) {
|
||||||
|
this.data = data;
|
||||||
|
this.updater.use(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
|
||||||
|
if (this.data) {
|
||||||
|
this.updater.updateStats(playerGameResults, ruleset);
|
||||||
|
this.data.gamesPlayed += 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data
|
||||||
|
to analyse.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/classes/PlayerStatsUpdater.ts
Normal file
24
src/classes/PlayerStatsUpdater.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {Ruleset} from "../rulesets";
|
||||||
|
import StatsUpdater, {OutcomeType, PlayerGameResults, PlayerStats} from "../models/Stats";
|
||||||
|
|
||||||
|
class PlayerStatsUpdater {
|
||||||
|
private data?: PlayerStats;
|
||||||
|
private readonly updater: StatsUpdater;
|
||||||
|
constructor(data?: PlayerStats) {
|
||||||
|
if (data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
this.updater = new StatsUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
use(data: PlayerStats) {
|
||||||
|
this.data = data;
|
||||||
|
this.updater.use(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
|
||||||
|
this.updater.updateStats(playerGameResults, ruleset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlayerStatsUpdater;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import ScoreCell, {
|
import ScoreCellCalculator, {
|
||||||
createCellFromDef,
|
createCellFromDef,
|
||||||
ScoreCellValue,
|
ScoreCellValue,
|
||||||
CellState,
|
CellState,
|
||||||
ScoreCellJSONRepresentation
|
ScoreCellJSONRepresentation
|
||||||
} from "./ScoreCell";
|
} from "./ScoreCellCalculator";
|
||||||
import {CellDef, BlockDef, BonusBlockDef, NoBonusBlockDef } from "../rulesets";
|
import {CellDef, BlockDef, BonusBlockDef, NoBonusBlockDef } from "../rulesets";
|
||||||
|
|
||||||
export const createBlockFromDef = (blockId: string, blockDef: BlockDef) : ScoreBlock => {
|
export const createBlockFromDef = (blockId: string, blockDef: BlockDef) : ScoreBlockCalculator => {
|
||||||
if (blockDef.hasBonus) {
|
if (blockDef.hasBonus) {
|
||||||
return new ScoreBlockWithBonus(blockId, blockDef);
|
return new ScoreBlockWithBonus(blockId, blockDef);
|
||||||
}
|
}
|
||||||
@@ -19,16 +19,16 @@ export interface ScoreBlockJSONRepresentation {
|
|||||||
cells: Record<string, ScoreCellJSONRepresentation>;
|
cells: Record<string, ScoreCellJSONRepresentation>;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ScoreBlock {
|
abstract class ScoreBlockCalculator {
|
||||||
protected cells: ScoreCell[];
|
protected cells: ScoreCellCalculator[];
|
||||||
protected id: string;
|
protected id: string;
|
||||||
|
|
||||||
protected constructor(blockId: string, blockDef: BlockDef) {
|
protected constructor(blockId: string, blockDef: BlockDef) {
|
||||||
this.cells = ScoreBlock.generateCells(blockDef.cells);
|
this.cells = ScoreBlockCalculator.generateCells(blockDef.cells);
|
||||||
this.id = blockId;
|
this.id = blockId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateCells(cellDefs: Record<string, CellDef>): ScoreCell[] {
|
private static generateCells(cellDefs: Record<string, CellDef>): ScoreCellCalculator[] {
|
||||||
const cells = [];
|
const cells = [];
|
||||||
for (const cellId in cellDefs) {
|
for (const cellId in cellDefs) {
|
||||||
cells.push(createCellFromDef(cellId, cellDefs[cellId]));
|
cells.push(createCellFromDef(cellId, cellDefs[cellId]));
|
||||||
@@ -56,7 +56,7 @@ abstract class ScoreBlock {
|
|||||||
return this.getCellById(cellId).isStruck();
|
return this.getCellById(cellId).isStruck();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCellById(cellId: string): ScoreCell {
|
private getCellById(cellId: string): ScoreCellCalculator {
|
||||||
const foundScoreCell = this.cells.find(cell => cell.getId() === cellId);
|
const foundScoreCell = this.cells.find(cell => cell.getId() === cellId);
|
||||||
if (foundScoreCell !== undefined) {
|
if (foundScoreCell !== undefined) {
|
||||||
return foundScoreCell;
|
return foundScoreCell;
|
||||||
@@ -77,7 +77,7 @@ abstract class ScoreBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScoreBlockWithBonus extends ScoreBlock {
|
class ScoreBlockWithBonus extends ScoreBlockCalculator {
|
||||||
protected readonly bonus: number;
|
protected readonly bonus: number;
|
||||||
protected readonly bonusFor: number;
|
protected readonly bonusFor: number;
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ class ScoreBlockWithBonus extends ScoreBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScoreBlockNoBonus extends ScoreBlock {
|
class ScoreBlockNoBonus extends ScoreBlockCalculator {
|
||||||
constructor(blockId: string, blockDef: NoBonusBlockDef) {
|
constructor(blockId: string, blockDef: NoBonusBlockDef) {
|
||||||
super(blockId, blockDef);
|
super(blockId, blockDef);
|
||||||
}
|
}
|
||||||
@@ -111,4 +111,4 @@ class ScoreBlockNoBonus extends ScoreBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ScoreBlock;
|
export default ScoreBlockCalculator;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {BlockDef, Ruleset} from "../rulesets";
|
import {BlockDef, Ruleset} from "../rulesets";
|
||||||
import ScoreBlock, {createBlockFromDef, ScoreBlockJSONRepresentation} from "./ScoreBlock";
|
import ScoreBlockCalculator, {createBlockFromDef, ScoreBlockJSONRepresentation} from "./ScoreBlockCalculator";
|
||||||
|
|
||||||
export type CellLocation = { blockId: string, cellId: string };
|
export type CellLocation = { blockId: string, cellId: string };
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export interface ScoreCardJSONRepresentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ScoreCalculator {
|
class ScoreCalculator {
|
||||||
private readonly blocks: ScoreBlock[];
|
private readonly blocks: ScoreBlockCalculator[];
|
||||||
|
|
||||||
constructor(gameSchema: Ruleset) {
|
constructor(gameSchema: Ruleset) {
|
||||||
this.blocks = ScoreCalculator.generateBlocks(gameSchema.blocks);
|
this.blocks = ScoreCalculator.generateBlocks(gameSchema.blocks);
|
||||||
@@ -20,7 +20,7 @@ class ScoreCalculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateBlocks(blockDefs: Record<string, BlockDef>): ScoreBlock[] {
|
private static generateBlocks(blockDefs: Record<string, BlockDef>): ScoreBlockCalculator[] {
|
||||||
const blocks = [];
|
const blocks = [];
|
||||||
for (const blockId in blockDefs) {
|
for (const blockId in blockDefs) {
|
||||||
blocks.push(createBlockFromDef(blockId, blockDefs[blockId]));
|
blocks.push(createBlockFromDef(blockId, blockDefs[blockId]));
|
||||||
@@ -56,7 +56,7 @@ class ScoreCalculator {
|
|||||||
return this.getBlockById(loc.blockId).cellWithIdIsStruck(loc.cellId);
|
return this.getBlockById(loc.blockId).cellWithIdIsStruck(loc.cellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBlockById(blockId: string): ScoreBlock {
|
private getBlockById(blockId: string): ScoreBlockCalculator {
|
||||||
const foundScoreBlock = this.blocks.find(block => block.getId() === blockId);
|
const foundScoreBlock = this.blocks.find(block => block.getId() === blockId);
|
||||||
if (foundScoreBlock !== undefined) {
|
if (foundScoreBlock !== undefined) {
|
||||||
return foundScoreBlock;
|
return foundScoreBlock;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from "../rulesets";
|
} from "../rulesets";
|
||||||
import { CellFlag, FieldType } from "../enums";
|
import { CellFlag, FieldType } from "../enums";
|
||||||
|
|
||||||
export const createCellFromDef = (cellId: string, cellDef: CellDef): ScoreCell => {
|
export const createCellFromDef = (cellId: string, cellDef: CellDef): ScoreCellCalculator => {
|
||||||
switch (cellDef.fieldType) {
|
switch (cellDef.fieldType) {
|
||||||
case FieldType.number:
|
case FieldType.number:
|
||||||
return new NumberScoreCell(cellId, cellDef);
|
return new NumberScoreCell(cellId, cellDef);
|
||||||
@@ -33,7 +33,7 @@ export interface ScoreCellJSONRepresentation {
|
|||||||
value: number | boolean | CellFlag.strike;
|
value: number | boolean | CellFlag.strike;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ScoreCell {
|
abstract class ScoreCellCalculator {
|
||||||
protected readonly id: string;
|
protected readonly id: string;
|
||||||
protected static readonly fieldType: FieldType;
|
protected static readonly fieldType: FieldType;
|
||||||
protected struck: boolean;
|
protected struck: boolean;
|
||||||
@@ -69,7 +69,7 @@ abstract class ScoreCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumberScoreCell extends ScoreCell {
|
class NumberScoreCell extends ScoreCellCalculator {
|
||||||
protected static readonly fieldType = FieldType.number;
|
protected static readonly fieldType = FieldType.number;
|
||||||
|
|
||||||
constructor(cellId: string, cellDef: NumberCellDef) {
|
constructor(cellId: string, cellDef: NumberCellDef) {
|
||||||
@@ -82,7 +82,7 @@ class NumberScoreCell extends ScoreCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoolScoreCell extends ScoreCell {
|
class BoolScoreCell extends ScoreCellCalculator {
|
||||||
protected static readonly fieldType = FieldType.bool;
|
protected static readonly fieldType = FieldType.bool;
|
||||||
private readonly score: number;
|
private readonly score: number;
|
||||||
protected value: boolean;
|
protected value: boolean;
|
||||||
@@ -103,7 +103,7 @@ class BoolScoreCell extends ScoreCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SuperkadiScoreCell extends ScoreCell {
|
class SuperkadiScoreCell extends ScoreCellCalculator {
|
||||||
protected static readonly fieldType = FieldType.superkadi;
|
protected static readonly fieldType = FieldType.superkadi;
|
||||||
private readonly score: number;
|
private readonly score: number;
|
||||||
protected value: number;
|
protected value: number;
|
||||||
@@ -124,7 +124,7 @@ class SuperkadiScoreCell extends ScoreCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultiplierScoreCell extends ScoreCell {
|
class MultiplierScoreCell extends ScoreCellCalculator {
|
||||||
protected static readonly fieldType = FieldType.multiplier;
|
protected static readonly fieldType = FieldType.multiplier;
|
||||||
protected readonly multiplier: number;
|
protected readonly multiplier: number;
|
||||||
protected value: number;
|
protected value: number;
|
||||||
@@ -145,4 +145,4 @@ class MultiplierScoreCell extends ScoreCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ScoreCell;
|
export default ScoreCellCalculator;
|
||||||
119
src/models/stats.ts → src/classes/StatsUpdater.ts
Executable file → Normal file
119
src/models/stats.ts → src/classes/StatsUpdater.ts
Executable file → Normal file
@@ -1,66 +1,18 @@
|
|||||||
import {Ruleset} from "../rulesets";
|
import {Ruleset} from "../rulesets";
|
||||||
|
import ScoreCalculator, {ScoreCardJSONRepresentation} from "./ScoreCalculator";
|
||||||
|
import {UpdateError} from "../errors";
|
||||||
import {FieldType} from "../enums";
|
import {FieldType} from "../enums";
|
||||||
import ScoreCalculator, {ScoreCardJSONRepresentation} from "../classes/ScoreCalculator";
|
import {
|
||||||
|
BaseStats,
|
||||||
|
BestableFieldStats,
|
||||||
|
BoolFieldStats,
|
||||||
|
CellStats,
|
||||||
|
OutcomeType,
|
||||||
|
PlayerGameResults, RulesetStats,
|
||||||
|
TotalFieldStats
|
||||||
|
} from "../models/Stats";
|
||||||
|
|
||||||
class UpdateError extends Error {
|
class StatsUpdater {
|
||||||
constructor(message: string) {
|
|
||||||
super(message);
|
|
||||||
this.name = "UpdateError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OutcomeType = "win" | "loss" | "runnerUp" | "draw";
|
|
||||||
export interface PlayerStats extends BaseStats {
|
|
||||||
}
|
|
||||||
export interface AccountStats extends BaseStats {
|
|
||||||
timesNoWinner: number;
|
|
||||||
}
|
|
||||||
interface BaseStats {
|
|
||||||
statsByRuleset: Record<string, RulesetStats>
|
|
||||||
gamesPlayed: number;
|
|
||||||
}
|
|
||||||
interface RulesetStats {
|
|
||||||
blockStats: Record<string, BlockStats>;
|
|
||||||
wins: number;
|
|
||||||
runnerUps: number;
|
|
||||||
draws: number;
|
|
||||||
losses: number;
|
|
||||||
grandTotal: TotalFieldStats;
|
|
||||||
}
|
|
||||||
interface BlockStats {
|
|
||||||
cellStats: Record<string, CellStats>;
|
|
||||||
timesHadBonus?: number;
|
|
||||||
total: TotalFieldStats;
|
|
||||||
}
|
|
||||||
interface BaseCellStats {
|
|
||||||
runningTotal: number;
|
|
||||||
}
|
|
||||||
interface StrikeableFieldStats extends BaseCellStats {
|
|
||||||
timesStruck: number;
|
|
||||||
}
|
|
||||||
interface BestableFieldStats extends BaseCellStats {
|
|
||||||
best: number;
|
|
||||||
worst: number;
|
|
||||||
}
|
|
||||||
type TotalFieldStats = BestableFieldStats;
|
|
||||||
type BoolFieldStats = StrikeableFieldStats & { total: number };
|
|
||||||
type NumberFieldStats = StrikeableFieldStats & BestableFieldStats;
|
|
||||||
type MultiplierFieldStats = NumberFieldStats;
|
|
||||||
type SuperkadiFieldStats = NumberFieldStats;
|
|
||||||
type CellStats = BoolFieldStats | NumberFieldStats | MultiplierFieldStats | SuperkadiFieldStats;
|
|
||||||
|
|
||||||
export interface PlayerGameResults {
|
|
||||||
blocks: Record<string, BlockResults>;
|
|
||||||
}
|
|
||||||
interface BlockResults {
|
|
||||||
cells: Record<string, CellResults>
|
|
||||||
}
|
|
||||||
interface CellResults {
|
|
||||||
value: CellValue;
|
|
||||||
}
|
|
||||||
type CellValue = "cellFlagStrike" | number | boolean;
|
|
||||||
|
|
||||||
class BaseStatsUpdater {
|
|
||||||
private data?: BaseStats;
|
private data?: BaseStats;
|
||||||
private validationRuleset?: Ruleset;
|
private validationRuleset?: Ruleset;
|
||||||
private calculator?: ScoreCalculator;
|
private calculator?: ScoreCalculator;
|
||||||
@@ -138,49 +90,4 @@ class BaseStatsUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlayerStatsUpdater {
|
export default StatsUpdater;
|
||||||
private data?: PlayerStats;
|
|
||||||
private readonly updater: BaseStatsUpdater;
|
|
||||||
constructor(data?: PlayerStats) {
|
|
||||||
if (data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
this.updater = new BaseStatsUpdater();
|
|
||||||
}
|
|
||||||
|
|
||||||
use(data: PlayerStats) {
|
|
||||||
this.data = data;
|
|
||||||
this.updater.use(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
|
|
||||||
this.updater.updateStats(playerGameResults, ruleset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountStatsUpdater {
|
|
||||||
private data?: AccountStats;
|
|
||||||
private readonly updater: BaseStatsUpdater;
|
|
||||||
constructor(data?: AccountStats) {
|
|
||||||
if (data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
this.updater = new BaseStatsUpdater();
|
|
||||||
}
|
|
||||||
|
|
||||||
use(data: AccountStats) {
|
|
||||||
this.data = data;
|
|
||||||
this.updater.use(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
|
|
||||||
if (this.data) {
|
|
||||||
this.updater.updateStats(playerGameResults, ruleset);
|
|
||||||
this.data.gamesPlayed += 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data
|
|
||||||
to analyse.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import passport from "passport";
|
import passport from "passport";
|
||||||
import DbUser from "../models/dbUser_old";
|
|
||||||
import {RequestHandler} from "express";
|
import {RequestHandler} from "express";
|
||||||
|
import StoredUsers, {LoginDetails} from "../models/StoredUser";
|
||||||
|
|
||||||
export const showLoginPage: RequestHandler = (req, res) => {
|
export const showLoginPage: RequestHandler = (req, res) => {
|
||||||
res.render("login.ejs");
|
res.render("login.ejs");
|
||||||
@@ -20,7 +20,8 @@ export const showRegistrationPage: RequestHandler = (req, res) => {
|
|||||||
|
|
||||||
export const registerNewUser: RequestHandler = async (req, res) => {
|
export const registerNewUser: RequestHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const newUser = await DbUser.registerUser(req.body.username, req.body.email, req.body.password);
|
const loginDetails: LoginDetails = req.body as LoginDetails;
|
||||||
|
const newUser = await StoredUsers.registerUser(loginDetails);
|
||||||
req.login(newUser, (err) => {
|
req.login(newUser, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
29
src/errors.ts
Normal file
29
src/errors.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export class KadiError extends Error {}
|
||||||
|
|
||||||
|
export class UpdateError extends KadiError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "UpdateError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenericPersistenceError extends KadiError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "GenericPersistenceError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MongoError extends GenericPersistenceError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "MongoError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModelParameterError extends GenericPersistenceError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "ModelParameterError";
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/models/MongoStoredObject.ts
Normal file
15
src/models/MongoStoredObject.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import StoredObject from "./StoredObject";
|
||||||
|
|
||||||
|
abstract class MongoStoredObject<RawDataInterface> implements StoredObject {
|
||||||
|
protected constructor(protected data: {_id: string} & RawDataInterface) {}
|
||||||
|
|
||||||
|
id(): string {
|
||||||
|
return this.data._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData(): RawDataInterface {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MongoStoredObject;
|
||||||
84
src/models/MongoStoredObjectCollection.ts
Normal file
84
src/models/MongoStoredObjectCollection.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import StoredObject, {StoredObjectConstructor, StoredObjectId} from "./StoredObject";
|
||||||
|
import mongo from "mongodb";
|
||||||
|
import {tryQuery} from "./utils";
|
||||||
|
import {MongoError} from "../errors";
|
||||||
|
import StoredObjectCollection from "./StoredObjectCollection";
|
||||||
|
|
||||||
|
|
||||||
|
abstract class MongoStoredObjectCollection<IRawData, IStoredObject extends StoredObject>
|
||||||
|
implements StoredObjectCollection<IStoredObject> {
|
||||||
|
|
||||||
|
protected mongoDbClientCollection: mongo.Collection;
|
||||||
|
protected StoredObjectConstructor: StoredObjectConstructor<IRawData, IStoredObject>;
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
collectionClient: mongo.Collection,
|
||||||
|
objectConstructor: StoredObjectConstructor<IRawData, IStoredObject>
|
||||||
|
) {
|
||||||
|
this.mongoDbClientCollection = collectionClient;
|
||||||
|
this.StoredObjectConstructor = objectConstructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async create(objectData: Omit<IRawData, "_id">): Promise<IRawData> {
|
||||||
|
return tryQuery(async () => {
|
||||||
|
const insertOneWriteOpResult = await this.mongoDbClientCollection.insertOne(objectData);
|
||||||
|
if (insertOneWriteOpResult.result.ok === 1) {
|
||||||
|
return insertOneWriteOpResult.ops[0]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new MongoError(`Error creating the object: ${JSON.stringify(objectData)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async newObject(objectData: Omit<IRawData, "_id">): Promise<IStoredObject> {
|
||||||
|
return new this.StoredObjectConstructor(await this.create(objectData));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async findObjectById(id: string): Promise<IRawData | null> {
|
||||||
|
return tryQuery(async () =>
|
||||||
|
await this.mongoDbClientCollection.findOne({_id: id})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async findObjectByAttribute(attribute: string, value: any): Promise<IRawData | null> {
|
||||||
|
return tryQuery(async () =>
|
||||||
|
await this.mongoDbClientCollection.findOne({[attribute]: value})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteById(objectId: StoredObjectId, returnObject?: boolean): Promise<IStoredObject | null | void> {
|
||||||
|
let deletedObject;
|
||||||
|
if (returnObject ?? true) {
|
||||||
|
deletedObject = await this.findById(objectId);
|
||||||
|
}
|
||||||
|
const deleteWriteOpResult = await this.mongoDbClientCollection.deleteOne({_id: objectId});
|
||||||
|
if (deleteWriteOpResult.result.ok === 1) {
|
||||||
|
return deletedObject;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new MongoError(`Error deleting the object with id: ${JSON.stringify(objectId)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async findById(id: string): Promise<IStoredObject | null> {
|
||||||
|
const data = await this.findObjectById(id);
|
||||||
|
if (data) {
|
||||||
|
return new this.StoredObjectConstructor(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async save(...objects: IStoredObject[]): Promise<void> {
|
||||||
|
await tryQuery(async () => {
|
||||||
|
for (const object of objects) {
|
||||||
|
await this.mongoDbClientCollection.findOneAndUpdate({_id: object.id()}, {...object.rawData()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MongoStoredObjectCollection;
|
||||||
100
src/models/Stats.ts
Executable file
100
src/models/Stats.ts
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
import {DEFAULT_RULESET_NAME, DefaultRulesetStats} from "../rulesets";
|
||||||
|
|
||||||
|
|
||||||
|
export type OutcomeType = "win" | "loss" | "runnerUp" | "draw";
|
||||||
|
export interface PlayerStats extends BaseStats {}
|
||||||
|
export interface AccountStats extends BaseStats {
|
||||||
|
timesNoWinner: number;
|
||||||
|
}
|
||||||
|
interface BaseStats {
|
||||||
|
statsByRuleset: Record<string, RulesetStats> & { [DEFAULT_RULESET_NAME]: DefaultRulesetStats }
|
||||||
|
gamesPlayed: number;
|
||||||
|
}
|
||||||
|
export interface RulesetStats {
|
||||||
|
blockStats: Record<string, BlockStats>;
|
||||||
|
wins: number;
|
||||||
|
runnerUps: number;
|
||||||
|
draws: number;
|
||||||
|
losses: number;
|
||||||
|
grandTotal: TotalFieldStats;
|
||||||
|
}
|
||||||
|
interface BlockStats {
|
||||||
|
cellStats: Record<string, CellStats>;
|
||||||
|
timesHadBonus?: number;
|
||||||
|
total: TotalFieldStats;
|
||||||
|
}
|
||||||
|
interface BaseCellStats {
|
||||||
|
runningTotal: number;
|
||||||
|
}
|
||||||
|
interface StrikeableFieldStats extends BaseCellStats {
|
||||||
|
timesStruck: number;
|
||||||
|
}
|
||||||
|
interface BestableFieldStats extends BaseCellStats {
|
||||||
|
best: number;
|
||||||
|
worst: number;
|
||||||
|
}
|
||||||
|
type TotalFieldStats = BestableFieldStats;
|
||||||
|
type BoolFieldStats = StrikeableFieldStats & { total: number };
|
||||||
|
type NumberFieldStats = StrikeableFieldStats & BestableFieldStats;
|
||||||
|
type MultiplierFieldStats = NumberFieldStats;
|
||||||
|
type SuperkadiFieldStats = NumberFieldStats;
|
||||||
|
type CellStats = BoolFieldStats | NumberFieldStats | MultiplierFieldStats | SuperkadiFieldStats;
|
||||||
|
|
||||||
|
|
||||||
|
export interface PlayerGameResults {
|
||||||
|
blocks: Record<string, BlockResults>;
|
||||||
|
}
|
||||||
|
interface BlockResults {
|
||||||
|
cells: Record<string, CellResults>
|
||||||
|
}
|
||||||
|
interface CellResults {
|
||||||
|
value: CellValue;
|
||||||
|
}
|
||||||
|
type CellValue = "cellFlagStrike" | number | boolean;
|
||||||
|
|
||||||
|
|
||||||
|
function default
|
||||||
|
|
||||||
|
function defaultBoolFieldStats(): BoolFieldStats {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultNumberFieldStats(): NumberFieldStats {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultMultiplierFieldStats(): MultiplierFieldStats {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultSuperkadiFieldStats(): SuperkadiFieldStats {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultBaseStatsData(): BaseStats {
|
||||||
|
return {
|
||||||
|
statsByRuleset: {
|
||||||
|
[DEFAULT_RULESET_NAME]: {}
|
||||||
|
wins: 0,
|
||||||
|
runnerUps: 0,
|
||||||
|
draws: 0,
|
||||||
|
losses: 0,
|
||||||
|
grandTotal: {},
|
||||||
|
},
|
||||||
|
gamesPlayed: 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultAccountStatsData(): AccountStats {
|
||||||
|
return {...defaultBaseStatsData(), timesNoWinner: 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultPlayerStatsData(): PlayerStats {
|
||||||
|
return defaultBaseStatsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
defaultPlayerStatsData: defaultPlayerStatsData,
|
||||||
|
defaultAccountStatsData: defaultAccountStatsData,
|
||||||
|
};
|
||||||
9
src/models/StoredObject.ts
Normal file
9
src/models/StoredObject.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export type StoredObjectConstructor<Data, Object> = new (data: Data, ...args: any[]) => Object;
|
||||||
|
export type StoredObjectId = string;
|
||||||
|
|
||||||
|
interface StoredObject {
|
||||||
|
id(): string;
|
||||||
|
rawData(): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StoredObject;
|
||||||
9
src/models/StoredObjectCollection.ts
Normal file
9
src/models/StoredObjectCollection.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import StoredObject, {StoredObjectId} from "./StoredObject";
|
||||||
|
|
||||||
|
interface StoredObjectCollection<StoredObjectInterface extends StoredObject> {
|
||||||
|
findById(id: string): Promise<StoredObjectInterface | null>;
|
||||||
|
deleteById(objectId: StoredObjectId, returnObject?: boolean): Promise<StoredObjectInterface | null | void>;
|
||||||
|
save(...objects: StoredObjectInterface[]): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StoredObjectCollection;
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
import {
|
import Stats, {OutcomeType, PlayerGameResults, PlayerStats} from "./Stats";
|
||||||
OutcomeType,
|
import {getMongoObjectCollection} from "./utils";
|
||||||
PlayerGameResults,
|
|
||||||
PlayerStats,
|
|
||||||
PlayerStatsUpdater
|
|
||||||
} from "./stats";
|
|
||||||
import {
|
|
||||||
getMongoObjectCollection,
|
|
||||||
MongoStoredObject, MongoStoredObjectCollection, StoredObject, StoredObjectCollection, tryQuery,
|
|
||||||
} from "./utils";
|
|
||||||
import {CellValue} from "../controllers/statsController";
|
import {CellValue} from "../controllers/statsController";
|
||||||
import mongo from "mongodb";
|
import mongo from "mongodb";
|
||||||
import {Ruleset} from "../rulesets";
|
import {Ruleset} from "../rulesets";
|
||||||
|
import StoredObjectCollection from "./StoredObjectCollection";
|
||||||
|
import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
|
||||||
|
import StoredObject from "./StoredObject";
|
||||||
|
import PlayerStatsUpdater from "../classes/PlayerStatsUpdater";
|
||||||
|
import MongoStoredObject from "./MongoStoredObject";
|
||||||
|
|
||||||
export interface CellDetails {
|
export interface CellDetails {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -24,6 +21,7 @@ export interface StoredPlayerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StoredPlayerCollection extends StoredObjectCollection<StoredPlayer> {
|
interface StoredPlayerCollection extends StoredObjectCollection<StoredPlayer> {
|
||||||
|
newPlayer(nick: string): Promise<StoredPlayer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MongoStoredPlayerCollection
|
class MongoStoredPlayerCollection
|
||||||
@@ -36,10 +34,9 @@ class MongoStoredPlayerCollection
|
|||||||
this.updater = new PlayerStatsUpdater();
|
this.updater = new PlayerStatsUpdater();
|
||||||
}
|
}
|
||||||
|
|
||||||
newPlayer(nick: string): Promise<StoredPlayer> {
|
async newPlayer(nick: string): Promise<StoredPlayer> {
|
||||||
return tryQuery(async () => {
|
const newPlayer = {nick, stats: Stats.makeBlankDataFields()};
|
||||||
return this.create({nick});
|
return this.newObject(newPlayer);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {SupportedLang} from "../enums";
|
import {SupportedLang} from "../enums";
|
||||||
import Player, {MongoStoredPlayer, StoredPlayer, StoredPlayerData} from "./StoredPlayer";
|
import {StoredPlayer} from "./StoredPlayer";
|
||||||
import {AccountStats} from "./stats";
|
import {AccountStats} from "./Stats";
|
||||||
import {SavedGameData, StoredSavedGame} from "./savedGame";
|
import {SavedGameData, StoredSavedGame} from "./savedGame";
|
||||||
import {
|
import {
|
||||||
GenericModelError, getMongoObjectCollection,
|
GenericPersistenceError, getMongoObjectCollection,
|
||||||
MongoStoredObject,
|
MongoStoredObject,
|
||||||
MongoStoredObjectCollection,
|
MongoStoredObjectCollection,
|
||||||
StoredObject,
|
StoredObject,
|
||||||
@@ -57,7 +57,7 @@ export interface StoredUser extends StoredObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GuestUpdateParams = { id: string, newNick: string };
|
type GuestUpdateParams = { id: string, newNick: string };
|
||||||
type LoginDetails = { username: string, email: string, password: string };
|
export type LoginDetails = { username: string, email: string, password: string };
|
||||||
|
|
||||||
class MongoStoredUser extends MongoStoredObject<StoredUserData> implements StoredUser {
|
class MongoStoredUser extends MongoStoredObject<StoredUserData> implements StoredUser {
|
||||||
constructor(data: StoredUserData) {
|
constructor(data: StoredUserData) {
|
||||||
@@ -165,17 +165,6 @@ class MongoStoredUserCollection extends MongoStoredObjectCollection<StoredUserDa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addNewUser(loginDetails: LoginDetails): Promise<StoredUser> {
|
|
||||||
const newPlayer = await StoredPlayers.newPlayer(loginDetails.username);
|
|
||||||
return this.create({
|
|
||||||
username: loginDetails.username,
|
|
||||||
email: loginDetails.email,
|
|
||||||
password: loginDetails.password,
|
|
||||||
lang: SupportedLang.gb,
|
|
||||||
player: newPlayer.id()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerUser(loginDetails: LoginDetails): Promise<StoredUser> {
|
async registerUser(loginDetails: LoginDetails): Promise<StoredUser> {
|
||||||
const usernameTaken = await this.userWithUsernameExists(loginDetails.username);
|
const usernameTaken = await this.userWithUsernameExists(loginDetails.username);
|
||||||
const emailTaken = await this.userWithEmailExists(loginDetails.email);
|
const emailTaken = await this.userWithEmailExists(loginDetails.email);
|
||||||
@@ -183,13 +172,23 @@ class MongoStoredUserCollection extends MongoStoredObjectCollection<StoredUserDa
|
|||||||
throw new CredentialsTakenError(usernameTaken, emailTaken);
|
throw new CredentialsTakenError(usernameTaken, emailTaken);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const hashedPassword = await bcrypt.hash(loginDetails.password, 10);
|
return this.addNewUser({...loginDetails})
|
||||||
return tryQuery(() =>
|
|
||||||
this.addNewUser({...loginDetails, password: hashedPassword})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async addNewUser(loginDetails: LoginDetails): Promise<StoredUser> {
|
||||||
|
const newPlayer = await StoredPlayers.newPlayer(loginDetails.username);
|
||||||
|
return tryQuery(async () =>
|
||||||
|
this.create({
|
||||||
|
username: loginDetails.username,
|
||||||
|
email: loginDetails.email,
|
||||||
|
password: await this.makePasswordSecure(loginDetails.password),
|
||||||
|
lang: SupportedLang.gb,
|
||||||
|
player: newPlayer.id()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async userWithEmailExists(email: string): Promise<boolean> {
|
async userWithEmailExists(email: string): Promise<boolean> {
|
||||||
const object = await this.findObjectByAttribute("email", email);
|
const object = await this.findObjectByAttribute("email", email);
|
||||||
return object !== null;
|
return object !== null;
|
||||||
@@ -206,9 +205,13 @@ class MongoStoredUserCollection extends MongoStoredObjectCollection<StoredUserDa
|
|||||||
return dbResult;
|
return dbResult;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new GenericModelError("User not found!");
|
throw new GenericPersistenceError("User not found!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async makePasswordSecure(password: string): Promise<string> {
|
||||||
|
return bcrypt.hash(password, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const StoredUsers = new MongoStoredUserCollection(getMongoObjectCollection("users"));
|
const StoredUsers = new MongoStoredUserCollection(getMongoObjectCollection("users"));
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import mongoose, {Types} from "mongoose";
|
|
||||||
import Player, {IPlayer} from "./StoredPlayer";
|
|
||||||
import {GameSubmission} from "../controllers/statsController";
|
import {GameSubmission} from "../controllers/statsController";
|
||||||
import {tryQuery, globalSchemaOptions, StoredObjectCollection, StoredObject} from "./utils";
|
import {StoredObjectCollection, StoredObject, StoredObjectId} from "./utils";
|
||||||
import DbUser from "./dbUser_old";
|
import {PlayerGameResults} from "./Stats";
|
||||||
import {Ruleset} from "../rulesets";
|
|
||||||
|
|
||||||
export interface SavedGameData {
|
export interface SavedGameData {
|
||||||
id: string;
|
id: string;
|
||||||
rulesetUsed: RulesetData;
|
rulesetUsed: RulesetData;
|
||||||
players: mongoose.Types.ObjectId[];
|
players: StoredObjectId[];
|
||||||
results: [];
|
results: PlayerGameResults[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredSavedGame extends StoredObject {
|
export interface StoredSavedGame extends StoredObject {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import mongo, {MongoClient, Db} from "mongodb";
|
import {MongoClient, Db} from "mongodb";
|
||||||
import Settings from "../server-config.json";
|
import Settings from "../server-config.json";
|
||||||
|
import {GenericPersistenceError, MongoError} from "../errors";
|
||||||
|
|
||||||
let SessionDbClient: Db;
|
let SessionDbClient: Db;
|
||||||
export async function initMongoSessionClient() {
|
export async function initMongoSessionClient() {
|
||||||
@@ -18,121 +19,13 @@ export function getMongoObjectCollection(collectionName: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StoredObjectId = string;
|
type CallbackWrapper = <T>(query: () => T) => Promise<T>;
|
||||||
|
|
||||||
export interface StoredObjectCollection<K> {
|
|
||||||
findById(id: string): Promise<K | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class MongoStoredObjectCollection<D, K extends StoredObject> implements StoredObjectCollection<K> {
|
|
||||||
protected mongoDbClientCollection: mongo.Collection;
|
|
||||||
protected MongoStoredObject: new(data: D, ...args: any[]) => K;
|
|
||||||
protected constructor(collectionClient: mongo.Collection, objectConstructor: new (data: D, ...args: any[]) => K) {
|
|
||||||
this.mongoDbClientCollection = collectionClient;
|
|
||||||
this.MongoStoredObject = objectConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async findObjectById(id: string): Promise<any | null> {
|
|
||||||
return tryQuery(async () =>
|
|
||||||
await this.mongoDbClientCollection.findOne({_id: id})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async findObjectByAttribute(attribute: string, value: any): Promise<any | null> {
|
|
||||||
return tryQuery(async () =>
|
|
||||||
await this.mongoDbClientCollection.findOne({attribute: value})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async create(objectData: Partial<D>): Promise<K> {
|
|
||||||
return tryQuery(async () =>
|
|
||||||
await this.mongoDbClientCollection.insertOne(objectData)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteById(objectId: StoredObjectId): Promise<K | null> {
|
|
||||||
const deletedObject = this.findById(objectId);
|
|
||||||
await this.mongoDbClientCollection.deleteOne({_id: objectId});
|
|
||||||
return deletedObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findById(id: string): Promise<K | null> {
|
|
||||||
const data = await this.findObjectById(id);
|
|
||||||
return new this.MongoStoredObject(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
async save(...objects: K[]): Promise<void> {
|
|
||||||
await tryQuery(async () => {
|
|
||||||
for (const object of objects) {
|
|
||||||
await this.mongoDbClientCollection.findOneAndUpdate({_id: object.id()}, {...object.rawData()});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StoredObject {
|
|
||||||
id(): string;
|
|
||||||
rawData(): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class MongoStoredObject<T> implements StoredObject {
|
|
||||||
protected constructor(protected data: {_id: string} & T) {}
|
|
||||||
|
|
||||||
id(): string {
|
|
||||||
return this.data._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawData(): T {
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class MongoError extends Error {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(message);
|
|
||||||
this.name = "MongoError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GenericModelError extends MongoError {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(message);
|
|
||||||
this.name = "GenericModelError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModelParameterError extends GenericModelError {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(message);
|
|
||||||
this.name = "ModelParameterError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallbackWrapper = <T>(query: () => T) => Promise<any>;
|
|
||||||
|
|
||||||
export const tryQuery: CallbackWrapper = async (cb) => {
|
export const tryQuery: CallbackWrapper = async (cb) => {
|
||||||
try {
|
try {
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
throw new GenericModelError(err);
|
throw new GenericPersistenceError(err);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const globalSchemaOptions = {
|
|
||||||
toObject: {
|
|
||||||
transform: function (doc: any, ret: any) {
|
|
||||||
ret.id = ret._id;
|
|
||||||
delete ret._id;
|
|
||||||
delete ret.__v;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toJSON: {
|
|
||||||
transform: function (doc: any, ret: any) {
|
|
||||||
ret.id = ret._id;
|
|
||||||
delete ret._id;
|
|
||||||
delete ret.__v;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import passport from "passport";
|
import passport from "passport";
|
||||||
import {Strategy as LocalStrategy, VerifyFunction} from "passport-local";
|
import {Strategy as LocalStrategy, VerifyFunction} from "passport-local";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import DbUser, {IDbUser} from "./models/dbUser_old";
|
|
||||||
import {NextFunction, Request, Response} from "express";
|
import {NextFunction, Request, Response} from "express";
|
||||||
|
import StoredUsers, {StoredUser} from "./models/StoredUser";
|
||||||
|
|
||||||
export const requireAuthenticated = (req: Request, res: Response, next: NextFunction) => {
|
export const requireAuthenticated = (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
@@ -23,12 +23,12 @@ export const requireNotAuthenticated = (req: Request, res: Response, next: NextF
|
|||||||
};
|
};
|
||||||
|
|
||||||
const authenticateUser: VerifyFunction = async (email, password, done) => {
|
const authenticateUser: VerifyFunction = async (email, password, done) => {
|
||||||
const user = await DbUser.findByEmail(email);
|
const user = await StoredUsers.findByEmail(email);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return done(null, false, { message: "A user with that email does not exist."} );
|
return done(null, false, { message: "A user with that email does not exist."} );
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (await bcrypt.compare(password, user.password)) {
|
if (await bcrypt.compare(password, (await user.getLoginDetails()).password)) {
|
||||||
return done(null, user);
|
return done(null, user);
|
||||||
} else {
|
} else {
|
||||||
return done(null, false, {message: "Password incorrect"});
|
return done(null, false, {message: "Password incorrect"});
|
||||||
@@ -41,11 +41,11 @@ const authenticateUser: VerifyFunction = async (email, password, done) => {
|
|||||||
|
|
||||||
export const initialisePassport = () => {
|
export const initialisePassport = () => {
|
||||||
passport.use(new LocalStrategy({ usernameField: "email" }, authenticateUser));
|
passport.use(new LocalStrategy({ usernameField: "email" }, authenticateUser));
|
||||||
passport.serializeUser((user: IDbUser, done) => {
|
passport.serializeUser((user: StoredUser, done) => {
|
||||||
done(null, user.id)
|
done(null, user.id())
|
||||||
});
|
});
|
||||||
passport.deserializeUser(async (id: string, done) => {
|
passport.deserializeUser(async (id: string, done) => {
|
||||||
const user: IDbUser | null = await DbUser.getSerializedAuthUser(id);
|
const user: StoredUser | null = await StoredUsers.getSerializedAuthUser(id);
|
||||||
done(null, user);
|
done(null, user);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import {requireAuthenticated} from "../passport-config";
|
import {requireAuthenticated} from "../passport-config";
|
||||||
import routers from "./routers";
|
import routers from "./routers";
|
||||||
import {IDbUser} from "../models/dbUser_old";
|
|
||||||
import {ModelParameterError} from "../models/utils";
|
import {ModelParameterError} from "../models/utils";
|
||||||
|
import {LoginDetails} from "../models/StoredUser";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -10,13 +10,13 @@ router.use("/account", routers.signup);
|
|||||||
router.use("/api", routers.api);
|
router.use("/api", routers.api);
|
||||||
router.get("/game", requireAuthenticated, (req, res) => {
|
router.get("/game", requireAuthenticated, (req, res) => {
|
||||||
res.render("gameIndex.ejs", {
|
res.render("gameIndex.ejs", {
|
||||||
username: (req.user as IDbUser).username,
|
username: (req.user as LoginDetails).username,
|
||||||
rootUrl: req.app.locals.rootUrl
|
rootUrl: req.app.locals.rootUrl
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
router.get("/**", requireAuthenticated, (req, res) => {
|
router.get("/**", requireAuthenticated, (req, res) => {
|
||||||
res.render("frontendIndex.ejs", {
|
res.render("frontendIndex.ejs", {
|
||||||
username: (req.user as IDbUser).username,
|
username: (req.user as LoginDetails).username,
|
||||||
rootUrl: req.app.locals.rootUrl
|
rootUrl: req.app.locals.rootUrl
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
108
src/rulesets.ts
108
src/rulesets.ts
@@ -1,4 +1,6 @@
|
|||||||
import { FieldType } from "./enums";
|
import {FieldType} from "./enums";
|
||||||
|
import RulesetsPage from "../../frontend/src/Components/RulesetsPage";
|
||||||
|
import {RulesetStats} from "./models/Stats";
|
||||||
|
|
||||||
export const defaultCellValues = {
|
export const defaultCellValues = {
|
||||||
[FieldType.number]: 0,
|
[FieldType.number]: 0,
|
||||||
@@ -66,12 +68,12 @@ interface DefaultCellDef {
|
|||||||
|
|
||||||
|
|
||||||
// ----- Predefined sets
|
// ----- Predefined sets
|
||||||
|
export const DEFAULT_RULESET_NAME = "DEFAULT_RULESET";
|
||||||
const defaultDiceCount = 5;
|
const defaultDiceCount = 5;
|
||||||
const DEFAULT_RULESET = "DEFAULT_RULESET";
|
|
||||||
|
|
||||||
const gameSchemas: Ruleset[] = [
|
const gameSchemas: Ruleset[] = [
|
||||||
{
|
{
|
||||||
id: DEFAULT_RULESET,
|
id: DEFAULT_RULESET_NAME,
|
||||||
label: "Standard Kadi Rules (en)",
|
label: "Standard Kadi Rules (en)",
|
||||||
blocks: {
|
blocks: {
|
||||||
top: {
|
top: {
|
||||||
@@ -155,7 +157,7 @@ const gameSchemas: Ruleset[] = [
|
|||||||
score: 40,
|
score: 40,
|
||||||
},
|
},
|
||||||
|
|
||||||
kadi: {
|
superkadi: {
|
||||||
fieldType: FieldType.superkadi,
|
fieldType: FieldType.superkadi,
|
||||||
label: "Super Kadis",
|
label: "Super Kadis",
|
||||||
score: 50,
|
score: 50,
|
||||||
@@ -169,101 +171,7 @@ const gameSchemas: Ruleset[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
{
|
|
||||||
id: DEFAULT_RULESET,
|
|
||||||
label: "Standard-Kadi-Regelwerk (de)",
|
|
||||||
blocks: {
|
|
||||||
top: {
|
|
||||||
label: "Oben",
|
|
||||||
hasBonus: true,
|
|
||||||
bonusScore: 35,
|
|
||||||
bonusFor: 63,
|
|
||||||
cells: {
|
|
||||||
aces: {
|
|
||||||
fieldType: FieldType.multiplier,
|
|
||||||
label: "Einser",
|
|
||||||
multiplier: 1,
|
|
||||||
maxMultiples: defaultDiceCount,
|
|
||||||
},
|
|
||||||
|
|
||||||
twos: {
|
|
||||||
fieldType: FieldType.multiplier,
|
|
||||||
label: "Zweier",
|
|
||||||
multiplier: 2,
|
|
||||||
maxMultiples: defaultDiceCount,
|
|
||||||
},
|
|
||||||
|
|
||||||
threes: {
|
|
||||||
fieldType: FieldType.multiplier,
|
|
||||||
label: "Dreier",
|
|
||||||
multiplier: 3,
|
|
||||||
maxMultiples: defaultDiceCount,
|
|
||||||
},
|
|
||||||
|
|
||||||
fours: {
|
|
||||||
fieldType: FieldType.multiplier,
|
|
||||||
label: "Vierer",
|
|
||||||
multiplier: 4,
|
|
||||||
maxMultiples: defaultDiceCount,
|
|
||||||
},
|
|
||||||
|
|
||||||
fives: {
|
|
||||||
fieldType: FieldType.multiplier,
|
|
||||||
label: "Fünfer",
|
|
||||||
multiplier: 5,
|
|
||||||
maxMultiples: defaultDiceCount,
|
|
||||||
},
|
|
||||||
|
|
||||||
sixes: {
|
|
||||||
fieldType: FieldType.multiplier,
|
|
||||||
label: "Sechser",
|
|
||||||
multiplier: 6,
|
|
||||||
maxMultiples: defaultDiceCount,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bottom: {
|
|
||||||
label: "Unten",
|
|
||||||
hasBonus: false,
|
|
||||||
cells: {
|
|
||||||
threeKind: {
|
|
||||||
fieldType: FieldType.number,
|
|
||||||
label: "Dreierpasch",
|
|
||||||
},
|
|
||||||
fourKind: {
|
|
||||||
fieldType: FieldType.number,
|
|
||||||
label: "Viererpasch",
|
|
||||||
},
|
|
||||||
fullSouse: {
|
|
||||||
fieldType: FieldType.bool,
|
|
||||||
label: "Full House",
|
|
||||||
score: 25,
|
|
||||||
},
|
|
||||||
smlStraight: {
|
|
||||||
fieldType: FieldType.bool,
|
|
||||||
label: "Kleine Straße",
|
|
||||||
score: 30,
|
|
||||||
},
|
|
||||||
lgStraight: {
|
|
||||||
fieldType: FieldType.bool,
|
|
||||||
label: "Große Straße",
|
|
||||||
score: 40,
|
|
||||||
},
|
|
||||||
kadi: {
|
|
||||||
fieldType: FieldType.superkadi,
|
|
||||||
label: "Ultrakadi",
|
|
||||||
score: 50,
|
|
||||||
maxSuperkadis: 5,
|
|
||||||
},
|
|
||||||
change: {
|
|
||||||
fieldType: FieldType.number,
|
|
||||||
label: "Chance",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getGameSchemaById(schemaId: string): Ruleset {
|
export function getGameSchemaById(schemaId: string): Ruleset {
|
||||||
@@ -281,5 +189,5 @@ export interface SchemaListing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSchemaListings(): SchemaListing[] {
|
export function getSchemaListings(): SchemaListing[] {
|
||||||
return gameSchemas.map((s) => ({ id: s.id, label: s.label }));
|
return gameSchemas.map((s) => ({id: s.id, label: s.label}));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user