/** * API ثبت‌نام و تأیید موبایل — Identity Service */ import type { TokenBundle } from "@/lib/auth"; const IDENTITY_API = process.env.NEXT_PUBLIC_IDENTITY_API_URL || "http://localhost:8001"; export interface OtpRequestResult { message: string; expires_in: number; skipped?: boolean; } export interface MobileAuthStartResult extends OtpRequestResult { intent: "login" | "register"; } export interface AuthSessionResult extends TokenBundle { message: string; intent: "login" | "register"; user_id?: string; mobile_verified: boolean; } export interface RegisterCompleteResult { message: string; mobile_verified: boolean; user_id?: string; } export interface MeProfile { user_id: string; mobile: string | null; mobile_verified: boolean; requires_mobile_verification: boolean; } export interface TenantMembershipSummary { tenant_id: string; role: string; is_active: boolean; } export interface AccountProfile { user_id: string; keycloak_sub: string; email: string | null; username: string | null; display_name: string | null; mobile: string | null; mobile_verified: boolean; requires_mobile_verification: boolean; roles: string[]; tenant_memberships: TenantMembershipSummary[]; } export interface AdminUser { id: string; keycloak_sub: string; email: string; username: string | null; display_name: string | null; mobile: string | null; mobile_verified: boolean; mobile_verified_at: string | null; core_user_id: string | null; status: string; created_at: string; } export type MembershipRole = "tenant_admin" | "tenant_member"; export interface TenantMember { id: string; tenant_id: string; user_id: string; role: MembershipRole; is_active: boolean; joined_at: string; } function parseIdentityError(body: Record, fallback: string): string { const err = body?.error as { message?: string } | undefined; if (err?.message) return err.message; if (typeof body?.detail === "string") return body.detail; if (Array.isArray(body?.detail) && body.detail[0]?.msg) { return String(body.detail[0].msg); } return fallback; } async function identityRequest( path: string, options: RequestInit & { token?: string | null } = {} ): Promise { const { token, headers: customHeaders, ...init } = options; const headers = new Headers(customHeaders); headers.set("Content-Type", "application/json"); if (token) headers.set("Authorization", `Bearer ${token}`); let res: Response; try { res = await fetch(`${IDENTITY_API}${path}`, { ...init, headers }); } catch { throw new Error(`اتصال به سرویس هویت برقرار نشد (${IDENTITY_API})`); } if (!res.ok) { const body = (await res.json().catch(() => ({}))) as Record; throw new Error(parseIdentityError(body, res.statusText)); } return res.json() as Promise; } export const identityApi = { mobileStart: (mobile: string, forceSms = false) => identityRequest("/api/v1/auth/mobile/start", { method: "POST", body: JSON.stringify({ mobile, force_sms: forceSms }), }), mobileComplete: (payload: { mobile: string; code: string; display_name?: string; email?: string; username?: string; password?: string; }) => identityRequest("/api/v1/auth/mobile/complete", { method: "POST", body: JSON.stringify(payload), }), register: (data: { email: string; username: string; password: string; mobile: string; display_name?: string; }) => identityRequest("/api/v1/auth/register", { method: "POST", body: JSON.stringify(data), }), verifyRegistrationMobile: (mobile: string, code: string) => identityRequest("/api/v1/auth/register/verify-mobile", { method: "POST", body: JSON.stringify({ mobile, code }), }), registerMobileRequest: (mobile: string, forceSms = false) => identityRequest("/api/v1/auth/register/mobile", { method: "POST", body: JSON.stringify({ mobile, force_sms: forceSms }), }), registerMobileVerify: (mobile: string, code: string, display_name?: string) => identityRequest("/api/v1/auth/register/mobile/verify", { method: "POST", body: JSON.stringify({ mobile, code, display_name }), }), requestMobileVerify: (mobile: string, token: string) => identityRequest("/api/v1/auth/mobile/request", { method: "POST", token, body: JSON.stringify({ mobile }), }), verifyMobile: (mobile: string, code: string, token: string) => identityRequest("/api/v1/auth/mobile/verify", { method: "POST", token, body: JSON.stringify({ mobile, code }), }), me: (token: string) => identityRequest("/api/v1/auth/me", { token }), profile: (token: string) => identityRequest("/api/v1/auth/me", { token }), updateProfile: ( token: string, data: { display_name?: string | null; email?: string } ) => identityRequest("/api/v1/auth/me", { method: "PATCH", token, body: JSON.stringify(data), }), changePassword: ( token: string, data: { current_password?: string; new_password: string } ) => identityRequest<{ message: string }>("/api/v1/auth/password", { method: "POST", token, body: JSON.stringify(data), }), listUsers: (token: string, options?: { offset?: number; limit?: number }) => { const params = new URLSearchParams({ offset: String(options?.offset ?? 0), limit: String(options?.limit ?? 50), }); return identityRequest(`/api/v1/users?${params.toString()}`, { token }); }, getUser: (token: string, userId: string) => identityRequest(`/api/v1/users/${userId}`, { token }), createUser: ( token: string, data: { email: string; username: string; password: string; mobile: string; display_name?: string; } ) => identityRequest("/api/v1/users", { method: "POST", token, body: JSON.stringify(data), }), listMembers: (token: string, tenantId: string) => identityRequest(`/api/v1/tenants/${tenantId}/members`, { token }), addMember: ( token: string, tenantId: string, data: { user_id: string; role?: MembershipRole } ) => identityRequest(`/api/v1/tenants/${tenantId}/members`, { method: "POST", token, body: JSON.stringify(data), }), };