refactor: serve static frontend with Go APIs

This commit is contained in:
2026-08-16 20:47:45 +08:00
parent 763d2f0648
commit b14b4fced7
101 changed files with 963 additions and 3928 deletions

View File

@@ -0,0 +1,93 @@
import type { AuthMode, AuthUser } from "@/lib/auth/session";
export type BrowserCurrentSession = {
authenticated: boolean;
authRequired: boolean;
authConfigured: boolean;
authMode: AuthMode | null;
user: AuthUser | null;
};
export async function fetchBrowserAuthState(signal?: AbortSignal): Promise<BrowserCurrentSession> {
const response = await fetch("/api/auth/me", {
method: "GET",
cache: "no-store",
credentials: "same-origin",
headers: { Accept: "application/json" },
signal
});
if (!response.ok) throw new Error(`认证状态请求失败(${response.status})`);
return parseCurrentSession(await response.json());
}
export function safeBrowserNext(value: string | null | undefined, fallback = "/create"): string {
if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\\")) return fallback;
try {
const base = "http://zhinian.local";
const parsed = new URL(value, base);
if (parsed.origin !== base) return fallback;
const path = `${parsed.pathname}${parsed.search}${parsed.hash}`;
if (parsed.pathname.startsWith("/api/auth") || parsed.pathname.startsWith("/auth/login") || parsed.pathname.startsWith("/auth/admin-login")) {
return fallback;
}
return path;
} catch {
return fallback;
}
}
export function safeBrowserLocationNext(
location: Pick<Location, "pathname" | "search" | "hash">,
fallback = "/create"
): string {
return safeBrowserNext(`${location.pathname}${location.search}${location.hash}`, fallback);
}
function parseCurrentSession(value: unknown): BrowserCurrentSession {
if (!isRecord(value) || typeof value.authenticated !== "boolean" || typeof value.authRequired !== "boolean" || typeof value.authConfigured !== "boolean") {
throw new Error("认证状态响应格式无效");
}
if (!value.authenticated) {
if (value.authMode !== null || value.user !== null) throw new Error("认证状态响应格式无效");
return {
authenticated: false,
authRequired: value.authRequired,
authConfigured: value.authConfigured,
authMode: null,
user: null
};
}
if (!value.authConfigured || (value.authMode !== "user" && value.authMode !== "admin") || !isAuthUser(value.user)) {
throw new Error("认证用户响应格式无效");
}
const expectedMode: AuthMode = value.user.role === "user" ? "user" : "admin";
if (value.authMode !== expectedMode) throw new Error("认证用户响应格式无效");
return {
authenticated: true,
authRequired: value.authRequired,
authConfigured: value.authConfigured,
authMode: value.authMode,
user: value.user
};
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function isAuthUser(value: unknown): value is AuthUser {
return isRecord(value) &&
typeof value.id === "string" &&
value.id.length > 0 &&
typeof value.subject === "string" &&
value.subject === value.id &&
typeof value.displayName === "string" &&
typeof value.clientId === "string" &&
(value.role === "user" || value.role === "organization_admin" || value.role === "super_admin") &&
value.status === "active" &&
(value.role === "super_admin" || (typeof value.organizationId === "string" && value.organizationId.length > 0)) &&
Array.isArray(value.authorities) &&
value.authorities.every((item) => typeof item === "string") &&
Array.isArray(value.scope) &&
value.scope.every((item) => typeof item === "string");
}