diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index b40fe72..2255fbb 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -15,25 +15,46 @@ func encodeJSON(_ response: T) throws -> String where T: Encodable { } func routes(_ app: Application) throws { - - // 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 -> EventLoopFuture in - guard let name = req.parameters.get("name"), - let password = req.body.string else { - throw Abort(.badRequest) // 400 - } + registerPlayer(app) + requestPlayerPasswordReset(app) + resetPlayerPasswordWithEmailToken(app) + deletePlayer(app) + loginPlayer(app) + resumeSession(app) + logoutPlayer(app) + getTableForPlayer(app) + openWebsocket(app) + createTable(app) + getPublicTables(app) + joinTable(app) + leaveTable(app) + performActionForPlayer(app) + playCard(app) +} + +// MARK: Players & Sessions + +/** + Create a new player. + + Headers: + - `name`: The username of the player + - `password`: The password of the player + - `email`: Optional email address for password reset + + Possible responses: + - `200`: On success, with the session token for the registered user in the reponse body + - `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 + */ +func registerPlayer(_ app: Application) { + app.post("player", "register") { req -> EventLoopFuture in + let name = try req.header(.name) + let password = try req.header(.password) + let mail = req.optionalHeader(.email)?.trimmed.nonEmpty + guard name.count < maximumPlayerNameLength, password.count < maximumPasswordLength else { throw Abort(.notAcceptable) // 406 @@ -45,17 +66,19 @@ func routes(_ app: Application) throws { // Can throw conflict (409) return server.registerPlayer(named: name, hash: hash, in: req.db) } - - /** - 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 - */ +} +/** + 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 + */ +func deletePlayer(_ app: Application) { app.post("player", "delete", ":name") { req -> EventLoopFuture in guard let name = req.parameters.get("name"), let password = req.body.string else { @@ -68,7 +91,8 @@ func routes(_ app: Application) throws { server.deletePlayer(named: name, in: req.db) }.map { "" } } - +} + /** Log in as an existing player. - Parameter name: The name of the player, included in the url @@ -79,6 +103,7 @@ func routes(_ app: Application) throws { - 424: The password could not be hashed - Returns: The session token for the user */ +func loginPlayer(_ app: Application) { app.post("player", "login", ":name") { req -> EventLoopFuture in guard let name = req.parameters.get("name"), let password = req.body.string else { @@ -91,7 +116,8 @@ func routes(_ app: Application) throws { server.startNewSessionForRegisteredPlayer(named: name) } } - +} + /** Log in using a session token. - Parameter token: The session token of the player, as a string in the request body @@ -100,6 +126,7 @@ func routes(_ app: Application) throws { - 401: The token is invalid - Returns: The player name associated with the session token */ +func resumeSession(_ app: Application) { app.post("player", "resume") { req -> String in guard let token = req.body.string else { throw Abort(.badRequest) // 400 @@ -109,7 +136,8 @@ func routes(_ app: Application) throws { } return player } - +} + /** Log out. - Parameter name: The name of the player, included in the url @@ -119,6 +147,7 @@ func routes(_ app: Application) throws { - Returns: Nothing - Note: The request always succeeds when correctly formed, even for invalid and expired tokens */ +func logoutPlayer(_ app: Application) { app.post("player", "logout") { req -> String in guard let token = req.body.string else { throw Abort(.badRequest) // 400 @@ -126,7 +155,8 @@ func routes(_ app: Application) throws { server.endSession(forSessionToken: token) return "" } - +} + /** Get the current table of the player, if one exists. - Parameter token: The session token of the player, as a string in the request body @@ -135,6 +165,7 @@ func routes(_ app: Application) throws { - 401: Invalid token - Returns: The table info, or an empty string */ +func getTableForPlayer(_ app: Application) { app.post("player", "table") { req -> String in guard let token = req.body.string else { throw Abort(.badRequest) // 400 @@ -147,12 +178,14 @@ func routes(_ app: Application) throws { } return try encodeJSON(info) } - +} + /** Start a new websocket connection for the client to receive table updates from the server - Returns: Nothing - Note: The first (and only) message from the client over the connection must be a valid session token. */ +func openWebsocket(_ app: Application) { app.webSocket("session", "start") { req, socket in socket.onText { socket, text in guard server.startSession(socket: socket, sessionToken: text) else { @@ -161,9 +194,10 @@ func routes(_ app: Application) throws { } } } - +} + // MARK: Tables - + /** Create a new table. - Parameter visibility: Indicate a `"public"` or `"private"` table @@ -173,12 +207,13 @@ func routes(_ app: Application) throws { - 400: Missing token, table name or invalid visibility - 401: The session token is invalid */ +func createTable(_ app: Application) { app.post("table", "create", ":visibility", ":name") { req -> EventLoopFuture in guard let visibility = req.parameters.get("visibility"), let tableName = req.parameters.get("name"), let token = req.body.string else { - throw Abort(.badRequest) // 400 - } + throw Abort(.badRequest) // 400 + } let isPublic: Bool if visibility == "private" { isPublic = false @@ -194,7 +229,8 @@ func routes(_ app: Application) throws { return server.createTable(named: tableName, player: player, isPublic: isPublic, in: req.db) .flatMapThrowing(encodeJSON) } - +} + /** List the public tables. - Parameter token: The session token of the player, as a string in the request body @@ -203,6 +239,7 @@ func routes(_ app: Application) throws { - 403: The session token is invalid - Returns: A JSON object with a list of public tables (id, name, player list) */ +func getPublicTables(_ app: Application) { app.post("tables", "public") { req -> String in guard let token = req.body.string else { throw Abort(.badRequest) // 400 @@ -213,7 +250,8 @@ func routes(_ app: Application) throws { let list = server.getPublicTableInfos() return try encodeJSON(list) } - +} + /** Join a table. - Parameter table: The table id @@ -226,6 +264,8 @@ func routes(_ app: Application) throws { - 417: The table is already full and can't be joined - Returns: Nothing */ + +func joinTable(_ app: Application) { app.post("table", "join", ":table") { req -> EventLoopFuture in guard let string = req.parameters.get("table"), let table = UUID(uuidString: string), @@ -235,7 +275,8 @@ func routes(_ app: Application) throws { return server.join(tableId: table, playerToken: token, in: req.db) .flatMapThrowing(encodeJSON) } - +} + /** Leave the current table. - Parameter token: The session token of the player, as a string in the request body @@ -244,18 +285,21 @@ func routes(_ app: Application) throws { - 401: The session token is invalid - Returns: Nothing */ +func leaveTable(_ app: Application) { app.post("table", "leave") { req -> EventLoopFuture in guard let token = req.body.string else { throw Abort(.badRequest) } return server.leaveTable(playerToken: token, in: req.db).map { "" } } - +} + +func performActionForPlayer(_ app: Application) { app.post("player", "action", ":action") { req -> String in guard let token = req.body.string, let actionString = req.parameters.get("action") else { - throw Abort(.badRequest) - } + throw Abort(.badRequest) + } let result: PlayerActionResult if let action = PlayerAction(rawValue: actionString) { result = server.performAction(playerToken: token, action: action) @@ -279,7 +323,9 @@ func routes(_ app: Application) throws { throw Abort(.preconditionFailed) // 412 } } +} +func playCard(_ app: Application) { app.post("player", "card", ":card") { req -> String in guard let token = req.body.string, let cardId = req.parameters.get("card"),