252 lines
6.6 KiB
TypeScript
252 lines
6.6 KiB
TypeScript
import { z } from "zod";
|
|
import {
|
|
createAiProvider,
|
|
getAiProvidersByType,
|
|
} from "@/lib/db/queries";
|
|
import { ChatSDKError } from "@/lib/errors";
|
|
import { requireAdmin } from "@/lib/middleware/admin";
|
|
import {
|
|
sanitizeWorldLabsApiKey,
|
|
sanitizeWorldLabsBaseUrl,
|
|
} from "@/lib/worldlabs/server";
|
|
import { getWorldLabsKeyPoolConfig } from "@/lib/worldlabs/key-pool-config";
|
|
|
|
export const runtime = "nodejs";
|
|
export const maxDuration = 60;
|
|
|
|
const DEFAULT_WORLDLABS_BASE_URL = "https://api.worldlabs.ai";
|
|
|
|
const importPayloadSchema = z.object({
|
|
keys: z
|
|
.array(
|
|
z.union([
|
|
z.string().min(1),
|
|
z.object({
|
|
apiKey: z.string().min(1),
|
|
name: z.string().min(1).max(128).optional(),
|
|
baseUrl: z.string().min(1).optional(),
|
|
isEnabled: z.boolean().optional(),
|
|
}),
|
|
])
|
|
)
|
|
.max(5000)
|
|
.optional(),
|
|
apiKeysText: z.string().optional(),
|
|
namePrefix: z.string().min(1).max(96).optional(),
|
|
baseUrl: z.string().min(1).optional(),
|
|
isEnabled: z.boolean().optional(),
|
|
});
|
|
|
|
function readImportToken(request: Request) {
|
|
const authHeader = request.headers.get("authorization") || "";
|
|
const bearerToken = authHeader.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
|
|
const customToken =
|
|
request.headers.get("x-worldlabs-import-token")?.trim() || "";
|
|
return bearerToken || customToken || "";
|
|
}
|
|
|
|
function getConfiguredImportToken() {
|
|
return String(
|
|
process.env.WORLDLABS_KEY_POOL_IMPORT_TOKEN ||
|
|
process.env.WORLDLABS_IMPORT_TOKEN ||
|
|
""
|
|
).trim();
|
|
}
|
|
|
|
function normalizeImportItems(payload: z.infer<typeof importPayloadSchema>) {
|
|
const items: Array<{
|
|
apiKey: string;
|
|
name?: string;
|
|
baseUrl?: string;
|
|
isEnabled?: boolean;
|
|
}> = [];
|
|
|
|
for (const item of payload.keys || []) {
|
|
if (typeof item === "string") {
|
|
items.push({ apiKey: item });
|
|
} else {
|
|
items.push(item);
|
|
}
|
|
}
|
|
|
|
const lines = String(payload.apiKeysText || "")
|
|
.split(/\r?\n/g)
|
|
.map((line) => line.trim())
|
|
.filter(Boolean);
|
|
for (const line of lines) {
|
|
items.push({ apiKey: line });
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
function buildProviderName(params: {
|
|
namePrefix: string;
|
|
index: number;
|
|
overrideName?: string;
|
|
}) {
|
|
const customName = String(params.overrideName || "").trim();
|
|
if (customName) return customName.slice(0, 128);
|
|
|
|
const datePart = new Date()
|
|
.toISOString()
|
|
.replace(/[-:TZ.]/g, "")
|
|
.slice(0, 14);
|
|
return `${params.namePrefix}-${datePart}-${String(params.index + 1).padStart(4, "0")}`.slice(
|
|
0,
|
|
128
|
|
);
|
|
}
|
|
|
|
function maskKey(value: string) {
|
|
if (value.length <= 10) {
|
|
return `${value.slice(0, 2)}****`;
|
|
}
|
|
return `${value.slice(0, 4)}****${value.slice(-4)}`;
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const dbConfig = await getWorldLabsKeyPoolConfig();
|
|
const configuredToken = dbConfig.importToken || getConfiguredImportToken();
|
|
const requestToken = readImportToken(request);
|
|
|
|
let authorizedByAdmin = false;
|
|
try {
|
|
await requireAdmin();
|
|
authorizedByAdmin = true;
|
|
} catch (_error) {
|
|
authorizedByAdmin = false;
|
|
}
|
|
|
|
if (!authorizedByAdmin) {
|
|
if (!configuredToken) {
|
|
return Response.json(
|
|
{
|
|
success: false,
|
|
error:
|
|
"服务端未配置 World Labs key 池导入令牌,无法使用专用导入接口",
|
|
},
|
|
{ status: 503 }
|
|
);
|
|
}
|
|
|
|
if (!requestToken || requestToken !== configuredToken) {
|
|
return Response.json(
|
|
{ success: false, error: "导入令牌无效" },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
}
|
|
|
|
const rawBody = await request.json().catch(() => ({}));
|
|
const payload = importPayloadSchema.parse(rawBody);
|
|
const inputItems = normalizeImportItems(payload);
|
|
|
|
if (inputItems.length === 0) {
|
|
return Response.json(
|
|
{ success: false, error: "请至少提供一个 World Labs API Key" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const existingProviders = await getAiProvidersByType({
|
|
type: "worldlabs-3d",
|
|
enabledOnly: false,
|
|
});
|
|
const existingKeySet = new Set(
|
|
existingProviders
|
|
.map((provider) => sanitizeWorldLabsApiKey(String(provider.apiKey || "")))
|
|
.filter(Boolean)
|
|
);
|
|
|
|
const defaultBaseUrl =
|
|
sanitizeWorldLabsBaseUrl(payload.baseUrl || "") ||
|
|
DEFAULT_WORLDLABS_BASE_URL;
|
|
const defaultIsEnabled = payload.isEnabled !== false;
|
|
const namePrefix = String(payload.namePrefix || "WorldLabsKey").trim();
|
|
|
|
const dedupedImports: Array<{
|
|
apiKey: string;
|
|
name?: string;
|
|
baseUrl: string;
|
|
isEnabled: boolean;
|
|
}> = [];
|
|
const requestKeySet = new Set<string>();
|
|
const skipped: Array<{ apiKey: string; reason: string }> = [];
|
|
|
|
for (const item of inputItems) {
|
|
const apiKey = sanitizeWorldLabsApiKey(item.apiKey);
|
|
if (!apiKey) {
|
|
skipped.push({ apiKey: "", reason: "empty_key" });
|
|
continue;
|
|
}
|
|
if (existingKeySet.has(apiKey)) {
|
|
skipped.push({ apiKey: maskKey(apiKey), reason: "duplicate_existing" });
|
|
continue;
|
|
}
|
|
if (requestKeySet.has(apiKey)) {
|
|
skipped.push({ apiKey: maskKey(apiKey), reason: "duplicate_in_request" });
|
|
continue;
|
|
}
|
|
|
|
requestKeySet.add(apiKey);
|
|
dedupedImports.push({
|
|
apiKey,
|
|
name: item.name,
|
|
baseUrl:
|
|
sanitizeWorldLabsBaseUrl(item.baseUrl || "") || defaultBaseUrl,
|
|
isEnabled: item.isEnabled ?? defaultIsEnabled,
|
|
});
|
|
}
|
|
|
|
const imported = [];
|
|
for (const [index, item] of dedupedImports.entries()) {
|
|
const created = await createAiProvider({
|
|
name: buildProviderName({
|
|
namePrefix,
|
|
index,
|
|
overrideName: item.name,
|
|
}),
|
|
type: "worldlabs-3d",
|
|
baseUrl: item.baseUrl,
|
|
apiKey: item.apiKey,
|
|
isEnabled: item.isEnabled,
|
|
});
|
|
imported.push({
|
|
id: created[0]?.id,
|
|
name: created[0]?.name,
|
|
isEnabled: created[0]?.isEnabled === true,
|
|
});
|
|
}
|
|
|
|
return Response.json({
|
|
success: true,
|
|
data: {
|
|
importedCount: imported.length,
|
|
skippedCount: skipped.length,
|
|
imported,
|
|
skipped,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof ChatSDKError) {
|
|
return error.toResponse();
|
|
}
|
|
if (error instanceof z.ZodError) {
|
|
return Response.json(
|
|
{ success: false, error: error.flatten() },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
return Response.json(
|
|
{
|
|
success: false,
|
|
error:
|
|
error instanceof Error ? error.message : "导入 World Labs key 失败",
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|