forked from EduCraft/curriculum-project-hub
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
/**
|
|
* GroupResolver 的 HTTP 实现(契约 C2,Group 团队服务到位后启用,
|
|
* 经 HUB_GROUP_SERVICE_URL 配置)。
|
|
*
|
|
* 语义红线:
|
|
* - 失败 → FileLibError(503, group_unavailable)。依赖故障不是"无权限",
|
|
* 绝不伪装成 404/403(计划 D13)。
|
|
* - 我方绝不自己推祖先:返回什么用什么,不在本地补逻辑。
|
|
*/
|
|
|
|
import { FileLibError } from "./model.js";
|
|
import type { GroupResolver } from "./groupResolver.js";
|
|
|
|
export interface HttpGroupResolverConfig {
|
|
readonly baseUrl: string;
|
|
readonly timeoutMs?: number;
|
|
/** 测试可注入假 fetch;生产用全局 fetch。 */
|
|
readonly fetchFn?: typeof fetch;
|
|
}
|
|
|
|
export function createHttpGroupResolver(config: HttpGroupResolverConfig): GroupResolver {
|
|
const timeoutMs = config.timeoutMs ?? 2_000;
|
|
const fetchFn = config.fetchFn ?? fetch;
|
|
return {
|
|
async resolveMemberGroupIds(userId) {
|
|
const url = `${config.baseUrl.replace(/\/$/, "")}/groups/resolve-member-groups?userId=${encodeURIComponent(userId)}`;
|
|
let response: Response;
|
|
try {
|
|
response = await fetchFn(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
} catch (error) {
|
|
throw new FileLibError(503, "group_unavailable", `group service unreachable: ${String(error)}`);
|
|
}
|
|
if (!response.ok) {
|
|
throw new FileLibError(503, "group_unavailable", `group service returned ${response.status}`);
|
|
}
|
|
let body: unknown;
|
|
try {
|
|
body = await response.json();
|
|
} catch {
|
|
throw new FileLibError(503, "group_unavailable", "group service returned malformed JSON");
|
|
}
|
|
const groupIds = (body as { groupIds?: unknown }).groupIds;
|
|
if (!Array.isArray(groupIds) || groupIds.some((id) => typeof id !== "string")) {
|
|
throw new FileLibError(503, "group_unavailable", "group service returned malformed payload");
|
|
}
|
|
return groupIds as readonly string[];
|
|
},
|
|
};
|
|
}
|