Registration, Login, Resume, Table Creation, Dark Style
This commit is contained in:
14
Sources/App/Model/Crypto+Extensions.swift
Normal file
14
Sources/App/Model/Crypto+Extensions.swift
Normal file
@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
import Crypto
|
||||
|
||||
extension String {
|
||||
|
||||
/**
|
||||
Create a new access token.
|
||||
*/
|
||||
static func newToken() -> String {
|
||||
Crypto.SymmetricKey.init(size: .bits128).withUnsafeBytes {
|
||||
$0.hexEncodedString()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +1,68 @@
|
||||
import Foundation
|
||||
import Crypto
|
||||
import Vapor
|
||||
|
||||
let playerPerTable = 4
|
||||
|
||||
typealias TableId = String
|
||||
typealias TableName = String
|
||||
|
||||
final class Database {
|
||||
|
||||
/// A mapping between usernames and their password hashes
|
||||
private var userPasswordHashes = [String: String]()
|
||||
private let players: PlayerManagement
|
||||
|
||||
/// A mapping between usernames and generated access tokens for a session
|
||||
private var authTokenForUser = [String: String]()
|
||||
private let tables: TableManagement
|
||||
|
||||
/// A reverse mapping between generated access tokens and usernames
|
||||
private var userForToken = [String: String]()
|
||||
|
||||
/// A list of table ids for public games
|
||||
private var publicTables = Set<String>()
|
||||
|
||||
/// A mapping from table id to table name (for all tables)
|
||||
private var tableNames = [String: String]()
|
||||
|
||||
/// A mapping from table id to participating players
|
||||
private var tablePlayers = [String: [String]]()
|
||||
|
||||
/// A reverse list of players and their table id
|
||||
private var playerTables = [String: String]()
|
||||
private var sessions: [SessionToken : WebSocket]
|
||||
|
||||
init() {
|
||||
|
||||
self.players = PlayerManagement()
|
||||
self.tables = TableManagement()
|
||||
self.sessions = [:]
|
||||
// TODO: Load server data from disk
|
||||
// TODO: Save data to disk
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a user exists.
|
||||
- Parameter name: The name of the user
|
||||
- Returns: true, if the user exists
|
||||
*/
|
||||
func has(user: String) -> Bool {
|
||||
userPasswordHashes[user] != nil
|
||||
// MARK: Players & Sessions
|
||||
|
||||
func registerPlayer(named name: PlayerName, hash: PasswordHash) -> SessionToken? {
|
||||
players.registerPlayer(named: name, hash: hash)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the password hash for a user, if the user exists.
|
||||
- Parameter name: The name of the user
|
||||
- Returns: The stored password hash, if the user exists
|
||||
*/
|
||||
func hash(ofUser name: String) -> String? {
|
||||
userPasswordHashes[name]
|
||||
func passwordHashForExistingPlayer(named name: PlayerName) -> PasswordHash? {
|
||||
players.passwordHash(ofRegisteredPlayer: name)
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new user and assign an access token.
|
||||
- Parameter name: The name of the new user
|
||||
- Parameter hash: The password hash of the user
|
||||
- Returns: The generated access token for the session
|
||||
*/
|
||||
func add(user name: String, hash: String) -> String {
|
||||
self.userPasswordHashes[name] = hash
|
||||
return startSession(forUser: name)
|
||||
func deletePlayer(named name: PlayerName) {
|
||||
if let sessionToken = players.deletePlayer(named: name) {
|
||||
closeAndRemoveSession(for: sessionToken)
|
||||
}
|
||||
// TODO: Delete player from tables
|
||||
}
|
||||
|
||||
func isValid(sessionToken token: SessionToken) -> Bool {
|
||||
players.isValid(sessionToken: token)
|
||||
}
|
||||
|
||||
func startSession(socket: WebSocket, sessionToken: SessionToken) {
|
||||
closeAndRemoveSession(for: sessionToken)
|
||||
sessions[sessionToken] = socket
|
||||
socket.onText { [weak self] socket, text in
|
||||
self?.didReceive(message: text, forSessionToken: sessionToken)
|
||||
}
|
||||
}
|
||||
|
||||
private func didReceive(message: String, forSessionToken token: SessionToken) {
|
||||
// TODO: Handle client requests
|
||||
print("Session \(token.prefix(6)): \(message)")
|
||||
}
|
||||
|
||||
func endSession(forSessionToken token: SessionToken) {
|
||||
players.endSession(forSessionToken: token)
|
||||
closeAndRemoveSession(for: token)
|
||||
}
|
||||
|
||||
private func closeAndRemoveSession(for token: SessionToken) {
|
||||
_ = sessions.removeValue(forKey: token)?.close()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,32 +70,22 @@ final class Database {
|
||||
- Parameter name: The user name
|
||||
- Returns: The generated access token for the session
|
||||
*/
|
||||
func startSession(forUser name: String) -> String {
|
||||
let token = newToken()
|
||||
self.authTokenForUser[name] = token
|
||||
self.userForToken[token] = name
|
||||
return token
|
||||
func startNewSessionForRegisteredPlayer(named name: PlayerName) -> SessionToken {
|
||||
players.startNewSessionForRegisteredPlayer(named: name)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the user for a session token.
|
||||
- Parameter token: The access token for the user
|
||||
- Returns: The name of the user, if it exists
|
||||
*/
|
||||
func user(forToken token: String) -> String? {
|
||||
userForToken[token]
|
||||
func registeredPlayerExists(withSessionToken token: SessionToken) -> PlayerName? {
|
||||
players.registeredPlayerExists(withSessionToken: token)
|
||||
}
|
||||
|
||||
func tableExists(named name: String) -> Bool {
|
||||
tableNames.contains { $0.value == name }
|
||||
// MARK: Tables
|
||||
|
||||
func tableExists(withId id: TableId) -> Bool {
|
||||
tables.tableExists(withId: id)
|
||||
}
|
||||
|
||||
func tableExists(withId id: String) -> Bool {
|
||||
tableNames[id] != nil
|
||||
}
|
||||
|
||||
func tableIsFull(withId id: String) -> Bool {
|
||||
tablePlayers[id]!.count < playerPerTable
|
||||
func tableIsFull(withId id: TableId) -> Bool {
|
||||
tables.tableIsFull(withId: id)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,43 +95,22 @@ final class Database {
|
||||
- Parameter visible: Indicates that this is a game joinable by everyone
|
||||
- Returns: The table id
|
||||
*/
|
||||
func createTable(named name: String, player: String, visible: Bool) -> String {
|
||||
let tableId = newToken()
|
||||
|
||||
tableNames[tableId] = tableId
|
||||
tablePlayers[tableId] = [player]
|
||||
playerTables[player] = tableId
|
||||
|
||||
if visible {
|
||||
publicTables.insert(tableId)
|
||||
}
|
||||
return tableId
|
||||
func createTable(named name: TableName, player: PlayerName, visible: Bool) -> TableId {
|
||||
tables.createTable(named: name, player: player, visible: visible)
|
||||
}
|
||||
|
||||
func getPublicTableInfos() -> [TableInfo] {
|
||||
publicTables.map { tableId in
|
||||
TableInfo(id: tableId, name: tableNames[tableId]!, players: tablePlayers[tableId]!)
|
||||
}.sorted()
|
||||
tables.getPublicTableInfos()
|
||||
}
|
||||
|
||||
func join(tableId: String, player: String) {
|
||||
tablePlayers[tableId]!.append(player)
|
||||
if let oldTable = playerTables[tableId] {
|
||||
remove(player: player, fromTable: oldTable)
|
||||
}
|
||||
playerTables[tableId] = tableId
|
||||
}
|
||||
|
||||
func remove(player: String, fromTable tableId: String) {
|
||||
tablePlayers[tableId] = tablePlayers[tableId]?.filter { $0 != player }
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new access token.
|
||||
*/
|
||||
private func newToken() -> String {
|
||||
Crypto.SymmetricKey.init(size: .bits128).withUnsafeBytes {
|
||||
$0.hexEncodedString()
|
||||
}
|
||||
func join(tableId: TableId, player: PlayerName) {
|
||||
let playersAtTable = tables.join(tableId: tableId, player: player)
|
||||
playersAtTable
|
||||
.compactMap { players.sessionToken(forPlayer: $0) } // Session Tokens
|
||||
.compactMap { sessions[$0] } // Sockets
|
||||
.forEach { socket in
|
||||
// TODO: Notify sessions about changed players
|
||||
// socket.send("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
106
Sources/App/Model/PlayerManagement.swift
Normal file
106
Sources/App/Model/PlayerManagement.swift
Normal file
@ -0,0 +1,106 @@
|
||||
import Foundation
|
||||
|
||||
typealias PlayerName = String
|
||||
typealias PasswordHash = String
|
||||
typealias SessionToken = String
|
||||
|
||||
/// Manages player registration, session tokens and password hashes
|
||||
final class PlayerManagement {
|
||||
|
||||
/// A mapping between player name and their password hashes
|
||||
private var playerPasswordHashes = [PlayerName: PasswordHash]()
|
||||
|
||||
/// 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]()
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a player exists.
|
||||
- Parameter name: The name of the player
|
||||
- Returns: true, if the player exists
|
||||
*/
|
||||
func hasRegisteredPlayer(named user: PlayerName) -> Bool {
|
||||
playerPasswordHashes[user] != nil
|
||||
}
|
||||
|
||||
/**
|
||||
Get the password hash for a player, if the player exists.
|
||||
- Parameter name: The name of the player
|
||||
- Returns: The stored password hash, if the player exists
|
||||
*/
|
||||
func passwordHash(ofRegisteredPlayer name: PlayerName) -> PasswordHash? {
|
||||
playerPasswordHashes[name]
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new player and assign an access token.
|
||||
- Parameter name: The name of the new player
|
||||
- Parameter hash: The password hash of the player
|
||||
- Returns: The generated access token for the session
|
||||
*/
|
||||
func registerPlayer(named name: PlayerName, hash: PasswordHash) -> SessionToken? {
|
||||
guard !hasRegisteredPlayer(named: name) else {
|
||||
return nil
|
||||
}
|
||||
self.playerPasswordHashes[name] = hash
|
||||
return startNewSessionForRegisteredPlayer(named: name)
|
||||
}
|
||||
|
||||
/**
|
||||
Delete a player
|
||||
- Parameter name: The name of the player to delete.
|
||||
- Returns: The session token of the current player, if one exists
|
||||
*/
|
||||
func deletePlayer(named name: PlayerName) -> SessionToken? {
|
||||
playerPasswordHashes.removeValue(forKey: name)
|
||||
guard let sessionToken = sessionTokenForPlayer.removeValue(forKey: name) else {
|
||||
return nil
|
||||
}
|
||||
playerNameForToken.removeValue(forKey: sessionToken)
|
||||
return sessionToken
|
||||
}
|
||||
|
||||
func isValid(sessionToken token: SessionToken) -> Bool {
|
||||
playerNameForToken[token] != nil
|
||||
}
|
||||
|
||||
func sessionToken(forPlayer player: PlayerName) -> SessionToken? {
|
||||
sessionTokenForPlayer[player]
|
||||
}
|
||||
|
||||
/**
|
||||
Start a new session for an existing player.
|
||||
- Parameter name: The player 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 endSession(forSessionToken token: SessionToken) {
|
||||
guard let player = playerNameForToken.removeValue(forKey: token) else {
|
||||
return
|
||||
}
|
||||
sessionTokenForPlayer.removeValue(forKey: player)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the player for a session token.
|
||||
- Parameter token: The access token for the player
|
||||
- Returns: The name of the player, if it exists
|
||||
*/
|
||||
func registeredPlayerExists(withSessionToken token: SessionToken) -> PlayerName? {
|
||||
playerNameForToken[token]
|
||||
}
|
||||
|
||||
|
||||
}
|
75
Sources/App/Model/TableManagement.swift
Normal file
75
Sources/App/Model/TableManagement.swift
Normal file
@ -0,0 +1,75 @@
|
||||
import Foundation
|
||||
|
||||
final class TableManagement {
|
||||
|
||||
/// A list of table ids for public games
|
||||
private var publicTables = Set<TableId>()
|
||||
|
||||
/// A mapping from table id to table name (for all tables)
|
||||
private var tableNames = [TableId: TableName]()
|
||||
|
||||
/// A mapping from table id to participating players
|
||||
private var tablePlayers = [TableId: [PlayerName]]()
|
||||
|
||||
/// A reverse list of players and their table id
|
||||
private var playerTables = [PlayerName: TableId]()
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
func tableExists(withId id: TableId) -> Bool {
|
||||
tableNames[id] != nil
|
||||
}
|
||||
|
||||
func tableIsFull(withId id: TableId) -> Bool {
|
||||
(tablePlayers[id]?.count ?? playerPerTable) < playerPerTable
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new table with optional players.
|
||||
- Parameter name: The name of the table
|
||||
- Parameter players: The player creating the table
|
||||
- Parameter visible: Indicates that this is a game joinable by everyone
|
||||
- Returns: The table id
|
||||
*/
|
||||
func createTable(named name: TableName, player: PlayerName, visible: Bool) -> TableId {
|
||||
let tableId = TableId.newToken()
|
||||
|
||||
tableNames[tableId] = name
|
||||
tablePlayers[tableId] = [player]
|
||||
playerTables[player] = tableId
|
||||
|
||||
if visible {
|
||||
publicTables.insert(tableId)
|
||||
}
|
||||
return tableId
|
||||
}
|
||||
|
||||
func getPublicTableInfos() -> [TableInfo] {
|
||||
publicTables.map { tableId in
|
||||
TableInfo(id: tableId, name: tableNames[tableId]!, players: tablePlayers[tableId]!)
|
||||
}.sorted()
|
||||
}
|
||||
|
||||
/**
|
||||
Join a table.
|
||||
- Returns: The player names present at the table
|
||||
*/
|
||||
func join(tableId: TableId, player: PlayerName) -> [PlayerName] {
|
||||
guard var players = tablePlayers[tableId] else {
|
||||
return []
|
||||
}
|
||||
players.append(player)
|
||||
if let oldTable = playerTables[tableId] {
|
||||
remove(player: player, fromTable: oldTable)
|
||||
}
|
||||
tablePlayers[tableId] = players
|
||||
playerTables[tableId] = tableId
|
||||
return players
|
||||
}
|
||||
|
||||
func remove(player: PlayerName, fromTable tableId: TableId) {
|
||||
tablePlayers[tableId] = tablePlayers[tableId]?.filter { $0 != player }
|
||||
}
|
||||
}
|
@ -1,99 +1,230 @@
|
||||
import Vapor
|
||||
|
||||
/// The JSON encoder for responses
|
||||
private let encoder = JSONEncoder()
|
||||
|
||||
/// The maximum length of a valid player name
|
||||
private let maximumPlayerNameLength = 40
|
||||
|
||||
/// The maximum length of a valid password
|
||||
private let maximumPasswordLength = 40
|
||||
|
||||
func routes(_ app: Application) throws {
|
||||
app.get { req in
|
||||
return "It works!"
|
||||
}
|
||||
|
||||
app.post("create", "user", ":name", ":hash") { req -> String in
|
||||
// MARK: Players & Sessions
|
||||
|
||||
/**
|
||||
Create a new player
|
||||
- Parameter name: The name of the player, included in the url
|
||||
- Parameter password: The password of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing name or password
|
||||
- 406: Password or name too long
|
||||
- 409: A player with the same name already exists
|
||||
- 424: The password could not be hashed
|
||||
- Returns: The session token for the registered user
|
||||
*/
|
||||
app.post("player", "register", ":name") { req -> String in
|
||||
guard let name = req.parameters.get("name"),
|
||||
let hash = req.parameters.get("hash") else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
let digest = try req.password.hash(hash)
|
||||
let password = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
guard name.count < maximumPlayerNameLength,
|
||||
password.count < maximumPasswordLength else {
|
||||
throw Abort(.notAcceptable) // 406
|
||||
}
|
||||
|
||||
guard !database.has(user: name) else {
|
||||
throw Abort(.conflict)
|
||||
guard let hash = try? req.password.hash(password) else {
|
||||
throw Abort(.failedDependency) // 424
|
||||
}
|
||||
guard let token = database.registerPlayer(named: name, hash: hash) else {
|
||||
throw Abort(.conflict) // 409
|
||||
}
|
||||
let token = database.add(user: name, hash: digest)
|
||||
return token
|
||||
}
|
||||
|
||||
app.get("create", "session", ":name", ":hash") { req -> String in
|
||||
/**
|
||||
Delete a player.
|
||||
- Parameter name: The name of the player, included in the url
|
||||
- Parameter password: The password of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing name or password
|
||||
- 403: The password or user name is invalid
|
||||
- 424: The password could not be hashed
|
||||
- Returns: Nothing
|
||||
*/
|
||||
app.post("player", "delete", ":name") { req -> String in
|
||||
guard let name = req.parameters.get("name"),
|
||||
let hash = req.parameters.get("hash") else {
|
||||
throw Abort(.badRequest)
|
||||
let password = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
guard let hash = database.passwordHashForExistingPlayer(named: name) else {
|
||||
throw Abort(.forbidden) // 403
|
||||
}
|
||||
guard let digest = database.hash(ofUser: name),
|
||||
try req.password.verify(hash, created: digest) else {
|
||||
throw Abort(.forbidden)
|
||||
guard let isValid = try? req.password.verify(password, created: hash) else {
|
||||
throw Abort(.failedDependency) // 424
|
||||
}
|
||||
let token = database.startSession(forUser: name)
|
||||
guard isValid else {
|
||||
throw Abort(.forbidden) // 403
|
||||
}
|
||||
database.deletePlayer(named: name)
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
Log in as an existing player.
|
||||
- Parameter name: The name of the player, included in the url
|
||||
- Parameter password: The password of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing name or password
|
||||
- 403: The password or user name is invalid
|
||||
- 424: The password could not be hashed
|
||||
- Returns: The session token for the user
|
||||
*/
|
||||
app.post("player", "login", ":name") { req -> String in
|
||||
guard let name = req.parameters.get("name"),
|
||||
let password = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
guard let hash = database.passwordHashForExistingPlayer(named: name) else {
|
||||
throw Abort(.forbidden) // 403
|
||||
}
|
||||
guard let isValid = try? req.password.verify(password, created: hash) else {
|
||||
throw Abort(.failedDependency) // 424
|
||||
}
|
||||
guard isValid else {
|
||||
throw Abort(.forbidden) // 403
|
||||
}
|
||||
let token = database.startNewSessionForRegisteredPlayer(named: name)
|
||||
return token
|
||||
}
|
||||
|
||||
app.get("session", "resume", ":token") { req -> String in
|
||||
guard let token = req.parameters.get("token") else {
|
||||
throw Abort(.badRequest)
|
||||
/**
|
||||
Log in using a session token.
|
||||
- Parameter token: The session token of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing token
|
||||
- 401: The token is invalid
|
||||
- Returns: The player name associated with the session token
|
||||
*/
|
||||
app.post("player", "resume") { req -> String in
|
||||
guard let token = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
guard let user = database.user(forToken: token) else {
|
||||
throw Abort(.forbidden)
|
||||
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
||||
throw Abort(.unauthorized) // 401
|
||||
}
|
||||
return user
|
||||
return player
|
||||
}
|
||||
|
||||
// TODO: Improve token handling (it will be logged when included in url!)
|
||||
app.get("create", "table", ":visibility", ":name", ":token") { req -> String in
|
||||
guard let name = req.parameters.get("name"),
|
||||
let token = req.parameters.get("token"),
|
||||
let visibility = req.parameters.get("visibility") else {
|
||||
throw Abort(.badRequest)
|
||||
/**
|
||||
Log out.
|
||||
- Parameter name: The name of the player, included in the url
|
||||
- Parameter token: The session token of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing token
|
||||
- Returns: Nothing
|
||||
- Note: The request always succeeds when correctly formed, even for invalid and expired tokens
|
||||
*/
|
||||
app.post("player", "logout") { req -> String in
|
||||
guard let token = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
database.endSession(forSessionToken: token)
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
Start a new bidirectional session connection.
|
||||
- Returns: Nothing
|
||||
- Note: The first message over the connection must be a valid session token.
|
||||
*/
|
||||
app.webSocket("session", "start") { req, socket in
|
||||
socket.onText { socket, text in
|
||||
guard database.isValid(sessionToken: text) else {
|
||||
_ = socket.close()
|
||||
return
|
||||
}
|
||||
database.startSession(socket: socket, sessionToken: text)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Tables
|
||||
|
||||
/**
|
||||
Create a new table.
|
||||
- Parameter visibility: Indicate a `"public"` or `"private"` table
|
||||
- Parameter token: The session token of the player, as a string in the request body
|
||||
- Returns: The table id
|
||||
- Throws:
|
||||
- 400: Missing token, table name or invalid visibility
|
||||
- 401: The session token is invalid
|
||||
*/
|
||||
app.post("table", "create", ":visibility", ":name") { req -> String in
|
||||
guard let visibility = req.parameters.get("visibility"),
|
||||
let tableName = req.parameters.get("name"),
|
||||
let token = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
let isVisible: Bool
|
||||
if visibility == "private" {
|
||||
isVisible = false
|
||||
} else if visibility == "public" {
|
||||
isVisible = true
|
||||
} else {
|
||||
throw Abort(.badRequest)
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
|
||||
guard let user = database.user(forToken: token) else {
|
||||
throw Abort(.forbidden)
|
||||
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
||||
throw Abort(.unauthorized) // 401
|
||||
}
|
||||
guard !database.tableExists(named: name) else {
|
||||
throw Abort(.conflict)
|
||||
}
|
||||
let tableId = database.createTable(named: name, player: user, visible: isVisible)
|
||||
let tableId = database.createTable(named: tableName, player: player, visible: isVisible)
|
||||
return tableId
|
||||
}
|
||||
|
||||
app.get("tables", "public", ":token") { req -> String in
|
||||
guard let token = req.parameters.get("token") else {
|
||||
throw Abort(.badRequest)
|
||||
/**
|
||||
List the public tables.
|
||||
- Parameter token: The session token of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing token
|
||||
- 403: The session token is invalid
|
||||
- Returns: A JSON object with a list of public tables (id, name, player list)
|
||||
*/
|
||||
app.post("tables", "public") { req -> String in
|
||||
guard let token = req.body.string else {
|
||||
throw Abort(.badRequest) // 400
|
||||
}
|
||||
guard let _ = database.user(forToken: token) else {
|
||||
throw Abort(.forbidden)
|
||||
guard database.isValid(sessionToken: token) else {
|
||||
throw Abort(.forbidden) // 403
|
||||
}
|
||||
let list = database.getPublicTableInfos()
|
||||
return try encoder.encode(list).base64EncodedString()
|
||||
}
|
||||
|
||||
app.post("table", "join", ":table", ":token") { req -> String in
|
||||
/**
|
||||
Join a table.
|
||||
- Parameter table: The table id
|
||||
- Parameter token: The session token of the player, as a string in the request body
|
||||
- Throws:
|
||||
- 400: Missing token
|
||||
- 401: The session token is invalid
|
||||
- 404: The table id doesn't exist
|
||||
- 406: The table is already full and can't be joined
|
||||
- Returns: Nothing
|
||||
*/
|
||||
app.post("table", "join", ":table") { req -> String in
|
||||
guard let table = req.parameters.get("table"),
|
||||
let token = req.parameters.get("token") else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
guard let player = database.user(forToken: token) else {
|
||||
throw Abort(.forbidden)
|
||||
let token = req.body.string else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
||||
throw Abort(.unauthorized) // 401
|
||||
}
|
||||
guard database.tableExists(withId: table) else {
|
||||
throw Abort(.notFound)
|
||||
throw Abort(.notFound) // 404
|
||||
}
|
||||
guard !database.tableIsFull(withId: table) else {
|
||||
throw Abort(.notAcceptable)
|
||||
throw Abort(.notAcceptable) // 406
|
||||
}
|
||||
database.join(tableId: table, player: player)
|
||||
return ""
|
||||
|
Reference in New Issue
Block a user