import {SupportedLang} from "../enums"; import Player, {MongoStoredPlayer, StoredPlayer, StoredPlayerData} from "./StoredPlayer"; import {AccountStats} from "./stats"; import {SavedGameData, StoredSavedGame} from "./savedGame"; import { GenericModelError, getMongoObjectCollection, MongoStoredObject, MongoStoredObjectCollection, StoredObject, StoredObjectCollection, StoredObjectId, tryQuery } from "./utils"; import mongo from "mongodb"; import StoredPlayers from "./StoredPlayer"; import bcrypt from "bcrypt"; export class CredentialsTakenError extends Error { public emailExists: boolean; public usernameExists: boolean; constructor(usernameExists: boolean, emailExists: boolean) { super("Registration failure:" + usernameExists + emailExists); this.usernameExists = usernameExists; this.emailExists = emailExists; this.name = "CredentialsTakenError"; } } export interface StoredUserData { _id: string; username: string; email: string; password: string; lang: SupportedLang; friends: string[]; player: StoredObjectId; guests: StoredObjectId[]; accountStats: AccountStats; savedGames: SavedGameData[]; } export interface StoredUser extends StoredObject { getLoginDetails(): Promise preferredLang(): Promise; getFriends(): Promise; getGuests(): Promise; getAccountStats(): Promise; getSavedGames(): Promise; getMainPlayerInfo(): Promise; findGuestByNick(nick: string): Promise; changeLang(lang: SupportedLang): Promise; addGame(game: any): Promise; getGuestById(guestId: string): Promise; addGuest(nick: string): Promise; updateGuest(guestParams: GuestUpdateParams): Promise; deleteGuest(guestId: string): Promise; } type GuestUpdateParams = { id: string, newNick: string }; type LoginDetails = { username: string, email: string, password: string }; class MongoStoredUser extends MongoStoredObject implements StoredUser { constructor(data: StoredUserData) { super(data); } async getLoginDetails(): Promise { return {username: this.data.username, email: this.data.email, password: this.data.password}; } async preferredLang(): Promise { return this.data.lang; } async getFriends(): Promise { const friends: StoredUser[] = []; for (const friendId in this.data.guests) { const foundFriend = await StoredUsers.findById(friendId) as StoredUser; friends.push(foundFriend); } return friends; } async getGuests(): Promise { const guests: StoredPlayer[] = []; for (const guestId in this.data.guests) { const foundGuest = await StoredPlayers.findById(guestId) as StoredPlayer; guests.push(foundGuest); } return guests; } async getAccountStats(): Promise { return this.data.accountStats; } async getSavedGames(): Promise { return this.data.savedGames.map(savedGame => new MongoStoredSavedGame(savedGame)); } async getMainPlayerInfo(): Promise { return StoredPlayers.findById(this.data.player) as Promise; } async findGuestByNick(nick: string): Promise { const guests = await this.getGuests(); for (const guest of guests) { if (guest.nick() == nick) { return guest; } } return null; } async changeLang(lang: SupportedLang): Promise { this.data.lang = lang; } async addGame(game: SavedGameData): Promise { this.data.savedGames.push(game); } getGuestById(guestId: string): Promise { return StoredPlayers.findById(guestId) as Promise; } async addGuest(nick: string): Promise { const newGuest = await StoredPlayers.newPlayer(nick); this.data.guests.push(newGuest.id()); return newGuest; } async updateGuest(guestParams: GuestUpdateParams): Promise { const guest = await StoredPlayers.findById(guestParams.id) as StoredPlayer; await guest.setNick(guestParams.newNick); await StoredPlayers.save(guest); return guest; } async deleteGuest(guestId: string): Promise { return StoredPlayers.deleteById(guestId); } } export interface StoredUserCollection extends StoredObjectCollection { findByEmail(emailQuery: string): Promise; registerUser(loginDetails: LoginDetails): Promise; userWithEmailExists(email: string): Promise; userWithUsernameExists(username: string): Promise; getSerializedAuthUser(id: string): Promise; } class MongoStoredUserCollection extends MongoStoredObjectCollection implements StoredUserCollection { constructor(collectionClient: mongo.Collection) { super(collectionClient, MongoStoredUser); } findById(id: string): Promise { return this.findObjectById(id); } findByEmail(emailQuery: string): Promise { return tryQuery(async () => await this.findObjectByAttribute("email", emailQuery) ); } private async addNewUser(loginDetails: LoginDetails): Promise { 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 { const usernameTaken = await this.userWithUsernameExists(loginDetails.username); const emailTaken = await this.userWithEmailExists(loginDetails.email); if (usernameTaken || emailTaken) { throw new CredentialsTakenError(usernameTaken, emailTaken); } else { const hashedPassword = await bcrypt.hash(loginDetails.password, 10); return tryQuery(() => this.addNewUser({...loginDetails, password: hashedPassword}) ); } } async userWithEmailExists(email: string): Promise { const object = await this.findObjectByAttribute("email", email); return object !== null; } async userWithUsernameExists(username: string): Promise { const object = await this.findObjectByAttribute("username", username); return object !== null; } async getSerializedAuthUser(id: string): Promise { const dbResult = await this.findById(id); if (dbResult) { return dbResult; } else { throw new GenericModelError("User not found!"); } } } const StoredUsers = new MongoStoredUserCollection(getMongoObjectCollection("users")); export default StoredUsers;