diff --git a/Sources/VaporWallet/Extensions/EventLoopFuture+extensions.swift b/Sources/VaporWallet/Extensions/EventLoopFuture+extensions.swift deleted file mode 100644 index 7791246..0000000 --- a/Sources/VaporWallet/Extensions/EventLoopFuture+extensions.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// File.swift -// -// -// Created by Hadi Sharghi on 4/12/21. -// - -import NIO - -extension EventLoopFuture { - public func throwingFlatMap(_ transform: @escaping (Value) throws -> EventLoopFuture) -> EventLoopFuture { - flatMap { value in - do { - return try transform(value) - } catch { - return self.eventLoop.makeFailedFuture(error) - } - } - } -} diff --git a/Sources/VaporWallet/HasWallet.swift b/Sources/VaporWallet/HasWallet.swift index cbaf5e7..1a5f379 100644 --- a/Sources/VaporWallet/HasWallet.swift +++ b/Sources/VaporWallet/HasWallet.swift @@ -27,7 +27,7 @@ extension HasWallet { } extension Wallet { - public func refreshBalanceAsync(on db: Database) async throws -> Double { + public func refreshBalance(on db: Database) async throws -> Double { var balance: Int // Temporary workaround for sum and average aggregates on Postgres DB @@ -53,19 +53,5 @@ extension Wallet { return Double(self.balance) } - public func refreshBalance(on db: Database) -> EventLoopFuture { - // Temporary workaround for sum and average aggregates on Postgres DB - self.$transactions - .query(on: db) - .filter(\.$confirmed == true) - .sum(\.$amount) - .unwrap(orReplace: 0) - .flatMap { (balance) -> EventLoopFuture in - self.balance = balance - return self.update(on: db).map { - return Double(balance) - } - } - } } diff --git a/Sources/VaporWallet/Middlewares/WalletModelMiddleware.swift b/Sources/VaporWallet/Middlewares/WalletModelMiddleware.swift index 25bd78d..980728c 100644 --- a/Sources/VaporWallet/Middlewares/WalletModelMiddleware.swift +++ b/Sources/VaporWallet/Middlewares/WalletModelMiddleware.swift @@ -8,22 +8,9 @@ import Vapor import Fluent -// -public struct WalletMiddleware: ModelMiddleware { - - public init() {} - public func create(model: M, on db: Database, next: AnyModelResponder) -> EventLoopFuture { - - // Create `default` wallet when new model is created - return next.create(model, on: db).flatMap { - db.logger.log(level: .info, "default wallet for user \(model._$idKey) has been created") - return model.walletsRepository(on: db).create().transform(to: ()) - } - } -} -public struct AsyncWalletMiddleware: AsyncModelMiddleware { +public struct WalletMiddleware: AsyncModelMiddleware { public init() {} @@ -31,6 +18,6 @@ public struct AsyncWalletMiddleware: AsyncModelMiddleware { try await next.create(model, on: db) db.logger.log(level: .info, "default wallet for user \(model._$idKey) has been created") let repo = model.walletsRepository(on: db) - try await repo.createAsync() + try await repo.create() } } diff --git a/Sources/VaporWallet/Middlewares/WalletTransactionModelMiddleware.swift b/Sources/VaporWallet/Middlewares/WalletTransactionModelMiddleware.swift index 787ac0e..883be6c 100644 --- a/Sources/VaporWallet/Middlewares/WalletTransactionModelMiddleware.swift +++ b/Sources/VaporWallet/Middlewares/WalletTransactionModelMiddleware.swift @@ -8,29 +8,14 @@ import Vapor import Fluent - -public struct WalletTransactionMiddleware: ModelMiddleware { - - public init() {} - - public func create(model: WalletTransaction, on db: Database, next: AnyModelResponder) -> EventLoopFuture { - return next.create(model, on: db).flatMap { - return model - .$wallet.get(on: db) - .map { $0.refreshBalance(on: db) } - .transform(to: ()) - } - } -} - -public struct AsyncWalletTransactionMiddleware: AsyncModelMiddleware { +public struct WalletTransactionMiddleware: AsyncModelMiddleware { public init() {} public func create(model: WalletTransaction, on db: Database, next: AnyAsyncModelResponder) async throws { try await next.create(model, on: db) let wallet = try await model.$wallet.get(on: db) - _ = try await wallet.refreshBalanceAsync(on: db) + _ = try await wallet.refreshBalance(on: db) } } diff --git a/Sources/VaporWallet/Migrations/CreateWallet.swift b/Sources/VaporWallet/Migrations/CreateWallet.swift index 559d92e..4d99e46 100644 --- a/Sources/VaporWallet/Migrations/CreateWallet.swift +++ b/Sources/VaporWallet/Migrations/CreateWallet.swift @@ -1,40 +1,8 @@ import Fluent import SQLKit -public struct CreateWallet: Migration { - private var idKey: String - public init(foreignKeyColumnName idKey: String = "id") { - self.idKey = idKey - } - - public func prepare(on database: Database) -> EventLoopFuture { - return database.schema(Wallet.schema) - .id() - .field("name", .string, .required) - .field("owner_type", .string, .required) - .field("owner_id", .uuid, .required) - .field("min_allowed_balance", .int, .required) - .field("balance", .int, .required) - .field("decimal_places", .uint8, .required) - .field("created_at", .datetime, .required) - .field("updated_at", .datetime, .required) - .field("deleted_at", .datetime) - .create().flatMap { _ in - let sqlDB = (database as! SQLDatabase) - return sqlDB - .create(index: "type_idx") - .on(Wallet.schema) - .column("owner_type") - .run() - } - } - - public func revert(on database: Database) -> EventLoopFuture { - return database.schema(Wallet.schema).delete() - } -} -public struct CreateWalletAsync: AsyncMigration { +public struct CreateWallet: AsyncMigration { private var idKey: String public init(foreignKeyColumnName idKey: String = "id") { self.idKey = idKey diff --git a/Sources/VaporWallet/Migrations/CreateWalletTransaction.swift b/Sources/VaporWallet/Migrations/CreateWalletTransaction.swift index 5903436..e5b493a 100644 --- a/Sources/VaporWallet/Migrations/CreateWalletTransaction.swift +++ b/Sources/VaporWallet/Migrations/CreateWalletTransaction.swift @@ -1,39 +1,7 @@ import Fluent -public struct CreateWalletTransaction: Migration { - public init() { } - - public func prepare(on database: Database) -> EventLoopFuture { - return database.enum("transaction_type") - .case("deposit") - .case("withdraw") - .create().flatMap { transactionType in - return database.schema(WalletTransaction.schema) - .id() - .field("wallet_id", .uuid, .required, .references(Wallet.schema, "id", onDelete: .cascade)) - .field("transaction_type", transactionType, .required) - .field("amount", .int, .required) - .field("confirmed", .bool, .required) - .field("meta", .json) - .field("created_at", .datetime, .required) - .field("updated_at", .datetime, .required) - .create() - } - } - - public func revert(on database: Database) -> EventLoopFuture { - return database.schema(WalletTransaction.schema).delete() - .flatMap { _ in - return database.enum("transaction_type") - .deleteCase("deposit") - .deleteCase("withdraw") - .update() - .transform(to: ()) - } - } -} -public struct CreateWalletTransactionAsync: AsyncMigration { +public struct CreateWalletTransaction: AsyncMigration { public init() { } public func prepare(on database: Database) async throws { diff --git a/Sources/VaporWallet/Models/Entities/WalletTransaction.swift b/Sources/VaporWallet/Models/Entities/WalletTransaction.swift index 2cd4da1..a3236a8 100644 --- a/Sources/VaporWallet/Models/Entities/WalletTransaction.swift +++ b/Sources/VaporWallet/Models/Entities/WalletTransaction.swift @@ -73,12 +73,7 @@ extension WalletTransaction { return self.confirmed } - public func confirm(on db: Database) -> EventLoopFuture { - self.confirmed = true - return self.update(on: db) - } - - public func confirmAsync(on db: Database) async throws { + public func confirm(on db: Database) async throws { self.confirmed = true try await self.update(on: db) } diff --git a/Sources/VaporWallet/WalletRepository.swift b/Sources/VaporWallet/WalletRepository.swift index d8381bf..334e3a6 100644 --- a/Sources/VaporWallet/WalletRepository.swift +++ b/Sources/VaporWallet/WalletRepository.swift @@ -33,7 +33,7 @@ public class WalletsRepository { /// extension WalletsRepository { - public func createAsync(type name: WalletType = .default, decimalPlaces: UInt8 = 2, minAllowedBalance: Int = 0) async throws { + public func create(type name: WalletType = .default, decimalPlaces: UInt8 = 2, minAllowedBalance: Int = 0) async throws { let wallet: Wallet = Wallet(ownerType: self.type, ownerID: self.id, name: name.value, @@ -42,14 +42,14 @@ extension WalletsRepository { try await wallet.save(on: db) } - public func allAsync() async throws -> [Wallet] { + public func all() async throws -> [Wallet] { return try await Wallet .query(on: self.db) .filter(\.$owner == self.id) .all() } - public func getAsync(type name: WalletType, withTransactions: Bool = false) async throws -> Wallet { + public func get(type name: WalletType, withTransactions: Bool = false) async throws -> Wallet { var walletQuery = Wallet.query(on: db) .filter(\.$owner == self.id) .filter(\.$ownerType == self.type) @@ -66,12 +66,12 @@ extension WalletsRepository { return wallet } - public func defaultAsync(withTransactions: Bool = false) async throws -> Wallet { - return try await getAsync(type: .default, withTransactions: withTransactions) + public func `default`(withTransactions: Bool = false) async throws -> Wallet { + return try await get(type: .default, withTransactions: withTransactions) } - public func balanceAsync(type name: WalletType = .default, withUnconfirmed: Bool = false, asDecimal: Bool = false) async throws -> Double { - let wallet = try await getAsync(type: name) + public func balance(type name: WalletType = .default, withUnconfirmed: Bool = false, asDecimal: Bool = false) async throws -> Double { + let wallet = try await get(type: name) if withUnconfirmed { // (1) Temporary workaround for sum and average aggregates, var balance: Double @@ -93,59 +93,11 @@ extension WalletsRepository { return asDecimal ? Double(wallet.balance).toDecimal(with: wallet.decimalPlaces) : Double(wallet.balance) } - public func refreshBalanceAsync(of walletType: WalletType = .default) async throws -> Double { - let wallet = try await getAsync(type: walletType) - return try await wallet.refreshBalanceAsync(on: self.db) + public func refreshBalance(of walletType: WalletType = .default) async throws -> Double { + let wallet = try await get(type: walletType) + return try await wallet.refreshBalance(on: self.db) } - - - public func create(type name: WalletType = .default, decimalPlaces: UInt8 = 2) -> EventLoopFuture { - let wallet: Wallet = Wallet(ownerType: String(describing: self), ownerID: self.id, name: name.value, decimalPlaces: decimalPlaces) - return wallet.save(on: db) - } - - public func all() -> EventLoopFuture<[Wallet]> { - Wallet.query(on: self.db) - .filter(\.$owner == self.id) - .all() - } - - public func get(type name: WalletType) -> EventLoopFuture { - Wallet.query(on: db) - .filter(\.$owner == self.id) - .filter(\.$ownerType == self.type) - .filter(\.$name == name.value) - .first() - .unwrap(or: WalletError.walletNotFound(name: name.value)) - } - - public func `default`() -> EventLoopFuture { - get(type: .default) - } - - public func balance(type name: WalletType = .default, withUnconfirmed: Bool = false, asDecimal: Bool = false) -> EventLoopFuture { - if withUnconfirmed { - return get(type: name).flatMap { wallet in - wallet.$transactions - .query(on: self.db) - .sum(\.$amount) - .unwrap(orReplace: 0) - .map { (intBalance) -> Double in - return asDecimal ? Double(intBalance).toDecimal(with: wallet.decimalPlaces) : Double(intBalance) - } - } - } - return get(type: name).map { wallet in - return asDecimal ? Double(wallet.balance).toDecimal(with: wallet.decimalPlaces) : Double(wallet.balance) - } - } - - public func refreshBalance(of walletType: WalletType = .default) -> EventLoopFuture { - return get(type: walletType).flatMap { wallet -> EventLoopFuture in - wallet.refreshBalance(on: self.db) - } - } - + } @@ -154,118 +106,57 @@ extension WalletsRepository { /// extension WalletsRepository { - public func canWithdrawAsync(from: WalletType = .default, amount: Int) async throws -> Bool { - let wallet = try await getAsync(type: from) - return try await self._canWithdrawAsync(on: self.db, from: wallet, amount: amount) + public func canWithdraw(from: WalletType = .default, amount: Int) async throws -> Bool { + let wallet = try await get(type: from) + return try await self._canWithdraw(on: self.db, from: wallet, amount: amount) } - public func withdrawAsync(from: WalletType = .default, amount: Double, meta: [String: String]? = nil) async throws { - let wallet = try await getAsync(type: from) + public func withdraw(from: WalletType = .default, amount: Double, meta: [String: String]? = nil) async throws { + let wallet = try await get(type: from) let intAmount = Int(amount * pow(10, Double(wallet.decimalPlaces))) - guard try await canWithdrawAsync(from: from, amount: intAmount) else { + guard try await canWithdraw(from: from, amount: intAmount) else { throw WalletError.insufficientBalance } - try await self._withdrawAsync(on: self.db, from: wallet, amount: intAmount, meta: meta) + try await self._withdraw(on: self.db, from: wallet, amount: intAmount, meta: meta) } - public func withdrawAsync(from: WalletType = .default, amount: Int, meta: [String: String]? = nil) async throws { - guard try await canWithdrawAsync(from: from, amount: amount) else { + public func withdraw(from: WalletType = .default, amount: Int, meta: [String: String]? = nil) async throws { + guard try await canWithdraw(from: from, amount: amount) else { throw WalletError.insufficientBalance } - let wallet = try await getAsync(type: from) - try await self._withdrawAsync(on: self.db, from: wallet, amount: amount, meta: meta) + let wallet = try await get(type: from) + try await self._withdraw(on: self.db, from: wallet, amount: amount, meta: meta) } - public func depositAsync(to: WalletType = .default, amount: Double, confirmed: Bool = true, meta: [String: String]? = nil) async throws { - let wallet = try await getAsync(type: to) + public func deposit(to: WalletType = .default, amount: Double, confirmed: Bool = true, meta: [String: String]? = nil) async throws { + let wallet = try await get(type: to) let intAmount = Int(amount * pow(10, Double(wallet.decimalPlaces))) - try await self._depositAsync(on: self.db, to: wallet, amount: intAmount, confirmed: confirmed, meta: meta) + try await self._deposit(on: self.db, to: wallet, amount: intAmount, confirmed: confirmed, meta: meta) } - public func depositAsync(to: WalletType = .default, amount: Int, confirmed: Bool = true, meta: [String: String]? = nil) async throws { - let wallet = try await getAsync(type: to) - try await self._depositAsync(on: self.db, to: wallet, amount: amount, confirmed: confirmed, meta: meta) + public func deposit(to: WalletType = .default, amount: Int, confirmed: Bool = true, meta: [String: String]? = nil) async throws { + let wallet = try await get(type: to) + try await self._deposit(on: self.db, to: wallet, amount: amount, confirmed: confirmed, meta: meta) } - public func transferAsync(from: Wallet, to: Wallet, amount: Int, meta: [String: String]? = nil) async throws { - try await self._transferAsync(from: from, to: to, amount: amount, meta: meta) + public func transfer(from: Wallet, to: Wallet, amount: Int, meta: [String: String]? = nil) async throws { + try await self._transfer(from: from, to: to, amount: amount, meta: meta) } - public func transferAsync(from: WalletType, to: Wallet, amount: Int, meta: [String: String]? = nil) async throws { - let fromWallet = try await getAsync(type: from) - try await self._transferAsync(from: fromWallet, to: to, amount: amount, meta: meta) + public func transfer(from: WalletType, to: Wallet, amount: Int, meta: [String: String]? = nil) async throws { + let fromWallet = try await get(type: from) + try await self._transfer(from: fromWallet, to: to, amount: amount, meta: meta) } - public func transferAsync(from: WalletType, to: WalletType, amount: Int, meta: [String: String]? = nil) async throws { - let fromWallet = try await getAsync(type: from) - let toWallet = try await getAsync(type: to) - try await self._transferAsync(from: fromWallet, to: toWallet, amount: amount, meta: meta) + public func transfer(from: WalletType, to: WalletType, amount: Int, meta: [String: String]? = nil) async throws { + let fromWallet = try await get(type: from) + let toWallet = try await get(type: to) + try await self._transfer(from: fromWallet, to: toWallet, amount: amount, meta: meta) } - - - public func canWithdraw(from: WalletType = .default, amount: Int) -> EventLoopFuture { - get(type: from).flatMap { self._canWithdraw(from: $0, amount: amount) } - } - - public func withdraw(from: WalletType = .default, amount: Double, meta: [String: String]? = nil) -> EventLoopFuture { - get(type: from).flatMap { wallet -> EventLoopFuture in - let intAmount = Int(amount * pow(10, Double(wallet.decimalPlaces))) - return self._withdraw(on: self.db, from: wallet, amount: intAmount, meta: meta) - } - } - - public func withdraw(from: WalletType = .default, amount: Int, meta: [String: String]? = nil) -> EventLoopFuture { - - canWithdraw(from: from, amount: amount) - .guard({ $0 == true }, else: WalletError.insufficientBalance) - .flatMap { _ in - self.get(type: from).flatMap { wallet -> EventLoopFuture in - self._withdraw(on: self.db, from: wallet, amount: amount, meta: meta) - } - } - } - - public func deposit(to: WalletType = .default, amount: Double, confirmed: Bool = true, meta: [String: String]? = nil) -> EventLoopFuture { - get(type: to).flatMap { wallet -> EventLoopFuture in - let intAmount = Int(amount * pow(10, Double(wallet.decimalPlaces))) - return self._deposit(on: self.db, to: wallet, amount: intAmount, confirmed: confirmed, meta: meta) - } - } - - public func deposit(to: WalletType = .default, amount: Int, confirmed: Bool = true, meta: [String: String]? = nil) -> EventLoopFuture { - get(type: to).flatMap { wallet -> EventLoopFuture in - self._deposit(on: self.db, to: wallet, amount: amount, confirmed: confirmed, meta: meta) - } - } - - public func transafer(from: Wallet, to: Wallet, amount: Int, meta: [String: String]? = nil) -> EventLoopFuture { - return _canWithdraw(from: from, amount: amount) - .guard({ $0 == true }, else: WalletError.insufficientBalance) - .flatMap { _ in - self._transfer(from: from, to: to, amount: amount, meta: meta) - } - } - - public func transfer(from: WalletType, to: Wallet, amount: Int, meta: [String: String]? = nil) -> EventLoopFuture { - return get(type: from).flatMap { fromWallet -> EventLoopFuture in - self._canWithdraw(from: fromWallet, amount: amount) - .guard({ $0 == true }, else: WalletError.insufficientBalance) - .flatMap { _ in - return self._transfer(from: fromWallet, to: to, amount: amount, meta: meta) - } - } - } - - public func transafer(from: WalletType, to: WalletType, amount: Int, meta: [String: String]? = nil) -> EventLoopFuture { - return get(type: to).flatMap { toWallet -> EventLoopFuture in - self.transfer(from: from, to: toWallet, amount: amount, meta: meta) - } - } - } @@ -273,10 +164,10 @@ extension WalletsRepository { /// Accessing transactions of a wallet and confirming transactions /// extension WalletsRepository { - public func transactionsAsync(type name: WalletType = .default, + public func transactions(type name: WalletType = .default, paginate: PageRequest = .init(page: 1, per: 10), sortOrder: DatabaseQuery.Sort.Direction = .descending) async throws -> Page { - let wallet = try await self.getAsync(type: name) + let wallet = try await self.get(type: name) return try await wallet.$transactions .query(on: self.db) .sort(\.$createdAt, sortOrder) @@ -284,10 +175,10 @@ extension WalletsRepository { .paginate(paginate) } - public func unconfirmedTransactionsAsync(type name: WalletType = .default, + public func unconfirmedTransactions(type name: WalletType = .default, paginate: PageRequest = .init(page: 1, per: 10), sortOrder: DatabaseQuery.Sort.Direction = .descending) async throws -> Page { - let wallet = try await self.getAsync(type: name, withTransactions: true) + let wallet = try await self.get(type: name, withTransactions: true) return try await wallet.$transactions .query(on: self.db) .sort(\.$createdAt, sortOrder) @@ -296,90 +187,37 @@ extension WalletsRepository { } - public func confirmAllAsync(type name: WalletType = .default) async throws -> Double { - let wallet = try await self.getAsync(type: name, withTransactions: true) + public func confirmAll(type name: WalletType = .default) async throws -> Double { + let wallet = try await self.get(type: name, withTransactions: true) return try await self.db.transaction { database in try await wallet.$transactions .query(on: database) .set(\.$confirmed, to: true) .update() - return try await wallet.refreshBalanceAsync(on: database) + return try await wallet.refreshBalance(on: database) } } - public func confirmAsync(transaction: WalletTransaction, refresh: Bool = true) async throws -> Double { + public func confirm(transaction: WalletTransaction, refresh: Bool = true) async throws -> Double { transaction.confirmed = true return try await self.db.transaction { database in try await transaction.update(on: database) let wallet = try await transaction.$wallet.get(on: database) - return try await wallet.refreshBalanceAsync(on: database) + return try await wallet.refreshBalance(on: database) } } - - - - public func transactions(type name: WalletType = .default, - paginate: PageRequest = .init(page: 1, per: 10), - sortOrder: DatabaseQuery.Sort.Direction = .descending) -> EventLoopFuture> { - return self.get(type: name).flatMap { - $0.$transactions - .query(on: self.db) - .sort(\.$createdAt, sortOrder) - .filter(\.$confirmed == true) - .paginate(paginate) - } - } - - public func unconfirmedTransactions(type name: WalletType = .default, - paginate: PageRequest = .init(page: 1, per: 10), - sortOrder: DatabaseQuery.Sort.Direction = .descending) -> EventLoopFuture> { - return self.get(type: name).flatMap { - $0.$transactions - .query(on: self.db) - .sort(\.$createdAt, sortOrder) - .filter(\.$confirmed == false) - .paginate(paginate) - } - } - - public func confirmAll(type name: WalletType = .default) -> EventLoopFuture { - get(type: name).flatMap { (wallet) -> EventLoopFuture in - self.db.transaction { (database) -> EventLoopFuture in - wallet.$transactions - .query(on: database) - .set(\.$confirmed, to: true) - .update() - .flatMap { _ -> EventLoopFuture in - wallet.refreshBalance(on: database) - } - } - } - } - - - public func confirm(transaction: WalletTransaction, refresh: Bool = true) -> EventLoopFuture { - transaction.confirmed = true - return self.db.transaction { (database) -> EventLoopFuture in - transaction.update(on: database).flatMap { () -> EventLoopFuture in - transaction.$wallet.get(on: database).flatMap { wallet -> EventLoopFuture in - wallet.refreshBalance(on: database) - } - } - } - } - } /// /// Private methdos /// extension WalletsRepository { - private func _canWithdrawAsync(on db: Database, from: Wallet, amount: Int) async throws -> Bool { - return try await from.refreshBalanceAsync(on: db) - Double(amount) >= Double(from.minAllowedBalance) + private func _canWithdraw(on db: Database, from: Wallet, amount: Int) async throws -> Bool { + return try await from.refreshBalance(on: db) - Double(amount) >= Double(from.minAllowedBalance) } - private func _depositAsync(on db: Database, to: Wallet, amount: Int, confirmed: Bool = true, meta: [String: String]? = nil) async throws { + private func _deposit(on db: Database, to: Wallet, amount: Int, confirmed: Bool = true, meta: [String: String]? = nil) async throws { try await db.transaction { database in var walletTransaction: WalletTransaction do { @@ -391,7 +229,7 @@ extension WalletsRepository { } } - private func _withdrawAsync(on db: Database, from: Wallet, amount: Int, meta: [String: String]? = nil) async throws { + private func _withdraw(on db: Database, from: Wallet, amount: Int, meta: [String: String]? = nil) async throws { try await db.transaction { database in var walletTransaction: WalletTransaction do { @@ -403,57 +241,17 @@ extension WalletsRepository { } } - private func _transferAsync(from: Wallet, to: Wallet, amount: Int, meta: [String: String]? = nil) async throws { + private func _transfer(from: Wallet, to: Wallet, amount: Int, meta: [String: String]? = nil) async throws { try await self.db.transaction { database in - guard try await self._canWithdrawAsync(on: database, from: from, amount: amount) else { + guard try await self._canWithdraw(on: database, from: from, amount: amount) else { throw WalletError.insufficientBalance } - try await self._withdrawAsync(on: database, from: from, amount: amount, meta: meta) - try await self._depositAsync(on: database, to: to, amount: amount, meta: meta) - _ = try await from.refreshBalanceAsync(on: database) - _ = try await to.refreshBalanceAsync(on: database) + try await self._withdraw(on: database, from: from, amount: amount, meta: meta) + try await self._deposit(on: database, to: to, amount: amount, meta: meta) + _ = try await from.refreshBalance(on: database) + _ = try await to.refreshBalance(on: database) } } - private func _canWithdraw(from: Wallet, amount: Int) -> EventLoopFuture { - from.refreshBalance(on: self.db).map { $0 >= Double(amount) } - } - - private func _deposit(on db: Database, to: Wallet, amount: Int, confirmed: Bool = true, meta: [String: String]? = nil) -> EventLoopFuture { - return db.transaction { database -> EventLoopFuture in - do { - return WalletTransaction(walletID: try to.requireID(), transactionType: .deposit, amount: amount, confirmed: confirmed, meta: meta) - .save(on: database) - } catch { - return self.db.eventLoop.makeFailedFuture(WalletError.walletNotFound(name: to.name)) - } - } - } - - private func _withdraw(on db: Database, from: Wallet, amount: Int, meta: [String: String]? = nil) -> EventLoopFuture { - return db.transaction { database -> EventLoopFuture in - do { - return WalletTransaction(walletID: try from.requireID(), transactionType: .withdraw, amount: -1 * amount, meta: meta) - .save(on: database) - } catch { - return database.eventLoop.makeFailedFuture(WalletError.walletNotFound(name: from.name)) - } - } - } - - private func _transfer(from: Wallet, to: Wallet, amount: Int, meta: [String: String]? = nil) -> EventLoopFuture { - return self.db.transaction { (database) -> EventLoopFuture in - return self._withdraw(on: database, from: from, amount: amount, meta: meta).flatMap { _ -> EventLoopFuture in - self._deposit(on: database, to: to, amount: amount, meta: meta).flatMap { _ -> EventLoopFuture in - let refreshFrom = from.refreshBalance(on: database) - let refreshTo = to.refreshBalance(on: database) - return refreshFrom.and(refreshTo).flatMap { (_, _) -> EventLoopFuture in - database.eventLoop.makeSucceededFuture(()) - } - } - } - } - } - } diff --git a/Tests/VaporWalletTests/Model+Test.swift b/Tests/VaporWalletTests/Model+Test.swift index 23f1761..bbc209f 100644 --- a/Tests/VaporWalletTests/Model+Test.swift +++ b/Tests/VaporWalletTests/Model+Test.swift @@ -42,31 +42,19 @@ extension User: HasWallet { } -struct CreateUser: Migration { - func prepare(on database: Database) -> EventLoopFuture { - return database.schema(User.schema) + +struct CreateUser: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(User.schema) .id() .field("username", .string, .required) .create() } - func revert(on database: Database) -> EventLoopFuture { - return database.schema(User.schema).delete() + func revert(on database: Database) async throws { + try await database.schema(User.schema).delete() } } -// -//struct CreateUserAsync: AsyncMigration { -// func prepare(on database: Database) async throws { -// try await database.schema(User.schema) -// .id() -// .field("username", .string, .required) -// .create() -// } -// -// func revert(on database: Database) async throws { -// try await database.schema(User.schema).delete() -// } -//} @@ -103,31 +91,17 @@ extension Game: HasWallet { } -struct CreateGame: Migration { - func prepare(on database: Database) -> EventLoopFuture { - return database.schema(Game.schema) +struct CreateGame: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(Game.schema) .id() .field("name", .string, .required) .create() } - func revert(on database: Database) -> EventLoopFuture { - return database.schema(Game.schema).delete() + func revert(on database: Database) async throws { + try await database.schema(Game.schema).delete() } } -// -//struct CreateGameAsync: AsyncMigration { -// func prepare(on database: Database) async throws { -// try await database.schema(Game.schema) -// .id() -// .field("name", .string, .required) -// .create() -// } -// -// func revert(on database: Database) async throws { -// try await database.schema(Game.schema).delete() -// } -//} -// diff --git a/Tests/VaporWalletTests/VaporWalletTests.swift b/Tests/VaporWalletTests/VaporWalletTests.swift index d93fe1c..8c205c3 100644 --- a/Tests/VaporWalletTests/VaporWalletTests.swift +++ b/Tests/VaporWalletTests/VaporWalletTests.swift @@ -34,20 +34,20 @@ class VaporWalletTests: XCTestCase { } - func resetDB() throws { - let db = (app.db as! SQLDatabase) - let query = db.raw(""" - SELECT Concat('DELETE FROM ', TABLE_NAME, ';') as truncate_query FROM INFORMATION_SCHEMA.TABLES where `TABLE_SCHEMA` = 'vp-test' and `TABLE_NAME` not like '_fluent_%'; - """) - - return try query.all().flatMap { results in - return results.compactMap { row in - try? row.decode(column: "truncate_query", as: String.self) - }.map { query in - return (db as! MySQLDatabase).simpleQuery(query).transform(to: ()) - }.flatten(on: self.app.db.eventLoop) - }.wait() - } +// func resetDB() throws { +// let db = (app.db as! SQLDatabase) +// let query = db.raw(""" +// SELECT Concat('DELETE FROM ', TABLE_NAME, ';') as truncate_query FROM INFORMATION_SCHEMA.TABLES where `TABLE_SCHEMA` = 'vp-test' and `TABLE_NAME` not like '_fluent_%'; +// """) +// +// return try query.all().flatMap { results in +// return results.compactMap { row in +// try? row.decode(column: "truncate_query", as: String.self) +// }.map { query in +// return (db as! MySQLDatabase).simpleQuery(query).transform(to: ()) +// }.flatten(on: self.app.db.eventLoop) +// }.wait() +// } override func tearDown() { @@ -69,78 +69,78 @@ class VaporWalletTests: XCTestCase { func testUserHasNoDefaultWallet() async throws { let userWithNoWallet = try await User.create(on: app.db) - await XCTAssertThrowsError(try await userWithNoWallet.walletsRepository(on: app.db).defaultAsync(), "expected throw") { (error) in + await XCTAssertThrowsError(try await userWithNoWallet.walletsRepository(on: app.db).default(), "expected throw") { (error) in XCTAssertEqual(error as! WalletError, WalletError.walletNotFound(name: WalletType.default.value)) } } func testUserHasDefaultWallet() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) + app.databases.middleware.use(WalletMiddleware()) let userWithDefaultWallet = try await User.create(on: app.db) - let defaultWallet = try await userWithDefaultWallet.walletsRepository(on: app.db).defaultAsync() + let defaultWallet = try await userWithDefaultWallet.walletsRepository(on: app.db).default() XCTAssertEqual(defaultWallet.name, WalletType.default.value) } func testCreateWallet() async throws { let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.createAsync(type: .init(name: "savings")) + try await wallets.create(type: .init(name: "savings")) - let userWallets = try await wallets.allAsync() + let userWallets = try await wallets.all() XCTAssertEqual(userWallets.count, 1) XCTAssertEqual(userWallets.first?.name, "savings") } func testWalletDeposit() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) + app.databases.middleware.use(WalletMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try! await wallets.depositAsync(amount: 10) - let balance = try await wallets.balanceAsync() + try! await wallets.deposit(amount: 10) + let balance = try await wallets.balance() XCTAssertEqual(balance, 0) - let refreshedBalance = try await wallets.refreshBalanceAsync() + let refreshedBalance = try await wallets.refreshBalance() XCTAssertEqual(refreshedBalance, 10) } func testWalletTransactionMiddleware() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let user = try await User.create(on: app.db) let walletsRepoWithMiddleware = user.walletsRepository(on: app.db) - try! await walletsRepoWithMiddleware.depositAsync(amount: 40) + try! await walletsRepoWithMiddleware.deposit(amount: 40) - let balance = try await walletsRepoWithMiddleware.balanceAsync() + let balance = try await walletsRepoWithMiddleware.balance() XCTAssertEqual(balance, 40) } func testWalletWithdraw() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.depositAsync(amount: 100) + try await wallets.deposit(amount: 100) - var balance = try await wallets.balanceAsync() + var balance = try await wallets.balance() XCTAssertEqual(balance, 100) - await XCTAssertThrowsError(try await wallets.withdrawAsync(amount: 200), "expected throw") { (error) in + await XCTAssertThrowsError(try await wallets.withdraw(amount: 200), "expected throw") { (error) in XCTAssertEqual(error as! WalletError, WalletError.insufficientBalance) } - try! await wallets.withdrawAsync(amount: 50) + try! await wallets.withdraw(amount: 50) - balance = try await wallets.balanceAsync() + balance = try await wallets.balance() XCTAssertEqual(balance, 50) @@ -148,38 +148,38 @@ class VaporWalletTests: XCTestCase { func testWalletCanWithdraw() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.depositAsync(amount: 100) + try await wallets.deposit(amount: 100) - var can = try! await wallets.canWithdrawAsync(amount: 100) + var can = try! await wallets.canWithdraw(amount: 100) XCTAssertTrue(can) - can = try! await wallets.canWithdrawAsync(amount: 200) + can = try! await wallets.canWithdraw(amount: 200) XCTAssertFalse(can) } func testWalletCanWithdrawWithMinAllowedBalance() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.createAsync(type: .init(name: "magical"), minAllowedBalance: -50) + try await wallets.create(type: .init(name: "magical"), minAllowedBalance: -50) - try await wallets.depositAsync(to: .init(name: "magical"), amount: 100) - var can = try await wallets.canWithdrawAsync(from: .default, amount: 130) + try await wallets.deposit(to: .init(name: "magical"), amount: 100) + var can = try await wallets.canWithdraw(from: .default, amount: 130) XCTAssertFalse(can) - can = try await wallets.canWithdrawAsync(from: .init(name: "magical"), amount: 130) + can = try await wallets.canWithdraw(from: .init(name: "magical"), amount: 130) XCTAssertTrue(can) } func testMultiWallet() async throws { - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) @@ -187,20 +187,20 @@ class VaporWalletTests: XCTestCase { let myWallet = WalletType(name: "my-wallet") let notExistsWallet = WalletType(name: "not-exists") - try await wallets.createAsync(type: myWallet) - try await wallets.createAsync(type: savingsWallet) + try await wallets.create(type: myWallet) + try await wallets.create(type: savingsWallet) - try await wallets.depositAsync(to: myWallet, amount: 100) - try await wallets.depositAsync(to: savingsWallet, amount: 200) + try await wallets.deposit(to: myWallet, amount: 100) + try await wallets.deposit(to: savingsWallet, amount: 200) do { - try await wallets.depositAsync(to: notExistsWallet, amount: 1000) + try await wallets.deposit(to: notExistsWallet, amount: 1000) } catch { XCTAssertEqual(error as! WalletError, WalletError.walletNotFound(name: "not-exists")) } - let balance1 = try await wallets.balanceAsync(type: myWallet) - let balance2 = try await wallets.balanceAsync(type: savingsWallet) + let balance1 = try await wallets.balance(type: myWallet) + let balance2 = try await wallets.balance(type: savingsWallet) XCTAssertEqual(balance1, 100) XCTAssertEqual(balance2, 200) @@ -209,13 +209,13 @@ class VaporWalletTests: XCTestCase { func testTransactionMetadata() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) + app.databases.middleware.use(WalletMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.depositAsync(amount: 100, meta: ["description": "tax payments"]) + try await wallets.deposit(amount: 100, meta: ["description": "tax payments"]) - let transaction = try await wallets.defaultAsync() + let transaction = try await wallets.default() .$transactions.get(on: app.db) .first! @@ -223,96 +223,96 @@ class VaporWalletTests: XCTestCase { } func testWalletDecimalBalance() async throws { - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let user = try await User.create(on: app.db) let wallets = user.walletsRepository(on: app.db) - try await wallets.createAsync(type: .default, decimalPlaces: 2) + try await wallets.create(type: .default, decimalPlaces: 2) - try await wallets.depositAsync(amount: 100) + try await wallets.deposit(amount: 100) - var balance = try await wallets.balanceAsync() + var balance = try await wallets.balance() XCTAssertEqual(balance, 100) - try await wallets.depositAsync(amount: 1.45) - balance = try await wallets.balanceAsync() + try await wallets.deposit(amount: 1.45) + balance = try await wallets.balance() XCTAssertEqual(balance, 245) - balance = try await wallets.balanceAsync(asDecimal: true) + balance = try await wallets.balance(asDecimal: true) XCTAssertEqual(balance, 2.45) // decmial values will be truncated to wallet's decimalPlace value - try await wallets.depositAsync(amount: 1.555) - balance = try await wallets.balanceAsync() + try await wallets.deposit(amount: 1.555) + balance = try await wallets.balance() XCTAssertEqual(balance, 400) } func testConfirmTransaction() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.depositAsync(amount: 10, confirmed: true) + try await wallets.deposit(amount: 10, confirmed: true) sleep(1) - try await wallets.depositAsync(amount: 40, confirmed: false) + try await wallets.deposit(amount: 40, confirmed: false) - var balance = try await wallets.balanceAsync() - let unconfirmedBalance = try await wallets.balanceAsync(withUnconfirmed: true) + var balance = try await wallets.balance() + let unconfirmedBalance = try await wallets.balance(withUnconfirmed: true) XCTAssertEqual(balance, 10) XCTAssertEqual(unconfirmedBalance, 50) let transaction = try await wallets - .unconfirmedTransactionsAsync() + .unconfirmedTransactions() .items .first! - balance = try await wallets.confirmAsync(transaction: transaction) + balance = try await wallets.confirm(transaction: transaction) XCTAssertEqual(balance, 50) } func testConfirmAllTransactionsOfWallet() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) - try await wallets.depositAsync(amount: 10, confirmed: false) - try await wallets.depositAsync(amount: 40, confirmed: false) + try await wallets.deposit(amount: 10, confirmed: false) + try await wallets.deposit(amount: 40, confirmed: false) - var balance = try await wallets.balanceAsync() + var balance = try await wallets.balance() XCTAssertEqual(balance, 0) - balance = try await wallets.confirmAllAsync() + balance = try await wallets.confirmAll() XCTAssertEqual(balance, 50) } func testTransferBetweenAUsersWallets() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let (_, wallets) = try await setupUserAndWalletsRepo(on: app.db) let wallet1 = WalletType(name: "wallet1") let wallet2 = WalletType(name: "wallet2") - try await wallets.createAsync(type: wallet1) - try await wallets.createAsync(type: wallet2) + try await wallets.create(type: wallet1) + try await wallets.create(type: wallet2) - try await wallets.depositAsync(to: wallet1, amount: 100) + try await wallets.deposit(to: wallet1, amount: 100) - try await wallets.transferAsync(from: wallet1, to: wallet2, amount: 20) + try await wallets.transfer(from: wallet1, to: wallet2, amount: 20) - let balance1 = try await wallets.balanceAsync(type: wallet1) - let balance2 = try await wallets.balanceAsync(type: wallet2) + let balance1 = try await wallets.balance(type: wallet1) + let balance2 = try await wallets.balance(type: wallet2) XCTAssertEqual(balance1, 80) XCTAssertEqual(balance2, 20) @@ -320,8 +320,8 @@ class VaporWalletTests: XCTestCase { } func testTransferBetweenTwoUsersWallets() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let user1 = try await User.create(username: "user1", on: app.db) let user2 = try await User.create(username: "user2", on: app.db) @@ -329,31 +329,31 @@ class VaporWalletTests: XCTestCase { let repo1 = user1.walletsRepository(on: app.db) let repo2 = user2.walletsRepository(on: app.db) - try await repo1.depositAsync(amount: 100) + try await repo1.deposit(amount: 100) - try await repo1.transferAsync(from: try await repo1.defaultAsync(), to: try await repo2.defaultAsync(), amount: 20) + try await repo1.transfer(from: try await repo1.default(), to: try await repo2.default(), amount: 20) - var balance1 = try await repo1.balanceAsync() - var balance2 = try await repo2.balanceAsync() + var balance1 = try await repo1.balance() + var balance2 = try await repo2.balance() XCTAssertEqual(balance1, 80) XCTAssertEqual(balance2, 20) - try await repo1.transferAsync(from: .default, to: try await repo2.defaultAsync(), amount: 20) + try await repo1.transfer(from: .default, to: try await repo2.default(), amount: 20) - balance1 = try await repo1.balanceAsync() - balance2 = try await repo2.balanceAsync() + balance1 = try await repo1.balance() + balance2 = try await repo2.balance() XCTAssertEqual(balance1, 60) XCTAssertEqual(balance2, 40) let savings = WalletType(name: "savings") - try await repo1.createAsync(type: savings) + try await repo1.create(type: savings) - try await repo1.transferAsync(from: .default, to: savings, amount: 20) + try await repo1.transfer(from: .default, to: savings, amount: 20) - balance1 = try await repo1.balanceAsync() - balance2 = try await repo1.balanceAsync(type: savings) + balance1 = try await repo1.balance() + balance2 = try await repo1.balance(type: savings) XCTAssertEqual(balance1, 40) XCTAssertEqual(balance2, 20) @@ -362,9 +362,9 @@ class VaporWalletTests: XCTestCase { func testMultiModelWallet() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let user = try await User.create(username: "user1", on: app.db) let game = Game(id: user.id, name: "game1") @@ -373,11 +373,11 @@ class VaporWalletTests: XCTestCase { let repo1 = user.walletsRepository(on: app.db) let repo2 = game.walletsRepository(on: app.db) - try await repo1.depositAsync(amount: 100) - try await repo2.depositAsync(amount: 500) + try await repo1.deposit(amount: 100) + try await repo2.deposit(amount: 500) - let balance1 = try await repo1.balanceAsync() - let balance2 = try await repo2.balanceAsync() + let balance1 = try await repo1.balance() + let balance2 = try await repo2.balance() XCTAssertEqual(balance1, 100) XCTAssertEqual(balance2, 500) @@ -385,9 +385,9 @@ class VaporWalletTests: XCTestCase { } func testMultiModelWalletTransfer() async throws { - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletMiddleware()) - app.databases.middleware.use(AsyncWalletTransactionMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletMiddleware()) + app.databases.middleware.use(WalletTransactionMiddleware()) let user = try await User.create(username: "user1", on: app.db) let game = Game(id: user.id, name: "game1") @@ -396,16 +396,16 @@ class VaporWalletTests: XCTestCase { let repo1 = user.walletsRepository(on: app.db) let repo2 = game.walletsRepository(on: app.db) - try await repo1.depositAsync(amount: 100) - try await repo2.depositAsync(amount: 500) + try await repo1.deposit(amount: 100) + try await repo2.deposit(amount: 500) - let userWallet = try await repo1.defaultAsync() - let gameWallet = try await repo2.defaultAsync() + let userWallet = try await repo1.default() + let gameWallet = try await repo2.default() - try await repo1.transferAsync(from: gameWallet, to: userWallet, amount: 100) + try await repo1.transfer(from: gameWallet, to: userWallet, amount: 100) - let balance1 = try await repo1.balanceAsync() - let balance2 = try await repo2.balanceAsync() + let balance1 = try await repo1.balance() + let balance2 = try await repo2.balance() XCTAssertEqual(balance1, 200) XCTAssertEqual(balance2, 400) @@ -425,8 +425,8 @@ class VaporWalletTests: XCTestCase { // Initial Migrations app.migrations.add(CreateUser()) app.migrations.add(CreateGame()) - app.migrations.add(CreateWalletAsync()) - app.migrations.add(CreateWalletTransactionAsync()) + app.migrations.add(CreateWallet()) + app.migrations.add(CreateWalletTransaction()) } }