diff --git a/MMEX.xcodeproj/project.pbxproj b/MMEX.xcodeproj/project.pbxproj index ba11ff2b..da52c24f 100644 --- a/MMEX.xcodeproj/project.pbxproj +++ b/MMEX.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 929EF6632C9FF3ED0051A3E6 /* AssetRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6622C9FF3ED0051A3E6 /* AssetRepository.swift */; }; 929EF6652C9FF3FB0051A3E6 /* StockRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6642C9FF3FB0051A3E6 /* StockRepository.swift */; }; 929EF6672CA023EE0051A3E6 /* EnumCollateNoCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6662CA023EE0051A3E6 /* EnumCollateNoCase.swift */; }; + 929EF6692CA034770051A3E6 /* RepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6682CA034770051A3E6 /* RepositoryProtocol.swift */; }; + 929EF66B2CA03AF90051A3E6 /* ModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF66A2CA03AF90051A3E6 /* ModelProtocol.swift */; }; A3363EE32C9323A5004696C7 /* CategoryEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE22C9323A5004696C7 /* CategoryEditView.swift */; }; A3363EE52C9323D2004696C7 /* CategoryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE42C9323D2004696C7 /* CategoryDetailView.swift */; }; A3363EE72C93249D004696C7 /* CategoryAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE62C93249D004696C7 /* CategoryAddView.swift */; }; @@ -77,6 +79,8 @@ 929EF6622C9FF3ED0051A3E6 /* AssetRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetRepository.swift; sourceTree = ""; }; 929EF6642C9FF3FB0051A3E6 /* StockRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StockRepository.swift; sourceTree = ""; }; 929EF6662CA023EE0051A3E6 /* EnumCollateNoCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumCollateNoCase.swift; sourceTree = ""; }; + 929EF6682CA034770051A3E6 /* RepositoryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryProtocol.swift; sourceTree = ""; }; + 929EF66A2CA03AF90051A3E6 /* ModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelProtocol.swift; sourceTree = ""; }; A3363EE22C9323A5004696C7 /* CategoryEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryEditView.swift; sourceTree = ""; }; A3363EE42C9323D2004696C7 /* CategoryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDetailView.swift; sourceTree = ""; }; A3363EE62C93249D004696C7 /* CategoryAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryAddView.swift; sourceTree = ""; }; @@ -154,6 +158,8 @@ children = ( A3462F652C94854800F79145 /* ExportableEntity.swift */, 929EF6662CA023EE0051A3E6 /* EnumCollateNoCase.swift */, + 929EF66A2CA03AF90051A3E6 /* ModelProtocol.swift */, + 929EF6682CA034770051A3E6 /* RepositoryProtocol.swift */, ); path = Protocols; sourceTree = ""; @@ -316,10 +322,10 @@ children = ( A39B1B3E2C9A6714003E5562 /* Settings */, A39B1B3D2C9A66F7003E5562 /* Currencies */, + A39B1B392C9A668F003E5562 /* Accounts */, A39B1B3C2C9A66E6003E5562 /* Categories */, - A39B1B3B2C9A66CB003E5562 /* Transactions */, A39B1B3A2C9A66B8003E5562 /* Payees */, - A39B1B392C9A668F003E5562 /* Accounts */, + A39B1B3B2C9A66CB003E5562 /* Transactions */, A3C142A72C906E0A00D3CEC0 /* CustomNumberPadView.swift */, A3462F632C9426F500F79145 /* InsightsViewModel.swift */, A3C142AD2C9134DD00D3CEC0 /* InsightsView.swift */, @@ -435,6 +441,7 @@ A3C1429A2C8FF77D00D3CEC0 /* TransactionDetailView.swift in Sources */, A3C142942C8FE13E00D3CEC0 /* PayeeRepository.swift in Sources */, A3C1429E2C8FFCF100D3CEC0 /* TransactionAddView.swift in Sources */, + 929EF66B2CA03AF90051A3E6 /* ModelProtocol.swift in Sources */, A3C142922C8FE11700D3CEC0 /* AccountRepository.swift in Sources */, A3C1422B2C89751500D3CEC0 /* MMEXApp.swift in Sources */, A39B1B362C99A0C2003E5562 /* CurrencyEditView.swift in Sources */, @@ -456,6 +463,7 @@ A3363EE32C9323A5004696C7 /* CategoryEditView.swift in Sources */, A3C142652C8ED8EA00D3CEC0 /* AccountEditView.swift in Sources */, A3C142632C8ED8C000D3CEC0 /* AccountAddView.swift in Sources */, + 929EF6692CA034770051A3E6 /* RepositoryProtocol.swift in Sources */, A3C142A22C90267F00D3CEC0 /* CategoryRepository.swift in Sources */, A37E7D862C9AC2D000B4ECFC /* AboutView.swift in Sources */, A3363EE92C9326A1004696C7 /* CategoryListView.swift in Sources */, diff --git a/MMEX/Models/Account.swift b/MMEX/Models/Account.swift index d28d7ec1..9acd3361 100644 --- a/MMEX/Models/Account.swift +++ b/MMEX/Models/Account.swift @@ -91,6 +91,14 @@ extension Account { ) } } +extension Account: ModelProtocol { + static let modelName = "Account" + + func shortDesc() -> String { + "\(self.name)" + } +} + extension Account { static let accountTypeToSFSymbol: [String: String] = [ "Cash" : "dollarsign.circle.fill", diff --git a/MMEX/Models/Asset.swift b/MMEX/Models/Asset.swift index a7424ea5..c12af451 100644 --- a/MMEX/Models/Asset.swift +++ b/MMEX/Models/Asset.swift @@ -74,6 +74,14 @@ struct Asset: ExportableEntity { } } +extension Asset: ModelProtocol { + static let modelName = "Asset" + + func shortDesc() -> String { + "\(self.name), \(self.id)" + } +} + extension Asset { static let sampleData: [Asset] = [ Asset( diff --git a/MMEX/Models/Category.swift b/MMEX/Models/Category.swift index d9bb6599..b2a120e3 100644 --- a/MMEX/Models/Category.swift +++ b/MMEX/Models/Category.swift @@ -16,7 +16,6 @@ struct Category: ExportableEntity { } extension Category { - // empty category static var empty : Category { Category( id: 1, name: "cateogry name", active: true, parentId: nil ) } @@ -26,6 +25,14 @@ extension Category { } } +extension Category: ModelProtocol { + static let modelName = "Category" + + func shortDesc() -> String { + "\(self.name), \(self.id)" + } +} + extension Category { static let categoryToSFSymbol: [String: String] = [ "Unknown": "camera.metering.unknown", diff --git a/MMEX/Models/Currency.swift b/MMEX/Models/Currency.swift index 5549d7b2..3f883d42 100644 --- a/MMEX/Models/Currency.swift +++ b/MMEX/Models/Currency.swift @@ -24,7 +24,6 @@ struct Currency: ExportableEntity { } extension Currency { - // empty currency static var empty: Currency { Currency( id: 0, name: "", prefixSymbol: nil, suffixSymbol: nil, decimalPoint: nil, groupSeparator: nil, unitName: nil, centName: nil, @@ -32,6 +31,14 @@ extension Currency { ) } } +extension Currency: ModelProtocol { + static let modelName = "Currency" + + func shortDesc() -> String { + "\(self.name)" + } +} + extension Currency { /// A `NumberFormatter` configured specifically for the currency. var formatter: NumberFormatter { diff --git a/MMEX/Models/Infotable.swift b/MMEX/Models/Infotable.swift index 0a7596c4..7eb80191 100644 --- a/MMEX/Models/Infotable.swift +++ b/MMEX/Models/Infotable.swift @@ -52,6 +52,14 @@ extension Infotable { ) } } +extension Infotable: ModelProtocol { + static let modelName = "Infotable" + + func shortDesc() -> String { + "\(self.name)" + } +} + extension Infotable { static let sampleData: [Infotable] = [ Infotable(id: 1, name: "DATAVERSION", value: "3"), diff --git a/MMEX/Models/Payee.swift b/MMEX/Models/Payee.swift index 55f821d2..bf15633a 100644 --- a/MMEX/Models/Payee.swift +++ b/MMEX/Models/Payee.swift @@ -34,10 +34,17 @@ struct Payee: ExportableEntity { } extension Payee { - // empty payee static var empty: Payee { Payee(id: 0, name: "", categoryId: 0) } } +extension Payee: ModelProtocol { + static let modelName = "Payee" + + func shortDesc() -> String { + "\(self.name)" + } +} + extension Payee { static let sampleData: [Payee] = [ Payee( diff --git a/MMEX/Models/Stock.swift b/MMEX/Models/Stock.swift index cc50ba99..390c68cb 100644 --- a/MMEX/Models/Stock.swift +++ b/MMEX/Models/Stock.swift @@ -48,6 +48,14 @@ struct Stock: ExportableEntity { } } +extension Stock: ModelProtocol { + static let modelName = "Stock" + + func shortDesc() -> String { + "\(self.name), \(self.id)" + } +} + extension Stock { static let sampleData: [Stock] = [ Stock( diff --git a/MMEX/Models/Transaction.swift b/MMEX/Models/Transaction.swift index 31c92320..b07375f0 100644 --- a/MMEX/Models/Transaction.swift +++ b/MMEX/Models/Transaction.swift @@ -53,14 +53,14 @@ struct Transaction: ExportableEntity { var deletedTime: String? var followUpId: Int64? var toTransAmount: Double? - var color: Int64 + var color: Int64? init( id: Int64, accountId: Int64, toAccountId: Int64? = nil, payeeId: Int64, transCode: Transcode, transAmount: Double, status: TransactionStatus, transactionNumber: String? = nil, notes: String? = nil, categId: Int64? = nil, transDate: String?, lastUpdatedTime: String? = nil, deletedTime: String? = nil, - followUpId: Int64? = nil, toTransAmount: Double? = nil, color: Int64 = -1 + followUpId: Int64? = nil, toTransAmount: Double? = nil, color: Int64? = nil ) { self.id = id self.accountId = accountId @@ -89,6 +89,14 @@ extension Transaction { ) } } +extension Transaction: ModelProtocol { + static let modelName = "Transaction" + + func shortDesc() -> String { + "\(self.id)" + } +} + extension Transaction { var day: String { // Extract the date portion (ignoring the time) from ISO-8601 string diff --git a/MMEX/Protocols/EnumCollateNoCase.swift b/MMEX/Protocols/EnumCollateNoCase.swift index 5958058e..2cf24c31 100644 --- a/MMEX/Protocols/EnumCollateNoCase.swift +++ b/MMEX/Protocols/EnumCollateNoCase.swift @@ -1,5 +1,5 @@ // -// Asset.swift +// EnumCollateNoCase.swift // MMEX // // Created 2024-09-22 by George Ef (george.a.ef@gmail.com) diff --git a/MMEX/Protocols/ModelProtocol.swift b/MMEX/Protocols/ModelProtocol.swift new file mode 100644 index 00000000..b1450451 --- /dev/null +++ b/MMEX/Protocols/ModelProtocol.swift @@ -0,0 +1,15 @@ +// +// ModelProtocal.swift +// MMEX +// +// Created 2024-09-22 by George Ef (george.a.ef@gmail.com) +// + +import Foundation + +protocol ModelProtocol { + static var modelName: String { get } + + var id: Int64 { get set } + func shortDesc() -> String +} diff --git a/MMEX/Protocols/RepositoryProtocol.swift b/MMEX/Protocols/RepositoryProtocol.swift new file mode 100644 index 00000000..febe8a2c --- /dev/null +++ b/MMEX/Protocols/RepositoryProtocol.swift @@ -0,0 +1,102 @@ +// +// RepositoryProtocaol.swift +// MMEX +// +// Created 2024-09-22 by George Ef (george.a.ef@gmail.com) +// + +import Foundation +import SQLite + +protocol RepositoryProtocol { + associatedtype RepositoryItem: ModelProtocol + + var db: Connection? { get } + + static var repositoryName: String { get } + static var repositoryTable: SQLite.Table { get } + static func selectQuery(from table: SQLite.Table) -> SQLite.Table + static func selectResult(_ row: SQLite.Row) -> RepositoryItem + static var col_id: SQLite.Expression { get } + static func itemSetters(_ item: RepositoryItem) -> [SQLite.Setter] +} + +extension RepositoryProtocol { + func pluck(table: SQLite.Table, key: String) -> RepositoryItem? { + guard let db else { return nil } + do { + if let row = try db.pluck(Self.selectQuery(from: table)) { + let item = Self.selectResult(row) + print("Successfull search for \(key) in \(Self.repositoryName): \(item.shortDesc())") + return item + } + else { + print("Unsuccefull search for \(key) in \(Self.repositoryName)") + return nil + } + } catch { + print("Failed search for \(key) in \(Self.repositoryName): \(error)") + return nil + } + } + + func select(table: SQLite.Table) -> [RepositoryItem] { + guard let db else { return [] } + do { + var results: [RepositoryItem] = [] + for row in try db.prepare(Self.selectQuery(from: table)) { + results.append(Self.selectResult(row)) + } + print("Successfull select in \(Self.repositoryName): \(results.count)") + return results + } catch { + print("Failed select in \(Self.repositoryName): \(error)") + return [] + } + } + + func insert(_ item: inout RepositoryItem) -> Bool { + guard let db else { return false } + do { + let query = Self.repositoryTable + .insert(Self.itemSetters(item)) + let rowid = try db.run(query) + item.id = rowid + print("Successfull insert in \(RepositoryItem.modelName): \(item.shortDesc())") + return true + } catch { + print("Failed insert in \(RepositoryItem.modelName): \(error)") + return false + } + } + + func update(_ item: RepositoryItem) -> Bool { + guard let db else { return false } + do { + let query = Self.repositoryTable + .filter(Self.col_id == item.id) + .update(Self.itemSetters(item)) + try db.run(query) + print("Successfull update in \(RepositoryItem.modelName): \(item.shortDesc())") + return true + } catch { + print("Failed update in \(RepositoryItem.modelName): \(error)") + return false + } + } + + func delete(_ item: RepositoryItem) -> Bool { + guard let db else { return false } + do { + let query = Self.repositoryTable + .filter(Self.col_id == item.id) + .delete() + try db.run(query) + print("Successfull delete in \(RepositoryItem.modelName): \(item.shortDesc())") + return true + } catch { + print("Failed delete in \(RepositoryItem.modelName): \(error)") + return false + } + } +} diff --git a/MMEX/Repositories/AccountRepository.swift b/MMEX/Repositories/AccountRepository.swift index e1396aca..7e019298 100644 --- a/MMEX/Repositories/AccountRepository.swift +++ b/MMEX/Repositories/AccountRepository.swift @@ -8,17 +8,16 @@ import Foundation import SQLite -class AccountRepository { - let db: Connection? +class AccountRepository: RepositoryProtocol { + typealias RepositoryItem = Account + let db: Connection? init(db: Connection?) { self.db = db } -} -extension AccountRepository { - // table query - static let table = SQLite.Table("ACCOUNTLIST_V1") + static let repositoryName = "ACCOUNTLIST_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ----------------+---------+------ @@ -44,7 +43,7 @@ extension AccountRepository { // PAYMENTDUEDATE | TEXT | // MINIMUMPAYMENT | NUMERIC | - // table columns + // columns static let col_id = SQLite.Expression("ACCOUNTID") static let col_name = SQLite.Expression("ACCOUNTNAME") static let col_type = SQLite.Expression("ACCOUNTTYPE") @@ -73,36 +72,34 @@ extension AccountRepository { static let cast_creditLimit = cast(col_creditLimit) as SQLite.Expression static let cast_interestRate = cast(col_interestRate) as SQLite.Expression static let cast_minimumPayment = cast(col_minimumPayment) as SQLite.Expression -} -extension AccountRepository { - // select query - static let selectQuery = table.select( - col_id, - col_name, - col_type, - col_num, - col_status, - col_notes, - col_heldAt, - col_website, - col_contactInfo, - col_accessInfo, - col_initialDate, - cast_initialBal, - col_favoriteAcct, - col_currencyId, - col_statementLocked, - col_statementDate, - cast_minimumBalance, - cast_creditLimit, - cast_interestRate, - col_paymentDueDate, - cast_minimumPayment - ) - - // select result - static func selectResult(_ row: Row) -> Account { + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_name, + col_type, + col_num, + col_status, + col_notes, + col_heldAt, + col_website, + col_contactInfo, + col_accessInfo, + col_initialDate, + cast_initialBal, + col_favoriteAcct, + col_currencyId, + col_statementLocked, + col_statementDate, + cast_minimumBalance, + cast_creditLimit, + cast_interestRate, + col_paymentDueDate, + cast_minimumPayment + ) + } + + static func selectResult(_ row: SQLite.Row) -> Account { return Account( id : row[col_id], name : row[col_name], @@ -129,7 +126,7 @@ extension AccountRepository { ) } - static func insertSetters(_ account: Account) -> [Setter] { + static func itemSetters(_ account: Account) -> [SQLite.Setter] { return [ col_name <- account.name, col_type <- account.type.id, @@ -153,47 +150,21 @@ extension AccountRepository { col_minimumPayment <- account.minimumPayment ] } - - // insert query - static func insertQuery(_ account: Account) -> SQLite.Insert { - return table.insert(insertSetters(account)) - } - - // update query - static func updateQuery(_ account: Account) -> SQLite.Update { - return table.filter(col_id == account.id).update(insertSetters(account)) - } - - // delete query - static func deleteQuery(_ account: Account) -> SQLite.Delete { - return table.filter(col_id == account.id).delete() - } } extension AccountRepository { - func loadAccounts() -> [Account] { - guard let db else { return [] } - do { - var accounts: [Account] = [] - for row in try db.prepare(AccountRepository.selectQuery - .order(AccountRepository.col_name) - ) { - accounts.append(AccountRepository.selectResult(row)) - } - print("Successfully loaded accountss: \(accounts.count)") - return accounts - } catch { - print("Error loading accounts: \(error)") - return [] - } + func load() -> [Account] { + return select(table: Self.repositoryTable + .order(Self.col_name) + ) } - func loadAccountsWithCurrency() -> [Account] { + func loadWithCurrency() -> [Account] { // TODO guard let db else {return []} - var accounts = loadAccounts(); - let currencies = CurrencyRepository(db: db).loadCurrencies(); + var accounts = load(); + let currencies = CurrencyRepository(db: db).load(); // Create a lookup dictionary for currencies by currencyId let currencyDictionary = Dictionary(uniqueKeysWithValues: currencies.map { ($0.id, $0) }) @@ -206,41 +177,4 @@ extension AccountRepository { return accounts } - - func addAccount(account: inout Account) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(AccountRepository.insertQuery(account)) - account.id = rowid - print("Successfully added account: \(account.name), \(account.id)") - return true - } catch { - print("Failed to add account: \(error)") - return false - } - } - - func updateAccount(account: Account) -> Bool { - guard let db else { return false } - do { - try db.run(AccountRepository.updateQuery(account)) - print("Successfully updated account: \(account.name)") - return true - } catch { - print("Failed to update account: \(error)") - return false - } - } - - func deleteAccount(account: Account) -> Bool { - guard let db else { return false } - do { - try db.run(AccountRepository.deleteQuery(account)) - print("Successfully deleted account: \(account.name)") - return true - } catch { - print("Failed to delete account: \(error)") - return false - } - } } diff --git a/MMEX/Repositories/AssetRepository.swift b/MMEX/Repositories/AssetRepository.swift index e794d327..09a6b6aa 100644 --- a/MMEX/Repositories/AssetRepository.swift +++ b/MMEX/Repositories/AssetRepository.swift @@ -8,17 +8,16 @@ import Foundation import SQLite -class AssetRepository { - let db: Connection? +class AssetRepository: RepositoryProtocol { + typealias RepositoryItem = Asset + let db: Connection? init(db: Connection?) { self.db = db } -} -extension AssetRepository { - // table query - static let table = SQLite.Table("ASSETS_V1") + static let repositoryName = "ASSETS_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ----------------+---------+------ @@ -33,8 +32,8 @@ extension AssetRepository { // VALUECHANGEMODE | TEXT | (Percentage, Linear) // VALUECHANGERATE | NUMERIC | // NOTES | TEXT | - - // table columns + + // columns static let col_id = SQLite.Expression("ASSETID") static let col_type = SQLite.Expression("ASSETTYPE") static let col_status = SQLite.Expression("ASSETSTATUS") @@ -46,30 +45,28 @@ extension AssetRepository { static let col_changeMode = SQLite.Expression("VALUECHANGEMODE") static let col_changeRate = SQLite.Expression("VALUECHANGERATE") static let col_notes = SQLite.Expression("NOTES") - + // cast NUMERIC to REAL static let cast_value = cast(col_value) as SQLite.Expression static let cast_changeRate = cast(col_changeRate) as SQLite.Expression -} -extension AssetRepository { - // select query - static let selectQuery = table.select( - col_id, - col_type, - col_status, - col_name, - col_startDate, - col_currencyId, - cast_value, - col_change, - col_changeMode, - cast_changeRate, - col_notes - ) + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_type, + col_status, + col_name, + col_startDate, + col_currencyId, + cast_value, + col_change, + col_changeMode, + cast_changeRate, + col_notes + ) + } - // select result - static func selectResult(_ row: Row) -> Asset { + static func selectResult(_ row: SQLite.Row) -> Asset { return Asset( id : row[col_id], type : AssetType(collateNoCase: row[col_type]), @@ -85,7 +82,7 @@ extension AssetRepository { ) } - static func insertSetters(_ asset: Asset) -> [Setter] { + static func itemSetters(_ asset: Asset) -> [SQLite.Setter] { return [ col_id <- asset.id, col_type <- asset.type.map { $0.name }, @@ -100,75 +97,10 @@ extension AssetRepository { col_notes <- asset.notes ] } - - // insert query - static func insertQuery(_ asset: Asset) -> SQLite.Insert { - return table.insert(insertSetters(asset)) - } - - // update query - static func updateQuery(_ asset: Asset) -> SQLite.Update { - return table.filter(col_id == asset.id).update(insertSetters(asset)) - } - - // delete query - static func deleteQuery(_ asset: Asset) -> SQLite.Delete { - return table.filter(col_id == asset.id).delete() - } } extension AssetRepository { - func loadAssets() -> [Asset] { - guard let db else { return [] } - do { - var assets: [Asset] = [] - for row in try db.prepare(AssetRepository.selectQuery - .order(AssetRepository.col_type, AssetRepository.col_status.desc, AssetRepository.col_name) - ) { - assets.append(AssetRepository.selectResult(row)) - } - print("Successfully loaded assets: \(assets.count)") - return assets - } catch { - print("Error loading assets: \(error)") - return [] - } - } - - func addAsset(asset: inout Asset) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(AssetRepository.insertQuery(asset)) - asset.id = rowid - print("Successfully added asset: \(asset.name), \(asset.id)") - return true - } catch { - print("Failed to add asset: \(error)") - return false - } - } - - func updateAsset(asset: Asset) -> Bool { - guard let db else { return false } - do { - try db.run(AssetRepository.updateQuery(asset)) - print("Successfully updated asset: \(asset.name), \(asset.id)") - return true - } catch { - print("Failed to update asset: \(error)") - return false - } - } - - func deleteAsset(asset: Asset) -> Bool { - guard let db else { return false } - do { - try db.run(AssetRepository.deleteQuery(asset)) - print("Successfully deleted asset: \(asset.name), \(asset.id)") - return true - } catch { - print("Failed to delete asset: \(error)") - return false - } - } + func load() -> [Asset] { select(table: Self.repositoryTable + .order(Self.col_type, Self.col_status.desc, Self.col_name) + ) } } diff --git a/MMEX/Repositories/CategoryRepository.swift b/MMEX/Repositories/CategoryRepository.swift index 7e85279d..07484dd7 100644 --- a/MMEX/Repositories/CategoryRepository.swift +++ b/MMEX/Repositories/CategoryRepository.swift @@ -7,17 +7,16 @@ import SQLite -class CategoryRepository { - let db: Connection? +class CategoryRepository: RepositoryProtocol { + typealias RepositoryItem = Category + let db: Connection? init(db: Connection?) { self.db = db } -} -extension CategoryRepository { - // table query - static let table = SQLite.Table("CATEGORY_V1") + static let repositoryName = "CATEGORY_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ----------+---------+------ @@ -27,24 +26,22 @@ extension CategoryRepository { // ACTIVE | INTEGER | // PARENTID | INTEGER | - // table columns + // columns static let col_id = SQLite.Expression("CATEGID") static let col_name = SQLite.Expression("CATEGNAME") static let col_active = SQLite.Expression("ACTIVE") static let col_parentId = SQLite.Expression("PARENTID") -} -extension CategoryRepository { - // select query - static let selectQuery = table.select( - col_id, - col_name, - col_active, - col_parentId - ) + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_name, + col_active, + col_parentId + ) + } - // select result - static func selectResult(_ row: Row) -> Category { + static func selectResult(_ row: SQLite.Row) -> Category { return Category( id : row[col_id], name : row[col_name], @@ -53,84 +50,18 @@ extension CategoryRepository { ) } - // insert query - static func insertQuery(_ category: Category) -> SQLite.Insert { - return table.insert( + static func itemSetters(_ category: Category) -> [SQLite.Setter] { + return [ col_name <- category.name, col_active <- category.active ?? false ? 1 : 0, col_parentId <- category.parentId - ) - } - - // update query - static func updateQuery(_ category: Category) -> SQLite.Update { - return table.filter(col_id == category.id).update( - col_name <- category.name, - col_active <- category.active ?? false ? 1 : 0, - col_parentId <- category.parentId - ) - } - - // delete query - static func deleteQuery(_ category: Category) -> SQLite.Delete { - return table.filter(col_id == category.id).delete() + ] } } extension CategoryRepository { // load all categories - func loadCategories() -> [Category] { - guard let db = db else { return [] } - do { - var categories: [Category] = [] - for row in try db.prepare(CategoryRepository.selectQuery) { - categories.append(CategoryRepository.selectResult(row)) - } - print("Successfully loaded categories: \(categories.count)") - return categories - } catch { - print("Error loading categories: \(error)") - return [] - } - } - - // add a new category - func addCategory(category: inout Category) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(CategoryRepository.insertQuery(category)) - category.id = rowid // Update the category ID with the inserted row ID - print("Successfully added category: \(category.name), \(category.id)") - return true - } catch { - print("Failed to add category: \(error)") - return false - } - } - - // update an existing category - func updateCategory(category: Category) -> Bool { - guard let db else { return false } - do { - try db.run(CategoryRepository.updateQuery(category)) - print("Successfully updated category: \(category.name), \(category.id)") - return true - } catch { - print("Failed to update category: \(error)") - return false - } - } - - // delete a category - func deleteCategory(category: Category) -> Bool { - guard let db else { return false } - do { - try db.run(CategoryRepository.deleteQuery(category)) - print("Successfully deleted category: \(category.name), \(category.id)") - return true - } catch { - print("Failed to delete category: \(error)") - return false - } + func load() -> [Category] { + return select(table: Self.repositoryTable) } } diff --git a/MMEX/Repositories/CurrencyRepository.swift b/MMEX/Repositories/CurrencyRepository.swift index 34e07427..e7c1d583 100644 --- a/MMEX/Repositories/CurrencyRepository.swift +++ b/MMEX/Repositories/CurrencyRepository.swift @@ -8,17 +8,16 @@ import Foundation import SQLite -class CurrencyRepository { - let db: Connection? +class CurrencyRepository: RepositoryProtocol { + typealias RepositoryItem = Currency + let db: Connection? init(db: Connection?) { self.db = db } -} -extension CurrencyRepository { - // table query - static let table = SQLite.Table("CURRENCYFORMATS_V1") + static let repositoryName = "CURRENCYFORMATS_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ----------------+---------+------ @@ -35,7 +34,7 @@ extension CurrencyRepository { // CURRENCY_SYMBOL | TEXT | NOT NULL COLLATE NOCASE UNIQUE // CURRENCY_TYPE | TEXT | NOT NULL (Fiat, Crypto) - // table columns + // columns static let col_id = SQLite.Expression("CURRENCYID") static let col_name = SQLite.Expression("CURRENCYNAME") static let col_prefixSymbol = SQLite.Expression("PFX_SYMBOL") @@ -51,27 +50,26 @@ extension CurrencyRepository { // cast NUMERIC to REAL static let cast_baseConversionRate = cast(col_baseConversionRate) as SQLite.Expression -} -extension CurrencyRepository { - // select query - static let selectQuery = table.select( - col_id, - col_name, - col_prefixSymbol, - col_suffixSymbol, - col_decimalPoint, - col_groupSeparator, - col_unitName, - col_centName, - col_scale, - cast_baseConversionRate, - col_symbol, - col_type - ) - // select result - static func selectResult(_ row: Row) -> Currency { + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_name, + col_prefixSymbol, + col_suffixSymbol, + col_decimalPoint, + col_groupSeparator, + col_unitName, + col_centName, + col_scale, + cast_baseConversionRate, + col_symbol, + col_type + ) + } + + static func selectResult(_ row: SQLite.Row) -> Currency { return Currency( id : row[col_id], name : row[col_name], @@ -88,9 +86,8 @@ extension CurrencyRepository { ) } - // insert query - static func insertQuery(_ currency: Currency) -> SQLite.Insert { - return table.insert( + static func itemSetters(_ currency: Currency) -> [SQLite.Setter] { + return [ col_name <- currency.name, col_prefixSymbol <- currency.prefixSymbol, col_suffixSymbol <- currency.suffixSymbol, @@ -102,88 +99,15 @@ extension CurrencyRepository { col_baseConversionRate <- currency.baseConversionRate, col_symbol <- currency.symbol, col_type <- currency.type - ) - } - - // update query - static func updateQuery(_ currency: Currency) -> SQLite.Update { - return table.filter(col_id == currency.id).update( - col_name <- currency.name, - col_prefixSymbol <- currency.prefixSymbol, - col_suffixSymbol <- currency.suffixSymbol, - col_decimalPoint <- currency.decimalPoint, - col_groupSeparator <- currency.groupSeparator, - col_unitName <- currency.unitName, - col_centName <- currency.centName, - col_scale <- currency.scale, - col_baseConversionRate <- currency.baseConversionRate, - col_symbol <- currency.symbol, - col_type <- currency.type - ) - } - - // delete query - static func deleteQuery(_ currency: Currency) -> SQLite.Delete { - return table.filter(col_id == currency.id).delete() + ] } } extension CurrencyRepository { // load all currencies - func loadCurrencies() -> [Currency] { - guard let db else { return [] } - do { - var currencies: [Currency] = [] - for row in try db.prepare(CurrencyRepository.selectQuery - .order(CurrencyRepository.col_name) - ) { - currencies.append(CurrencyRepository.selectResult(row)) - } - print("Successfully loaded currencies: \(currencies.count)") - return currencies - } catch { - print("Error loading currencies: \(error)") - return [] - } - } - - // add a new currency - func addCurrency(currency: inout Currency) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(CurrencyRepository.insertQuery(currency)) - currency.id = rowid - print("Successfully added currency: \(currency.name), \(currency.id)") - return true - } catch { - print("Failed to add currency: \(error)") - return false - } - } - - // update an existing currency - func updateCurrency(currency: Currency) -> Bool { - guard let db else { return false } - do { - try db.run(CurrencyRepository.updateQuery(currency)) - print("Successfully updated currency: \(currency.name)") - return true - } catch { - print("Failed to update currency: \(error)") - return false - } - } - - // delete a currency - func deleteCurrency(currency: Currency) -> Bool { - guard let db else { return false } - do { - try db.run(CurrencyRepository.deleteQuery(currency)) - print("Successfully deleted currency: \(currency.name)") - return true - } catch { - print("Failed to delete currency: \(error)") - return false - } + func load() -> [Currency] { + return select(table: Self.repositoryTable + .order(Self.col_name) + ) } } diff --git a/MMEX/Repositories/InfotableRepository.swift b/MMEX/Repositories/InfotableRepository.swift index 37fafa65..a6fe4cc3 100644 --- a/MMEX/Repositories/InfotableRepository.swift +++ b/MMEX/Repositories/InfotableRepository.swift @@ -8,40 +8,37 @@ import Foundation import SQLite -class InfotableRepository { - let db: Connection? +class InfotableRepository: RepositoryProtocol { + typealias RepositoryItem = Infotable + let db: Connection? init(db: Connection?) { self.db = db } -} -extension InfotableRepository { - // table query - static let table = SQLite.Table("INFOTABLE_V1") + static let repositoryName = "INFOTABLE_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ----------+---------+------ // INFOID | INTEGER | NOT NULL PRIMARY KEY // INFONAME | TEXT | NOT NULL UNIQUE COLLATE NOCASE - // INFOVALUE | TEXT | NOT NUL + // INFOVALUE | TEXT | NOT NULL - // table columns + // columns static let col_id = SQLite.Expression("INFOID") static let col_name = SQLite.Expression("INFONAME") static let col_value = SQLite.Expression("INFOVALUE") -} -extension InfotableRepository { - // select query - static let selectQuery = table.select( - col_id, - col_name, - col_value - ) + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_name, + col_value + ) + } - // select result - static func selectResult(_ row: Row) -> Infotable { + static func selectResult(_ row: SQLite.Row) -> Infotable { return Infotable( id : row[col_id], name : row[col_name], @@ -49,129 +46,57 @@ extension InfotableRepository { ) } - // insert query - static func insertQuery(_ info: Infotable) -> SQLite.Insert { - return table.insert( + static func itemSetters(_ info: Infotable) -> [SQLite.Setter] { + return [ col_name <- info.name, col_value <- info.value - ) - } - - // update query - static func updateQuery(_ info: Infotable) -> SQLite.Update { - return table.filter(col_id == info.id).update( - col_name <- info.name, - col_value <- info.value - ) - } - - // delete query - static func deleteQuery(_ info: Infotable) -> SQLite.Delete { - return table.filter(col_id == info.id).delete() + ] } } extension InfotableRepository { // load all keys - func loadInfo() -> [Infotable] { - guard let db else { return [] } - do { - var results: [Infotable] = [] - for row in try db.prepare(InfotableRepository.selectQuery) { - results.append(InfotableRepository.selectResult(row)) - } - print("Successfully loaded infotable: \(results.count)") - return results - } catch { - print("Error loading infotable: \(error)") - return [] - } + func load() -> [Infotable] { + return select(table: Self.repositoryTable) } // load specific keys into a dictionary - func loadInfo(for keys: [InfoKey]) -> [InfoKey: Infotable] { - guard let db else { return [:] } - do { - var results: [InfoKey: Infotable] = [:] - for key in keys { - if let row = try db.pluck(InfotableRepository.selectQuery - .filter(InfotableRepository.col_name == key.rawValue) - ) { - results[key] = InfotableRepository.selectResult(row) - print("Successfully loaded infokey: \(key.rawValue)") - } - else { - print("Unknown infokey: \(key.rawValue)") - } + func load(for keys: [InfoKey]) -> [InfoKey: Infotable] { + if db == nil { return [:] } + var results: [InfoKey: Infotable] = [:] + for key in keys { + if let info = pluck( + table: Self.repositoryTable + .filter(Self.col_name == key.rawValue), + key: key.rawValue + ) { + results[key] = info } - return results - } catch { - print("Error loading info: \(error)") - return [:] - } - } - - func addInfo(info: inout Infotable) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(InfotableRepository.insertQuery(info)) - info.id = rowid - print("Successfully added infokey: \(info.name), \(info.id)") - return true - } catch { - print("Failed to add infotable: \(error)") - return false - } - } - - func updateInfo(info: Infotable) -> Bool { - guard let db else { return false } - do { - try db.run(InfotableRepository.updateQuery(info)) - print("Successfully updated infokey: \(info.name)") - return true - } catch { - print("Failed to update info: \(error)") - return false - } - } - - func deleteInfo(info: Infotable) -> Bool { - guard let db else { return false } - do { - try db.run(InfotableRepository.deleteQuery(info)) - print("Successfully deleted infokey: \(info.name)") - return true - } catch { - print("Failed to delete infokey: \(error)") - return false } + return results } // New Methods for Key-Value Pairs // Fetch value for a specific key, allowing for String or Int64 func getValue(for key: String, as type: T.Type) -> T? { - guard let db else { return nil } - do { - if let row = try db.pluck(InfotableRepository.selectQuery - .filter(InfotableRepository.col_name == key) - ) { - let value = row[InfotableRepository.col_value] - if type == String.self { - return value as? T - } else if type == Int64.self { - return Int64(value) as? T - } + if db == nil { return nil } + if let info = pluck( + table: Self.repositoryTable + .filter(Self.col_name == key), + key: key + ) { + if type == String.self { + return info.value as? T + } else if type == Int64.self { + return Int64(info.value) as? T } - } catch { - print("Error fetching value for key \(key): \(error)") } return nil } // Update or insert a setting with support for String or Int64 values func setValue(_ value: T, for key: String) { - guard let db else { return } + if db == nil { return } var stringValue: String if let stringVal = value as? String { @@ -183,22 +108,17 @@ extension InfotableRepository { return } - let query = InfotableRepository.table.filter(InfotableRepository.col_name == key) - do { - if let _ = try db.pluck(query) { - // Update existing setting - try db.run(query.update( - InfotableRepository.col_value <- stringValue - ) ) - } else { - // Insert new setting - try db.run(InfotableRepository.table.insert( - InfotableRepository.col_name <- key, - InfotableRepository.col_value <- stringValue - ) ) - } - } catch { - print("Error setting value for key \(key): \(error)") + if var info = pluck( + table: Self.repositoryTable.filter(Self.col_name == key), + key: key + ) { + // Update existing setting + info.value = stringValue + _ = update(info) + } else { + // Insert new setting + var info = Infotable(id: 0, name: key, value: stringValue) + _ = insert(&info) } } } diff --git a/MMEX/Repositories/PayeeRepository.swift b/MMEX/Repositories/PayeeRepository.swift index e4f38859..62ca7df4 100644 --- a/MMEX/Repositories/PayeeRepository.swift +++ b/MMEX/Repositories/PayeeRepository.swift @@ -8,17 +8,16 @@ import Foundation import SQLite -class PayeeRepository { - let db: Connection? +class PayeeRepository: RepositoryProtocol { + typealias RepositoryItem = Payee + let db: Connection? init(db: Connection?) { self.db = db } -} -extension PayeeRepository { - // table query - static let table = SQLite.Table("PAYEE_V1") + static let repositoryName = "PAYEE_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ----------+---------+------ @@ -31,7 +30,7 @@ extension PayeeRepository { // ACTIVE | INTEGER | // PATTERN | TEXT | DEFAULT '' - // table columns + // columns static let col_id = SQLite.Expression("PAYEEID") static let col_name = SQLite.Expression("PAYEENAME") static let col_categoryId = SQLite.Expression("CATEGID") @@ -39,24 +38,22 @@ extension PayeeRepository { static let col_website = SQLite.Expression("WEBSITE") static let col_notes = SQLite.Expression("NOTES") static let col_active = SQLite.Expression("ACTIVE") - static let col_pattern = SQLite.Expression("PATTERN") -} + static let col_pattern = SQLite.Expression("PATTERN") -extension PayeeRepository { - // select query - static let selectQuery = table.select( - col_id, - col_name, - col_categoryId, - col_number, - col_website, - col_notes, - col_active, - col_pattern - ) + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_name, + col_categoryId, + col_number, + col_website, + col_notes, + col_active, + col_pattern + ) + } - // select result - static func selectResult(_ row: Row) -> Payee { + static func selectResult(_ row: SQLite.Row) -> Payee { return Payee( id : row[col_id], name : row[col_name], @@ -65,26 +62,12 @@ extension PayeeRepository { website : row[col_website], notes : row[col_notes], active : row[col_active] ?? 1, - pattern : row[col_pattern] - ) - } - - // insert query - static func insertQuery(_ payee: Payee) -> SQLite.Insert { - return table.insert( - col_name <- payee.name, - col_categoryId <- payee.categoryId, - col_number <- payee.number, - col_website <- payee.website, - col_notes <- payee.notes, - col_active <- payee.active, - col_pattern <- payee.pattern + pattern : row[col_pattern] ?? "" ) } - // update query - static func updateQuery(_ payee: Payee) -> SQLite.Update { - return table.filter(col_id == payee.id).update( + static func itemSetters(_ payee: Payee) -> [SQLite.Setter] { + return [ col_name <- payee.name, col_categoryId <- payee.categoryId, col_number <- payee.number, @@ -92,67 +75,14 @@ extension PayeeRepository { col_notes <- payee.notes, col_active <- payee.active, col_pattern <- payee.pattern - ) - } - - // delete query - static func deleteQuery(_ payee: Payee) -> SQLite.Delete { - return table.filter(col_id == payee.id).delete() + ] } } extension PayeeRepository { - func loadPayees() -> [Payee] { - guard let db else { return [] } - do { - var payees: [Payee] = [] - for row in try db.prepare(PayeeRepository.selectQuery - .order(PayeeRepository.col_active.desc, PayeeRepository.col_name) - ) { - payees.append(PayeeRepository.selectResult(row)) - } - print("Successfully loaded payees: \(payees.count)") - return payees - } catch { - print("Error loading payees: \(error)") - return [] - } - } - - func addPayee(payee: inout Payee) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(PayeeRepository.insertQuery(payee)) - payee.id = rowid - print("Successfully added payee: \(payee.name), \(payee.id)") - return true - } catch { - print("Failed to add payee: \(error)") - return false - } - } - - func updatePayee(payee: Payee) -> Bool { - guard let db else { return false } - do { - try db.run(PayeeRepository.updateQuery(payee)) - print("Successfully updated payee: \(payee.name)") - return true - } catch { - print("Failed to update payee: \(error)") - return false - } - } - - func deletePayee(payee: Payee) -> Bool { - guard let db else { return false } - do { - try db.run(PayeeRepository.deleteQuery(payee)) - print("Successfully deleted payee: \(payee.name)") - return true - } catch { - print("Failed to delete payee: \(error)") - return false - } + func load() -> [Payee] { + return select(table: Self.repositoryTable + .order(Self.col_active.desc, Self.col_name) + ) } } diff --git a/MMEX/Repositories/StockRepository.swift b/MMEX/Repositories/StockRepository.swift index f2007c78..3a6193a6 100644 --- a/MMEX/Repositories/StockRepository.swift +++ b/MMEX/Repositories/StockRepository.swift @@ -8,17 +8,15 @@ import Foundation import SQLite -class StockRepository { - let db: Connection? +class StockRepository: RepositoryProtocol { + typealias RepositoryItem = Stock + let db: Connection? init(db: Connection?) { self.db = db } -} - -extension StockRepository { - // table query - static let table = SQLite.Table("STOCK_V1") + static let repositoryName = "STOCK_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // --------------+---------+------ @@ -34,7 +32,7 @@ extension StockRepository { // COMMISSION | NUMERIC | // NOTES | TEXT | - // table columns + // columns static let col_id = SQLite.Expression("STOCKID") static let col_accountId = SQLite.Expression("HELDAT") static let col_name = SQLite.Expression("STOCKNAME") @@ -53,26 +51,24 @@ extension StockRepository { static let cast_currentPrice = cast(col_currentPrice) as SQLite.Expression static let cast_value = cast(col_value) as SQLite.Expression static let cast_commisison = cast(col_commisison) as SQLite.Expression -} -extension StockRepository { - // select query - static let selectQuery = table.select( - col_id, - col_accountId, - col_name, - col_symbol, - cast_numShares, - col_purchaseDate, - cast_purchasePrice, - cast_currentPrice, - cast_value, - cast_commisison, - col_notes - ) + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( + col_id, + col_accountId, + col_name, + col_symbol, + cast_numShares, + col_purchaseDate, + cast_purchasePrice, + cast_currentPrice, + cast_value, + cast_commisison, + col_notes + ) + } - // select result - static func selectResult(_ row: Row) -> Stock { + static func selectResult(_ row: SQLite.Row) -> Stock { return Stock( id : row[col_id], accountId : row[col_accountId], @@ -88,7 +84,7 @@ extension StockRepository { ) } - static func insertSetters(_ stock: Stock) -> [Setter] { + static func itemSetters(_ stock: Stock) -> [SQLite.Setter] { return [ col_id <- stock.id, col_accountId <- stock.accountId, @@ -103,75 +99,12 @@ extension StockRepository { col_notes <- stock.notes ] } - - // insert query - static func insertQuery(_ stock: Stock) -> SQLite.Insert { - return table.insert(insertSetters(stock)) - } - - // update query - static func updateQuery(_ stock: Stock) -> SQLite.Update { - return table.filter(col_id == stock.id).update(insertSetters(stock)) - } - - // delete query - static func deleteQuery(_ stock: Stock) -> SQLite.Delete { - return table.filter(col_id == stock.id).delete() - } } extension StockRepository { - func loadStocks() -> [Stock] { - guard let db else { return [] } - do { - var stocks: [Stock] = [] - for row in try db.prepare(StockRepository.selectQuery - .order(StockRepository.col_name) - ) { - stocks.append(StockRepository.selectResult(row)) - } - print("Successfully loaded stocks: \(stocks.count)") - return stocks - } catch { - print("Error loading stocks: \(error)") - return [] - } - } - - func addStock(stock: inout Stock) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(StockRepository.insertQuery(stock)) - stock.id = rowid - print("Successfully added stock: \(stock.name), \(stock.id)") - return true - } catch { - print("Failed to add stock: \(error)") - return false - } - } - - func updateStock(stock: Stock) -> Bool { - guard let db else { return false } - do { - try db.run(StockRepository.updateQuery(stock)) - print("Successfully updated stock: \(stock.name), \(stock.id)") - return true - } catch { - print("Failed to update stock: \(error)") - return false - } - } - - func deleteStock(stock: Stock) -> Bool { - guard let db else { return false } - do { - try db.run(StockRepository.deleteQuery(stock)) - print("Successfully deleted stock: \(stock.name), \(stock.id)") - return true - } catch { - print("Failed to delete stock: \(error)") - return false - } + func load() -> [Stock] { + return select(table: Self.repositoryTable + .order(Self.col_name) + ) } } diff --git a/MMEX/Repositories/TransactionRepository.swift b/MMEX/Repositories/TransactionRepository.swift index 9191a4fe..f90ba00b 100644 --- a/MMEX/Repositories/TransactionRepository.swift +++ b/MMEX/Repositories/TransactionRepository.swift @@ -8,17 +8,16 @@ import Foundation import SQLite -class TransactionRepository { +class TransactionRepository: RepositoryProtocol { + typealias RepositoryItem = Transaction + let db: Connection? - init(db: Connection?) { self.db = db } -} -extension TransactionRepository { - // table query - static let table = SQLite.Table("CHECKINGACCOUNT_V1") + static let repositoryName = "CHECKINGACCOUNT_V1" + static let repositoryTable = SQLite.Table(repositoryName) // column | type | other // ------------------+---------+------ @@ -39,7 +38,7 @@ extension TransactionRepository { // TOTRANSAMOUNT | NUMERIC | // COLOR | INTEGER | DEFAULT -1 - // table columns + // columns static let col_id = SQLite.Expression("TRANSID") static let col_accountId = SQLite.Expression("ACCOUNTID") static let col_toAccountId = SQLite.Expression("TOACCOUNTID") @@ -55,17 +54,14 @@ extension TransactionRepository { static let col_deletedTime = SQLite.Expression("DELETEDTIME") static let col_followUpId = SQLite.Expression("FOLLOWUPID") static let col_toTransAmount = SQLite.Expression("TOTRANSAMOUNT") - static let col_color = SQLite.Expression("COLOR") + static let col_color = SQLite.Expression("COLOR") // cast NUMERIC to REAL static let cast_transAmount = cast(col_transAmount) as SQLite.Expression static let cast_toTransAmount = cast(col_toTransAmount) as SQLite.Expression -} - -extension TransactionRepository { - // select query - static func selectQuery(from: SQLite.Table) -> SQLite.Table { - return from.select( + + static func selectQuery(from table: SQLite.Table) -> SQLite.Table { + return table.select( col_id, col_accountId, col_toAccountId, @@ -85,8 +81,7 @@ extension TransactionRepository { ) } - // select result - static func selectResult(_ row: Row) -> Transaction { + static func selectResult(_ row: SQLite.Row) -> Transaction { return Transaction( id : row[col_id], accountId : row[col_accountId], @@ -107,7 +102,7 @@ extension TransactionRepository { ) } - static func insertSetters(_ txn: Transaction) -> [Setter] { + static func itemSetters(_ txn: Transaction) -> [SQLite.Setter] { return [ col_accountId <- txn.accountId, col_toAccountId <- txn.toAccountId, @@ -126,102 +121,25 @@ extension TransactionRepository { col_color <- txn.color, ] } - - // insert query - static func insertQuery(_ txn: Transaction) -> SQLite.Insert { - return table.insert(insertSetters(txn)) - } - - // update query - static func updateQuery(_ txn: Transaction) -> SQLite.Update { - return table.filter(col_id == txn.id).update(insertSetters(txn)) - } - - // delete query - static func deleteQuery(_ txn: Transaction) -> SQLite.Delete { - return table.filter(col_id == txn.id).delete() - } } extension TransactionRepository { // load all transactions - func loadTransactions() -> [Transaction] { - guard let db else { return [] } - do { - var results: [Transaction] = [] - let query = TransactionRepository.selectQuery(from: TransactionRepository.table) - for row in try db.prepare(query) { - results.append(TransactionRepository.selectResult(row)) - } - print("Successfully loaded transactions: \(results.count)") - return results - } catch { - print("Failed to fetch transactions: \(error)") - return [] - } + func load() -> [Transaction] { + return select(table: Self.repositoryTable) } // load recent transactions - func loadRecentTransactions( + func loadRecent( startDate: Date? = Calendar.current.date(byAdding: .month, value: -3, to: Date()), endDate: Date? = Date() ) -> [Transaction] { - guard let db else { return [] } - do { - var results: [Transaction] = [] - var from = TransactionRepository.table - // If startDate is set, add filtering by date range - if let startDate { - from = from.filter(TransactionRepository.col_transDate >= startDate.ISO8601Format()) - } - let query = TransactionRepository.selectQuery(from: from) - for row in try db.prepare(query) { - results.append(TransactionRepository.selectResult(row)) - } - print("Successfully loaded transactions: \(results.count)") - return results - } catch { - print("Failed to fetch transactions: \(error)") - return [] - } - } - - // add a new transaction - func addTransaction(txn: inout Transaction) -> Bool { - guard let db else { return false } - do { - let rowid = try db.run(TransactionRepository.insertQuery(txn)) - txn.id = rowid - print("Successfully added transaction with ID: \(txn.id), \(txn)") - return true - } catch { - print("Failed to add transaction: \(error)") - return false - } - } - - // update an existing transaction - func updateTransaction(txn: Transaction) -> Bool { - guard let db else { return false } - do { - try db.run(TransactionRepository.updateQuery(txn)) - print("Successfully updated transaction: \(txn.id)") - return true - } catch { - print("Failed to update transaction: \(error)") - return false - } - } - - func deleteTransaction(txn: Transaction) -> Bool { - guard let db else { return false } - do { - try db.run(TransactionRepository.deleteQuery(txn)) - print("Successfully deleted transaction: \(txn.id)") - return true - } catch { - print("Failed to delete transaction: \(error)") - return false + let table = if let startDate { + Self.repositoryTable + .filter(Self.col_transDate >= startDate.ISO8601Format()) + } else { + Self.repositoryTable } + return select(table: table) } } diff --git a/MMEX/Views/Accounts/AccountDetailView.swift b/MMEX/Views/Accounts/AccountDetailView.swift index b08b199e..5c1f76d9 100644 --- a/MMEX/Views/Accounts/AccountDetailView.swift +++ b/MMEX/Views/Accounts/AccountDetailView.swift @@ -112,7 +112,7 @@ struct AccountDetailView: View { func saveChanges() { let repository = DataManager(databaseURL: databaseURL).getAccountRepository() - if repository.updateAccount(account: account) { + if repository.update(account) { // Successfully updated } else { // Handle failure @@ -121,7 +121,7 @@ struct AccountDetailView: View { func deleteAccount() { let repository = DataManager(databaseURL: databaseURL).getAccountRepository() - if repository.deleteAccount(account: account) { + if repository.delete(account) { presentationMode.wrappedValue.dismiss() } else { // Handle deletion failure diff --git a/MMEX/Views/Accounts/AccountListView.swift b/MMEX/Views/Accounts/AccountListView.swift index dce4ac8d..456c63e0 100644 --- a/MMEX/Views/Accounts/AccountListView.swift +++ b/MMEX/Views/Accounts/AccountListView.swift @@ -106,7 +106,7 @@ struct AccountListView: View { print("Loading payees in AccountListView...") DispatchQueue.global(qos: .background).async { - let loadedAccounts = repository.loadAccountsWithCurrency() + let loadedAccounts = repository.loadWithCurrency() DispatchQueue.main.async { self.accounts_by_type = Dictionary(grouping: loadedAccounts) { account in account.type.name @@ -120,7 +120,7 @@ struct AccountListView: View { let repo = DataManager(databaseURL: self.databaseURL).getCurrencyRepository() DispatchQueue.global(qos: .background).async { - let loadedCurrencies = repo.loadCurrencies() + let loadedCurrencies = repo.load() DispatchQueue.main.async { self.currencies = loadedCurrencies // other post op @@ -129,7 +129,7 @@ struct AccountListView: View { } func addAccount(account: inout Account) { - if repository.addAccount(account: &account) { + if repository.insert(&account) { // self.accounts.append(account) self.loadAccounts() } diff --git a/MMEX/Views/Categories/CategoryDetailView.swift b/MMEX/Views/Categories/CategoryDetailView.swift index faabf0ed..c585e9cc 100644 --- a/MMEX/Views/Categories/CategoryDetailView.swift +++ b/MMEX/Views/Categories/CategoryDetailView.swift @@ -91,7 +91,7 @@ struct CategoryDetailView: View { func saveChanges() { let repository = DataManager(databaseURL: databaseURL).getCategoryRepository() - if repository.updateCategory(category: category) { + if repository.update(category) { // Handle success } else { // Handle failure @@ -100,7 +100,7 @@ struct CategoryDetailView: View { func deleteCategory() { let repository = DataManager(databaseURL: databaseURL).getCategoryRepository() - if repository.deleteCategory(category: category) { + if repository.delete(category) { // Dismiss the view and go back presentationMode.wrappedValue.dismiss() } else { diff --git a/MMEX/Views/Categories/CategoryListView.swift b/MMEX/Views/Categories/CategoryListView.swift index 2bfb8a00..96fcb7b5 100644 --- a/MMEX/Views/Categories/CategoryListView.swift +++ b/MMEX/Views/Categories/CategoryListView.swift @@ -57,11 +57,11 @@ struct CategoryListView: View { func loadCategories() { let repository = DataManager(databaseURL: databaseURL).getCategoryRepository() - categories = repository.loadCategories() + categories = repository.load() } func addCategory(category: inout Category) { - if repository.addCategory(category: &category) { + if repository.insert(&category) { self.categories.append(category) } } diff --git a/MMEX/Views/Currencies/CurrencyDetailView.swift b/MMEX/Views/Currencies/CurrencyDetailView.swift index 21bd9896..34319c4a 100644 --- a/MMEX/Views/Currencies/CurrencyDetailView.swift +++ b/MMEX/Views/Currencies/CurrencyDetailView.swift @@ -70,7 +70,7 @@ struct CurrencyDetailView: View { func saveChanges() { let repository = DataManager(databaseURL: databaseURL).getCurrencyRepository() - if repository.updateCurrency(currency: currency) { + if repository.update(currency) { // Handle success } else { // Handle failure @@ -79,7 +79,7 @@ struct CurrencyDetailView: View { func deleteCurrency() { let repository = DataManager(databaseURL: databaseURL).getCurrencyRepository() - if repository.deleteCurrency(currency: currency) { + if repository.delete(currency) { presentationMode.wrappedValue.dismiss() } else { // Handle deletion failure diff --git a/MMEX/Views/Currencies/CurrencyListView.swift b/MMEX/Views/Currencies/CurrencyListView.swift index 7bd210a3..d71ec546 100644 --- a/MMEX/Views/Currencies/CurrencyListView.swift +++ b/MMEX/Views/Currencies/CurrencyListView.swift @@ -103,8 +103,8 @@ struct CurrencyListView: View { func loadCurrencies() { // Fetch accounts using repository and update the view DispatchQueue.global(qos: .background).async { - let loadedCurrencies = repository.loadCurrencies() - let loadedAccounts = DataManager(databaseURL: databaseURL).getAccountRepository().loadAccounts() + let loadedCurrencies = repository.load() + let loadedAccounts = DataManager(databaseURL: databaseURL).getAccountRepository().load() // Get a set of all currency IDs used by accounts let usedCurrencyIds = Set(loadedAccounts.map { $0.currencyId }) @@ -120,7 +120,7 @@ struct CurrencyListView: View { } func addCurrency(_ currency: inout Currency) { - if repository.addCurrency(currency: ¤cy) { + if repository.insert(¤cy) { self.loadCurrencies() } else { // TODO diff --git a/MMEX/Views/InsightsViewModel.swift b/MMEX/Views/InsightsViewModel.swift index 906584b6..308be215 100644 --- a/MMEX/Views/InsightsViewModel.swift +++ b/MMEX/Views/InsightsViewModel.swift @@ -41,7 +41,7 @@ class InsightsViewModel: ObservableObject { // Fetch transactions asynchronously DispatchQueue.global(qos: .background).async { - let transactions = repository.loadRecentTransactions(startDate: self.startDate, endDate: self.endDate) + let transactions = repository.loadRecent(startDate: self.startDate, endDate: self.endDate) // Update the published stats on the main thread DispatchQueue.main.async { diff --git a/MMEX/Views/ManagementView.swift b/MMEX/Views/ManagementView.swift index 21dddf83..de18fcc6 100644 --- a/MMEX/Views/ManagementView.swift +++ b/MMEX/Views/ManagementView.swift @@ -112,7 +112,7 @@ struct ManagementView: View { let repo = DataManager(databaseURL: self.databaseURL).getAccountRepository() DispatchQueue.global(qos: .background).async { - let loadedAccounts = repo.loadAccountsWithCurrency() + let loadedAccounts = repo.loadWithCurrency() DispatchQueue.main.async { self.accounts = loadedAccounts @@ -127,7 +127,7 @@ struct ManagementView: View { let repo = DataManager(databaseURL: self.databaseURL).getCurrencyRepository() DispatchQueue.global(qos: .background).async { - let loadedCurrencies = repo.loadCurrencies() + let loadedCurrencies = repo.load() DispatchQueue.main.async { self.currencies = loadedCurrencies diff --git a/MMEX/Views/Payees/PayeeDetailView.swift b/MMEX/Views/Payees/PayeeDetailView.swift index ff74fba2..d2cbbd4a 100644 --- a/MMEX/Views/Payees/PayeeDetailView.swift +++ b/MMEX/Views/Payees/PayeeDetailView.swift @@ -122,7 +122,7 @@ struct PayeeDetailView: View { func saveChanges() { let repository = DataManager(databaseURL: databaseURL).getPayeeRepository() // pass URL here - if repository.updatePayee(payee: payee) { + if repository.update(payee) { // TODO } else { // TODO update failure @@ -131,7 +131,7 @@ struct PayeeDetailView: View { func deletePayee(){ let repository = DataManager(databaseURL: databaseURL).getPayeeRepository() // pass URL here - if repository.deletePayee(payee: payee) { + if repository.delete(payee) { // Dismiss the PayeeDetailView and go back to the previous view presentationMode.wrappedValue.dismiss() } else { diff --git a/MMEX/Views/Payees/PayeeListView.swift b/MMEX/Views/Payees/PayeeListView.swift index 1a161450..4621ccc6 100644 --- a/MMEX/Views/Payees/PayeeListView.swift +++ b/MMEX/Views/Payees/PayeeListView.swift @@ -66,7 +66,7 @@ struct PayeeListView: View { // Fetch accounts using repository and update the view DispatchQueue.global(qos: .background).async { - let loadedPayees = repository.loadPayees() + let loadedPayees = repository.load() // Update UI on the main thread DispatchQueue.main.async { @@ -80,7 +80,7 @@ struct PayeeListView: View { let repository = DataManager(databaseURL: self.databaseURL).getCategoryRepository() DispatchQueue.global(qos: .background).async { - let loadedCategories = repository.loadCategories() + let loadedCategories = repository.load() DispatchQueue.main.async { self.categories = loadedCategories @@ -90,7 +90,7 @@ struct PayeeListView: View { func addPayee(payee: inout Payee) { // TODO - if self.repository.addPayee(payee: &payee) { + if self.repository.insert(&payee) { self.payees.append(payee) // id is ready after repo call // loadPayees() } else { diff --git a/MMEX/Views/Transactions/TransactionAddView2.swift b/MMEX/Views/Transactions/TransactionAddView2.swift index 6a5a2130..8da84e62 100644 --- a/MMEX/Views/Transactions/TransactionAddView2.swift +++ b/MMEX/Views/Transactions/TransactionAddView2.swift @@ -59,7 +59,7 @@ struct TransactionAddView2: View { func addTransaction(txn: inout Transaction) { let repository = DataManager(databaseURL: self.databaseURL).getTransactionRepository() - if repository.addTransaction(txn:&txn) { + if repository.insert(&txn) { // id is ready after repo call } else { // TODO @@ -71,7 +71,7 @@ struct TransactionAddView2: View { // Fetch accounts using repository and update the view DispatchQueue.global(qos: .background).async { - let loadedPayees = repository.loadPayees() + let loadedPayees = repository.load() // Update UI on the main thread DispatchQueue.main.async { @@ -84,7 +84,7 @@ struct TransactionAddView2: View { let repository = DataManager(databaseURL: self.databaseURL).getCategoryRepository() DispatchQueue.global(qos: .background).async { - let loadedCategories = repository.loadCategories() + let loadedCategories = repository.load() DispatchQueue.main.async { self.categories = loadedCategories @@ -96,7 +96,7 @@ struct TransactionAddView2: View { let repository = DataManager(databaseURL: self.databaseURL).getAccountRepository() DispatchQueue.global(qos: .background).async { - let loadedAccounts = repository.loadAccountsWithCurrency() + let loadedAccounts = repository.loadWithCurrency() DispatchQueue.main.async { self.accounts = loadedAccounts diff --git a/MMEX/Views/Transactions/TransactionDetailView.swift b/MMEX/Views/Transactions/TransactionDetailView.swift index 1b799c0d..07ee704a 100644 --- a/MMEX/Views/Transactions/TransactionDetailView.swift +++ b/MMEX/Views/Transactions/TransactionDetailView.swift @@ -127,7 +127,7 @@ struct TransactionDetailView: View { } func saveChanges() { let repository = DataManager(databaseURL: databaseURL).getTransactionRepository() // pass URL here - if repository.updateTransaction(txn: txn) { + if repository.update(txn) { // TODO } else { // TODO update failure @@ -136,7 +136,7 @@ struct TransactionDetailView: View { func deleteTxn(){ let repository = DataManager(databaseURL: databaseURL).getTransactionRepository() // pass URL here - if repository.deleteTransaction(txn: txn) { + if repository.delete(txn) { // Dismiss the TransactionDetailView and go back to the previous view presentationMode.wrappedValue.dismiss() } else { diff --git a/MMEX/Views/Transactions/TransactionListView.swift b/MMEX/Views/Transactions/TransactionListView.swift index 63a5f532..b3ef131f 100644 --- a/MMEX/Views/Transactions/TransactionListView.swift +++ b/MMEX/Views/Transactions/TransactionListView.swift @@ -94,7 +94,7 @@ struct TransactionListView: View { // Fetch accounts using repository and update the view DispatchQueue.global(qos: .background).async { - let loadTransactions = repository.loadTransactions() + let loadTransactions = repository.load() // Update UI on the main thread DispatchQueue.main.async { @@ -108,7 +108,7 @@ struct TransactionListView: View { // Fetch accounts using repository and update the view DispatchQueue.global(qos: .background).async { - let loadedPayees = repository.loadPayees() + let loadedPayees = repository.load() // Update UI on the main thread DispatchQueue.main.async { @@ -121,7 +121,7 @@ struct TransactionListView: View { let repository = DataManager(databaseURL: self.databaseURL).getCategoryRepository() DispatchQueue.global(qos: .background).async { - let loadedCategories = repository.loadCategories() + let loadedCategories = repository.load() DispatchQueue.main.async { self.categories = loadedCategories @@ -133,7 +133,7 @@ struct TransactionListView: View { let repository = DataManager(databaseURL: self.databaseURL).getAccountRepository() DispatchQueue.global(qos: .background).async { - let loadedAccounts = repository.loadAccountsWithCurrency() + let loadedAccounts = repository.loadWithCurrency() DispatchQueue.main.async { self.accounts = loadedAccounts @@ -153,7 +153,7 @@ struct TransactionListView: View { func addTransaction(txn: inout Transaction) { // TODO - if self.repository.addTransaction(txn:&txn) { + if self.repository.insert(&txn) { self.txns.append(txn) // id is ready after repo call } else { // TODO diff --git a/MMEX/Views/Transactions/TransactionListView2.swift b/MMEX/Views/Transactions/TransactionListView2.swift index 11f00d98..0b3df42d 100644 --- a/MMEX/Views/Transactions/TransactionListView2.swift +++ b/MMEX/Views/Transactions/TransactionListView2.swift @@ -98,7 +98,7 @@ struct TransactionListView2: View { func loadTransactions() { DispatchQueue.global(qos: .background).async { - let loadTransactions = repository.loadRecentTransactions() + let loadTransactions = repository.loadRecent() DispatchQueue.main.async { self.txns = loadTransactions @@ -121,7 +121,7 @@ struct TransactionListView2: View { let repository = DataManager(databaseURL: self.databaseURL).getPayeeRepository() DispatchQueue.global(qos: .background).async { - let loadedPayees = repository.loadPayees() + let loadedPayees = repository.load() DispatchQueue.main.async { self.payees = loadedPayees @@ -134,7 +134,7 @@ struct TransactionListView2: View { let repository = DataManager(databaseURL: self.databaseURL).getCategoryRepository() DispatchQueue.global(qos: .background).async { - let loadedCategories = repository.loadCategories() + let loadedCategories = repository.load() DispatchQueue.main.async { self.categories = loadedCategories @@ -147,7 +147,7 @@ struct TransactionListView2: View { let repository = DataManager(databaseURL: self.databaseURL).getAccountRepository() DispatchQueue.global(qos: .background).async { - let loadedAccounts = repository.loadAccountsWithCurrency() + let loadedAccounts = repository.loadWithCurrency() DispatchQueue.main.async { self.accounts = loadedAccounts