thread-master/apps/web/src/data/live/inviteRepo.ts

108 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-07-13 03:18:08 +00:00
/**
* 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<string, unknown>): 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<string, unknown>): InviteTreeNode {
const childrenRaw = Array.isArray(raw.children) ? raw.children : [];
return {
...mapBrief(raw),
children: childrenRaw.map((c) => mapTree(c as Record<string, unknown>)),
};
}
function mapNetwork(raw: Record<string, unknown>): MyInviteNetwork {
const me = mapBrief((raw.me || {}) as Record<string, unknown>);
const uplineRaw = raw.upline as Record<string, unknown> | 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<string, unknown>)),
total_downline_count: Number(raw.total_downline_count ?? me.total_downline_count ?? 0),
};
}
export function createLiveInviteRepo(): InviteRepo {
return {
async getMyNetwork() {
const data = await apiRequest<Record<string, unknown>>("/api/v1/invite/me");
return mapNetwork(data);
},
async applyInviteCode(code) {
const data = await apiRequest<Record<string, unknown>>("/api/v1/invite/apply", {
method: "POST",
body: { code },
});
return mapNetwork(data);
},
async getTree() {
const data = await apiRequest<{ list?: Record<string, unknown>[] }>("/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<Record<string, unknown>>("/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<string, unknown>[] }>(
`/api/v1/invite/parent-candidates${q}`,
);
return (data.list ?? []).map((b) => mapBrief(b));
},
};
}