Big update

This commit is contained in:
Daniel Ledda
2020-08-13 15:10:55 +02:00
parent 42b7560d6d
commit 5f747142e5
28 changed files with 310 additions and 179 deletions

View File

@@ -27,7 +27,7 @@ export const changeLang: RequestHandler = async (req, res) => {
export const addGuest: RequestHandler = async (req, res) => { export const addGuest: RequestHandler = async (req, res) => {
const user = (req.user as KadiUser); const user = (req.user as KadiUser);
if (req.body.guestName) { if (req.body.guestName) {
const newGuest: Player = await KadiUserCollection.addGuestForUser(user, req.body.guestName); const newGuest: Player = await KadiUserCollection().addGuestForUser(user, req.body.guestName);
res.send({ res.send({
username: user.getUsername(), username: user.getUsername(),
userId: user.getId(), userId: user.getId(),
@@ -47,9 +47,9 @@ export const updateGuest: RequestHandler = async (req, res) => {
const {id: guestId} = req.params; const {id: guestId} = req.params;
if (req.body.newName) { if (req.body.newName) {
const {newName} = req.body; const {newName} = req.body;
const guest = await PlayerCollection.read(guestId); const guest = await PlayerCollection().read(guestId);
guest.setNick(newName); guest.setNick(newName);
const updatedGuest = await PlayerCollection.save(guest); const updatedGuest = await PlayerCollection().save(guest);
res.status(200).send({ res.status(200).send({
userId: user.getId(), userId: user.getId(),
username: user.getUsername(), username: user.getUsername(),
@@ -67,7 +67,7 @@ export const updateGuest: RequestHandler = async (req, res) => {
export const getGuest: RequestHandler = async (req, res) => { export const getGuest: RequestHandler = async (req, res) => {
const user = (req.user as KadiUser); const user = (req.user as KadiUser);
const {id: guestId} = req.params; const {id: guestId} = req.params;
const guest = await PlayerCollection.read(guestId); const guest = await PlayerCollection().read(guestId);
res.status(200).send({ res.status(200).send({
userId: user.getId(), userId: user.getId(),
username: user.getUsername(), username: user.getUsername(),
@@ -78,7 +78,7 @@ export const getGuest: RequestHandler = async (req, res) => {
export const deleteGuest: RequestHandler = async (req, res) => { export const deleteGuest: RequestHandler = async (req, res) => {
const user = (req.user as KadiUser); const user = (req.user as KadiUser);
const {id: guestId} = req.params; const {id: guestId} = req.params;
const deletedGuest = await KadiUserCollection.deleteGuestFromUser(user, guestId); const deletedGuest = await KadiUserCollection().deleteGuestFromUser(user, guestId);
res.status(200).send({ res.status(200).send({
userId: user.getId(), userId: user.getId(),
username: user.getUsername(), username: user.getUsername(),
@@ -88,7 +88,7 @@ export const deleteGuest: RequestHandler = async (req, res) => {
export const getGuests: RequestHandler = async (req, res) => { export const getGuests: RequestHandler = async (req, res) => {
const user = (req.user as KadiUser); const user = (req.user as KadiUser);
const guests = await KadiUserCollection.getAllGuestsForUser(user); const guests = await KadiUserCollection().getAllGuestsForUser(user);
res.status(200).send({ res.status(200).send({
userId: user.getId(), userId: user.getId(),
username: user.getUsername(), username: user.getUsername(),
@@ -98,7 +98,7 @@ export const getGuests: RequestHandler = async (req, res) => {
export const getAllPlayersAssociatedWithAccount: RequestHandler = async (req, res) => { export const getAllPlayersAssociatedWithAccount: RequestHandler = async (req, res) => {
const user = (req.user as KadiUser); const user = (req.user as KadiUser);
const guests = await KadiUserCollection.getAllGuestsForUser(user); const guests = await KadiUserCollection().getAllGuestsForUser(user);
const mainPlayer = await KadiUserCollection.getMainPlayerForUser(user); const mainPlayer = await KadiUserCollection().getMainPlayerForUser(user);
res.status(200).send({guests, mainPlayer}); res.status(200).send({guests, mainPlayer});
}; };

View File

@@ -0,0 +1,7 @@
import {RequestHandler} from "express";
import RulesetCollection from "../ObjectCollections/RulesetCollection";
export const getRuleset: RequestHandler = async (req, res) => {
const ruleset = await RulesetCollection().read(req.params.id);
res.json(ruleset.getSchemaJSON());
};

View File

@@ -24,7 +24,7 @@ export const showRegistrationPage: RequestHandler = (req, res) => {
export const registerNewUser: RequestHandler = async (req, res) => { export const registerNewUser: RequestHandler = async (req, res) => {
try { try {
const loginDetails: LoginDetails = req.body as LoginDetails; const loginDetails: LoginDetails = req.body as LoginDetails;
const newUser = await KadiUserCollection.registerUser(loginDetails); const newUser = await KadiUserCollection().registerUser(loginDetails);
req.login(newUser, (err) => { req.login(newUser, (err) => {
if (err) { if (err) {
throw err; throw err;
@@ -54,7 +54,7 @@ export const logoutUser: RequestHandler = (req, res) => {
}; };
export const authenticateKadiUser: VerifyFunction = async (email, password, done) => { export const authenticateKadiUser: VerifyFunction = async (email, password, done) => {
const user = await KadiUserCollection.findByEmail(email); const user = await KadiUserCollection().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."} );
} }
@@ -75,6 +75,6 @@ export async function serializeKadiUser(user: KadiUser, done: (err: any, id?: un
} }
export async function deserializeKadiUser(id: string, done: (err: any, id?: unknown) => void): Promise<void> { export async function deserializeKadiUser(id: string, done: (err: any, id?: unknown) => void): Promise<void> {
const user: KadiUser | null = await KadiUserCollection.getSerializedAuthUser(id); const user: KadiUser | null = await KadiUserCollection().read(id);
done(null, user); done(null, user);
} }

View File

@@ -9,12 +9,18 @@ import RulesetCollection from "../ObjectCollections/RulesetCollection";
import Ruleset from "../Objects/Ruleset"; import Ruleset from "../Objects/Ruleset";
import {OutcomeType} from "../Objects/DefaultStatsMongoData"; import {OutcomeType} from "../Objects/DefaultStatsMongoData";
export interface GameSubmission { interface GameSubmission {
rulesetId: string; ruleset: string;
players: { id: string; nick: string }[]; players: { id: string; nick: string }[];
results: PlayerGameResult[]; results: PlayerGameResult[];
} }
export interface ProcessedGameSubmission {
ruleset: string;
players: {id: string, nick: string}[];
results: ScoredResultsWithOutcome[];
}
type PlayerGameResult = { playerId: string; blocks: Record<string, Block> }; type PlayerGameResult = { playerId: string; blocks: Record<string, Block> };
type Block = { cells: Record<string, Cell> }; type Block = { cells: Record<string, Cell> };
type Cell = { value: CellValue }; type Cell = { value: CellValue };
@@ -27,11 +33,11 @@ enum ResultType {
} }
type ScoredResults = {score: number, results: PlayerGameResult}; type ScoredResults = {score: number, results: PlayerGameResult};
type ScoredResultsWithOutcome = {score: number, results: PlayerGameResult & {outcome: OutcomeType}}; export type ScoredResultsWithOutcome = {score: number, results: PlayerGameResult & {outcome: OutcomeType}};
export const listGames: RequestHandler = async (req, res) => { export const listGames: RequestHandler = async (req, res) => {
const user = req.user as KadiUser; const user = req.user as KadiUser;
const gamesList = await KadiUserCollection.getSavedGamesForUser(user); const gamesList = await KadiUserCollection().getSavedGamesForUser(user);
if (gamesList) { if (gamesList) {
res.json({ games: gamesList }); res.json({ games: gamesList });
} }
@@ -47,17 +53,32 @@ export const saveGame: RequestHandler = async (req, res) => {
if (newGuests.length > 0) { if (newGuests.length > 0) {
fillOutSubmissionWithNewIds(submission, newGuests); fillOutSubmissionWithNewIds(submission, newGuests);
} }
const newGame = await KadiUserCollection.addGameForUser(user, submission); const rulesetUsed = await RulesetCollection().read(submission.ruleset);
processStats(await RulesetCollection.read(submission.rulesetId), submission.results, user); const scoredResultsWithOutcomes = await processStats(rulesetUsed, submission.results, user);
const newGame = await KadiUserCollection().addGameForUser(user, {...submission, results: scoredResultsWithOutcomes});
res.send({ message: "Game submitted successfully!", newGame: newGame }); res.send({ message: "Game submitted successfully!", newGame: newGame });
}; };
export const getStats: RequestHandler = async (req, res) => {
const user = req.user as KadiUser;
const stats = await KadiUserCollection().getAllStatsForUser(user);
if (stats) {
res.json({
pStats: stats.pStats.map(pStats => ({...pStats, stats: pStats.stats.getData()})),
accStats: stats.accStats.getData()
});
}
else {
res.sendStatus(404);
}
};
async function addNewGuests(submission: GameSubmission, user: KadiUser): Promise<Player[]> { async function addNewGuests(submission: GameSubmission, user: KadiUser): Promise<Player[]> {
const newGuestIds: Player[] = []; const newGuestIds: Player[] = [];
for (const playerDetails of submission.players) { for (const playerDetails of submission.players) {
const isNewPlayer = playerDetails.id === playerDetails.nick; const isNewPlayer = playerDetails.id === playerDetails.nick;
if (isNewPlayer) { if (isNewPlayer) {
const newGuest: Player = await KadiUserCollection.addGuestForUser(user, playerDetails.nick); const newGuest: Player = await KadiUserCollection().addGuestForUser(user, playerDetails.nick);
newGuestIds.push(newGuest); newGuestIds.push(newGuest);
} }
} }
@@ -68,17 +89,17 @@ function fillOutSubmissionWithNewIds(submission: GameSubmission, newGuestList: P
for (const newGuest of newGuestList) { for (const newGuest of newGuestList) {
const gameResultsFromNewGuest = submission.results.find((result) => result.playerId === newGuest.getNick()); const gameResultsFromNewGuest = submission.results.find((result) => result.playerId === newGuest.getNick());
if (gameResultsFromNewGuest) { if (gameResultsFromNewGuest) {
gameResultsFromNewGuest.playerId = newGuest.getId(); gameResultsFromNewGuest.playerId = newGuest.getId().toString();
} }
const playerEntryForNewGuest = submission.players.find((player) => player.id === newGuest.getNick()); const playerEntryForNewGuest = submission.players.find((player) => player.id === newGuest.getNick());
if (playerEntryForNewGuest) { if (playerEntryForNewGuest) {
playerEntryForNewGuest.id = newGuest.getId(); playerEntryForNewGuest.id = newGuest.getId().toString();
} }
} }
return submission; return submission;
} }
async function processStats(ruleset: Ruleset, results: PlayerGameResult[], account: KadiUser) { async function processStats(ruleset: Ruleset, results: PlayerGameResult[], account: KadiUser): Promise<ScoredResultsWithOutcome[]> {
const calc = new ScoreCalculator(ruleset); const calc = new ScoreCalculator(ruleset);
let playerScoreList: ScoredResults[] = []; let playerScoreList: ScoredResults[] = [];
for (const result of results) { for (const result of results) {
@@ -88,59 +109,60 @@ async function processStats(ruleset: Ruleset, results: PlayerGameResult[], accou
results: result results: result
}); });
} }
const playerScoreListWithOutcomes = updateScoreListWithOutcomes(playerScoreList, ruleset); const playerScoreListWithOutcomes = updateScoreListWithOutcomes(playerScoreList);
updateStatsForIndividualPlayers(playerScoreListWithOutcomes, ruleset); await updateStatsForIndividualPlayers(playerScoreListWithOutcomes, ruleset);
const gameResults = playerScoreListWithOutcomes.map(scoredResults => scoredResults.results); const gameResults = playerScoreListWithOutcomes.map(scoredResults => scoredResults.results);
await KadiUserCollection.updateAccountStats(account.getId(), gameResults, ruleset); await KadiUserCollection().updateAccountStats(account.getId(), gameResults, ruleset);
return playerScoreListWithOutcomes;
} }
function updateScoreListWithOutcomes(playerScoreList: ScoredResults[], rulesetUsed: Ruleset): ScoredResultsWithOutcome[] { function updateScoreListWithOutcomes(scoreResultsList: ScoredResults[]): ScoredResultsWithOutcome[] {
playerScoreList = sortDescendingByScore(playerScoreList); scoreResultsList = sortDescendingByScore(scoreResultsList);
const playerScoreListWithOutcomes: ScoredResultsWithOutcome[] = playerScoreList.map(scoredResults => { const playerScoreListWithOutcomes: ScoredResultsWithOutcome[] = scoreResultsList.map(scoredResults => {
const newResults = {...scoredResults.results, outcome: OutcomeType.loss}; const newResults = {...scoredResults.results, outcome: OutcomeType.loss};
return {...scoredResults, results: newResults}; return {...scoredResults, results: newResults};
}); });
let runnerUpsStart: number; let runnerUpsStart: number;
if (playerScoreListWithOutcomes[0].score !== playerScoreListWithOutcomes[1].score) { if (playerScoreListWithOutcomes[0].score !== playerScoreListWithOutcomes[1].score) {
playerScoreListWithOutcomes[0].results.outcome = OutcomeType.win; playerScoreListWithOutcomes[0].results.outcome = OutcomeType.win;
runnerUpsStart = 1; runnerUpsStart = 1;
} }
else { else {
runnerUpsStart = updateScoreListWithDraws(playerScoreListWithOutcomes, rulesetUsed); runnerUpsStart = updateScoreListWithDraws(playerScoreListWithOutcomes);
} }
const losersStart = updateScoreListWithRunnerUps(playerScoreListWithOutcomes.slice(runnerUpsStart), rulesetUsed); const losersStart = updateScoreListWithRunnerUps(playerScoreListWithOutcomes, runnerUpsStart);
updateScoreListWithLosses(playerScoreListWithOutcomes.slice(losersStart), rulesetUsed); updateScoreListWithLosses(playerScoreListWithOutcomes, losersStart);
return playerScoreListWithOutcomes; return playerScoreListWithOutcomes;
} }
function updateScoreListWithDraws(playerScoreList: ScoredResultsWithOutcome[], rulesetUsed: Ruleset): number { function updateScoreListWithDraws(scoreResultsList: ScoredResultsWithOutcome[]): number {
for (let i = 0; i < playerScoreList.length; i++) { for (let i = 0; i < scoreResultsList.length; i++) {
if (playerScoreList[i].score === playerScoreList[0].score) { if (scoreResultsList[i].score === scoreResultsList[0].score) {
playerScoreList[i].results.outcome = OutcomeType.draw; scoreResultsList[i].results.outcome = OutcomeType.draw;
} }
else { else {
return i; return i;
} }
} }
return playerScoreList.length; return scoreResultsList.length;
} }
function updateScoreListWithRunnerUps(playerScoreList: ScoredResultsWithOutcome[], rulesetUsed: Ruleset): number { function updateScoreListWithRunnerUps(scoreResultsList: ScoredResultsWithOutcome[], runnerUpsStartIndex: number): number {
for (let i = 0; i < playerScoreList.length; i++) { scoreResultsList[runnerUpsStartIndex].results.outcome = OutcomeType.runnerUp;
if (playerScoreList[i].score === playerScoreList[0].score) { for (let i = runnerUpsStartIndex + 1; i < scoreResultsList.length; i++) {
playerScoreList[i].results.outcome = OutcomeType.runnerUp; if (scoreResultsList[i].score === scoreResultsList[i - 1].score) {
scoreResultsList[i].results.outcome = OutcomeType.runnerUp;
} }
else { else {
return i; return i;
} }
} }
return playerScoreList.length; return scoreResultsList.length;
} }
function updateScoreListWithLosses(scoreResultsList: ScoredResultsWithOutcome[], rulesetUsed: Ruleset) { function updateScoreListWithLosses(scoreResultsList: ScoredResultsWithOutcome[], losersStartIndex: number) {
for (const lostPlayerResults of scoreResultsList) { for (let i = losersStartIndex; i < scoreResultsList.length; i++) {
lostPlayerResults.results.outcome = OutcomeType.loss; scoreResultsList[i].results.outcome = OutcomeType.loss;
} }
} }
@@ -148,9 +170,9 @@ function sortDescendingByScore(playerScoreList: ScoredResults[]) {
return playerScoreList.sort((a, b) => b.score - a.score); return playerScoreList.sort((a, b) => b.score - a.score);
} }
function updateStatsForIndividualPlayers(playerScoreListWithOutcomes: ScoredResultsWithOutcome[], rulesetUsed: Ruleset): void { async function updateStatsForIndividualPlayers(playerScoreListWithOutcomes: ScoredResultsWithOutcome[], rulesetUsed: Ruleset): Promise<void> {
for (const scoredResults of playerScoreListWithOutcomes) { for (const scoredResults of playerScoreListWithOutcomes) {
PlayerCollection.updateStatsForPlayer( await PlayerCollection().updateStatsForPlayer(
scoredResults.results.playerId, scoredResults.results.playerId,
{...scoredResults.results, outcome: scoredResults.results.outcome}, {...scoredResults.results, outcome: scoredResults.results.outcome},
rulesetUsed); rulesetUsed);

View File

@@ -0,0 +1 @@

View File

@@ -1,5 +1,4 @@
import {CredentialsTakenError, GenericPersistenceError} from "../errors"; import {CredentialsTakenError} from "../errors";
import mongo from "mongodb";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import {SupportedLang} from "../enums"; import {SupportedLang} from "../enums";
import {AccountStatsMongoData, defaultAccountStatsMongoData, OutcomeType, PlayerGameResults} from "../Objects/DefaultStatsMongoData"; import {AccountStatsMongoData, defaultAccountStatsMongoData, OutcomeType, PlayerGameResults} from "../Objects/DefaultStatsMongoData";
@@ -11,13 +10,14 @@ import SavedGameCollection from "./SavedGameCollection";
import SavedGame from "../Objects/SavedGame"; import SavedGame from "../Objects/SavedGame";
import PlayerCollection from "../ObjectCollections/PlayerCollection"; import PlayerCollection from "../ObjectCollections/PlayerCollection";
import Player from "../Objects/Player"; import Player from "../Objects/Player";
import {GameSubmission} from "../controllers/statsController"; import {ProcessedGameSubmission} from "../Controllers/statsController";
import Ruleset from "../Objects/Ruleset"; import Ruleset from "../Objects/Ruleset";
import RulesetCollection from "./RulesetCollection"; import RulesetCollection from "./RulesetCollection";
import AccountStats from "../Objects/AccountStats"; import AccountStats from "../Objects/AccountStats";
import PlayerStats from "../Objects/PlayerStats";
export interface KadiUserMongoData { export interface KadiUserMongoData {
id: string; id: ActiveRecordId;
username: string; username: string;
email: string; email: string;
password: string; password: string;
@@ -30,8 +30,20 @@ export interface KadiUserMongoData {
} }
class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData> { class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData> {
constructor(collectionClient: mongo.Collection) { private static instance?: KadiUserCollection;
super(collectionClient); private constructor() {
super();
}
static getInstance(): KadiUserCollection {
if (KadiUserCollection.instance === undefined) {
KadiUserCollection.instance = new KadiUserCollection();
}
return KadiUserCollection.instance;
}
async init() {
this.mongoDbClientCollection = getMongoObjectCollection("users")
} }
private kadiUserFrom(data: KadiUserMongoData): KadiUser { private kadiUserFrom(data: KadiUserMongoData): KadiUser {
@@ -70,7 +82,7 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
} }
private async addNewUser(loginDetails: LoginDetails): Promise<KadiUser> { private async addNewUser(loginDetails: LoginDetails): Promise<KadiUser> {
const newPlayer = await PlayerCollection.create(loginDetails.username); const newPlayer = await PlayerCollection().create(loginDetails.username);
const securePassword = await this.makePasswordSecure(loginDetails.password); const securePassword = await this.makePasswordSecure(loginDetails.password);
const newUser = await this.mongoCreate({ const newUser = await this.mongoCreate({
username: loginDetails.username, username: loginDetails.username,
@@ -96,58 +108,52 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
return object !== null; return object !== null;
} }
async getSerializedAuthUser(id: string): Promise<KadiUser> {
const foundUser = await this.mongoRead(id);
if (foundUser) {
return this.kadiUserFrom(foundUser);
}
else {
throw new GenericPersistenceError("User not found!");
}
}
async makePasswordSecure(password: string): Promise<string> { async makePasswordSecure(password: string): Promise<string> {
return bcrypt.hash(password, 10); return bcrypt.hash(password, 10);
} }
async addGuestForUser(userOrId: OrId<KadiUser>, newGuestNick: string): Promise<Player> { async addGuestForUser(userOrId: OrId<KadiUser>, newGuestNick: string): Promise<Player> {
const newGuest = await PlayerCollection.create(newGuestNick); const newGuest = await PlayerCollection().create(newGuestNick);
await this.mongoDbClientCollection.findOneAndUpdate( await this.mongoDbClientCollection!.findOneAndUpdate(
{_id: this.idFromRecordOrId(userOrId)}, {_id: this.idFromRecordOrId(userOrId)},
{$push: {guests: newGuest.getId()}}); {$push: {guests: newGuest.getId()}});
return newGuest; return newGuest;
} }
async deleteGuestFromUser(userOrId: OrId<KadiUser>, guestOrGuestId: OrId<Player>): Promise<Player> { async deleteGuestFromUser(userOrId: OrId<KadiUser>, guestOrGuestId: OrId<Player>): Promise<Player> {
const deletedGuest = await PlayerCollection.delete(this.idFromRecordOrId(guestOrGuestId)); const deletedGuest = await PlayerCollection().delete(this.idFromRecordOrId(guestOrGuestId));
await this.mongoDbClientCollection.findOneAndUpdate( await this.mongoDbClientCollection!.findOneAndUpdate(
{_id: this.idFromRecordOrId(userOrId)}, {_id: this.idFromRecordOrId(userOrId)},
{$pull: {guests: this.idFromRecordOrId(guestOrGuestId)}}); {$pull: {guests: this.idFromRecordOrId(deletedGuest)}});
return deletedGuest; return deletedGuest;
} }
async getAllGuestsForUser(userOrId: OrId<KadiUser>): Promise<Promise<Player>[]> { async getAllGuestsForUser(userOrId: OrId<KadiUser>): Promise<Player[]> {
const guestIdList = (await this.mongoRead(this.idFromRecordOrId(userOrId)))?.guests; const guestIdList = (await this.mongoRead(this.idFromRecordOrId(userOrId)))?.guests;
return guestIdList.map(async (guestId) => { return Promise.all<Player>(
return await PlayerCollection.read(guestId); guestIdList.map(async (guestId) => {
}); return await PlayerCollection().read(guestId);
}
));
} }
async getMainPlayerForUser(userOrId: OrId<KadiUser>): Promise<Player> { async getMainPlayerForUser(userOrId: OrId<KadiUser>): Promise<Player> {
const userData = await this.mongoRead(this.idFromRecordOrId(userOrId)); const userData = await this.mongoRead(this.idFromRecordOrId(userOrId));
return PlayerCollection.read(userData?.player); return PlayerCollection().read(userData?.player);
} }
async getSavedGamesForUser(userOrId: OrId<KadiUser>): Promise<Promise<SavedGame>[]> { async getSavedGamesForUser(userOrId: OrId<KadiUser>): Promise<SavedGame[]> {
const savedGameIds = (await this.mongoRead(this.idFromRecordOrId(userOrId)))?.savedGames; const savedGameIds = (await this.mongoRead(this.idFromRecordOrId(userOrId)))?.savedGames;
return savedGameIds.map(async (savedGameId) => { return Promise.all<SavedGame>(
return await SavedGameCollection.read(savedGameId); savedGameIds.map(async (savedGameId) => {
}); return await SavedGameCollection().read(savedGameId);
}
));
} }
async addGameForUser(userOrId: OrId<KadiUser>, gameSubmission: GameSubmission): Promise<SavedGame> { async addGameForUser(userOrId: OrId<KadiUser>, submission: ProcessedGameSubmission): Promise<SavedGame> {
const newGame = await SavedGameCollection.create(gameSubmission); const newGame = await SavedGameCollection().create(submission);
await this.mongoDbClientCollection.findOneAndUpdate( await this.mongoDbClientCollection!.findOneAndUpdate(
{_id: this.idFromRecordOrId(userOrId)}, {_id: this.idFromRecordOrId(userOrId)},
{$push: {savedGames: newGame.getId()}}); {$push: {savedGames: newGame.getId()}});
return newGame; return newGame;
@@ -155,16 +161,36 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
async updateAccountStats(userOrId: OrId<KadiUser>, gameResults: (PlayerGameResults & {outcome: OutcomeType})[], rulesetUsedOrId: OrId<Ruleset>): Promise<void> { async updateAccountStats(userOrId: OrId<KadiUser>, gameResults: (PlayerGameResults & {outcome: OutcomeType})[], rulesetUsedOrId: OrId<Ruleset>): Promise<void> {
const userId = this.idFromRecordOrId(userOrId); const userId = this.idFromRecordOrId(userOrId);
const rulesetUsed = rulesetUsedOrId instanceof Ruleset ? rulesetUsedOrId : await RulesetCollection.read(rulesetUsedOrId); const rulesetUsed = rulesetUsedOrId instanceof Ruleset ? rulesetUsedOrId : await RulesetCollection().read(rulesetUsedOrId);
const accountStatsMongoData = await this.mongoRead(userId); const accountStatsMongoData = (await this.mongoRead(userId)).accountStats;
const accountStatsObject = new AccountStats(accountStatsMongoData.accountStats); const accountStatsObject = new AccountStats(accountStatsMongoData);
accountStatsObject.updateStats(gameResults, rulesetUsed); accountStatsObject.updateStats(gameResults, rulesetUsed);
this.mongoUpdate({ this.mongoUpdate({
id: this.idFromRecordOrId(userId), id: userId,
accountStats: accountStatsObject.getData() accountStats: accountStatsObject.getData()
}); });
} }
async getAllStatsForUser(userOrId: OrId<KadiUser>): Promise<StatsListing> {
const players = [...(await this.getAllGuestsForUser(userOrId)), await this.getMainPlayerForUser(userOrId)];
const playerStats = players.map(player => ({
nick: player.getNick(),
playerId: player.getId(),
stats: player.getStats()
}));
const accountStatsMongoData = (await this.mongoRead(this.idFromRecordOrId(userOrId))).accountStats;
const accountStats = new AccountStats(accountStatsMongoData);
return {pStats: playerStats, accStats: accountStats};
}
} }
const KadiUserCollectionSingleton = new KadiUserCollection(getMongoObjectCollection("users")); export interface StatsListing {
export default KadiUserCollectionSingleton; accStats: AccountStats;
pStats: {
nick: string,
playerId: ActiveRecordId,
stats: PlayerStats
}[];
}
export default KadiUserCollection.getInstance;

View File

@@ -5,35 +5,47 @@ import ActiveRecord, {ActiveRecordId} from "../Objects/ActiveRecord";
abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}> { abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}> {
protected mongoDbClientCollection?: mongo.Collection;
protected constructor() {}
protected constructor(protected mongoDbClientCollection: mongo.Collection) {} abstract init(): Promise<void>;
protected async mongoCreate(objectData: Omit<IRawData, "id">): Promise<IRawData> { protected async mongoCreate(objectData: Omit<IRawData, "id">): Promise<IRawData> {
return tryQuery(async () => { return tryQuery(async () => {
const insertOneWriteOpResult = await this.mongoDbClientCollection.insertOne(objectData); const insertOneWriteOpResult = await this.mongoDbClientCollection!.insertOne(objectData);
if (insertOneWriteOpResult.result.ok === 1) { if (insertOneWriteOpResult.result.ok === 1) {
return insertOneWriteOpResult.ops[0] const newObject = insertOneWriteOpResult.ops[0];
newObject.id = newObject._id;
newObject._id = undefined;
return insertOneWriteOpResult.ops[0];
} else { } else {
throw new MongoError(`Error creating the object: ${JSON.stringify(objectData)}`); throw new MongoError(`Error creating the object: ${JSON.stringify(objectData)}`);
} }
}); });
} }
protected async mongoRead(id: string): Promise<IRawData> { protected async mongoRead(id: ActiveRecordId): Promise<IRawData> {
return tryQuery(async () => { return tryQuery(async () => {
const result = await this.mongoDbClientCollection.findOne({_id: id}); const result = await this.mongoDbClientCollection!.findOne({_id: new mongo.ObjectID(id)});
if (result) { if (result) {
result.id = result._id;
result._id = undefined;
return result; return result;
} else { } else {
throw new InvalidIdError(`Object in collection "${typeof this}" with id ${JSON.stringify(id)} not found!`); throw new InvalidIdError(`Object in collection "${this.constructor.name}" with id ${JSON.stringify(id)} not found!`);
} }
}); });
} }
protected async mongoFindByAttribute(attribute: string, value: any): Promise<IRawData | null> { protected async mongoFindByAttribute(attribute: string, value: any): Promise<IRawData | null> {
return tryQuery(async () => return tryQuery(async () => {
await this.mongoDbClientCollection.findOne({[attribute]: value}) const result = await this.mongoDbClientCollection!.findOne({[attribute]: value});
); if (result) {
result.id = result._id;
result._id = undefined;
}
return result;
});
} }
protected async mongoDelete(objectId: ActiveRecordId, returnObject?: boolean): Promise<IRawData | void> { protected async mongoDelete(objectId: ActiveRecordId, returnObject?: boolean): Promise<IRawData | void> {
@@ -41,7 +53,7 @@ abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}
if (returnObject ?? true) { if (returnObject ?? true) {
deletedObject = await this.mongoRead(objectId); deletedObject = await this.mongoRead(objectId);
} }
const deleteWriteOpResult = await this.mongoDbClientCollection.deleteOne({_id: objectId}); const deleteWriteOpResult = await this.mongoDbClientCollection!.deleteOne({_id: objectId});
if (deleteWriteOpResult.result.ok === 1) { if (deleteWriteOpResult.result.ok === 1) {
return deletedObject; return deletedObject;
} else { } else {
@@ -49,16 +61,17 @@ abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}
} }
} }
protected async mongoUpdate(object: Partial<IRawData> & {id: ActiveRecordId}) { protected async mongoUpdate(object: Partial<IRawData>) {
await tryQuery(() => await tryQuery(() =>
this.mongoDbClientCollection.findOneAndUpdate({_id: object.id}, {$set: {...object, id: undefined}}) this.mongoDbClientCollection!.findOneAndUpdate({_id: object.id}, {$set: {...object, id: undefined}})
); );
} }
protected idFromRecordOrId<T extends ActiveRecord>(recordOrRecordId: T | ActiveRecordId): ActiveRecordId { protected idFromRecordOrId<T extends ActiveRecord>(recordOrRecordId: T | ActiveRecordId): ActiveRecordId {
return typeof recordOrRecordId === "string" ? recordOrRecordId : recordOrRecordId.getId(); return recordOrRecordId instanceof mongo.ObjectId || typeof recordOrRecordId === "string" ?
recordOrRecordId :
recordOrRecordId.getId();
} }
} }
export default MongoStoredObjectCollection; export default MongoStoredObjectCollection;

View File

@@ -1,5 +1,4 @@
import MongoStoredObjectCollection from "./MongoStoredObjectCollection"; import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
import mongo from "mongodb";
import { import {
defaultPlayerStatsMongoData, defaultPlayerStatsMongoData,
OutcomeType, OutcomeType,
@@ -14,18 +13,30 @@ import Ruleset from "../Objects/Ruleset";
import RulesetCollection from "./RulesetCollection"; import RulesetCollection from "./RulesetCollection";
export interface PlayerMongoData { export interface PlayerMongoData {
id: string; id: ActiveRecordId;
nick: string; nick: string;
stats?: PlayerStatsMongoData; stats: PlayerStatsMongoData;
} }
class PlayerCollection extends MongoStoredObjectCollection<PlayerMongoData> { class PlayerCollection extends MongoStoredObjectCollection<PlayerMongoData> {
constructor(collectionClient: mongo.Collection) { private static instance?: PlayerCollection;
super(collectionClient); constructor() {
super();
}
static getInstance(): PlayerCollection {
if (PlayerCollection.instance === undefined) {
PlayerCollection.instance = new PlayerCollection();
}
return PlayerCollection.instance;
}
async init() {
this.mongoDbClientCollection = getMongoObjectCollection("players");
} }
private playerFrom(data: PlayerMongoData): Player { private playerFrom(data: PlayerMongoData): Player {
return new Player(data.id, data.nick, data.stats ? new PlayerStats(data.stats) : undefined); return new Player(data.id, data.nick, new PlayerStats(data.stats));
} }
async create(nick: string): Promise<Player> { async create(nick: string): Promise<Player> {
@@ -47,16 +58,15 @@ class PlayerCollection extends MongoStoredObjectCollection<PlayerMongoData> {
} }
async updateStatsForPlayer(playerOrId: OrId<Player>, gameResults: PlayerGameResults & {outcome: OutcomeType}, rulesetUsedOrId: OrId<Ruleset>): Promise<void> { async updateStatsForPlayer(playerOrId: OrId<Player>, gameResults: PlayerGameResults & {outcome: OutcomeType}, rulesetUsedOrId: OrId<Ruleset>): Promise<void> {
playerOrId = playerOrId instanceof Player ? playerOrId : await this.read(playerOrId); const player = playerOrId instanceof Player ? playerOrId : await this.read(playerOrId);
rulesetUsedOrId = rulesetUsedOrId instanceof Ruleset ? rulesetUsedOrId : await RulesetCollection.read(rulesetUsedOrId); const ruleset = rulesetUsedOrId instanceof Ruleset ? rulesetUsedOrId : await RulesetCollection().read(rulesetUsedOrId);
playerOrId.updateStats(gameResults, rulesetUsedOrId); player.updateStats(gameResults, ruleset);
this.mongoUpdate({ this.mongoUpdate({
id: this.idFromRecordOrId(playerOrId), id: this.idFromRecordOrId(player),
stats: playerOrId.getStats()?.getData() stats: player.getStats()?.getData()
}); });
} }
} }
const PlayerCollectionSingleton = new PlayerCollection(getMongoObjectCollection("players")); export default PlayerCollection.getInstance;
export default PlayerCollectionSingleton;

View File

@@ -1,21 +1,33 @@
import MongoStoredObjectCollection from "./MongoStoredObjectCollection"; import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
import mongo from "mongodb";
import {DEFAULT_RULESET, DEFAULT_RULESET_NAME, RulesetSchema} from "../rulesets"; import {DEFAULT_RULESET, DEFAULT_RULESET_NAME, RulesetSchema} from "../rulesets";
import {getMongoObjectCollection} from "../database"; import {getMongoObjectCollection} from "../database";
import Ruleset from "../Objects/Ruleset"; import Ruleset from "../Objects/Ruleset";
import {ActiveRecordId} from "../Objects/ActiveRecord";
type RulesetMongoData = RulesetSchema; type RulesetMongoData = RulesetSchema & {id: ActiveRecordId};
class RulesetCollection extends MongoStoredObjectCollection<RulesetMongoData> { class RulesetCollection extends MongoStoredObjectCollection<RulesetMongoData> {
constructor(collectionClient: mongo.Collection) { private static instance?: RulesetCollection;
super(collectionClient); constructor() {
super();
}
static getInstance(): RulesetCollection {
if (RulesetCollection.instance === undefined) {
RulesetCollection.instance = new RulesetCollection();
}
return RulesetCollection.instance;
}
async init() {
this.mongoDbClientCollection = getMongoObjectCollection("rulesets");
} }
private async rulesetFrom(data: RulesetMongoData): Promise<Ruleset> { private async rulesetFrom(data: RulesetMongoData): Promise<Ruleset> {
return new Ruleset(data.id, data); return new Ruleset(data.id, data);
} }
async read(id: string): Promise<Ruleset> { async read(id: ActiveRecordId): Promise<Ruleset> {
if (id === DEFAULT_RULESET_NAME) { if (id === DEFAULT_RULESET_NAME) {
return new Ruleset(DEFAULT_RULESET_NAME, DEFAULT_RULESET); return new Ruleset(DEFAULT_RULESET_NAME, DEFAULT_RULESET);
} }
@@ -26,5 +38,4 @@ class RulesetCollection extends MongoStoredObjectCollection<RulesetMongoData> {
} }
} }
const RulesetCollectionSingleton = new RulesetCollection(getMongoObjectCollection("users")); export default RulesetCollection.getInstance;
export default RulesetCollectionSingleton;

View File

@@ -1,54 +1,63 @@
import MongoStoredObjectCollection from "./MongoStoredObjectCollection"; import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
import mongo from "mongodb";
import {ActiveRecordId} from "../Objects/ActiveRecord"; import {ActiveRecordId} from "../Objects/ActiveRecord";
import PlayerCollection from "./PlayerCollection"; import PlayerCollection from "./PlayerCollection";
import SavedGame from "../Objects/SavedGame"; import SavedGame from "../Objects/SavedGame";
import {getMongoObjectCollection} from "../database"; import {getMongoObjectCollection} from "../database";
import {PlayerGameResults} from "../Objects/DefaultStatsMongoData";
import RulesetCollection from "./RulesetCollection"; import RulesetCollection from "./RulesetCollection";
import {GameSubmission} from "../controllers/statsController"; import {ProcessedGameSubmission, ScoredResultsWithOutcome} from "../Controllers/statsController";
export interface SavedGameMongoData { export interface SavedGameMongoData {
id: string; id: string;
rulesetUsed: ActiveRecordId; ruleset: ActiveRecordId;
players: ActiveRecordId[]; players: ActiveRecordId[];
results: PlayerGameResults[]; results: ScoredResultsWithOutcome[];
} }
class SavedGameCollection extends MongoStoredObjectCollection<SavedGameMongoData> { class SavedGameCollection extends MongoStoredObjectCollection<SavedGameMongoData> {
constructor(collectionClient: mongo.Collection) { private static instance?: SavedGameCollection;
super(collectionClient); constructor() {
super();
}
static getInstance(): SavedGameCollection {
if (SavedGameCollection.instance === undefined) {
SavedGameCollection.instance = new SavedGameCollection();
}
return SavedGameCollection.instance;
}
async init() {
this.mongoDbClientCollection = getMongoObjectCollection("savedGames");
} }
private async savedGameFrom(data: SavedGameMongoData): Promise<SavedGame> { private async savedGameFrom(data: SavedGameMongoData): Promise<SavedGame> {
const playerList: {name: string, id: ActiveRecordId}[] = []; const playerList: {name: string, id: ActiveRecordId}[] = [];
for (const playerId of data.players) { for (const playerId of data.players) {
const player = await PlayerCollection.read(playerId); const player = await PlayerCollection().read(playerId);
playerList.push({name: player.getNick(), id: playerId}) playerList.push({name: player.getNick(), id: playerId})
} }
const rulesetUsed = await RulesetCollection.read(data.rulesetUsed); const rulesetUsed = await RulesetCollection().read(data.ruleset);
return new SavedGame( return new SavedGame(
data.id, data.id,
{name: rulesetUsed.getName(), id: data.rulesetUsed}, {name: rulesetUsed.getName(), id: data.ruleset},
playerList, playerList,
data.results); data.results);
} }
async read(id: string): Promise<SavedGame> { async read(id: ActiveRecordId): Promise<SavedGame> {
const foundGame = await this.mongoRead(id); const foundGame = await this.mongoRead(id);
return this.savedGameFrom(foundGame); return this.savedGameFrom(foundGame);
} }
async create(gameSubmission: GameSubmission): Promise<SavedGame> { async create(submission: ProcessedGameSubmission): Promise<SavedGame> {
const pids = gameSubmission.players.map(playerIdAndNick => playerIdAndNick.id); const pids = submission.players.map(playerIdAndNick => playerIdAndNick.id);
return this.savedGameFrom( return this.savedGameFrom(
await this.mongoCreate({ await this.mongoCreate({
rulesetUsed: gameSubmission.rulesetId, ruleset: submission.ruleset,
players: pids, players: pids,
results: gameSubmission.results}) results: submission.results})
); );
} }
} }
const SavedGameCollectionSingleton = new SavedGameCollection(getMongoObjectCollection("users")); export default SavedGameCollection.getInstance;
export default SavedGameCollectionSingleton;

View File

@@ -10,6 +10,7 @@ class AccountStats {
constructor(data: AccountStatsMongoData) { constructor(data: AccountStatsMongoData) {
this.data = data; this.data = data;
this.updater = new StatsUpdater(); this.updater = new StatsUpdater();
this.updater.use(data);
} }
use(data: AccountStatsMongoData) { use(data: AccountStatsMongoData) {
@@ -21,8 +22,8 @@ class AccountStats {
if (this.data) { if (this.data) {
for (const playerGameResult of playerGameResults) { for (const playerGameResult of playerGameResults) {
this.updater.updateStats(playerGameResult, ruleset); this.updater.updateStats(playerGameResult, ruleset);
this.data.gamesPlayed += 1;
} }
this.data.gamesPlayed += 1;
} }
else { else {
throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data

View File

@@ -1,4 +1,6 @@
export type ActiveRecordId = string; import mongo from "mongodb";
export type ActiveRecordId = mongo.ObjectId | string;
export type OrId<T extends ActiveRecord> = T | ActiveRecordId; export type OrId<T extends ActiveRecord> = T | ActiveRecordId;
interface ActiveRecord { interface ActiveRecord {

View File

@@ -1,12 +1,11 @@
import {CellDef, DEFAULT_RULESET, DEFAULT_RULESET_NAME, RulesetSchema} from "../rulesets"; import {CellDef, DEFAULT_RULESET, DEFAULT_RULESET_NAME, RulesetSchema} from "../rulesets";
import {FieldType} from "../enums"; import {FieldType} from "../enums";
export enum OutcomeType { export enum OutcomeType {
win, win = "win",
loss, loss = "loss",
runnerUp, runnerUp = "runnerUp",
draw, draw = "draw",
} }
export interface PlayerStatsMongoData extends BaseStatsMongoData {} export interface PlayerStatsMongoData extends BaseStatsMongoData {}

View File

@@ -5,7 +5,7 @@ export type LoginDetails = { username: string, email: string, password: string }
class KadiUser implements ActiveRecord { class KadiUser implements ActiveRecord {
constructor( constructor(
private id: string, private id: ActiveRecordId,
private username: string, private username: string,
private email: string, private email: string,
private password: string, private password: string,

View File

@@ -1,21 +1,14 @@
import {CellValue} from "../controllers/statsController";
import {RulesetSchema} from "../rulesets";
import ActiveRecord, {ActiveRecordId} from "./ActiveRecord"; import ActiveRecord, {ActiveRecordId} from "./ActiveRecord";
import {UpdateError} from "../errors"; import {UpdateError} from "../errors";
import {OutcomeType, PlayerGameResults} from "./DefaultStatsMongoData"; import {OutcomeType, PlayerGameResults} from "./DefaultStatsMongoData";
import PlayerStats from "./PlayerStats"; import PlayerStats from "./PlayerStats";
import Ruleset from "./Ruleset"; import Ruleset from "./Ruleset";
export interface CellDetails {
id: string;
value: CellValue;
}
export class Player implements ActiveRecord { export class Player implements ActiveRecord {
constructor( constructor(
private id: ActiveRecordId, private id: ActiveRecordId,
private nick: string, private nick: string,
private stats?: PlayerStats private stats: PlayerStats
) {} ) {}
getId(): ActiveRecordId { getId(): ActiveRecordId {
@@ -30,7 +23,7 @@ export class Player implements ActiveRecord {
this.nick = newNick; this.nick = newNick;
} }
getStats(): PlayerStats | undefined { getStats(): PlayerStats {
return this.stats; return this.stats;
} }

View File

@@ -9,6 +9,7 @@ class PlayerStats {
constructor(data: PlayerStatsMongoData) { constructor(data: PlayerStatsMongoData) {
this.data = data; this.data = data;
this.updater = new StatsUpdater(); this.updater = new StatsUpdater();
this.updater.use(data);
} }
use(data: PlayerStatsMongoData) { use(data: PlayerStatsMongoData) {
@@ -18,6 +19,7 @@ class PlayerStats {
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void { updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
this.updater.updateStats(playerGameResults, ruleset); this.updater.updateStats(playerGameResults, ruleset);
this.data.gamesPlayed += 1;
} }
getData(): PlayerStatsMongoData { getData(): PlayerStatsMongoData {

View File

@@ -26,6 +26,10 @@ export class Ruleset implements ActiveRecord {
getCellsInBlock(blockId: string): Record<string, CellDef> { getCellsInBlock(blockId: string): Record<string, CellDef> {
return Object.assign({}, this.schema.blocks[blockId].cells); return Object.assign({}, this.schema.blocks[blockId].cells);
} }
getSchemaJSON(): RulesetSchema {
return Object.assign({}, this.schema);
}
} }
export default Ruleset; export default Ruleset;

View File

@@ -1,12 +1,12 @@
import ActiveRecord, {ActiveRecordId} from "./ActiveRecord"; import ActiveRecord, {ActiveRecordId} from "./ActiveRecord";
import {PlayerGameResults} from "./DefaultStatsMongoData"; import {ScoredResultsWithOutcome} from "../Controllers/statsController";
class SavedGame implements ActiveRecord { class SavedGame implements ActiveRecord {
constructor( constructor(
private id: string, private id: string,
private rulesetUsed: {name: string, id: ActiveRecordId}, private rulesetUsed: {name: string, id: ActiveRecordId},
private players: {name: string, id: ActiveRecordId}[], private players: {name: string, id: ActiveRecordId}[],
private results: PlayerGameResults[], private results: ScoredResultsWithOutcome[],
) {} ) {}
getId() { getId() {

View File

@@ -60,6 +60,7 @@ abstract class ScoreCellCalculator {
} }
hydrateWithJSON(jsonRep: ScoreCellJSONRepresentation): void { hydrateWithJSON(jsonRep: ScoreCellJSONRepresentation): void {
this.reset();
if (jsonRep.value === CellFlag.strike) { if (jsonRep.value === CellFlag.strike) {
this.struck = true; this.struck = true;
} }
@@ -67,6 +68,11 @@ abstract class ScoreCellCalculator {
this.value = jsonRep.value; this.value = jsonRep.value;
} }
} }
reset(): void {
this.struck = false;
this.value = 0;
}
} }
class NumberScoreCell extends ScoreCellCalculator { class NumberScoreCell extends ScoreCellCalculator {
@@ -78,9 +84,14 @@ class NumberScoreCell extends ScoreCellCalculator {
} }
getScore(): number { getScore(): number {
if (this.isStruck()) {
return 0;
}
else {
return this.value as number; return this.value as number;
} }
} }
}
class BoolScoreCell extends ScoreCellCalculator { class BoolScoreCell extends ScoreCellCalculator {
protected static readonly fieldType = FieldType.bool; protected static readonly fieldType = FieldType.bool;
@@ -101,6 +112,11 @@ class BoolScoreCell extends ScoreCellCalculator {
return 0; return 0;
} }
} }
reset(): void {
super.reset();
this.value = false;
}
} }
class SuperkadiScoreCell extends ScoreCellCalculator { class SuperkadiScoreCell extends ScoreCellCalculator {

View File

@@ -1,4 +1,3 @@
import {RulesetSchema} from "../rulesets";
import ScoreCalculator, {ScoreCardJSONRepresentation} from "./ScoreCalculator"; import ScoreCalculator, {ScoreCardJSONRepresentation} from "./ScoreCalculator";
import {UpdateError} from "../errors"; import {UpdateError} from "../errors";
import {FieldType} from "../enums"; import {FieldType} from "../enums";
@@ -8,7 +7,8 @@ import {
BoolFieldStatsMongoData, BoolFieldStatsMongoData,
CellStatsMongoData, CellStatsMongoData,
OutcomeType, OutcomeType,
PlayerGameResults, RulesetStatsMongoData, PlayerGameResults,
RulesetStatsMongoData,
TotalFieldStatsMongoData TotalFieldStatsMongoData
} from "./DefaultStatsMongoData"; } from "./DefaultStatsMongoData";
import Ruleset from "./Ruleset"; import Ruleset from "./Ruleset";
@@ -30,15 +30,15 @@ class StatsUpdater {
this.validationRuleset = ruleset; this.validationRuleset = ruleset;
this.calculator = new ScoreCalculator(ruleset); this.calculator = new ScoreCalculator(ruleset);
this.calculator.hydrateWithJSON(playerGameResults as ScoreCardJSONRepresentation); this.calculator.hydrateWithJSON(playerGameResults as ScoreCardJSONRepresentation);
this.currentStatsObject = this.data.statsByRuleset[ruleset.getId()]; this.currentStatsObject = this.data.statsByRuleset[ruleset.getId().toString()];
for (const blockId in ruleset.getBlocks()) { for (const blockId in ruleset.getBlocks()) {
this.updateBlockStats(blockId); this.updateBlockStats(blockId);
} }
this.updateTotalFieldStats(this.currentStatsObject.grandTotal, this.calculator.getTotal()); this.updateTotalFieldStats(this.currentStatsObject.grandTotal, this.calculator.getTotal());
this.currentStatsObject.wins += Number(playerGameResults.outcome === "win"); this.currentStatsObject.wins += Number(playerGameResults.outcome === OutcomeType.win);
this.currentStatsObject.draws += Number(playerGameResults.outcome === "draw"); this.currentStatsObject.draws += Number(playerGameResults.outcome === OutcomeType.draw);
this.currentStatsObject.runnerUps += Number(playerGameResults.outcome === "runnerUp"); this.currentStatsObject.runnerUps += Number(playerGameResults.outcome === OutcomeType.runnerUp);
this.currentStatsObject.losses += Number(playerGameResults.outcome === "loss"); this.currentStatsObject.losses += Number(playerGameResults.outcome === OutcomeType.loss);
} }
else { else {
throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data
@@ -66,9 +66,10 @@ class StatsUpdater {
} }
private updateCellStatsByIds(ids: {cellId: string, blockId: string}) { private updateCellStatsByIds(ids: {cellId: string, blockId: string}) {
const cellStats = this.getCellStatsByIds({...ids, rulesetId: this.validationRuleset!.getId()}); const cellStats = this.getCellStatsByIds({...ids, rulesetId: this.validationRuleset!.getId().toString()});
const cellFieldType = this.validationRuleset?.getBlocks()[ids.blockId].cells[ids.cellId].fieldType; const cellFieldType = this.validationRuleset?.getBlocks()[ids.blockId].cells[ids.cellId].fieldType;
const cellScore = this.calculator!.getCellScoreByLocation({...ids}); const cellScore = this.calculator!.getCellScoreByLocation({...ids});
cellStats.runningTotal += cellScore;
if (cellScore > 0 && cellFieldType === FieldType.bool) { if (cellScore > 0 && cellFieldType === FieldType.bool) {
(cellStats as BoolFieldStatsMongoData).total += 1; (cellStats as BoolFieldStatsMongoData).total += 1;
} }

View File

@@ -1,7 +1,8 @@
import express from "express"; import express from "express";
import * as statsController from "../controllers/statsController"; import * as statsController from "../Controllers/statsController";
import * as KadiUserController from "../controllers/kadiUserController" import * as KadiUserController from "../Controllers/kadiUserController"
import {requireAuthenticated} from "./routerMiddleware"; import {requireAuthenticated} from "./routerMiddleware";
import * as rulesetController from "../Controllers/rulesetController";
const router = express.Router(); const router = express.Router();
@@ -21,4 +22,8 @@ router.delete("/guest/:id", requireAuthenticated, KadiUserController.deleteGuest
router.get("/games", requireAuthenticated, statsController.listGames); router.get("/games", requireAuthenticated, statsController.listGames);
router.post("/games", requireAuthenticated, statsController.saveGame); router.post("/games", requireAuthenticated, statsController.saveGame);
//Stats
router.get("/stats", requireAuthenticated, statsController.getStats);
router.get("/ruleset/:id", rulesetController.getRuleset);
export default router; export default router;

View File

@@ -1,4 +1,4 @@
import express, {NextFunction} from "express"; import express from "express";
import routers from "./routers"; import routers from "./routers";
import {LoginDetails} from "../Objects/KadiUser"; import {LoginDetails} from "../Objects/KadiUser";
import {requireAuthenticated} from "./routerMiddleware"; import {requireAuthenticated} from "./routerMiddleware";
@@ -22,6 +22,7 @@ router.get("/**", requireAuthenticated, (req, res) => {
}); });
const topLevelErrorHandler: express.ErrorRequestHandler = (err, req, res, next) => { const topLevelErrorHandler: express.ErrorRequestHandler = (err, req, res, next) => {
console.log(err.message);
if (err instanceof GenericPersistenceError) { if (err instanceof GenericPersistenceError) {
res.status(500).send({message: "An internal error occurred accessing the database."}); res.status(500).send({message: "An internal error occurred accessing the database."});
} }

View File

@@ -1,17 +1,13 @@
import express from "express"; import express from "express";
import {requireAuthenticated, requireNotAuthenticated} from "../passport-config"; import * as signup from "../Controllers/signupController";
import * as signup from "../controllers/signupController"; import {requireAuthenticated, requireNotAuthenticated} from "./routerMiddleware";
const router = express.Router(); const router = express.Router();
router.get("/login", requireNotAuthenticated, signup.showLoginPage); router.get("/login", requireNotAuthenticated, signup.showLoginPage);
router.post("/login", requireNotAuthenticated, signup.loginUser); router.post("/login", requireNotAuthenticated, signup.loginUser);
router.get("/register", requireNotAuthenticated, signup.showRegistrationPage); router.get("/register", requireNotAuthenticated, signup.showRegistrationPage);
router.post("/register", requireNotAuthenticated, signup.registerNewUser); router.post("/register", requireNotAuthenticated, signup.registerNewUser);
router.get("/logout", requireAuthenticated, signup.logoutUser); router.get("/logout", requireAuthenticated, signup.logoutUser);
export default router; export default router;

View File

@@ -1,6 +1,10 @@
import {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"; import {GenericPersistenceError, MongoError} from "./errors";
import KadiUserCollection from "./ObjectCollections/KadiUserCollection";
import PlayerCollection from "./ObjectCollections/PlayerCollection";
import RulesetCollection from "./ObjectCollections/RulesetCollection";
import SavedGameCollection from "./ObjectCollections/SavedGameCollection";
let SessionDbClient: Db; let SessionDbClient: Db;
@@ -21,6 +25,13 @@ export function getMongoObjectCollection(collectionName: string) {
} }
} }
export async function initCollections() {
KadiUserCollection().init();
PlayerCollection().init();
SavedGameCollection().init();
RulesetCollection().init();
}
type CallbackWrapper = <T>(query: () => T) => Promise<T>; type CallbackWrapper = <T>(query: () => T) => Promise<T>;
export const tryQuery: CallbackWrapper = async (cb) => { export const tryQuery: CallbackWrapper = async (cb) => {
try { try {

View File

@@ -1,15 +1,16 @@
import express, {NextFunction, Request, Response} from "express"; import express from "express";
import Settings from "./server-config.json"; import Settings from "./server-config.json";
import flash from "express-flash"; import flash from "express-flash";
import passport from "passport"; import passport from "passport";
import session from "express-session"; import session from "express-session";
import MainRouter from "./routers/mainRouter"; import MainRouter from "./Routers/mainRouter";
import {initMongoSessionClient} from "./database"; import {initCollections, initMongoSessionClient} from "./database";
import {Strategy as LocalStrategy} from "passport-local"; import {Strategy as LocalStrategy} from "passport-local";
import {authenticateKadiUser, deserializeKadiUser, serializeKadiUser} from "./controllers/signupController"; import {authenticateKadiUser, deserializeKadiUser, serializeKadiUser} from "./Controllers/signupController";
async function startApp() { async function startApp() {
await initMongoSessionClient(); await initMongoSessionClient();
await initCollections();
passport.use(new LocalStrategy({ usernameField: "email" }, authenticateKadiUser)); passport.use(new LocalStrategy({ usernameField: "email" }, authenticateKadiUser));
passport.serializeUser(serializeKadiUser); passport.serializeUser(serializeKadiUser);
passport.deserializeUser(deserializeKadiUser); passport.deserializeUser(deserializeKadiUser);

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "target": "ES5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */ // "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */ "allowJs": true, /* Allow javascript files to be compiled. */