// // AuthService.swift // Snail // import Foundation nonisolated struct User: Codable, Identifiable, Hashable, Sendable { let id: String let name: String let email: String let emailVerified: Bool let image: String? let createdAt: Date /// При входе по коду better-auth создаёт пользователя с пустым именем. var displayName: String { name.trimmingCharacters(in: .whitespaces).isEmpty ? email : name } var initials: String { let source = name.trimmingCharacters(in: .whitespaces).isEmpty ? email : name let letters = source .split(whereSeparator: { $0 == " " || $0 == "@" || $0 == "." }) .prefix(2) .compactMap(\.first) return String(letters).uppercased() } } /// Эндпоинты better-auth (плагин emailOTP) и профиля. nonisolated struct AuthService: Sendable { private let client: APIClient init(client: APIClient = APIClient()) { self.client = client } func sendSignInCode(to email: String) async throws { let _: SuccessResponse = try await client.send( "POST", "auth/email-otp/send-verification-otp", body: ["email": email, "type": "sign-in"] ) } func signIn(email: String, code: String) async throws -> User { let response: UserResponse = try await client.send( "POST", "auth/sign-in/email-otp", body: ["email": email, "otp": code] ) return response.user } /// Текущий пользователь или nil, если сессии нет. func currentUser() async throws -> User? { do { let response: Envelope = try await client.send("GET", "users/me") return response.data.user } catch APIError.unauthorized { return nil } } func signOut() async { let _: SuccessResponse? = try? await client.send("POST", "auth/sign-out", body: [:]) client.clearCookies() } } private nonisolated struct SuccessResponse: Decodable { let success: Bool } private nonisolated struct UserResponse: Decodable { let user: User }