Schafkopf-Server/Sources/App/Management/SQLiteDatabase.swift
2021-12-22 22:11:37 +01:00

178 lines
6.3 KiB
Swift

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<SessionToken> {
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<PasswordHash> {
User.query(on: database).filter(\.$name == name).first()
.unwrap(or: Abort(.forbidden)).map { $0.passwordHash }
}
func deletePlayer(named name: PlayerName, in database: Database) -> EventLoopFuture<Void> {
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<Int> {
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> {
User.query(on: database)
.filter(\.$name == name)
.first()
.unwrap(or: Abort(.notFound))
}
private func user(withToken token: SessionToken, in database: Database) -> EventLoopFuture<User> {
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<TableInfo> {
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<TableInfo> {
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<Void> {
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)
}
}