Various changes/fixes to API
I don't recall everything that's changed and I've been bad about making smaller commits but at a high level, custom initializers were added, network failures are handled better, amountStrings are empty when amount is 0, and some other minor things were changed.
This commit is contained in:
parent
544f9de221
commit
58eb9699a4
6 changed files with 264 additions and 52 deletions
|
@ -5,15 +5,31 @@ public struct Budget: Identifiable, Hashable, Codable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let description: String?
|
public let description: String?
|
||||||
public let currencyCode: String?
|
public let currencyCode: String?
|
||||||
|
|
||||||
|
public init(id: String, name: String, description: String?, currencyCode: String?) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.currencyCode = currencyCode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct BudgetOverview {
|
public struct BudgetOverview: Equatable {
|
||||||
public let budget: Budget
|
public let budget: Budget
|
||||||
public let balance: Int
|
public let balance: Int
|
||||||
public var expectedIncome: Int = 0
|
public var expectedIncome: Int
|
||||||
public var actualIncome: Int = 0
|
public var actualIncome: Int
|
||||||
public var expectedExpenses: Int = 0
|
public var expectedExpenses: Int
|
||||||
public var actualExpenses: Int = 0
|
public var actualExpenses: Int
|
||||||
|
|
||||||
|
public init(budget: Budget, balance: Int, expectedIncome: Int = 0, actualIncome: Int = 0, expectedExpenses: Int = 0, actualExpenses: Int = 0) {
|
||||||
|
self.budget = budget
|
||||||
|
self.balance = balance
|
||||||
|
self.expectedIncome = expectedIncome
|
||||||
|
self.actualIncome = actualIncome
|
||||||
|
self.expectedExpenses = expectedExpenses
|
||||||
|
self.actualExpenses = actualExpenses
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol BudgetRepository {
|
public protocol BudgetRepository {
|
||||||
|
|
|
@ -8,6 +8,38 @@ public struct Category: Identifiable, Hashable, Codable {
|
||||||
public let amount: Int
|
public let amount: Int
|
||||||
public let expense: Bool
|
public let expense: Bool
|
||||||
public let archived: Bool
|
public let archived: Bool
|
||||||
|
|
||||||
|
public init(
|
||||||
|
budgetId: String,
|
||||||
|
id: String = "",
|
||||||
|
title: String = "",
|
||||||
|
description: String? = "",
|
||||||
|
amount: Int = 0,
|
||||||
|
expense: Bool = true,
|
||||||
|
archived: Bool = false
|
||||||
|
) {
|
||||||
|
self.budgetId = budgetId
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.amount = amount
|
||||||
|
self.expense = expense
|
||||||
|
self.archived = archived
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Category {
|
||||||
|
public var type: TransactionType {
|
||||||
|
if (self.expense) {
|
||||||
|
return .expense
|
||||||
|
} else {
|
||||||
|
return .income
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var amountString: String {
|
||||||
|
return self.amount > 0 ? String(format: "%.02d", Double(self.amount) / 100.0) : ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol CategoryRepository {
|
public protocol CategoryRepository {
|
||||||
|
|
|
@ -13,12 +13,52 @@ public struct RecurringTransaction: Identifiable, Hashable, Codable {
|
||||||
public let description: String?
|
public let description: String?
|
||||||
public let frequency: Frequency
|
public let frequency: Frequency
|
||||||
public let start: Date
|
public let start: Date
|
||||||
public let end: Date?
|
public let finish: Date?
|
||||||
public let amount: Int
|
public let amount: Int
|
||||||
public let categoryId: String?
|
public let categoryId: String?
|
||||||
public let expense: Bool
|
public let expense: Bool
|
||||||
public let createdBy: String
|
public let createdBy: String
|
||||||
public let budgetId: String
|
public let budgetId: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: String = "",
|
||||||
|
title: String = "",
|
||||||
|
description: String? = nil,
|
||||||
|
frequency: Frequency = Frequency(unit: FrequencyUnit.daily, count: 1, time: Time(hours: 9, minutes: 0, seconds: 0)!)!,
|
||||||
|
start: Date = Date(),
|
||||||
|
finish: Date? = nil,
|
||||||
|
amount: Int = 0,
|
||||||
|
categoryId: String? = nil,
|
||||||
|
expense: Bool = true,
|
||||||
|
createdBy: String,
|
||||||
|
budgetId: String
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.frequency = frequency
|
||||||
|
self.start = start
|
||||||
|
self.finish = finish
|
||||||
|
self.amount = amount
|
||||||
|
self.categoryId = categoryId
|
||||||
|
self.expense = expense
|
||||||
|
self.createdBy = createdBy
|
||||||
|
self.budgetId = budgetId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RecurringTransaction {
|
||||||
|
public var type: TransactionType {
|
||||||
|
if (self.expense) {
|
||||||
|
return .expense
|
||||||
|
} else {
|
||||||
|
return .income
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var amountString: String {
|
||||||
|
return self.amount > 0 ? String(format: "%.02f", Double(self.amount) / 100.0) : ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Frequency: Hashable, Codable, CustomStringConvertible {
|
public struct Frequency: Hashable, Codable, CustomStringConvertible {
|
||||||
|
@ -100,7 +140,18 @@ public struct Frequency: Hashable, Codable, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FrequencyUnit: Hashable, CustomStringConvertible {
|
public enum FrequencyUnit: Identifiable, Hashable, CustomStringConvertible, CaseIterable {
|
||||||
|
public var id: String {
|
||||||
|
return self.baseName
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var allCases: [FrequencyUnit] = [
|
||||||
|
.daily,
|
||||||
|
.weekly(Set()),
|
||||||
|
.monthly(.fixed(1)),
|
||||||
|
.yearly(DayOfYear(month: 1, day: 1)!),
|
||||||
|
]
|
||||||
|
|
||||||
case daily
|
case daily
|
||||||
case weekly(Set<DayOfWeek>)
|
case weekly(Set<DayOfWeek>)
|
||||||
case monthly(DayOfMonth)
|
case monthly(DayOfMonth)
|
||||||
|
@ -111,11 +162,11 @@ public enum FrequencyUnit: Hashable, CustomStringConvertible {
|
||||||
case .daily:
|
case .daily:
|
||||||
return "D"
|
return "D"
|
||||||
case .weekly(let daysOfWeek):
|
case .weekly(let daysOfWeek):
|
||||||
return String(format: "W;%s", daysOfWeek.map { $0.rawValue }.joined(separator: ","))
|
return "W;\(daysOfWeek.map { $0.rawValue }.joined(separator: ","))"
|
||||||
case .monthly(let dayOfMonth):
|
case .monthly(let dayOfMonth):
|
||||||
return String(format: "M;%s", dayOfMonth.description)
|
return "M;\(dayOfMonth.description)"
|
||||||
case .yearly(let dayOfYear):
|
case .yearly(let dayOfYear):
|
||||||
return String(format: "Y;%s", dayOfYear.description)
|
return "Y;\(dayOfYear.description)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +182,19 @@ public enum FrequencyUnit: Hashable, CustomStringConvertible {
|
||||||
return String(localized: "Every \(count) year(s) on \(dayOfYear.description) at \(time.description)")
|
return String(localized: "Every \(count) year(s) on \(dayOfYear.description) at \(time.description)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var baseName: String {
|
||||||
|
switch self {
|
||||||
|
case .daily:
|
||||||
|
return "day"
|
||||||
|
case .weekly(_):
|
||||||
|
return "week"
|
||||||
|
case .monthly(_):
|
||||||
|
return "month"
|
||||||
|
case .yearly(_):
|
||||||
|
return "year"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Time: Hashable, CustomStringConvertible {
|
public struct Time: Hashable, CustomStringConvertible {
|
||||||
|
@ -169,13 +233,13 @@ public struct Time: Hashable, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DayOfMonth: Hashable, CustomStringConvertible {
|
public enum DayOfMonth: Hashable, CustomStringConvertible {
|
||||||
case positional(Position, DayOfWeek)
|
case ordinal(Ordinal, DayOfWeek)
|
||||||
case fixed(Int)
|
case fixed(Int)
|
||||||
public init?(position: Position, dayOfWeek: DayOfWeek) {
|
public init?(ordinal: Ordinal, dayOfWeek: DayOfWeek) {
|
||||||
if position == .day {
|
if ordinal == .day {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self = .positional(position, dayOfWeek)
|
self = .ordinal(ordinal, dayOfWeek)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init?(day: Int) {
|
public init?(day: Int) {
|
||||||
|
@ -187,7 +251,7 @@ public enum DayOfMonth: Hashable, CustomStringConvertible {
|
||||||
|
|
||||||
public init?(from string: String) {
|
public init?(from string: String) {
|
||||||
let parts = string.split(separator: "-")
|
let parts = string.split(separator: "-")
|
||||||
guard let position = Position.init(rawValue: String(parts[0])) else {
|
guard let position = Ordinal.init(rawValue: String(parts[0])) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if position == .day {
|
if position == .day {
|
||||||
|
@ -199,21 +263,21 @@ public enum DayOfMonth: Hashable, CustomStringConvertible {
|
||||||
guard let dayOfWeek = DayOfWeek(rawValue: String(parts[1])) else {
|
guard let dayOfWeek = DayOfWeek(rawValue: String(parts[1])) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self = .positional(position, dayOfWeek)
|
self = .ordinal(position, dayOfWeek)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .positional(let position, let dayOfWeek):
|
case .ordinal(let position, let dayOfWeek):
|
||||||
return "\(position)-\(dayOfWeek)"
|
return "\(position.rawValue)-\(dayOfWeek)"
|
||||||
case .fixed(let day):
|
case .fixed(let day):
|
||||||
return "\(Position.day)-\(day)"
|
return "\(Ordinal.day.rawValue)-\(day)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Position: String, Hashable {
|
public enum Ordinal: String, Hashable, CaseIterable {
|
||||||
case day = "DAY"
|
case day = "DAY"
|
||||||
case first = "FIRST"
|
case first = "FIRST"
|
||||||
case second = "SECOND"
|
case second = "SECOND"
|
||||||
|
@ -222,14 +286,18 @@ public enum Position: String, Hashable {
|
||||||
case last = "LAST"
|
case last = "LAST"
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DayOfWeek: String, Hashable {
|
public enum DayOfWeek: String, Hashable, CaseIterable, Identifiable {
|
||||||
|
public var id: String {
|
||||||
|
return self.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
case sunday = "SUNDAY"
|
||||||
case monday = "MONDAY"
|
case monday = "MONDAY"
|
||||||
case tuesday = "TUESDAY"
|
case tuesday = "TUESDAY"
|
||||||
case wednesday = "WEDNESDAY"
|
case wednesday = "WEDNESDAY"
|
||||||
case thursday = "THURSDAY"
|
case thursday = "THURSDAY"
|
||||||
case friday = "FRIDAY"
|
case friday = "FRIDAY"
|
||||||
case saturday = "SATURDAY"
|
case saturday = "SATURDAY"
|
||||||
case sunday = "SUNDAY"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct DayOfYear: Hashable, CustomStringConvertible {
|
public struct DayOfYear: Hashable, CustomStringConvertible {
|
||||||
|
@ -237,17 +305,7 @@ public struct DayOfYear: Hashable, CustomStringConvertible {
|
||||||
public let day: Int
|
public let day: Int
|
||||||
|
|
||||||
public init?(month: Int, day: Int) {
|
public init?(month: Int, day: Int) {
|
||||||
var maxDay: Int
|
let maxDay = DayOfYear.maxDays(inMonth: month)
|
||||||
switch month {
|
|
||||||
case 2:
|
|
||||||
maxDay = 29;
|
|
||||||
break;
|
|
||||||
case 4, 6, 9, 11:
|
|
||||||
maxDay = 30;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
maxDay = 31;
|
|
||||||
}
|
|
||||||
if day < 1 || day > maxDay {
|
if day < 1 || day > maxDay {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -271,10 +329,21 @@ public struct DayOfYear: Hashable, CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return String(format: "%02d-%02d", self.month, self.day)
|
return String(format: "%02d-%02d", self.month, self.day)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func maxDays(inMonth month: Int) -> Int {
|
||||||
|
switch month {
|
||||||
|
case 2:
|
||||||
|
return 29;
|
||||||
|
case 4, 6, 9, 11:
|
||||||
|
return 30;
|
||||||
|
default:
|
||||||
|
return 31;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol RecurringTransactionsRepository {
|
public protocol RecurringTransactionsRepository {
|
||||||
func getRecurringTransactions(budgetId: String) async throws -> [RecurringTransaction]
|
func getRecurringTransactions(_ budgetId: String) async throws -> [RecurringTransaction]
|
||||||
func getRecurringTransaction(_ id: String) async throws -> RecurringTransaction
|
func getRecurringTransaction(_ id: String) async throws -> RecurringTransaction
|
||||||
func createRecurringTransaction(_ transaction: RecurringTransaction) async throws -> RecurringTransaction
|
func createRecurringTransaction(_ transaction: RecurringTransaction) async throws -> RecurringTransaction
|
||||||
func updateRecurringTransaction(_ transaction: RecurringTransaction) async throws -> RecurringTransaction
|
func updateRecurringTransaction(_ transaction: RecurringTransaction) async throws -> RecurringTransaction
|
||||||
|
|
|
@ -17,10 +17,26 @@ public struct Transaction: Identifiable, Hashable, Codable {
|
||||||
public let expense: Bool
|
public let expense: Bool
|
||||||
public let createdBy: String
|
public let createdBy: String
|
||||||
public let budgetId: String
|
public let budgetId: String
|
||||||
|
|
||||||
|
public init(id: String = "", title: String = "", description: String? = "", date: Date = Date(), amount: Int = 0, categoryId: String? = "", expense: Bool = true, createdBy: String, budgetId: String) {
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.date = date
|
||||||
|
self.amount = amount
|
||||||
|
self.categoryId = categoryId
|
||||||
|
self.expense = expense
|
||||||
|
self.createdBy = createdBy
|
||||||
|
self.budgetId = budgetId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct BalanceResponse: Codable {
|
public struct BalanceResponse: Codable {
|
||||||
public let balance: Int
|
public let balance: Int
|
||||||
|
|
||||||
|
public init(balance: Int) {
|
||||||
|
self.balance = balance
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TransactionType: Int, CaseIterable, Identifiable, Hashable {
|
public enum TransactionType: Int, CaseIterable, Identifiable, Hashable {
|
||||||
|
@ -40,7 +56,7 @@ extension Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var amountString: String {
|
public var amountString: String {
|
||||||
return String(Double(self.amount) / 100.0)
|
return self.amount > 0 ? String(format: "%.02d", Double(self.amount) / 100.0) : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ open class TwigsApiService: BudgetRepository, CategoryRepository, RecurringTrans
|
||||||
self.init(RequestHelper())
|
self.init(RequestHelper())
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ requestHelper: RequestHelper) {
|
public init(_ requestHelper: RequestHelper) {
|
||||||
self.requestHelper = requestHelper
|
self.requestHelper = requestHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ open class TwigsApiService: BudgetRepository, CategoryRepository, RecurringTrans
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Recurring Transactions
|
// MARK: Recurring Transactions
|
||||||
open func getRecurringTransactions(budgetId: String) async throws -> [RecurringTransaction] {
|
open func getRecurringTransactions(_ budgetId: String) async throws -> [RecurringTransaction] {
|
||||||
return try await requestHelper.get("/api/recurringtransactions", queries: ["budgetId": [budgetId]])
|
return try await requestHelper.get("/api/recurringtransactions", queries: ["budgetId": [budgetId]])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,26 +242,26 @@ open class TwigsApiService: BudgetRepository, CategoryRepository, RecurringTrans
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestHelper {
|
public class RequestHelper {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
private var _baseUrl: String? = nil
|
private var _baseUrl: String? = nil
|
||||||
var baseUrl: String? {
|
var baseUrl: String? {
|
||||||
get {
|
get {
|
||||||
self.baseUrl
|
self._baseUrl
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
guard var correctServer = newValue?.lowercased() else {
|
guard var correctServer = newValue?.lowercased() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !correctServer.starts(with: "http://") && !correctServer.starts(with: "https://") {
|
if !correctServer.starts(with: "http://") && !correctServer.starts(with: "https://") {
|
||||||
correctServer = "http://\(correctServer)"
|
correctServer = "https://\(correctServer)"
|
||||||
}
|
}
|
||||||
self._baseUrl = correctServer
|
self._baseUrl = correctServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var token: String?
|
var token: String?
|
||||||
|
|
||||||
init() {
|
public init() {
|
||||||
self.decoder.dateDecodingStrategy = .formatted(Date.iso8601DateFormatter)
|
self.decoder.dateDecodingStrategy = .formatted(Date.iso8601DateFormatter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,11 +317,14 @@ class RequestHelper {
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
request.httpMethod = "DELETE"
|
request.httpMethod = "DELETE"
|
||||||
|
if let token = self.token {
|
||||||
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
}
|
||||||
|
|
||||||
let (_, res) = try await URLSession.shared.data(for: request)
|
let (_, res) = try await URLSession.shared.data(for: request)
|
||||||
guard let response = res as? HTTPURLResponse, 200...299 ~= response.statusCode else {
|
guard let response = res as? HTTPURLResponse, 200...299 ~= response.statusCode else {
|
||||||
switch (res as? HTTPURLResponse)?.statusCode {
|
switch (res as? HTTPURLResponse)?.statusCode {
|
||||||
case 400: throw NetworkError.badRequest
|
case 400: throw NetworkError.badRequest(nil)
|
||||||
case 401, 403: throw NetworkError.unauthorized
|
case 401, 403: throw NetworkError.unauthorized
|
||||||
case 404: throw NetworkError.notFound
|
case 404: throw NetworkError.notFound
|
||||||
default: throw NetworkError.unknown
|
default: throw NetworkError.unknown
|
||||||
|
@ -351,16 +354,46 @@ class RequestHelper {
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
}
|
}
|
||||||
|
|
||||||
let (data, res) = try await URLSession.shared.data(for: request)
|
var data: Data? = nil
|
||||||
guard let response = res as? HTTPURLResponse, 200...299 ~= response.statusCode else {
|
var res: URLResponse?
|
||||||
switch (res as? HTTPURLResponse)?.statusCode {
|
do {
|
||||||
case 400: throw NetworkError.badRequest
|
(data, res) = try await URLSession.shared.data(for: request)
|
||||||
|
guard let response = res as? HTTPURLResponse, 200...299 ~= response.statusCode else {
|
||||||
|
switch (res as? HTTPURLResponse)?.statusCode ?? -1 {
|
||||||
|
case 400:
|
||||||
|
var reason: String? = nil
|
||||||
|
if let data = data {
|
||||||
|
reason = String(decoding: data, as: UTF8.self)
|
||||||
|
}
|
||||||
|
throw NetworkError.badRequest(reason)
|
||||||
|
case 401, 403: throw NetworkError.unauthorized
|
||||||
|
case 404: throw NetworkError.notFound
|
||||||
|
case 500...599: throw NetworkError.server
|
||||||
|
default: throw NetworkError.unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
switch (res as? HTTPURLResponse)?.statusCode ?? -1 {
|
||||||
case 401, 403: throw NetworkError.unauthorized
|
case 401, 403: throw NetworkError.unauthorized
|
||||||
case 404: throw NetworkError.notFound
|
case 404: throw NetworkError.notFound
|
||||||
default: throw NetworkError.unknown
|
case 500...599: throw NetworkError.server
|
||||||
|
default:
|
||||||
|
var reason: String = error.localizedDescription
|
||||||
|
if let data = data {
|
||||||
|
reason = String(decoding: data, as: UTF8.self)
|
||||||
|
}
|
||||||
|
throw NetworkError.badRequest(reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return try self.decoder.decode(ResultType.self, from: data)
|
do {
|
||||||
|
guard let data = data else {
|
||||||
|
throw NetworkError.unknown
|
||||||
|
}
|
||||||
|
return try self.decoder.decode(ResultType.self, from: data)
|
||||||
|
} catch {
|
||||||
|
print("error decoding json: \(error)")
|
||||||
|
throw NetworkError.jsonParsingFailed(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,10 +406,12 @@ public enum NetworkError: Error, Equatable {
|
||||||
return true
|
return true
|
||||||
case (.unauthorized, .unauthorized):
|
case (.unauthorized, .unauthorized):
|
||||||
return true
|
return true
|
||||||
case (.badRequest, .badRequest):
|
case (let .badRequest(reason1), let .badRequest(reason2)):
|
||||||
return true
|
return reason1 == reason2
|
||||||
case (.invalidUrl, .invalidUrl):
|
case (.invalidUrl, .invalidUrl):
|
||||||
return true
|
return true
|
||||||
|
case (.server, .server):
|
||||||
|
return true
|
||||||
case (let .jsonParsingFailed(error1), let .jsonParsingFailed(error2)):
|
case (let .jsonParsingFailed(error1), let .jsonParsingFailed(error2)):
|
||||||
return error1.localizedDescription == error2.localizedDescription
|
return error1.localizedDescription == error2.localizedDescription
|
||||||
default:
|
default:
|
||||||
|
@ -395,10 +430,12 @@ public enum NetworkError: Error, Equatable {
|
||||||
return "deleted"
|
return "deleted"
|
||||||
case .unauthorized:
|
case .unauthorized:
|
||||||
return "unauthorized"
|
return "unauthorized"
|
||||||
case .badRequest:
|
case .badRequest(_):
|
||||||
return "badRequest"
|
return "badRequest"
|
||||||
case .invalidUrl:
|
case .invalidUrl:
|
||||||
return "invalidUrl"
|
return "invalidUrl"
|
||||||
|
case .server:
|
||||||
|
return "server"
|
||||||
case .jsonParsingFailed(_):
|
case .jsonParsingFailed(_):
|
||||||
return "jsonParsingFailed"
|
return "jsonParsingFailed"
|
||||||
}
|
}
|
||||||
|
@ -409,8 +446,9 @@ public enum NetworkError: Error, Equatable {
|
||||||
case notFound
|
case notFound
|
||||||
case deleted
|
case deleted
|
||||||
case unauthorized
|
case unauthorized
|
||||||
case badRequest
|
case badRequest(String?)
|
||||||
case invalidUrl
|
case invalidUrl
|
||||||
|
case server
|
||||||
case jsonParsingFailed(Error)
|
case jsonParsingFailed(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,25 +10,66 @@ import Foundation
|
||||||
public struct User: Codable, Equatable, Hashable {
|
public struct User: Codable, Equatable, Hashable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public let username: String
|
public let username: String
|
||||||
|
public let password: String?
|
||||||
public let email: String?
|
public let email: String?
|
||||||
public let avatar: String?
|
public let avatar: String?
|
||||||
|
|
||||||
|
public init(id: String, username: String, email: String?, password: String?, avatar: String?) {
|
||||||
|
self.id = id
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
self.avatar = avatar
|
||||||
|
}
|
||||||
|
|
||||||
|
public func copy(
|
||||||
|
username: String? = nil,
|
||||||
|
email: String? = nil,
|
||||||
|
password: String? = nil,
|
||||||
|
avatar: String? = nil
|
||||||
|
) -> User {
|
||||||
|
return User(
|
||||||
|
id: self.id,
|
||||||
|
username: username ?? self.username,
|
||||||
|
email: email ?? self.email,
|
||||||
|
password: password ?? self.password,
|
||||||
|
avatar: avatar ?? self.avatar
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LoginRequest: Codable {
|
public struct LoginRequest: Codable {
|
||||||
public let username: String
|
public let username: String
|
||||||
public let password: String
|
public let password: String
|
||||||
|
|
||||||
|
public init(username: String, password: String) {
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LoginResponse: Codable {
|
public struct LoginResponse: Codable {
|
||||||
public let token: String
|
public let token: String
|
||||||
public let expiration: String
|
public let expiration: String
|
||||||
public let userId: String
|
public let userId: String
|
||||||
|
|
||||||
|
public init(token: String, expiration: String, userId: String) {
|
||||||
|
self.token = token
|
||||||
|
self.expiration = expiration
|
||||||
|
self.userId = userId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RegistrationRequest: Codable {
|
public struct RegistrationRequest: Codable {
|
||||||
public let username: String
|
public let username: String
|
||||||
public let email: String
|
public let email: String
|
||||||
public let password: String
|
public let password: String
|
||||||
|
|
||||||
|
public init(username: String, email: String, password: String) {
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol UserRepository {
|
public protocol UserRepository {
|
||||||
|
|
Loading…
Reference in a new issue