Add first database version and model
This commit is contained in:
parent
fcc1d21e5f
commit
7ac0f29904
177
Sources/App/Management/SQLiteDatabase.swift
Normal file
177
Sources/App/Management/SQLiteDatabase.swift
Normal file
@ -0,0 +1,177 @@
|
||||
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)
|
||||
}
|
||||
}
|
32
Sources/App/Model/Migrations/UserTableMigration.swift
Normal file
32
Sources/App/Model/Migrations/UserTableMigration.swift
Normal file
@ -0,0 +1,32 @@
|
||||
import FluentSQLiteDriver
|
||||
|
||||
struct UserTableMigration: Migration {
|
||||
|
||||
func prepare(on database: FluentSQLiteDriver.Database) -> EventLoopFuture<Void> {
|
||||
let one = database.schema(User.schema)
|
||||
.id()
|
||||
.field(User.Key.name.key, .string, .required)
|
||||
.field(User.Key.hash.key, .string, .required)
|
||||
.field(User.Key.points.key, .int, .required)
|
||||
.field(User.Key.table.key, .uuid, .references(Table.schema, Table.Key.id.key))
|
||||
.create()
|
||||
let two = database.enum(Table.Key.language.rawValue)
|
||||
.case(SupportedLanguage.german.rawValue)
|
||||
.case(SupportedLanguage.english.rawValue)
|
||||
.create()
|
||||
|
||||
let three = database.enum(Table.Key.language.rawValue).read().flatMap { lang in
|
||||
database.schema(Table.schema)
|
||||
.id()
|
||||
.field(Table.Key.name.key, .string, .required)
|
||||
.field(Table.Key.isPublic.key, .bool, .required)
|
||||
.field(Table.Key.language.key, lang, .required)
|
||||
.create()
|
||||
}
|
||||
return one.and(two).and(three).map { _ in }
|
||||
}
|
||||
|
||||
func revert(on database: FluentSQLiteDriver.Database) -> EventLoopFuture<Void> {
|
||||
database.eventLoop.makeCompletedFuture(.success(()))
|
||||
}
|
||||
}
|
52
Sources/App/Model/Table.swift
Normal file
52
Sources/App/Model/Table.swift
Normal file
@ -0,0 +1,52 @@
|
||||
import FluentSQLiteDriver
|
||||
import Vapor
|
||||
|
||||
/// A registered user
|
||||
class Table: Model {
|
||||
|
||||
enum Key: String {
|
||||
case id = "id"
|
||||
case name = "name"
|
||||
case isPublic = "public"
|
||||
case language = "language"
|
||||
|
||||
var key: FieldKey {
|
||||
.init(stringLiteral: rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the SQLite table
|
||||
static let schema = "table"
|
||||
|
||||
/// The unique identifier for this table.
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
/// The user's full name.
|
||||
@Field(key: Key.name.key)
|
||||
var name: String
|
||||
|
||||
/// The players sitting at the table
|
||||
@Children(for: \.$table)
|
||||
var players: [User]
|
||||
|
||||
@Field(key: Key.isPublic.key)
|
||||
var isPublic: Bool
|
||||
|
||||
@Enum(key: Key.language.key)
|
||||
var language: SupportedLanguage
|
||||
|
||||
required init() { }
|
||||
|
||||
/// Creates a new table.
|
||||
init(id: UUID? = nil, name: String, isPublic: Bool = true, language: SupportedLanguage = .english) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.isPublic = isPublic
|
||||
self.language = language
|
||||
}
|
||||
|
||||
var stringId: String {
|
||||
"\(id!)"
|
||||
}
|
||||
}
|
55
Sources/App/Model/User.swift
Normal file
55
Sources/App/Model/User.swift
Normal file
@ -0,0 +1,55 @@
|
||||
import FluentSQLiteDriver
|
||||
import Vapor
|
||||
|
||||
|
||||
|
||||
/// A registered user
|
||||
final class User: Model {
|
||||
|
||||
enum Key: String {
|
||||
case id = "id"
|
||||
case name = "name"
|
||||
case hash = "hash"
|
||||
case points = "points"
|
||||
case table = "table_id"
|
||||
|
||||
var key: FieldKey {
|
||||
.init(stringLiteral: rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the SQLite table
|
||||
static let schema = "user"
|
||||
|
||||
/// The unique identifier for this user.
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
/// The user's full name.
|
||||
@Field(key: Key.name.key)
|
||||
var name: String
|
||||
|
||||
/// The hash of the user's password
|
||||
@Field(key: Key.hash.key)
|
||||
var passwordHash: String
|
||||
|
||||
/// The user's total points
|
||||
@Field(key: Key.points.key)
|
||||
var points: Int
|
||||
|
||||
// Example of an optional parent relation.
|
||||
@OptionalParent(key: Key.table.key)
|
||||
var table: Table?
|
||||
|
||||
init() { }
|
||||
|
||||
/// Creates a new user.
|
||||
init(id: UUID? = nil, name: String, hash: String) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.passwordHash = hash
|
||||
self.points = 0
|
||||
}
|
||||
}
|
||||
|
||||
//extension User: Content { }
|
Loading…
Reference in New Issue
Block a user