import Foundation import Fluent import Vapor typealias PasswordHash = String typealias SessionToken = String final class SQLiteDatabase { /// A mapping between player name and generated access tokens for a session private var sessionTokenForPlayer = [PlayerName: SessionToken]() /// A reverse mapping between generated access tokens and player name private var playerNameForToken = [SessionToken: PlayerName]() private let tables: TableManagement init(db: Database) throws { self.tables = try TableManagement(db: db) } func registerPlayer(named name: PlayerName, hash: PasswordHash, in database: Database) -> EventLoopFuture { User.query(on: database).filter(\.$name == name).first() .guard({ $0 == nil }, else: Abort(.conflict)).flatMap { _ in let user = User(name: name, hash: hash) return user.create(on: database).map { // Create a new token and store it for the user let token = SessionToken.newToken() self.sessionTokenForPlayer[name] = token self.playerNameForToken[token] = name return token } } } func passwordHashForExistingPlayer(named name: PlayerName, in database: Database) -> EventLoopFuture { User.query(on: database).filter(\.$name == name).first() .unwrap(or: Abort(.forbidden)).map { $0.passwordHash } } func deletePlayer(named name: PlayerName, in database: Database) -> EventLoopFuture { user(named: name, in: database).flatMap { user in self.tables.leaveTable(player: user, in: database) }.flatMap { User.query(on: database).filter(\.$name == name).delete() } } func isValid(sessionToken token: SessionToken) -> Bool { playerNameForToken[token] != nil } func startSession(socket: WebSocket, sessionToken token: SessionToken) -> Bool { guard let player = playerNameForToken[token] else { return false } return tables.connect(player: player, using: socket) } private func didReceive(message: String, forSessionToken token: SessionToken) { // TODO: Handle client requests print("Session \(token.prefix(6)): \(message)") } func endSession(forSessionToken sessionToken: SessionToken) { guard let player = endExistingSession(forSessionToken: sessionToken) else { return } tables.disconnect(player: player) } private func endExistingSession(forSessionToken token: SessionToken) -> PlayerName? { guard let player = playerNameForToken.removeValue(forKey: token) else { return nil } sessionTokenForPlayer.removeValue(forKey: player) return player } /** Start a new session for an existing user. - Parameter name: The user name - Returns: The generated access token for the session */ func startNewSessionForRegisteredPlayer(named name: PlayerName) -> SessionToken { let token = SessionToken.newToken() self.sessionTokenForPlayer[name] = token self.playerNameForToken[token] = name return token } func registeredPlayerExists(withSessionToken token: SessionToken) -> PlayerName? { playerNameForToken[token] } func currentTableOfPlayer(named player: PlayerName) -> TableInfo? { tables.tableInfo(player: player) } private func points(for player: PlayerName, in database: Database) -> EventLoopFuture { User.query(on: database) .filter(\.$name == player) .first() .unwrap(or: Abort(.notFound)) .map { $0.points } } private func user(named name: PlayerName, in database: Database) -> EventLoopFuture { User.query(on: database) .filter(\.$name == name) .first() .unwrap(or: Abort(.notFound)) } private func user(withToken token: SessionToken, in database: Database) -> EventLoopFuture { database.eventLoop .future() .map { self.playerNameForToken[token] } .unwrap(or: Abort(.unauthorized)) .flatMap { name in self.user(named: name, in: database) } } // MARK: Tables /** Create a new table with optional players. - Parameter name: The name of the table - Parameter players: The player creating the table - Parameter isPublic: Indicates that this is a game joinable by everyone - Returns: The table id */ func createTable(named name: TableName, player: PlayerName, isPublic: Bool, in database: Database) -> EventLoopFuture { user(named: player, in: database).flatMap { player in self.tables.createTable(named: name, player: player, isPublic: isPublic, in: database) } } func getPublicTableInfos() -> [PublicTableInfo] { tables.publicTableList } func join(tableId: UUID, playerToken: SessionToken, in database: Database) -> EventLoopFuture { user(withToken: playerToken, in: database).flatMap { player in self.tables.join(tableId: tableId, player: player, in: database) } } func leaveTable(playerToken: SessionToken, in database: Database) -> EventLoopFuture { user(withToken: playerToken, in: database).flatMap { player in self.tables.leaveTable(player: player, in: database) } } func performAction(playerToken: SessionToken, action: PlayerAction) -> PlayerActionResult { guard let player = playerNameForToken[playerToken] else { return .invalidToken } return tables.performAction(player: player, action: action) } func select(game: GameType, playerToken: SessionToken) -> PlayerActionResult { guard let player = playerNameForToken[playerToken] else { return .invalidToken } return tables.select(game: game, player: player) } func play(card: Card, playerToken: SessionToken) -> PlayerActionResult { guard let player = playerNameForToken[playerToken] else { return .invalidToken } return tables.play(card: card, player: player) } }