Convert functions to async/await
This commit is contained in:
parent
7ef5da08d0
commit
26adcc2868
18
Sources/App/Extensions/Optional+Extensions.swift
Normal file
18
Sources/App/Extensions/Optional+Extensions.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by CH on 12.10.22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Optional {
|
||||||
|
|
||||||
|
func unwrap(or error: @autoclosure () -> Error) throws -> Wrapped {
|
||||||
|
guard let unwrapped = self else {
|
||||||
|
throw error()
|
||||||
|
}
|
||||||
|
return unwrapped
|
||||||
|
}
|
||||||
|
}
|
@ -87,17 +87,14 @@ final class SQLiteDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func passwordHashForExistingPlayer(named name: PlayerName, in database: Database) -> EventLoopFuture<PasswordHash> {
|
/**
|
||||||
User.query(on: database).filter(\.$name == name).first()
|
Change the password of a user with a recovery token
|
||||||
.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 deletePlayer(named name: PlayerName, in database: Database) async throws {
|
||||||
|
let user = try await user(named: name, in: database)
|
||||||
|
try await tables.leaveTable(player: user, in: database)
|
||||||
|
try await User.query(on: database).filter(\.$name == name).delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValid(sessionToken token: SessionToken) -> Bool {
|
func isValid(sessionToken token: SessionToken) -> Bool {
|
||||||
@ -147,29 +144,25 @@ final class SQLiteDatabase {
|
|||||||
tables.tableInfo(player: player)
|
tables.tableInfo(player: player)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func points(for player: PlayerName, in database: Database) -> EventLoopFuture<Int> {
|
private func points(for player: PlayerName, in database: Database) async throws -> Int {
|
||||||
User.query(on: database)
|
try await User.query(on: database)
|
||||||
.filter(\.$name == player)
|
.filter(\.$name == player)
|
||||||
.first()
|
.first()
|
||||||
.unwrap(or: Abort(.notFound))
|
.unwrap(or: Abort(.notFound))
|
||||||
.map { $0.points }
|
.points
|
||||||
}
|
}
|
||||||
|
|
||||||
private func user(named name: PlayerName, in database: Database) -> EventLoopFuture<User> {
|
private func user(named name: PlayerName, in database: Database) async throws -> User {
|
||||||
User.query(on: database)
|
try await User
|
||||||
|
.query(on: database)
|
||||||
.filter(\.$name == name)
|
.filter(\.$name == name)
|
||||||
.first()
|
.first()
|
||||||
.unwrap(or: Abort(.notFound))
|
.unwrap(or: Abort(.notFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func user(withToken token: SessionToken, in database: Database) -> EventLoopFuture<User> {
|
private func user(withToken token: SessionToken, in database: Database) async throws -> User {
|
||||||
database.eventLoop
|
let name = try playerNameForToken[token].unwrap(or: Abort(.unauthorized))
|
||||||
.future()
|
return try await user(named: name, in: database)
|
||||||
.map { self.playerNameForToken[token] }
|
|
||||||
.unwrap(or: Abort(.unauthorized))
|
|
||||||
.flatMap { name in
|
|
||||||
self.user(named: name, in: database)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Tables
|
// MARK: Tables
|
||||||
@ -181,26 +174,23 @@ final class SQLiteDatabase {
|
|||||||
- Parameter isPublic: Indicates that this is a game joinable by everyone
|
- Parameter isPublic: Indicates that this is a game joinable by everyone
|
||||||
- Returns: The table id
|
- Returns: The table id
|
||||||
*/
|
*/
|
||||||
func createTable(named name: TableName, player: PlayerName, isPublic: Bool, in database: Database) -> EventLoopFuture<TableInfo> {
|
func createTable(named name: TableName, player: PlayerName, isPublic: Bool, in database: Database) async throws -> TableInfo {
|
||||||
user(named: player, in: database).flatMap { player in
|
let user = try await user(named: player, in: database)
|
||||||
self.tables.createTable(named: name, player: player, isPublic: isPublic, in: database)
|
return try await tables.createTable(named: name, player: user, isPublic: isPublic, in: database)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPublicTableInfos() -> [PublicTableInfo] {
|
func getPublicTableInfos() -> [PublicTableInfo] {
|
||||||
tables.publicTableList
|
tables.publicTableList
|
||||||
}
|
}
|
||||||
|
|
||||||
func join(tableId: UUID, playerToken: SessionToken, in database: Database) -> EventLoopFuture<TableInfo> {
|
func join(tableId: UUID, playerToken: SessionToken, in database: Database) async throws -> TableInfo {
|
||||||
user(withToken: playerToken, in: database).flatMap { player in
|
let user = try await user(withToken: playerToken, in: database)
|
||||||
self.tables.join(tableId: tableId, player: player, in: database)
|
return try await tables.join(tableId: tableId, player: user, in: database)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func leaveTable(playerToken: SessionToken, in database: Database) -> EventLoopFuture<Void> {
|
func leaveTable(playerToken: SessionToken, in database: Database) async throws {
|
||||||
user(withToken: playerToken, in: database).flatMap { player in
|
let user = try await user(withToken: playerToken, in: database)
|
||||||
self.tables.leaveTable(player: player, in: database)
|
try await tables.leaveTable(player: user, in: database)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func performAction(playerToken: SessionToken, action: PlayerAction) -> PlayerActionResult {
|
func performAction(playerToken: SessionToken, action: PlayerAction) -> PlayerActionResult {
|
||||||
|
@ -38,19 +38,14 @@ final class TableManagement {
|
|||||||
- Parameter isPublic: Indicates that this is a game joinable by everyone
|
- Parameter isPublic: Indicates that this is a game joinable by everyone
|
||||||
- Returns: The table id
|
- Returns: The table id
|
||||||
*/
|
*/
|
||||||
func createTable(named name: TableName, player: User, isPublic: Bool, in database: Database) -> EventLoopFuture<TableInfo> {
|
func createTable(named name: TableName, player: User, isPublic: Bool, in database: Database) async throws -> TableInfo {
|
||||||
let table = Table(name: name, isPublic: isPublic)
|
let table = Table(name: name, isPublic: isPublic)
|
||||||
return table.create(on: database).flatMap {
|
try await table.create(on: database)
|
||||||
player.$table.id = table.id
|
player.$table.id = table.id
|
||||||
return player.update(on: database)
|
try await player.update(on: database)
|
||||||
}.flatMap {
|
let waitingTable = WaitingTable(newTable: table)
|
||||||
Table.query(on: database).with(\.$players).filter(\.$id == table.id!).first()
|
self.tables[waitingTable.id] = waitingTable
|
||||||
}.unwrap(or: Abort(.notFound))
|
return waitingTable.tableInfo(forPlayer: player.name)
|
||||||
.map { storedTable in
|
|
||||||
let table = WaitingTable(newTable: storedTable)
|
|
||||||
self.tables[table.id] = table
|
|
||||||
return table.tableInfo(forPlayer: player.name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A list of all public tables
|
/// A list of all public tables
|
||||||
@ -77,8 +72,15 @@ final class TableManagement {
|
|||||||
- Parameter player: The name of the player who wants to join.
|
- Parameter player: The name of the player who wants to join.
|
||||||
- Returns: The result of the join operation
|
- Returns: The result of the join operation
|
||||||
*/
|
*/
|
||||||
func join(tableId: UUID, player: User, in database: Database) -> EventLoopFuture<TableInfo> {
|
func join(tableId: UUID, player: User, in database: Database) async throws -> TableInfo {
|
||||||
return database.eventLoop.future().flatMapThrowing { _ -> ManageableTable in
|
let table = try joinableTable(for: player, id: tableId)
|
||||||
|
player.$table.id = table.id
|
||||||
|
try await player.update(on: database)
|
||||||
|
table.sendUpdateToAllPlayers()
|
||||||
|
return table.tableInfo(forPlayer: player.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func joinableTable(for player: User, id tableId: UUID) throws -> ManageableTable {
|
||||||
if let existing = self.currentTable(for: player.name) {
|
if let existing = self.currentTable(for: player.name) {
|
||||||
guard existing.id == tableId else {
|
guard existing.id == tableId else {
|
||||||
throw Abort(.forbidden) // 403
|
throw Abort(.forbidden) // 403
|
||||||
@ -93,35 +95,28 @@ final class TableManagement {
|
|||||||
throw Abort(.expectationFailed) // 417
|
throw Abort(.expectationFailed) // 417
|
||||||
}
|
}
|
||||||
return joinableTable
|
return joinableTable
|
||||||
}.flatMap { table -> EventLoopFuture<ManageableTable> in
|
|
||||||
player.$table.id = table.id
|
|
||||||
return player.update(on: database).map { table }
|
|
||||||
}.map { table in
|
|
||||||
table.sendUpdateToAllPlayers()
|
|
||||||
return table.tableInfo(forPlayer: player.name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A player leaves the table it previously joined
|
A player leaves the table it previously joined
|
||||||
- Parameter player: The player leaving the table
|
- Parameter player: The player leaving the table
|
||||||
*/
|
*/
|
||||||
func leaveTable(player: User, in database: Database) -> EventLoopFuture<Void> {
|
func leaveTable(player: User, in database: Database) async throws {
|
||||||
guard let oldTable = currentTable(for: player.name) else {
|
guard let oldTable = currentTable(for: player.name) else {
|
||||||
return database.eventLoop.makeSucceededVoidFuture()
|
return
|
||||||
}
|
}
|
||||||
player.$table.id = nil
|
player.$table.id = nil
|
||||||
guard let table = WaitingTable(oldTable: oldTable, removing: player.name) else {
|
guard let table = WaitingTable(oldTable: oldTable, removing: player.name) else {
|
||||||
tables[oldTable.id] = nil
|
tables[oldTable.id] = nil
|
||||||
return player.update(on: database).flatMap {
|
try await player.update(on: database)
|
||||||
Table.query(on: database).filter(\.$id == oldTable.id).delete()
|
try await Table.query(on: database).filter(\.$id == oldTable.id).delete()
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
/// `player.canStartGame` is automatically set to false, because table is not full
|
/// `player.canStartGame` is automatically set to false, because table is not full
|
||||||
tables[table.id] = table
|
tables[table.id] = table
|
||||||
table.sendUpdateToAllPlayers()
|
table.sendUpdateToAllPlayers()
|
||||||
// TODO: Update points for all players
|
// TODO: Update points for all players
|
||||||
return player.update(on: database)
|
try await player.update(on: database)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(player: PlayerName, using socket: WebSocket) -> Bool {
|
func connect(player: PlayerName, using socket: WebSocket) -> Bool {
|
||||||
|
@ -48,13 +48,25 @@ final class PasswordReset: Model {
|
|||||||
@Field(.expiry)
|
@Field(.expiry)
|
||||||
var expiryDate: Date
|
var expiryDate: Date
|
||||||
|
|
||||||
init() { }
|
init() {
|
||||||
|
self.resetToken = .newToken()
|
||||||
|
self.expiryDate = Self.currentExpiryDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func renew() {
|
||||||
|
self.resetToken = .newToken()
|
||||||
|
self.expiryDate = Self.currentExpiryDate()
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new password reset.
|
/// Creates a new password reset.
|
||||||
init(id: UUID? = nil, user: User) {
|
init(id: UUID? = nil, user: User) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.user = user
|
self.$user.id = user.id!
|
||||||
self.resetToken = .newToken()
|
self.resetToken = .newToken()
|
||||||
self.expiryDate = Date().addingTimeInterval(15*60)
|
self.expiryDate = Self.currentExpiryDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func currentExpiryDate() -> Date {
|
||||||
|
Date().addingTimeInterval(15*60)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,17 +76,17 @@ func registerPlayer(_ app: Application) {
|
|||||||
- Returns: Nothing
|
- Returns: Nothing
|
||||||
*/
|
*/
|
||||||
func deletePlayer(_ app: Application) {
|
func deletePlayer(_ app: Application) {
|
||||||
app.post("player", "delete", ":name") { req -> EventLoopFuture<String> in
|
app.post("player", "delete", ":name") { request async throws -> HTTPResponseStatus in
|
||||||
guard let name = req.parameters.get("name"),
|
guard let name = request.parameters.get("name"),
|
||||||
let password = req.body.string else {
|
let password = request.body.string else {
|
||||||
throw Abort(.badRequest) // 400
|
return .badRequest // 400
|
||||||
}
|
}
|
||||||
return server.passwordHashForExistingPlayer(named: name, in: req.db)
|
let hash = try await server.passwordHashForExistingPlayer(named: name, in: request.db)
|
||||||
.guard({ hash in
|
guard try request.password.verify(password, created: hash) else {
|
||||||
(try? req.password.verify(password, created: hash)) ?? false
|
return .forbidden // 403
|
||||||
}, else: Abort(.forbidden)).flatMap { _ in
|
}
|
||||||
server.deletePlayer(named: name, in: req.db)
|
try await server.deletePlayer(named: name, in: request.db)
|
||||||
}.map { "" }
|
return .ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,17 +101,16 @@ func deletePlayer(_ app: Application) {
|
|||||||
- Returns: The session token for the user
|
- Returns: The session token for the user
|
||||||
*/
|
*/
|
||||||
func loginPlayer(_ app: Application) {
|
func loginPlayer(_ app: Application) {
|
||||||
app.post("player", "login", ":name") { req -> EventLoopFuture<String> in
|
app.post("player", "login", ":name") { request async throws -> String in
|
||||||
guard let name = req.parameters.get("name"),
|
guard let name = request.parameters.get("name"),
|
||||||
let password = req.body.string else {
|
let password = request.body.string else {
|
||||||
throw Abort(.badRequest) // 400
|
throw Abort(.badRequest) // 400
|
||||||
}
|
}
|
||||||
return server.passwordHashForExistingPlayer(named: name, in: req.db)
|
let hash = try await server.passwordHashForExistingPlayer(named: name, in: request.db)
|
||||||
.guard({ hash in
|
guard try request.password.verify(password, created: hash) else {
|
||||||
(try? req.password.verify(password, created: hash)) ?? false
|
throw Abort(.forbidden) // 403
|
||||||
}, else: Abort(.forbidden)).map { _ in
|
|
||||||
server.startNewSessionForRegisteredPlayer(named: name)
|
|
||||||
}
|
}
|
||||||
|
return server.startNewSessionForRegisteredPlayer(named: name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,10 +204,10 @@ func openWebsocket(_ app: Application) {
|
|||||||
- 401: The session token is invalid
|
- 401: The session token is invalid
|
||||||
*/
|
*/
|
||||||
func createTable(_ app: Application) {
|
func createTable(_ app: Application) {
|
||||||
app.post("table", "create", ":visibility", ":name") { req -> EventLoopFuture<String> in
|
app.post("table", "create", ":visibility", ":name") { request -> String in
|
||||||
guard let visibility = req.parameters.get("visibility"),
|
guard let visibility = request.parameters.get("visibility"),
|
||||||
let tableName = req.parameters.get("name"),
|
let tableName = request.parameters.get("name"),
|
||||||
let token = req.body.string else {
|
let token = request.body.string else {
|
||||||
throw Abort(.badRequest) // 400
|
throw Abort(.badRequest) // 400
|
||||||
}
|
}
|
||||||
let isPublic: Bool
|
let isPublic: Bool
|
||||||
@ -223,8 +222,8 @@ func createTable(_ app: Application) {
|
|||||||
guard let player = server.registeredPlayerExists(withSessionToken: token) else {
|
guard let player = server.registeredPlayerExists(withSessionToken: token) else {
|
||||||
throw Abort(.unauthorized) // 401
|
throw Abort(.unauthorized) // 401
|
||||||
}
|
}
|
||||||
return server.createTable(named: tableName, player: player, isPublic: isPublic, in: req.db)
|
let result = try await server.createTable(named: tableName, player: player, isPublic: isPublic, in: request.db)
|
||||||
.flatMapThrowing(encodeJSON)
|
return try encodeJSON(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,14 +262,14 @@ func getPublicTables(_ app: Application) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func joinTable(_ app: Application) {
|
func joinTable(_ app: Application) {
|
||||||
app.post("table", "join", ":table") { req -> EventLoopFuture<String> in
|
app.post("table", "join", ":table") { request -> String in
|
||||||
guard let string = req.parameters.get("table"),
|
guard let string = request.parameters.get("table"),
|
||||||
let table = UUID(uuidString: string),
|
let table = UUID(uuidString: string),
|
||||||
let token = req.body.string else {
|
let token = request.body.string else {
|
||||||
throw Abort(.badRequest)
|
throw Abort(.badRequest)
|
||||||
}
|
}
|
||||||
return server.join(tableId: table, playerToken: token, in: req.db)
|
let result = try await server.join(tableId: table, playerToken: token, in: request.db)
|
||||||
.flatMapThrowing(encodeJSON)
|
return try encodeJSON(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,11 +282,12 @@ func joinTable(_ app: Application) {
|
|||||||
- Returns: Nothing
|
- Returns: Nothing
|
||||||
*/
|
*/
|
||||||
func leaveTable(_ app: Application) {
|
func leaveTable(_ app: Application) {
|
||||||
app.post("table", "leave") { req -> EventLoopFuture<String> in
|
app.post("table", "leave") { request -> HTTPResponseStatus in
|
||||||
guard let token = req.body.string else {
|
guard let token = request.body.string else {
|
||||||
throw Abort(.badRequest)
|
throw Abort(.badRequest)
|
||||||
}
|
}
|
||||||
return server.leaveTable(playerToken: token, in: req.db).map { "" }
|
try await server.leaveTable(playerToken: token, in: request.db)
|
||||||
|
return .ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user