import Foundation import Observation @Observable @MainActor final class AuthSession { var token: String? var userName: String? var isPremium: Bool = false var profile: UserProfile? var isLoggingIn: Bool = false var errorMessage: String? private let tokenKey = "by_token" init() { if let savedToken = KeychainStore.read(tokenKey) { token = savedToken APIClient.shared.token = savedToken } NotificationCenter.default.addObserver( forName: .apiUnauthorized, object: nil, queue: .main ) { [weak self] _ in Task { @MainActor in self?.logout() } } } var isLoggedIn: Bool { token != nil } func login(email: String, password: String) async { isLoggingIn = true errorMessage = nil defer { isLoggingIn = false } do { let response: LoginResponse = try await APIClient.shared.post( "/api/auth/login", body: LoginRequest(email: email, password: password) ) KeychainStore.save(response.token, for: tokenKey) APIClient.shared.token = response.token self.token = response.token self.userName = response.name self.isPremium = response.isPremium } catch { self.errorMessage = error.localizedDescription } } func logout() { KeychainStore.delete(tokenKey) APIClient.shared.token = nil token = nil userName = nil isPremium = false profile = nil } /// Fetches the full user profile from /api/auth/me. Called after login and /// when MainTabView appears, so admin/founder/role info shows up. func loadProfile() async { guard token != nil else { return } do { let me: UserProfile = try await APIClient.shared.get("/api/auth/me") self.profile = me if let premium = me.isPremium { self.isPremium = premium } } catch { // Profil-Refresh ist non-critical; Settings zeigt sonst nur die Basics. // Loggen ist trotzdem nützlich, damit Decode-Fehler nicht stillschweigend untergehen. print("loadProfile failed: \(error)") } } }