Files
snail/Snail/Auth/AuthService.swift
2026-10-29 13:14:46 +03:00

79 lines
2.2 KiB
Swift

//
// 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<UserResponse> = 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
}