import type { AuthTokens, LoginRequest, RefreshTokenRequest, RegisterRequest } from "@oj2/contract" import type { ClassActivity, MarkAchievementsRead, ProblemAuthor, ProblemList, Quote, WebsiteConfig, } from "@oj2/contract" import api from "utils/api" import { getRefreshToken, saveTokens } from "utils/auth" import { contractDeferred } from "utils/contract" import { toProblemRow } from "utils/transforms" import type { PendingAchievement, Profile, Tag } from "utils/types" export async function login(data: { username: string; password: string }) { saveTokens(await api.post("auth/login", data satisfies LoginRequest)) } export function signup(data: { username: string; email: string; password: string }) { return api.post("users", data satisfies RegisterRequest) } /** 吊销服务端那张会话。本地的 token 由调用方清(user store 的 clearProfile) */ export async function logout() { const refreshToken = getRefreshToken() if (refreshToken) await api.post("auth/logout", { refreshToken } satisfies RefreshTokenRequest) } export async function getProfile(username: string = ""): Promise { const endpoint = username ? `profiles/${encodeURIComponent(username)}` : "me" const response = await api.get(endpoint) if (response === null) return null // 形状与契约一致,不再逐字段搬运。走契约闸门而不是裸 parse():原来这里是 // `userProfileSchema.parse(response) as Profile`,`as` 把校验结果又断言回本地 // 类型(Profile 把 user 收窄成 SessionUser、acmProblemsStatus 收窄成具体形状), // 形状对不上时页面白屏。现在记一条分歧日志后放行原始数据。 // 用 deferred 版:启动就要拉 /me,同步校验会把 zod 整个打进首屏包 return contractDeferred( "GET /profiles/:username", () => import("./profileSchema").then((m) => m.userProfileSchema), response, ) } export function getProblemTagList() { return api.get("problem-tags") } export function getHitokoto() { return api.get("quotes/random") } export function getClassUsernames(classroom: string) { return api.get(`classes/${encodeURIComponent(classroom)}/usernames`) } // 下面这个是启动就要调的(config store),所以放在 shared 而不是 // oj/api.ts:oj/api.ts 同步 import 了题目详情、提交详情两个 schema(它们的契约闸门必须 // 同步,submissionDetailSchema 里有 `.catch()`,放行原文和解析结果不等价),入口一旦 // 引到它,zod 整个就进了首屏包。 export function getWebsiteConfig() { return api.get("site") } export async function getProblemList( offset = 0, limit = 10, searchParams: Record = {}, ) { const res = await api.get("problems", { params: { paging: true, offset, limit, ...searchParams }, }) return { results: res.results.map(toProblemRow), total: res.total, } } export function getAuthors(all = false) { return api.get("problem-authors", { params: { all: all ? "1" : "0" }, }) } export function getClassActivity() { return api.get("me/class-activity") } export function getPendingAchievements() { return api.get("achievements/pending") } export function markAchievementsRead(ids: number[]) { return api.post("achievements/pending/read", { ids } satisfies MarkAchievementsRead) }