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
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>
301 lines
8.8 KiB
TypeScript
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 };
|