ExpenseCategory DTO + GET /api/expenses/categories beim Öffnen der Liste sowie bei Refresh. Falls der Endpunkt noch nicht ausgerollt ist (oder fehlschlägt), Fallback auf eine lokale Default-Liste mit den aktuellen sechs Kategorien. AddExpenseSheet bekommt die Kategorien als Parameter, statt eigene Liste zu führen — Source of Truth ist jetzt das Backend.
308 lines
6.4 KiB
Swift
308 lines
6.4 KiB
Swift
import Foundation
|
|
|
|
// MARK: - Auth
|
|
|
|
struct LoginRequest: Encodable {
|
|
let email: String
|
|
let password: String
|
|
}
|
|
|
|
struct LoginResponse: Decodable {
|
|
let token: String
|
|
let name: String
|
|
let isPremium: Bool
|
|
}
|
|
|
|
struct UserProfile: Decodable {
|
|
let id: Int
|
|
let name: String
|
|
let email: String
|
|
let realName: String?
|
|
let rolle: String?
|
|
let isPremium: Bool?
|
|
// Backend bool-konvertiert nur is_premium; is_founder/is_partner kommen
|
|
// als SQLite-Int 0/1 zurück — deshalb hier Int? statt Bool?.
|
|
let isFounder: Int?
|
|
let isPartner: Int?
|
|
let founderNumber: Int?
|
|
let subscriptionTier: String?
|
|
let avatarUrl: String?
|
|
let wohnort: String?
|
|
let bio: String?
|
|
|
|
var isFounderFlag: Bool { isFounder == 1 }
|
|
var isPartnerFlag: Bool { isPartner == 1 }
|
|
}
|
|
|
|
// MARK: - Dogs
|
|
|
|
struct Dog: Decodable, Identifiable {
|
|
let id: Int
|
|
let name: String
|
|
let rasse: String?
|
|
let fotoUrl: String?
|
|
let geburtstag: String?
|
|
}
|
|
|
|
// MARK: - Routes
|
|
|
|
struct GPSPoint: Codable, Hashable {
|
|
let lat: Double
|
|
let lon: Double
|
|
let alt: Double?
|
|
}
|
|
|
|
struct RouteListItem: Decodable, Identifiable {
|
|
let id: Int
|
|
let userId: Int
|
|
let name: String
|
|
let beschreibung: String?
|
|
let distanzKm: Double?
|
|
let dauerMin: Int?
|
|
let createdAt: String?
|
|
let previewTrack: [GPSPoint]
|
|
let fotoUrls: [String]?
|
|
let userName: String?
|
|
let isPublic: Bool?
|
|
}
|
|
|
|
struct RouteDetail: Decodable, Identifiable {
|
|
let id: Int
|
|
let userId: Int
|
|
let name: String
|
|
let beschreibung: String?
|
|
let distanzKm: Double?
|
|
let dauerMin: Int?
|
|
let gpsTrack: [GPSPoint]
|
|
let fotoUrls: [String]?
|
|
let createdAt: String?
|
|
let userName: String?
|
|
let dogIds: [Int]?
|
|
}
|
|
|
|
struct RouteCreateBody: Encodable {
|
|
let name: String
|
|
let gpsTrack: [GPSPoint]
|
|
let distanzKm: Double
|
|
let dauerMin: Int
|
|
let dogIds: [Int]
|
|
let isPublic: Bool
|
|
}
|
|
|
|
// MARK: - Expenses
|
|
|
|
struct Expense: Decodable, Identifiable {
|
|
let id: Int
|
|
let dogId: Int?
|
|
let kategorie: String
|
|
let betrag: Double
|
|
let datum: String
|
|
let notiz: String?
|
|
let dogName: String?
|
|
}
|
|
|
|
struct ExpenseCreateBody: Encodable {
|
|
let dogId: Int?
|
|
let kategorie: String
|
|
let betrag: Double
|
|
let datum: String
|
|
let notiz: String?
|
|
}
|
|
|
|
struct ExpenseCategory: Decodable, Identifiable {
|
|
let id: String
|
|
let label: String
|
|
let color: String?
|
|
}
|
|
|
|
// MARK: - Gassi-Zeiten
|
|
|
|
struct GassiZeit: Decodable, Identifiable {
|
|
let id: Int
|
|
let dogId: Int?
|
|
let wochentage: [String]
|
|
let uhrzeit: String
|
|
let ortName: String?
|
|
let lat: Double?
|
|
let lon: Double?
|
|
let radiusM: Int?
|
|
let notiz: String?
|
|
let aktiv: Int?
|
|
let distanceM: Int?
|
|
let isMine: Bool?
|
|
let userName: String?
|
|
let dogName: String?
|
|
let dogRasse: String?
|
|
}
|
|
|
|
struct GassiZeitCreateBody: Encodable {
|
|
let dogId: Int?
|
|
let wochentage: [String]
|
|
let uhrzeit: String
|
|
let ortName: String?
|
|
let lat: Double?
|
|
let lon: Double?
|
|
let radiusM: Int
|
|
let notiz: String?
|
|
}
|
|
|
|
// MARK: - Poison
|
|
|
|
struct PoisonAlert: Decodable, Identifiable {
|
|
let id: Int
|
|
let lat: Double
|
|
let lon: Double
|
|
let beschreibung: String?
|
|
let typ: String?
|
|
let distanzM: Int?
|
|
let fotoUrl: String?
|
|
let melderName: String?
|
|
let createdAt: String?
|
|
}
|
|
|
|
struct PoisonCreateBody: Encodable {
|
|
let lat: Double
|
|
let lon: Double
|
|
let beschreibung: String?
|
|
let typ: String
|
|
}
|
|
|
|
// MARK: - Lost Dogs
|
|
|
|
struct LostDog: Decodable, Identifiable {
|
|
let id: Int
|
|
let name: String
|
|
let rasse: String?
|
|
let beschreibung: String
|
|
let lat: Double
|
|
let lon: Double
|
|
let distanzM: Int?
|
|
let fotoUrl: String?
|
|
let melderName: String?
|
|
let createdAt: String?
|
|
}
|
|
|
|
struct LostDogCreateBody: Encodable {
|
|
let name: String
|
|
let rasse: String?
|
|
let beschreibung: String
|
|
let lat: Double
|
|
let lon: Double
|
|
let dogId: Int?
|
|
}
|
|
|
|
// MARK: - Diary (Tagebuch)
|
|
|
|
struct DiaryEntry: Decodable, Identifiable {
|
|
let id: Int
|
|
let dogId: Int?
|
|
let datum: String?
|
|
let typ: String?
|
|
let titel: String?
|
|
let text: String?
|
|
let tags: [String]?
|
|
let gpsLat: Double?
|
|
let gpsLon: Double?
|
|
let locationName: String?
|
|
let isMilestone: Bool?
|
|
let media: [DiaryMedia]?
|
|
let createdAt: String?
|
|
}
|
|
|
|
struct DiaryMedia: Decodable, Identifiable {
|
|
let id: Int
|
|
let url: String
|
|
let mediaType: String?
|
|
let imgWidth: Int?
|
|
let imgHeight: Int?
|
|
}
|
|
|
|
struct DiaryCreateBody: Encodable {
|
|
let datum: String?
|
|
let typ: String
|
|
let titel: String?
|
|
let text: String?
|
|
let tags: [String]?
|
|
let gpsLat: Double?
|
|
let gpsLon: Double?
|
|
let locationName: String?
|
|
let isMilestone: Bool
|
|
}
|
|
|
|
// MARK: - Welcome Dashboard
|
|
|
|
struct DashboardSnapshot: Decodable {
|
|
let randomPhoto: DashboardPhoto?
|
|
let lastDiary: DashboardLastDiary?
|
|
let nextAppointment: DashboardNextAppointment?
|
|
let lastWeight: DashboardLastWeight?
|
|
let diaryCount: Int?
|
|
}
|
|
|
|
struct DashboardPhoto: Decodable {
|
|
let url: String
|
|
let previewUrl: String?
|
|
}
|
|
|
|
struct DashboardLastDiary: Decodable {
|
|
let titel: String?
|
|
let datum: String?
|
|
}
|
|
|
|
struct DashboardNextAppointment: Decodable {
|
|
let bezeichnung: String?
|
|
let naechstes: String?
|
|
let typ: String?
|
|
}
|
|
|
|
struct DashboardLastWeight: Decodable {
|
|
let wert: Double?
|
|
let einheit: String?
|
|
let datum: String?
|
|
}
|
|
|
|
// MARK: - Weather
|
|
|
|
struct WeatherForecast: Decodable {
|
|
let days: [WeatherDay]
|
|
}
|
|
|
|
struct WeatherDay: Decodable, Identifiable {
|
|
let date: String
|
|
let wday: String?
|
|
let weathercode: Int?
|
|
let desc: String?
|
|
let icon: String?
|
|
let tempMax: Double?
|
|
let tempMin: Double?
|
|
let precipProb: Int?
|
|
let precipSum: Double?
|
|
let windKmh: Double?
|
|
let uvIndex: Double?
|
|
let sunrise: String?
|
|
let sunset: String?
|
|
let asphaltTemp: Double?
|
|
let zecken: String?
|
|
|
|
var id: String { date }
|
|
}
|
|
|
|
/// Patch body for PATCH /api/routes/{id}. Only non-nil fields are encoded.
|
|
struct RouteUpdateBody: Encodable {
|
|
var name: String?
|
|
var beschreibung: String?
|
|
var isPublic: Bool?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case name
|
|
case beschreibung
|
|
case isPublic
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var c = encoder.container(keyedBy: CodingKeys.self)
|
|
try c.encodeIfPresent(name, forKey: .name)
|
|
try c.encodeIfPresent(beschreibung, forKey: .beschreibung)
|
|
try c.encodeIfPresent(isPublic, forKey: .isPublic)
|
|
}
|
|
}
|