feat(desktop): drop parked review flow, add publish management
SaaS 侧人工审核后才创建 publish job,desktop 不再做二次审核:移除 manual/waiting_user/parked/from_parked 状态机与 LeaseFromParked 查询, desktop client 只执行发布并新增"发布管理"页(待发布队列 / 历史 / 再次 发送)。同时抽离 @geo/publisher-platforms 共享适配器包、新增 Redis-based desktop_presence 与 publish_record_support,刷新 admin-web 发布弹窗与 媒体库;plan A / spec 文档同步口径。
This commit is contained in:
@@ -1,19 +1,25 @@
|
||||
import { createApiClient, type ApiClient } from "@geo/http-client";
|
||||
import { ApiClientError, createApiClient, type ApiClient } from "@geo/http-client";
|
||||
import type {
|
||||
ApiEnvelope,
|
||||
ApiErrorEnvelope,
|
||||
CompleteDesktopTaskRequest,
|
||||
CreatePublishJobResponse,
|
||||
DesktopArticleContent,
|
||||
DesktopAccountInfo,
|
||||
DesktopClientHeartbeatRequest,
|
||||
DesktopClientHeartbeatResponse,
|
||||
DesktopPublishTaskListResponse,
|
||||
DesktopTaskInfo,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
ExtendDesktopTaskRequest,
|
||||
LeaseDesktopTaskRequest,
|
||||
LeaseDesktopTaskResponse,
|
||||
ParkDesktopTaskRequest,
|
||||
ListDesktopPublishTasksParams,
|
||||
UpsertDesktopAccountRequest,
|
||||
} from "@geo/shared-types";
|
||||
|
||||
import { canUseRendererDevtoolsProxy, rendererDevtoolsFetch } from "../renderer-devtools-proxy";
|
||||
|
||||
type TransportAuthState = "pending" | "registered" | "expired";
|
||||
type TransportSseState = "idle" | "connecting" | "streaming";
|
||||
|
||||
@@ -32,6 +38,7 @@ const transportState = {
|
||||
baseURL: transportSession.baseURL,
|
||||
initializedAt: 0,
|
||||
mode: "rabbitmq-first" as const,
|
||||
requestDebugMode: "main-process" as "main-process" | "renderer-devtools",
|
||||
auth: "pending" as TransportAuthState,
|
||||
sseState: "idle" as TransportSseState,
|
||||
lastHeartbeatAt: 0,
|
||||
@@ -57,6 +64,97 @@ function createDesktopClient(baseURL: string): ApiClient {
|
||||
return client;
|
||||
}
|
||||
|
||||
function currentRequestDebugMode(): "main-process" | "renderer-devtools" {
|
||||
return canUseRendererDevtoolsProxy() ? "renderer-devtools" : "main-process";
|
||||
}
|
||||
|
||||
function resolveDesktopURL(path: string): string {
|
||||
return new URL(path, transportSession.baseURL).toString();
|
||||
}
|
||||
|
||||
async function proxyDesktopRequest<T, B = unknown>(
|
||||
method: "GET" | "POST" | "DELETE",
|
||||
path: string,
|
||||
body?: B,
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
};
|
||||
|
||||
let serializedBody: string | undefined;
|
||||
if (body !== undefined) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
serializedBody = JSON.stringify(body);
|
||||
}
|
||||
if (transportSession.clientToken) {
|
||||
headers.Authorization = `Bearer ${transportSession.clientToken}`;
|
||||
}
|
||||
|
||||
const response = await rendererDevtoolsFetch({
|
||||
url: resolveDesktopURL(path),
|
||||
method,
|
||||
headers,
|
||||
body: serializedBody,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new ApiClientError({ message: response.error });
|
||||
}
|
||||
|
||||
let payload: ApiEnvelope<T> | Partial<ApiErrorEnvelope> | null = null;
|
||||
if (response.bodyText) {
|
||||
try {
|
||||
payload = JSON.parse(response.bodyText) as ApiEnvelope<T> | Partial<ApiErrorEnvelope>;
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorPayload = payload as Partial<ApiErrorEnvelope> | null;
|
||||
throw new ApiClientError({
|
||||
message: errorPayload?.message ?? `desktop_http_${response.status}`,
|
||||
code: errorPayload?.code ?? 50000,
|
||||
status: response.status,
|
||||
detail: errorPayload?.detail,
|
||||
requestId: errorPayload?.request_id,
|
||||
});
|
||||
}
|
||||
|
||||
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
||||
throw new ApiClientError({
|
||||
message: "desktop_invalid_api_envelope",
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
|
||||
return payload.data as T;
|
||||
}
|
||||
|
||||
async function desktopGet<T>(path: string): Promise<T> {
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
if (transportState.requestDebugMode === "renderer-devtools") {
|
||||
return proxyDesktopRequest<T>("GET", path);
|
||||
}
|
||||
return getDesktopApiClient().get<T>(path);
|
||||
}
|
||||
|
||||
async function desktopPost<T, B = unknown>(path: string, body?: B): Promise<T> {
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
if (transportState.requestDebugMode === "renderer-devtools") {
|
||||
return proxyDesktopRequest<T, B>("POST", path, body);
|
||||
}
|
||||
return getDesktopApiClient().post<T, B>(path, body);
|
||||
}
|
||||
|
||||
async function desktopDelete<T>(path: string): Promise<T> {
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
if (transportState.requestDebugMode === "renderer-devtools") {
|
||||
return proxyDesktopRequest<T>("DELETE", path);
|
||||
}
|
||||
return getDesktopApiClient().remove<T>(path);
|
||||
}
|
||||
|
||||
export function initTransport(): ApiClient {
|
||||
if (desktopApiClient) {
|
||||
return desktopApiClient;
|
||||
@@ -64,6 +162,7 @@ export function initTransport(): ApiClient {
|
||||
|
||||
transportState.initializedAt = transportState.initializedAt || Date.now();
|
||||
transportState.baseURL = transportSession.baseURL;
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
desktopApiClient = createDesktopClient(transportSession.baseURL);
|
||||
return desktopApiClient;
|
||||
}
|
||||
@@ -75,6 +174,7 @@ export function configureTransport(session: DesktopRuntimeSessionSyncRequest | n
|
||||
clientToken: null,
|
||||
};
|
||||
transportState.baseURL = transportSession.baseURL;
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
transportState.auth = "pending";
|
||||
desktopApiClient = null;
|
||||
return null;
|
||||
@@ -85,6 +185,7 @@ export function configureTransport(session: DesktopRuntimeSessionSyncRequest | n
|
||||
clientToken: session.client_token,
|
||||
};
|
||||
transportState.baseURL = transportSession.baseURL;
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
transportState.auth = session.client_token ? "registered" : "pending";
|
||||
transportState.initializedAt = Date.now();
|
||||
desktopApiClient = createDesktopClient(transportSession.baseURL);
|
||||
@@ -124,38 +225,86 @@ export function getTransportSnapshot() {
|
||||
export async function heartbeatDesktopClient(
|
||||
payload: DesktopClientHeartbeatRequest,
|
||||
): Promise<DesktopClientHeartbeatResponse> {
|
||||
return getDesktopApiClient().post<DesktopClientHeartbeatResponse, DesktopClientHeartbeatRequest>(
|
||||
return desktopPost<DesktopClientHeartbeatResponse, DesktopClientHeartbeatRequest>(
|
||||
"/api/desktop/clients/heartbeat",
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
export async function offlineDesktopClient(): Promise<void> {
|
||||
await desktopPost<{ server_time: string }, Record<string, never>>(
|
||||
"/api/desktop/clients/offline",
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export async function revokeDesktopClient(): Promise<void> {
|
||||
await desktopPost<{ id: string }, Record<string, never>>(
|
||||
"/api/desktop/clients/revoke",
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export async function listDesktopAccounts(): Promise<DesktopAccountInfo[]> {
|
||||
return getDesktopApiClient().get<DesktopAccountInfo[]>("/api/desktop/accounts");
|
||||
return desktopGet<DesktopAccountInfo[]>("/api/desktop/accounts");
|
||||
}
|
||||
|
||||
export async function getDesktopArticleContent(articleId: number): Promise<DesktopArticleContent> {
|
||||
return getDesktopApiClient().get<DesktopArticleContent>(`/api/desktop/content/articles/${articleId}`);
|
||||
return desktopGet<DesktopArticleContent>(`/api/desktop/content/articles/${articleId}`);
|
||||
}
|
||||
|
||||
export async function listDesktopPublishTasks(
|
||||
params: ListDesktopPublishTasksParams = {},
|
||||
): Promise<DesktopPublishTaskListResponse> {
|
||||
const search = new URLSearchParams();
|
||||
if (params.page) {
|
||||
search.set("page", String(params.page));
|
||||
}
|
||||
if (params.page_size) {
|
||||
search.set("page_size", String(params.page_size));
|
||||
}
|
||||
if (params.title?.trim()) {
|
||||
search.set("title", params.title.trim());
|
||||
}
|
||||
|
||||
const query = search.toString();
|
||||
const suffix = query ? `?${query}` : "";
|
||||
return desktopGet<DesktopPublishTaskListResponse>(`/api/desktop/publish-tasks${suffix}`);
|
||||
}
|
||||
|
||||
export async function retryDesktopPublishTask(taskId: string): Promise<CreatePublishJobResponse> {
|
||||
return desktopPost<CreatePublishJobResponse, Record<string, never>>(
|
||||
`/api/desktop/publish-tasks/${encodeURIComponent(taskId)}/retry`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export async function upsertDesktopAccount(
|
||||
payload: UpsertDesktopAccountRequest,
|
||||
): Promise<DesktopAccountInfo> {
|
||||
return getDesktopApiClient().post<DesktopAccountInfo, UpsertDesktopAccountRequest>(
|
||||
return desktopPost<DesktopAccountInfo, UpsertDesktopAccountRequest>(
|
||||
"/api/desktop/accounts",
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteDesktopAccount(
|
||||
desktopId: string,
|
||||
ifSyncVersion: number,
|
||||
): Promise<DesktopAccountInfo> {
|
||||
const params = new URLSearchParams({ if_sync_version: String(ifSyncVersion) });
|
||||
return desktopDelete<DesktopAccountInfo>(
|
||||
`/api/desktop/accounts/${encodeURIComponent(desktopId)}?${params.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function leaseDesktopTask(
|
||||
request: LeaseDesktopTaskRequest,
|
||||
options: { fromParked?: boolean } = {},
|
||||
): Promise<LeaseDesktopTaskResponse> {
|
||||
const taskId = request.task_id?.trim();
|
||||
const path = taskId ? `/api/desktop/tasks/${taskId}/lease` : "/api/desktop/tasks/lease";
|
||||
const query = options.fromParked ? "?from_parked=true" : "";
|
||||
return getDesktopApiClient().post<LeaseDesktopTaskResponse, LeaseDesktopTaskRequest>(
|
||||
`${path}${query}`,
|
||||
return desktopPost<LeaseDesktopTaskResponse, LeaseDesktopTaskRequest>(
|
||||
path,
|
||||
taskId ? { kind: request.kind } : request,
|
||||
);
|
||||
}
|
||||
@@ -164,27 +313,17 @@ export async function extendDesktopTask(
|
||||
taskId: string,
|
||||
payload: ExtendDesktopTaskRequest,
|
||||
): Promise<DesktopTaskInfo> {
|
||||
return getDesktopApiClient().post<DesktopTaskInfo, ExtendDesktopTaskRequest>(
|
||||
return desktopPost<DesktopTaskInfo, ExtendDesktopTaskRequest>(
|
||||
`/api/desktop/tasks/${taskId}/extend`,
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
export async function parkDesktopTask(
|
||||
taskId: string,
|
||||
payload: ParkDesktopTaskRequest,
|
||||
): Promise<DesktopTaskInfo> {
|
||||
return getDesktopApiClient().post<DesktopTaskInfo, ParkDesktopTaskRequest>(
|
||||
`/api/desktop/tasks/${taskId}/park`,
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
export async function completeDesktopTask(
|
||||
taskId: string,
|
||||
payload: CompleteDesktopTaskRequest,
|
||||
): Promise<DesktopTaskInfo> {
|
||||
return getDesktopApiClient().post<DesktopTaskInfo, CompleteDesktopTaskRequest>(
|
||||
return desktopPost<DesktopTaskInfo, CompleteDesktopTaskRequest>(
|
||||
`/api/desktop/tasks/${taskId}/result`,
|
||||
payload,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user