Files
john 4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search),
MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 21:30:20 +08:00

301 lines
8.8 KiB
TypeScript

import type {
PlazaCategoriesResponse,
PlazaCommentsResponse,
PlazaCreatePostResponse,
PlazaFeedResponse,
PlazaPostResponse,
PlazaSitemapData,
PlazaUserProfileResponse,
} from '@/types/plaza';
import { apiBase } from '@/lib/site';
type FetchOptions = {
revalidate?: number | false;
cache?: RequestCache;
cookie?: string;
};
export type PlazaAuthStatus = {
authenticated: boolean;
user?: { id: string; username: string; slug?: string; displayName: string } | null;
};
class PlazaApiError extends Error {
code: string;
constructor(message: string, code: string) {
super(message);
this.code = code;
}
}
async function plazaFetch<T>(path: string, init?: RequestInit, options?: FetchOptions): Promise<T> {
const base = apiBase();
const url = `${base}${path.startsWith('/') ? path : `/${path}`}`;
const headers = new Headers(init?.headers);
if (options?.cookie) headers.set('cookie', options.cookie);
if (!headers.has('Accept')) headers.set('Accept', 'application/json');
const next =
options?.revalidate === false
? { revalidate: 0 }
: options?.revalidate != null
? { revalidate: options.revalidate }
: undefined;
const response = await fetch(url, {
...init,
headers,
credentials: typeof window !== 'undefined' ? 'include' : init?.credentials,
cache: options?.cache ?? (options?.revalidate === false ? 'no-store' : undefined),
next,
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const code = payload?.error?.code ?? 'request_failed';
const message = payload?.error?.message ?? `请求失败 (${response.status})`;
throw new PlazaApiError(message, code);
}
return payload.data as T;
}
export async function fetchAuthStatus(cookie?: string): Promise<PlazaAuthStatus> {
try {
const base = apiBase();
const url = `${base}/auth/status`;
const headers = new Headers({ Accept: 'application/json' });
if (cookie) headers.set('cookie', cookie);
const response = await fetch(url, {
headers,
credentials: typeof window !== 'undefined' ? 'include' : undefined,
cache: 'no-store',
});
if (!response.ok) return { authenticated: false };
const status = (await response.json()) as PlazaAuthStatus;
return status;
} catch {
return { authenticated: false };
}
}
export async function fetchCategories(options?: FetchOptions) {
return plazaFetch<PlazaCategoriesResponse>('/api/plaza/v1/categories', undefined, {
revalidate: 300,
...options,
});
}
export async function fetchFeed(
params: {
sort?: 'hot' | 'new';
category?: string;
cursor?: string;
limit?: number;
} = {},
options?: FetchOptions,
) {
const query = new URLSearchParams();
if (params.sort) query.set('sort', params.sort);
if (params.category) query.set('category', params.category);
if (params.cursor) query.set('cursor', params.cursor);
if (params.limit) query.set('limit', String(params.limit));
const suffix = query.size ? `?${query.toString()}` : '';
return plazaFetch<PlazaFeedResponse>(`/api/plaza/v1/feed${suffix}`, undefined, {
revalidate: 60,
...options,
});
}
export async function fetchPost(id: string, options?: FetchOptions) {
return plazaFetch<PlazaPostResponse>(`/api/plaza/v1/posts/${encodeURIComponent(id)}`, undefined, {
revalidate: false,
cache: 'no-store',
...options,
});
}
export async function createPlazaPost(
body: {
publication_id: string;
category_id: string;
tags?: string[];
cover_url?: string;
allow_comment?: boolean;
},
cookie: string,
) {
return plazaFetch<PlazaCreatePostResponse>(
'/api/plaza/v1/posts',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
},
{ revalidate: false, cookie },
);
}
export async function togglePostReaction(
postId: string,
type: 'like' | 'collect' | 'share',
active: boolean,
) {
if (type === 'share') {
return plazaFetch<{ post_id: string; type: string; active: boolean }>(
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reactions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'share' }),
},
{ revalidate: false },
);
}
if (active) {
return plazaFetch<{ post_id: string; type: string; active: boolean }>(
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reactions/${type}`,
{ method: 'DELETE' },
{ revalidate: false },
);
}
return plazaFetch<{ post_id: string; type: string; active: boolean }>(
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reactions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type }),
},
{ revalidate: false },
);
}
export async function fetchComments(
postId: string,
params: { parent_id?: string; cursor?: string } = {},
) {
const query = new URLSearchParams();
if (params.parent_id) query.set('parent_id', params.parent_id);
if (params.cursor) query.set('cursor', params.cursor);
const suffix = query.size ? `?${query.toString()}` : '';
return plazaFetch<PlazaCommentsResponse>(
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/comments${suffix}`,
undefined,
{ revalidate: false, cache: 'no-store' },
);
}
export async function createComment(postId: string, content: string, parentId?: string | null) {
return plazaFetch<{ comment: { id: string } }>(
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/comments`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, parent_id: parentId ?? null }),
},
{ revalidate: false },
);
}
export async function toggleCommentLike(commentId: string, liked: boolean) {
return plazaFetch<{ id: string; liked: boolean }>(
`/api/plaza/v1/comments/${encodeURIComponent(commentId)}/reactions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ liked }),
},
{ revalidate: false },
);
}
export async function fetchUserProfile(slug: string, options?: FetchOptions) {
return plazaFetch<PlazaUserProfileResponse>(
`/api/plaza/v1/users/${encodeURIComponent(slug)}`,
undefined,
{ revalidate: 300, ...options },
);
}
export async function fetchUserPosts(
slug: string,
params: { cursor?: string; limit?: number } = {},
options?: FetchOptions,
) {
const query = new URLSearchParams();
if (params.cursor) query.set('cursor', params.cursor);
if (params.limit) query.set('limit', String(params.limit));
const suffix = query.size ? `?${query.toString()}` : '';
return plazaFetch<Pick<PlazaFeedResponse, 'posts' | 'next_cursor' | 'has_more'>>(
`/api/plaza/v1/users/${encodeURIComponent(slug)}/posts${suffix}`,
undefined,
{ revalidate: false, cache: 'no-store', ...options },
);
}
export async function toggleFollow(slug: string, following: boolean) {
if (following) {
return plazaFetch<{ following: boolean }>(
`/api/plaza/v1/users/${encodeURIComponent(slug)}/follow`,
{ method: 'DELETE' },
{ revalidate: false },
);
}
return plazaFetch<{ following: boolean }>(
`/api/plaza/v1/users/${encodeURIComponent(slug)}/follow`,
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' },
{ revalidate: false },
);
}
export async function fetchSitemapData(options?: FetchOptions) {
return plazaFetch<PlazaSitemapData>('/api/plaza/v1/seo/sitemap', undefined, {
revalidate: 3600,
...options,
});
}
export async function recordAttributionEvent(body: {
event_type: 'landing' | 'signup';
utm_source: string;
utm_medium?: string;
utm_campaign?: string;
ref_id?: string;
}) {
return plazaFetch<{ id: string; event_type: string }>(
'/api/plaza/v1/attribution/events',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
},
{ revalidate: false },
);
}
export async function submitPostReport(postId: string, reason: string, detail?: string) {
return plazaFetch<{ report: { id: string; status: string } }>(
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reports`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason, detail: detail ?? '' }),
},
{ revalidate: false },
);
}
export async function submitCommentReport(commentId: string, reason: string, detail?: string) {
return plazaFetch<{ report: { id: string; status: string } }>(
`/api/plaza/v1/comments/${encodeURIComponent(commentId)}/reports`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason, detail: detail ?? '' }),
},
{ revalidate: false },
);
}
export { PlazaApiError };