diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 0ca43fb..46fc5c8 100755
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,9 +2,7 @@
-
-
-
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a27f03a..8c7af84 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -133,9 +133,9 @@
"dev": true
},
"@types/mongodb": {
- "version": "3.5.16",
- "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.16.tgz",
- "integrity": "sha512-q12k9vFEGfQTUTC9KiN+Lf1nPpEawuPyTfIHBgGn+lSD/4ICZhePxQqEe/ukRgAjS63vdc+Td758VBr4bUGNjg==",
+ "version": "3.5.18",
+ "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.18.tgz",
+ "integrity": "sha512-fEmnRmwXt4pEFhqWB/ZlyNaDhLfQv5GALaZAlH9Pb0kEttvsCr66umJ9pfBEEP3ks1hjlwAuMtqk/+DyZDLAXQ==",
"dev": true,
"requires": {
"@types/bson": "*",
@@ -143,9 +143,9 @@
}
},
"@types/mongoose": {
- "version": "5.7.16",
- "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.7.16.tgz",
- "integrity": "sha512-QuWb8Tqjq1r/ZEpBi9MBWpuu8upe+4Co89GExyIFb0Q7TCmeMQsxG1lVfkmjk8GVm/qMIwUMdpuopVLPhmnFUA==",
+ "version": "5.7.21",
+ "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.7.21.tgz",
+ "integrity": "sha512-KPJ4zVHWZK5vKlnJj2YDDCTmesYkW9iGB95ATfFYcdvx394Xuyh9/Cq0NSJnJ8J8PFVlhx9iBQ+Q425tEuEJvA==",
"dev": true,
"requires": {
"@types/mongodb": "*",
@@ -466,9 +466,9 @@
"dev": true
},
"array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
+ "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY="
},
"array-initial": {
"version": "1.1.0",
@@ -1883,9 +1883,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"ejs": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.2.tgz",
- "integrity": "sha512-zFuywxrAWtX5Mk2KAuoJNkXXbfezpNA0v7i+YC971QORguPekpjpAgeOv99YWSdKXwj7JxI2QAWDeDkE8fWtXw==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz",
+ "integrity": "sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg==",
"requires": {
"jake": "^10.6.1"
}
@@ -2141,18 +2141,18 @@
}
},
"express": {
- "version": "4.17.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
- "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+ "version": "5.0.0-alpha.8",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.0.0-alpha.8.tgz",
+ "integrity": "sha512-PL8wTLgaNOiq7GpXt187/yWHkrNSfbr4H0yy+V0fpqJt5wpUzBi9DprAkwGKBFOqWHylJ8EyPy34V5u9YArfng==",
"requires": {
"accepts": "~1.3.7",
- "array-flatten": "1.1.1",
+ "array-flatten": "2.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
- "debug": "2.6.9",
+ "debug": "3.1.0",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
@@ -2163,10 +2163,11 @@
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.7",
+ "path-is-absolute": "1.0.1",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
+ "router": "2.0.0-alpha.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
@@ -2175,6 +2176,16 @@
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
}
},
"express-flash": {
@@ -7147,9 +7158,9 @@
}
},
"jake": {
- "version": "10.6.1",
- "resolved": "https://registry.npmjs.org/jake/-/jake-10.6.1.tgz",
- "integrity": "sha512-pHUK3+V0BjOb1XSi95rbBksrMdIqLVC9bJqDnshVyleYsET3H0XAq+3VB2E3notcYvv4wRdRHn13p7vobG+wfQ==",
+ "version": "10.7.1",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.7.1.tgz",
+ "integrity": "sha512-FUkLZXms1LSTQop5EJBdXVzbM0q6yYWMM4vo/TiLQeHJ4UMJVO8DBTZFiAgMBJctin9q92xnr2vdH7Wrpn7tTQ==",
"requires": {
"async": "0.9.x",
"chalk": "^2.4.2",
@@ -8039,9 +8050,9 @@
}
},
"mongoose": {
- "version": "5.9.13",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.13.tgz",
- "integrity": "sha512-MsFdJAaCTVbDA3gYskUEpUN1kThL7sp4zh8N9rGt0+9vYMn28q92NLK90vGssM9qjOGWp8HqLeT1fBgfMZDnKA==",
+ "version": "5.9.15",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.15.tgz",
+ "integrity": "sha512-dGIDqaQkAJoLl7lsRLy70mDg+VcL1IPOHr/0f23MLF45PtnM5exRdmienfyVjdrSVGgTus+1sMUKef6vSnrDZg==",
"requires": {
"bson": "^1.1.4",
"kareem": "2.3.1",
@@ -8245,9 +8256,9 @@
}
},
"nodemon": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.3.tgz",
- "integrity": "sha512-lLQLPS90Lqwc99IHe0U94rDgvjo+G9I4uEIxRG3evSLROcqQ9hwc0AxlSHKS4T1JW/IMj/7N5mthiN58NL/5kw==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz",
+ "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==",
"dev": true,
"requires": {
"chokidar": "^3.2.2",
@@ -9059,9 +9070,9 @@
"optional": true
},
"pstree.remy": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz",
- "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==",
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true
},
"pump": {
@@ -9594,6 +9605,35 @@
"glob": "^7.1.3"
}
},
+ "router": {
+ "version": "2.0.0-alpha.1",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.0.0-alpha.1.tgz",
+ "integrity": "sha512-fz/T/qLkJM6RTtbqGqA1+uZ88ejqJoPyKeJAeXPYjebA7HzV/UyflH4gXWqW/Y6SERnp4kDwNARjqy6se3PcOw==",
+ "requires": {
+ "array-flatten": "2.1.1",
+ "debug": "3.1.0",
+ "methods": "~1.1.2",
+ "parseurl": "~1.3.2",
+ "path-to-regexp": "0.1.7",
+ "setprototypeof": "1.1.0",
+ "utils-merge": "1.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+ }
+ }
+ },
"rtlcss": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.5.0.tgz",
@@ -10570,9 +10610,9 @@
"dev": true
},
"ts-loader": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.3.tgz",
- "integrity": "sha512-BXAHfPjm3J//20ibuI30M+xgLpdIng68p2H952QqbbmDk7SW72HV42k9Gop7rMxuHvrXWjazWhKuyr9D9kKe3A==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.5.tgz",
+ "integrity": "sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
@@ -10698,9 +10738,9 @@
}
},
"typescript": {
- "version": "3.8.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
- "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
+ "version": "3.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
+ "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
"dev": true
},
"uglify-js": {
diff --git a/package.json b/package.json
index bb72f5d..fe399a2 100755
--- a/package.json
+++ b/package.json
@@ -14,30 +14,30 @@
"license": "ISC",
"dependencies": {
"bcrypt": "^4.0.1",
- "ejs": "^3.1.2",
- "express": "^4.17.1",
+ "diff": ">=3.5.0",
+ "ejs": "^3.1.3",
+ "express": "^5.0.0-alpha.8",
"express-flash": "0.0.2",
"express-session": "^1.17.1",
- "mongoose": "^5.9.11",
+ "lodash.template": ">=4.5.0",
+ "mongoose": "^5.9.15",
"passport": "^0.4.1",
- "passport-local": "^1.0.0",
- "diff": ">=3.5.0",
- "lodash.template": ">=4.5.0"
+ "passport-local": "^1.0.0"
},
"devDependencies": {
"@types/bcrypt": "^3.0.0",
"@types/express": "^4.17.6",
"@types/express-flash": "0.0.2",
"@types/express-session": "^1.17.0",
- "@types/mongoose": "^5.7.14",
+ "@types/mongoose": "^5.7.21",
"@types/passport": "^1.0.3",
"@types/passport-local": "^1.0.33",
- "semantic-ui": "^2.4.2",
- "nodemon": "^2.0.3",
"concurrently": "^5.2.0",
- "typescript": "^3.8.3",
"gulp": "^3.9.1",
- "ts-loader": "^7.0.3",
- "tslint": "^6.1.1"
+ "nodemon": "^2.0.4",
+ "semantic-ui": "^2.4.2",
+ "ts-loader": "^7.0.5",
+ "tslint": "^6.1.1",
+ "typescript": "^3.9.3"
}
}
diff --git a/src/controllers/dbUserController.ts b/src/controllers/dbUserController.ts
index b1eeca3..9124ff4 100755
--- a/src/controllers/dbUserController.ts
+++ b/src/controllers/dbUserController.ts
@@ -1,9 +1,10 @@
-import {IDbUserDoc} from "../models/dbUser";
+import DbUser, {IDbUser, IDbUserDoc} from "../models/dbUser";
import {RequestHandler} from "express";
+import {IPlayer} from "../models/player";
export const whoAmI: RequestHandler = async (req, res) => {
if (req.isAuthenticated()) {
- const user = req.user as IDbUserDoc;
+ const user = req.user as IDbUser;
res.json({loggedIn: true, username: user.username, lang: user.lang});
}
else {
@@ -12,6 +13,88 @@ export const whoAmI: RequestHandler = async (req, res) => {
};
export const changeLang: RequestHandler = async (req, res) => {
- const user = (req.user as IDbUserDoc);
- user.changeLang(req.body.lang);
+ const user = (req.user as IDbUser);
+ await user.changeLang(req.body.lang);
+ res.send({
+ username: user.username,
+ updatedLang: req.body.lang,
+ userId: user.id,
+ });
+};
+
+export const addGuest: RequestHandler = async (req, res) => {
+ const user = (req.user as IDbUser);
+ if (req.body.guestName) {
+ const newGuest: IPlayer = await user.addGuest(req.body.guestName);
+ res.send({
+ username: user.username,
+ userId: user.id,
+ newGuest: {
+ id: newGuest.id,
+ name: newGuest.nick,
+ }
+ });
+ }
+ else {
+ res.status(400).send({message: "This request requires the parameter 'guestName'"});
+ }
+};
+
+export const updateGuest: RequestHandler = async (req, res) => {
+ const user = (req.user as IDbUser);
+ const {id: guestId} = req.params;
+ if (req.body.newName) {
+ const {newName} = req.body;
+ const updatedGuest = await user.updateGuest({id: guestId, newNick: newName});
+ res.status(200).send({
+ userId: user.id,
+ username: user.username,
+ updatedGuest: {
+ id: updatedGuest.id,
+ nick: updatedGuest.nick,
+ },
+ });
+ }
+ else {
+ res.status(400).send({message: "This request requires the parameter 'newName'"});
+ }
+};
+
+export const getGuest: RequestHandler = async (req, res) => {
+ const user = (req.user as IDbUser);
+ const {id: guestId} = req.params;
+ const guest = await user.getGuest(guestId);
+ res.status(200).send({
+ userId: user.id,
+ username: user.username,
+ guest: guest,
+ });
+};
+
+export const deleteGuest: RequestHandler = async (req, res) => {
+ const user = (req.user as IDbUser);
+ const {id: guestId} = req.params;
+ const deletedGuest = await user.deleteGuest(guestId);
+ res.status(200).send({
+ userId: user.id,
+ username: user.username,
+ deletedGuest: deletedGuest,
+ });
+};
+
+export const getGuests: RequestHandler = async (req, res) => {
+ const user = (req.user as IDbUser);
+ const guests = await user.getGuests();
+ res.status(200).send({
+ userId: user.id,
+ username: user.username,
+ guests: guests,
+ });
+};
+
+export const getAllPlayersAssociatedWithAccount: RequestHandler = async (req, res) => {
+ const user = (req.user as IDbUser);
+ const guests = await user.getGuests();
+ const mainPlayer = await user.getMainPlayerInfo();
+ res.status(200).send({guests, mainPlayer});
};
\ No newline at end of file
diff --git a/src/controllers/statsController.ts b/src/controllers/statsController.ts
index ea897e9..5d39750 100755
--- a/src/controllers/statsController.ts
+++ b/src/controllers/statsController.ts
@@ -2,12 +2,22 @@ import passport from "passport";
import DbUser, {IDbUser, IDbUserDoc} from "../models/dbUser";
import {RequestHandler} from "express";
import SavedGame from "../models/savedGame";
+import Player, {IPlayer} from "../models/player";
+
+export interface GameSubmission {
+ players: {id: string, nick: string}[],
+ results: GameResults[];
+}
+
+interface GameResults {
+ playerId: string;
+ blocks: any;
+}
export const listGames: RequestHandler = async (req, res) => {
- const user = (req.user as IDbUserDoc);
- const dbUser = await DbUser.findById(user._id, {"savedGames.game": 1, "savedGames.createdAt": 1});
+ const user = (req.user as IDbUser);
+ const dbUser = await DbUser.findById(user.id, {"savedGames._id": 1, "savedGames.results": 1, "savedGames.createdAt": 1});
if (dbUser) {
- console.log(dbUser.savedGames);
res.json({games: dbUser.savedGames});
}
else {
@@ -16,6 +26,19 @@ export const listGames: RequestHandler = async (req, res) => {
};
export const saveGame: RequestHandler = async (req, res) => {
- const user = (req.user as IDbUserDoc);
- await user.addGame(req.body);
+ const user = (req.user as IDbUser);
+ const submission = (req.body as GameSubmission);
+ for (let player of submission.players) {
+ if (player.id === player.nick) {
+ const newGuest = await user.addGuest(player.nick);
+ player.id = newGuest.id;
+ const playerResult = submission.results.find(result => result.playerId === player.nick);
+ playerResult!.playerId = player.id;
+ }
+ }
+ const newGame = await user.addGame(submission);
+ res.send({message: "Game submitted successfully!", newGame: newGame});
+};
+
+const processStatistics = (results: GameResults, account: IDbUser) => {
};
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 4e1124c..22d56a8 100755
--- a/src/index.ts
+++ b/src/index.ts
@@ -39,10 +39,6 @@ app.locals = {
initialisePassport();
app.use(passport.initialize());
app.use(passport.session());
-app.use((req, res, next) => {
- console.log(req.originalUrl);
- next();
-});
app.use(Settings.serverRoot + "/static", express.static("static"));
app.use(Settings.serverRoot, MainRouter);
diff --git a/src/models/dbUser.ts b/src/models/dbUser.ts
index d1aed0a..03561a3 100755
--- a/src/models/dbUser.ts
+++ b/src/models/dbUser.ts
@@ -1,9 +1,11 @@
-import mongoose from "mongoose";
+import mongoose, {Model} from "mongoose";
import Player, {IPlayer, IPlayerDoc, PlayerSchema} from "./player";
-import {AccountStats, AccountStatsSchema, IAccountStats, IAccountStatsDoc} from "./stats";
+import {AccountStatsSchema, IAccountStats, IAccountStatsDoc} from "./stats";
import SavedGame, {ISavedGame, ISavedGameDoc, SavedGameSchema} from "./savedGame";
import bcrypt from "bcrypt";
import {SupportedLang} from "../enums";
+import {GenericModelError, globalSchemaOptions, ModelParameterError, tryQuery} from "./utils";
+import {GameSubmission} from "../controllers/statsController";
export class CredentialsTakenError extends Error {
public emailExists: boolean;
@@ -17,6 +19,7 @@ export class CredentialsTakenError extends Error {
}
export interface IDbUser {
+ id: string;
username: string;
email: string;
password: string;
@@ -26,9 +29,21 @@ export interface IDbUser {
guests?: IPlayer[];
accountStats?: IAccountStats;
savedGames?: ISavedGame[];
+ getMainPlayerInfo(): Promise;
+ findGuestByNick(nick: string): Promise;
+ changeLang(lang: SupportedLang): void;
+ addGame(game: any): Promise;
+ getGuests(): Promise;
+ getGuest(guestId: string): Promise;
+ addGuest(nick: string): Promise;
+ updateGuest(guestParams: GuestUpdateParams): Promise;
+ deleteGuest(guestId: string): Promise;
}
+type GuestUpdateParams = {id: string, newNick: string};
+
export interface IDbUserDoc extends mongoose.Document {
+ id: string;
username: string;
email: string;
password: string;
@@ -37,17 +52,25 @@ export interface IDbUserDoc extends mongoose.Document {
player: IPlayerDoc;
guests: IPlayerDoc[];
accountStats: IAccountStatsDoc;
- savedGames: ISavedGameDoc[];
+ savedGames: mongoose.Types.Array;
+ getMainPlayerInfo(): Promise;
+ findGuestByNick(nick: string): Promise;
changeLang(lang: SupportedLang): void;
addGame(game: any): Promise;
+ getGuests(): Promise;
+ getGuest(guestId: string): Promise;
+ addGuest(nick: string): Promise;
+ updateGuest(guestParams: GuestUpdateParams): Promise;
+ deleteGuest(guestId: string): Promise;
}
export interface IDbUserModel extends mongoose.Model {
findByEmail(emailQuery: string): IDbUserDoc;
addNewUser(user: IDbUser): IDbUserDoc;
registerUser(username: string, email: string, password: string): IDbUserDoc;
- userWithEmailExists(email: string): boolean;
- userWithUsernameExists(username: string): boolean;
+ userWithEmailExists(email: string): Promise;
+ userWithUsernameExists(username: string): Promise;
+ getSerializedAuthUser(id: string): Promise;
}
export const DbUserSchema = new mongoose.Schema({
@@ -60,21 +83,25 @@ export const DbUserSchema = new mongoose.Schema({
guests: {type: [PlayerSchema], default: []},
accountStats: {type: AccountStatsSchema, default: () => ({}) },
savedGames: {type: [SavedGameSchema], default: []},
-});
+}, {...globalSchemaOptions});
DbUserSchema.statics.findByEmail = async function (emailQuery: string) {
- return this.findOne({email: emailQuery});
+ return tryQuery(() =>
+ this.findOne({email: emailQuery})
+ );
};
-DbUserSchema.statics.addNewUser = async function (user: IDbUser) {
- const player = new Player( { nick: user.username });
- return this.create({
- username: user.username,
- email: user.email,
- password: user.password,
- lang: SupportedLang.gb,
- player
- });
+DbUserSchema.statics.addNewUser = async function (username: string, email: string, hashedPw: string) {
+ const player = new Player( { nick: username });
+ return tryQuery(() =>
+ this.create({
+ username: username,
+ email: email,
+ password: hashedPw,
+ lang: SupportedLang.gb,
+ player
+ })
+ );
};
DbUserSchema.statics.registerUser = async function (username: string, email: string, password: string) {
@@ -85,30 +112,136 @@ DbUserSchema.statics.registerUser = async function (username: string, email: str
}
else {
const hashedPassword = await bcrypt.hash(password, 10);
- return this.addNewUser({username, email, password: hashedPassword});
+ return tryQuery(() =>
+ this.addNewUser(username, email, hashedPassword)
+ );
}
};
-DbUserSchema.statics.userWithEmailExists = async function (email: string) {
- return this.exists({email});
+DbUserSchema.statics.userWithEmailExists = async function (email: string): Promise {
+ return tryQuery(() => this.exists({email}));
};
-DbUserSchema.statics.userWithUsernameExists = async function (username: string) {
- return this.exists({username});
+DbUserSchema.statics.userWithUsernameExists = async function (username: string): Promise {
+ return tryQuery(() => this.exists({username}));
};
-DbUserSchema.methods.addGame = function (game: any): void {
- const newGame = new SavedGame();
- newGame.game = game;
- DbUser.updateOne(this, {$push: {savedGames: newGame}}, (err) => {
- console.log(err);
+DbUserSchema.statics.getSerializedAuthUser = async function (id: string): Promise {
+ return tryQuery(() => {
+ return DbUser.findById(id, {id: 1, username: 1, password: 1, lang: 1, email: 1});
});
};
-DbUserSchema.methods.changeLang = function (lang: SupportedLang): void {
- if (lang in SupportedLang) {
- DbUser.updateOne(this, {lang: lang});
+DbUserSchema.methods.getGuests = async function (this: IDbUser): Promise {
+ const user: IDbUserDoc = await tryQuery(async () => {
+ return DbUser.findById(this.id, {"guests.nick": 1, "guests._id": 1}).exec();
+ });
+ return user.guests;
+};
+
+DbUserSchema.methods.getGuest = async function (this: IDbUser, guestId: string): Promise {
+ return tryQuery(async () => {
+ const user = await DbUser.findById(this.id, {guests: {$elemMatch: {_id: guestId}}});
+ if (user!.guests.length > 0) {
+ return user!.guests[0];
+ }
+ else {
+ throw new ModelParameterError("Guest with ID " + guestId + " doesn't exist!");
+ }
+ });
+};
+
+DbUserSchema.methods.findGuestByNick = async function (this: IDbUser, guestNick: string): Promise {
+ return tryQuery(async () => {
+ const user = await DbUser.findById(this.id, {guests: {$elemMatch: {nick: guestNick}}});
+ if (user!.guests.length > 0) {
+ return user!.guests[0];
+ }
+ else {
+ return null;
+ }
+ });
+};
+
+DbUserSchema.methods.addGuest = async function (this: IDbUser, newGuestNick: string): Promise {
+ if (this.username !== newGuestNick) {
+ const guestLookup = await this.findGuestByNick(newGuestNick);
+ if (!guestLookup) {
+ return saveGuest(this, newGuestNick);
+ }
+ else {
+ throw new ModelParameterError(`Cannot add a guest with the same name of another guest in this account.`);
+ }
}
+ else {
+ throw new ModelParameterError("Cannot add a guest with the same name as the account holder's username.")
+ }
+};
+
+async function saveGuest(user: IDbUser, newGuestNick: string): Promise {
+ const newGuest: IPlayerDoc = new Player();
+ newGuest.nick = newGuestNick;
+ await tryQuery(() => {
+ DbUser.findByIdAndUpdate(user.id, {$push: {guests: newGuest}}).exec();
+ });
+ return newGuest;
+}
+
+DbUserSchema.methods.updateGuest = async function (this: IDbUser, guestParams: GuestUpdateParams): Promise {
+ return tryQuery(async () => {
+ const user = await DbUser.findById(this.id);
+ const updatableGuest = user!.guests.find(guest => guest.id === guestParams.id);
+ if (updatableGuest) {
+ updatableGuest.nick = guestParams.newNick;
+ await user!.save();
+ return updatableGuest;
+ }
+ else {
+ throw new ModelParameterError("Guest with ID " + guestParams.id + " doesn't exist!");
+ }
+ });
+};
+
+DbUserSchema.methods.deleteGuest = async function (this: IDbUser, guestId: string): Promise {
+ return tryQuery(async () => {
+ const user = await DbUser.findById(this.id);
+ const deleteGuestIndex = user!.guests.findIndex(guest => guest.id === guestId);
+ if (deleteGuestIndex !== -1) {
+ const deletedGuest = user!.guests[deleteGuestIndex];
+ user!.guests[deleteGuestIndex].remove();
+ await user!.save();
+ return deletedGuest;
+ }
+ else {
+ throw new ModelParameterError("Guest with ID " + guestId + " doesn't exist!");
+ }
+ });
+};
+
+DbUserSchema.methods.addGame = async function (submission: GameSubmission): Promise {
+ const newGame = await SavedGame.createFromGameSubmission(submission);
+ await tryQuery(() => {
+ DbUser.findByIdAndUpdate(this.id, {$push: {savedGames: newGame}}).exec();
+ });
+ return newGame;
+};
+
+DbUserSchema.methods.changeLang = async function (lang: SupportedLang): Promise {
+ if (lang in SupportedLang) {
+ await tryQuery(() =>
+ DbUser.findByIdAndUpdate(this.id, {lang: lang})
+ );
+ }
+ else {
+ throw new ModelParameterError(lang + " is not a supported language code!");
+ }
+};
+
+DbUserSchema.methods.getMainPlayerInfo = async function (): Promise {
+ const user = await tryQuery(() =>
+ DbUser.findById(this.id, {"player.nick": 1, "player._id": 1}).exec()
+ );
+ return user.player;
};
const DbUser = mongoose.model("DbUser", DbUserSchema);
diff --git a/src/models/player.ts b/src/models/player.ts
index 54d3618..474fc37 100755
--- a/src/models/player.ts
+++ b/src/models/player.ts
@@ -1,14 +1,17 @@
import mongoose from "mongoose";
import {IPlayerStats, IPlayerStatsDoc, PlayerStatsSchema} from "./stats";
+import {globalSchemaOptions} from "./utils";
export interface IPlayer {
+ id: string;
nick: string;
stats: IPlayerStats;
}
export interface IPlayerDoc extends mongoose.Document {
+ id: string;
nick: string;
- stats: IPlayerStats;
+ stats: IPlayerStatsDoc;
}
export interface IPlayerModel extends mongoose.Model {
@@ -18,7 +21,7 @@ export interface IPlayerModel extends mongoose.Model {
export const PlayerSchema = new mongoose.Schema({
nick: { type: String, required: true },
stats: { type: PlayerStatsSchema, required: true, default: () => ({}) },
-});
+}, {...globalSchemaOptions});
const Player = mongoose.model("Player", PlayerSchema);
export default Player;
\ No newline at end of file
diff --git a/src/models/savedGame.ts b/src/models/savedGame.ts
index dfb5267..8b370bc 100755
--- a/src/models/savedGame.ts
+++ b/src/models/savedGame.ts
@@ -1,24 +1,45 @@
-import mongoose from "mongoose";
-import {IPlayer} from "./player";
+import mongoose, {Types} from "mongoose";
+import Player, {IPlayer} from "./player";
+import {GameSubmission} from "../controllers/statsController";
+import {tryQuery, globalSchemaOptions} from "./utils";
+import DbUser from "./dbUser";
export interface ISavedGame {
- //players: IPlayer[],
- game: any,
+ id: string;
+ //rulesetUsed?: ruleset;
+ players: mongoose.Types.ObjectId[];
+ results: any[];
}
export interface ISavedGameDoc extends mongoose.Document {
- //players: mongoose.Types.ObjectId[],
- game: mongoose.Types.Subdocument,
+ //rulesetUsed: mongoose.Types.ObjectId[];
+ id: string;
+ players: mongoose.Types.Array;
+ results: mongoose.Types.Array;
}
export interface ISavedGameModel extends mongoose.Model {
// virtual static methods
+ createFromGameSubmission(submission: GameSubmission): Promise;
}
export const SavedGameSchema = new mongoose.Schema({
- //players: [mongoose.Schema.Types.ObjectId],
- game: mongoose.Schema.Types.Mixed,
-}, {timestamps: true});
+ //rulesetUsed: [mongoose.Schema.Types.ObjectId],
+ players: [mongoose.Schema.Types.ObjectId],
+ results: [mongoose.Schema.Types.Mixed],
+}, {
+ timestamps: true,
+ ...globalSchemaOptions
+});
+
+SavedGameSchema.statics.createFromGameSubmission = async function(submission: GameSubmission) {
+ const newGame = new SavedGame();
+ newGame.results.addToSet(...submission.results);
+ await tryQuery(async () => {
+ newGame.players.addToSet(...submission.players.map(player => player.id));
+ });
+ return newGame;
+};
const SavedGame = mongoose.model("SavedGame", SavedGameSchema);
export default SavedGame;
\ No newline at end of file
diff --git a/src/models/stats.ts b/src/models/stats.ts
index c5ae3fd..8443e17 100755
--- a/src/models/stats.ts
+++ b/src/models/stats.ts
@@ -53,31 +53,16 @@ type IMultiplierFieldStats = INumberFieldStats;
type IYahtzeeFieldStats = INumberFieldStats;
// Mongoose doc interfaces and types
-export interface IPlayerStats extends IBaseStats {
- one: IMultiplierFieldStatsDoc;
- two: IMultiplierFieldStatsDoc;
- three: IMultiplierFieldStatsDoc;
- four: IMultiplierFieldStatsDoc;
- five: IMultiplierFieldStatsDoc;
- six: IMultiplierFieldStatsDoc;
- upperBonus: IBonusFieldStatsDoc;
- upperTotal: ITotalFieldStatsDoc;
- threeKind: INumberFieldStatsDoc;
- fourKind: INumberFieldStatsDoc;
- fullHouse: IBoolFieldStatsDoc;
- smlStraight: IBoolFieldStatsDoc;
- lgStraight: IBoolFieldStatsDoc;
- yahtzee: IYahtzeeFieldStatsDoc;
- chance: INumberFieldStatsDoc;
- grandTotal: ITotalFieldStatsDoc;
- lowerTotal: ITotalFieldStatsDoc;
- gamesPlayed: number;
+export interface IPlayerStatsDoc extends IBaseStatsDoc {
wins: number;
runnerUps: number;
draws: number;
losses: number;
}
-export interface IAccountStats extends IBaseStats {
+export interface IAccountStatsDoc extends IBaseStatsDoc {
+ timesNoWinner: number;
+}
+interface IBaseStatsDoc extends mongoose.Document {
one: IMultiplierFieldStatsDoc;
two: IMultiplierFieldStatsDoc;
three: IMultiplierFieldStatsDoc;
@@ -96,16 +81,14 @@ export interface IAccountStats extends IBaseStats {
grandTotal: ITotalFieldStatsDoc;
lowerTotal: ITotalFieldStatsDoc;
gamesPlayed: number;
- timesNoWinner: number;
}
+
type IBonusFieldStatsDoc = mongoose.Document & IBonusFieldStats;
type ITotalFieldStatsDoc = mongoose.Document & ITotalFieldStats;
type IBoolFieldStatsDoc = mongoose.Document & IBoolFieldStats;
type INumberFieldStatsDoc = mongoose.Document & INumberFieldStats;
type IMultiplierFieldStatsDoc = mongoose.Document & IMultiplierFieldStats;
type IYahtzeeFieldStatsDoc = mongoose.Document & IYahtzeeFieldStats;
-export type IPlayerStatsDoc = mongoose.Document & IPlayerStats;
-export type IAccountStatsDoc = mongoose.Document & IAccountStats;
// Mongoose schemata
class Int extends mongoose.SchemaType {
diff --git a/src/models/utils.ts b/src/models/utils.ts
new file mode 100644
index 0000000..231178b
--- /dev/null
+++ b/src/models/utils.ts
@@ -0,0 +1,41 @@
+export class GenericModelError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "GenericModelError";
+ }
+}
+
+export class ModelParameterError extends GenericModelError {
+ constructor(message: string) {
+ super(message);
+ this.name = "ModelParameterError";
+ }
+}
+
+type CallbackWrapper = (query: () => T) => Promise;
+
+export const tryQuery: CallbackWrapper = async (cb) => {
+ try {
+ return cb();
+ }
+ catch (err) {
+ throw new GenericModelError(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;
+ },
+ }
+};
\ No newline at end of file
diff --git a/src/passport-config.ts b/src/passport-config.ts
index 1298842..79f7ab2 100755
--- a/src/passport-config.ts
+++ b/src/passport-config.ts
@@ -1,7 +1,7 @@
import passport from "passport";
import {Strategy as LocalStrategy, VerifyFunction} from "passport-local";
import bcrypt from "bcrypt";
-import DbUser, {IDbUserDoc} from "./models/dbUser";
+import DbUser, {IDbUser} from "./models/dbUser";
import {NextFunction, Request, Response} from "express";
export const requireAuthenticated = (req: Request, res: Response, next: NextFunction) => {
@@ -41,11 +41,11 @@ const authenticateUser: VerifyFunction = async (email, password, done) => {
export const initialisePassport = () => {
passport.use(new LocalStrategy({ usernameField: "email" }, authenticateUser));
- passport.serializeUser((user: IDbUserDoc, done) => {
- done(null, user._id)
+ passport.serializeUser((user: IDbUser, done) => {
+ done(null, user.id)
});
passport.deserializeUser(async (id: string, done) => {
- const user: IDbUserDoc | null = await DbUser.findById(id, {username: 1, password: 1, lang: 1, email: 1});
+ const user: IDbUser | null = await DbUser.getSerializedAuthUser(id);
done(null, user);
});
};
\ No newline at end of file
diff --git a/src/routers/apiRouter.ts b/src/routers/apiRouter.ts
index 4e04071..f62e53f 100755
--- a/src/routers/apiRouter.ts
+++ b/src/routers/apiRouter.ts
@@ -1,13 +1,24 @@
import express from "express";
import {requireAuthenticated} from "../passport-config";
-import * as stats from "../controllers/statsController";
-import * as dbUser from "../controllers/dbUserController"
+import * as statsController from "../controllers/statsController";
+import * as dbUserController from "../controllers/dbUserController"
const router = express.Router();
-router.get("/user", dbUser.whoAmI);
-router.post("/changeLang", requireAuthenticated, dbUser.changeLang);
-router.get("/games", requireAuthenticated, stats.listGames);
-router.post("/savegame", requireAuthenticated, stats.saveGame);
+// Basic User Settings
+router.get("/user", dbUserController.whoAmI);
+router.put("/lang", requireAuthenticated, dbUserController.changeLang);
+
+// Guests
+router.get("/players", requireAuthenticated, dbUserController.getAllPlayersAssociatedWithAccount);
+router.get("/guests", requireAuthenticated, dbUserController.getGuests);
+router.get("/guest/:id", requireAuthenticated, dbUserController.getGuest);
+router.put("/guest/:id", requireAuthenticated, dbUserController.updateGuest);
+router.post("/guests", requireAuthenticated, dbUserController.addGuest);
+router.delete("/guest/:id", requireAuthenticated, dbUserController.deleteGuest);
+
+// Games
+router.get("/games", requireAuthenticated, statsController.listGames);
+router.post("/games", requireAuthenticated, statsController.saveGame);
export default router;
\ No newline at end of file
diff --git a/src/routers/mainRouter.ts b/src/routers/mainRouter.ts
index dca6787..d4d0a17 100755
--- a/src/routers/mainRouter.ts
+++ b/src/routers/mainRouter.ts
@@ -2,19 +2,18 @@ import express from "express";
import {requireAuthenticated} from "../passport-config";
import routers from "./routers";
import {IDbUser} from "../models/dbUser";
+import {ModelParameterError} from "../models/utils";
const router = express.Router();
router.use("/account", routers.signup);
router.use("/api", routers.api);
-
router.get("/game", requireAuthenticated, (req, res) => {
res.render("gameIndex.ejs", {
username: (req.user as IDbUser).username,
rootUrl: req.app.locals.rootUrl
});
});
-
router.get("/**", requireAuthenticated, (req, res) => {
res.render("frontendIndex.ejs", {
username: (req.user as IDbUser).username,
@@ -22,4 +21,16 @@ router.get("/**", requireAuthenticated, (req, res) => {
});
});
+const genericErrorHandler: express.ErrorRequestHandler =
+ (err, req, res, next) => {
+ if (err instanceof ModelParameterError) {
+ res.status(500).send({message: "An internal error occurred in the database."});
+ }
+ else {
+ res.status(500).send({message: "An unknown error occurred."});
+ }
+ };
+
+router.use(genericErrorHandler);
+
export default router;
\ No newline at end of file
diff --git a/src/server-config.json b/src/server-config.json
index e55f4f9..aef19a5 100755
--- a/src/server-config.json
+++ b/src/server-config.json
@@ -1,4 +1,4 @@
{
- "mongodb_uri": "mongodb://127.0.0.1:27017/test",
+ "mongodb_uri": "mongodb://127.0.0.1:27017/local",
"serverRoot": "/kadi"
}
\ No newline at end of file