/** * Live 邀請網絡 — 對齊 apps/backend /api/v1/invite/* */ import type { InviteMemberBrief, InviteTreeNode, MyInviteNetwork, Role, MemberStatus, } from "../../domain/types"; import type { InviteRepo } from "../repos"; import { apiRequest } from "./http"; function uidToString(uid: unknown): string { if (typeof uid === "number" || typeof uid === "bigint") return String(uid); if (typeof uid === "string") return uid; return String(uid ?? ""); } function mapBrief(raw: Record): InviteMemberBrief { const parentRaw = raw.parent_uid; return { uid: uidToString(raw.uid), email: String(raw.email ?? ""), display_name: String(raw.display_name ?? ""), invite_code: String(raw.invite_code ?? "—"), parent_uid: parentRaw === null || parentRaw === undefined || parentRaw === 0 || parentRaw === "0" ? null : uidToString(parentRaw), roles: (Array.isArray(raw.roles) ? raw.roles : ["member"]) as Role[], status: (raw.status === "suspended" ? "suspended" : "active") as MemberStatus, downline_count: Number(raw.downline_count ?? 0), total_downline_count: Number(raw.total_downline_count ?? 0), depth: Number(raw.depth ?? 0), joined_at: Number(raw.joined_at ?? 0), avatar_url: raw.avatar_url != null && String(raw.avatar_url).trim() !== "" ? String(raw.avatar_url) : null, }; } function mapTree(raw: Record): InviteTreeNode { const childrenRaw = Array.isArray(raw.children) ? raw.children : []; return { ...mapBrief(raw), children: childrenRaw.map((c) => mapTree(c as Record)), }; } function mapNetwork(raw: Record): MyInviteNetwork { const me = mapBrief((raw.me || {}) as Record); const uplineRaw = raw.upline as Record | null | undefined; const downRaw = Array.isArray(raw.downlines) ? raw.downlines : []; return { me, upline: uplineRaw ? mapBrief(uplineRaw) : null, downlines: downRaw.map((d) => mapBrief(d as Record)), total_downline_count: Number(raw.total_downline_count ?? me.total_downline_count ?? 0), }; } export function createLiveInviteRepo(): InviteRepo { return { async getMyNetwork() { const data = await apiRequest>("/api/v1/invite/me"); return mapNetwork(data); }, async applyInviteCode(code) { const data = await apiRequest>("/api/v1/invite/apply", { method: "POST", body: { code }, }); return mapNetwork(data); }, async getTree() { const data = await apiRequest<{ list?: Record[] }>("/api/v1/invite/tree"); return (data.list ?? []).map((n) => mapTree(n)); }, async moveMember(uid, newParentUid) { const body: { uid: number; parent_uid?: number } = { uid: Number(uid), }; if (newParentUid != null && String(newParentUid).trim() !== "") { body.parent_uid = Number(newParentUid); } else { body.parent_uid = 0; } const data = await apiRequest>("/api/v1/invite/move", { method: "POST", body, }); return mapBrief(data); }, async listParentCandidates(excludeSubtreeRootUid) { const q = excludeSubtreeRootUid && String(excludeSubtreeRootUid).trim() ? `?exclude_subtree_root_uid=${encodeURIComponent(String(excludeSubtreeRootUid))}` : ""; const data = await apiRequest<{ list?: Record[] }>( `/api/v1/invite/parent-candidates${q}`, ); return (data.list ?? []).map((b) => mapBrief(b)); }, }; }