完善站点运营设置:分路由管理、登录可见评论、邮件模板内联与关闭注册 SSR。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,11 +10,13 @@ import {
|
||||
type BrandMark,
|
||||
type BrandSlot,
|
||||
type FooterLink,
|
||||
type FooterLinksAlign,
|
||||
} from "./brand";
|
||||
import { CSRF_COOKIE } from "./cookies";
|
||||
import { publishPublicMetaSnapshot } from "./publicMetaSnapshot";
|
||||
import { emitForceLogout } from "./userEvents";
|
||||
|
||||
export type { FooterLink };
|
||||
export type { FooterLink, FooterLinksAlign };
|
||||
|
||||
// API 基础配置
|
||||
// 注意:客户端请求使用相对路径 /api/*,走 Next.js rewrite 代理到后端,
|
||||
@@ -684,6 +686,8 @@ export interface PublicSettings {
|
||||
allow_register: boolean;
|
||||
/** 全站是否开放评论;关闭后前台不展示评论能力 */
|
||||
allow_comments: boolean;
|
||||
/** 评论仅登录可见;缺省 false(游客可读) */
|
||||
comments_require_login: boolean;
|
||||
/** 全站是否开放私聊/群聊;关闭后前台不展示消息入口 */
|
||||
allow_messages: boolean;
|
||||
post_cooldown_hours: number;
|
||||
@@ -749,6 +753,7 @@ const DEFAULT_PUBLIC_SETTINGS: PublicSettings = {
|
||||
footer_links: [],
|
||||
allow_register: true,
|
||||
allow_comments: true,
|
||||
comments_require_login: false,
|
||||
allow_messages: true,
|
||||
post_cooldown_hours: 24,
|
||||
code_block_auto_fold: true,
|
||||
@@ -821,6 +826,7 @@ function normalizePublicSettings(data: Partial<PublicSettings> | null | undefine
|
||||
footer_links: normalizeFooterLinks(data?.footer_links),
|
||||
allow_register: data?.allow_register !== false,
|
||||
allow_comments: data?.allow_comments !== false,
|
||||
comments_require_login: data?.comments_require_login === true,
|
||||
allow_messages: data?.allow_messages !== false,
|
||||
post_cooldown_hours:
|
||||
typeof data?.post_cooldown_hours === "number" && data.post_cooldown_hours >= 0
|
||||
@@ -857,10 +863,52 @@ function normalizePublicSettings(data: Partial<PublicSettings> | null | undefine
|
||||
export async function fetchPublicSettings(): Promise<PublicSettings> {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/settings`, ssrInit());
|
||||
if (!res.ok) return { ...DEFAULT_PUBLIC_SETTINGS };
|
||||
return normalizePublicSettings((await res.json()) as Partial<PublicSettings>);
|
||||
if (!res.ok) {
|
||||
const fallback = { ...DEFAULT_PUBLIC_SETTINGS };
|
||||
publishPublicMetaSnapshot(fallback);
|
||||
return fallback;
|
||||
}
|
||||
const normalized = normalizePublicSettings((await res.json()) as Partial<PublicSettings>);
|
||||
publishPublicMetaSnapshot(normalized);
|
||||
return normalized;
|
||||
} catch {
|
||||
return { ...DEFAULT_PUBLIC_SETTINGS };
|
||||
const fallback = { ...DEFAULT_PUBLIC_SETTINGS };
|
||||
publishPublicMetaSnapshot(fallback);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/** 公开站点运行态(注册/找回/维护等);SSR 与客户端同源字段 */
|
||||
export type PublicSiteState = {
|
||||
allow_register: boolean;
|
||||
register_notice: string;
|
||||
verify_email: boolean;
|
||||
password_reset: boolean;
|
||||
site_url: string;
|
||||
};
|
||||
|
||||
const DEFAULT_SITE_STATE: PublicSiteState = {
|
||||
allow_register: true,
|
||||
register_notice: "",
|
||||
verify_email: false,
|
||||
password_reset: false,
|
||||
site_url: "",
|
||||
};
|
||||
|
||||
export async function fetchSiteState(): Promise<PublicSiteState> {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/site-state`, ssrInit());
|
||||
if (!res.ok) return { ...DEFAULT_SITE_STATE };
|
||||
const data = (await res.json()) as Partial<PublicSiteState>;
|
||||
return {
|
||||
allow_register: data.allow_register !== false,
|
||||
register_notice: typeof data.register_notice === "string" ? data.register_notice : "",
|
||||
verify_email: data.verify_email === true,
|
||||
password_reset: data.password_reset === true,
|
||||
site_url: typeof data.site_url === "string" ? data.site_url : "",
|
||||
};
|
||||
} catch {
|
||||
return { ...DEFAULT_SITE_STATE };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,12 +1002,12 @@ export async function apiLogin(username: string, password: string) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function apiRegister(username: string, email: string, password: string) {
|
||||
export async function apiRegister(username: string, email: string, password: string, code = "") {
|
||||
const res = await fetch("/api/register", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: clientHeaders({ "Content-Type": "application/json" }),
|
||||
body: JSON.stringify({ username, email, password }),
|
||||
body: JSON.stringify({ username, email, password, code }),
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
@@ -1878,19 +1926,6 @@ export async function apiAdminDeleteSitePage(id: number): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiGetAdminSettings(): Promise<
|
||||
PublicSettings & { timeline_git_import?: string }
|
||||
> {
|
||||
const res = await fetchWithRefresh("/api/admin/settings", { headers: clientHeaders() });
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.error || "获取设置失败");
|
||||
return {
|
||||
...normalizePublicSettings(data as Partial<PublicSettings>),
|
||||
timeline_git_import:
|
||||
typeof data.timeline_git_import === "string" ? data.timeline_git_import : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 用户管理(管理员) =====
|
||||
|
||||
// 后台用户列表项:email/最近活跃/登录 IP 仅管理员接口返回
|
||||
@@ -2223,6 +2258,7 @@ export type UpdateSiteSettingsBody = {
|
||||
footer_links?: FooterLink[];
|
||||
allow_register?: boolean;
|
||||
allow_comments?: boolean;
|
||||
comments_require_login?: boolean;
|
||||
allow_messages?: boolean;
|
||||
post_cooldown_hours?: number;
|
||||
code_block_auto_fold?: boolean;
|
||||
@@ -2241,7 +2277,6 @@ export type UpdateSiteSettingsBody = {
|
||||
bg_site_mode?: string;
|
||||
bg_admin_url?: string;
|
||||
bg_admin_mode?: string;
|
||||
timeline_git_import?: string;
|
||||
};
|
||||
|
||||
// 更新站点设置(字段可选);accent 传空串恢复默认主题色
|
||||
@@ -2980,3 +3015,10 @@ export async function apiChatUnreadSummary(): Promise<ChatUnreadSummaryResponse>
|
||||
if (!res.ok) return { total: 0, rooms: [] };
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function apiOperations<T = Record<string, unknown>>(path: string, method = "GET", body?: unknown): Promise<T> {
|
||||
const res = await fetchWithRefresh(path, { method, cache: "no-store", headers: clientHeaders({ "Content-Type": "application/json" }), ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || "操作失败,请重试");
|
||||
return data as T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user