import { isManagerRequest, managerForbidden } from "../../../lib/user-auth"; import { runInBackground } from "../../../lib/background"; import { parseResourceImportFile, RESOURCE_IMPORT_MAX_BYTES, RESOURCE_IMPORT_MAX_ROWS, resourceImportMissingFields, type ResourceImportRow, } from "../../../lib/resource-import"; import { analyzeRows, enrichRows, loadAccounts, mergeExistingFields, writeAnalyzedRows, RESOURCE_IMPORT_SYNC_ENRICH_ROWS, type AnalyzedRow, type AccountRow, } from "../../../lib/resource-write"; const RESOURCE_IMPORT_PREVIEW_ROWS = 100; function summarize(rows: AnalyzedRow[]) { return { total: rows.length, create: rows.filter((row) => row.action === "create").length, update: rows.filter((row) => row.action === "update").length, error: rows.filter((row) => row.action === "error").length, }; } function previewAnalyzedRows(rows: AnalyzedRow[]) { const errorRows = rows.filter((row) => row.action === "error"); if (errorRows.length === 0) { return rows.slice(0, RESOURCE_IMPORT_PREVIEW_ROWS); } const importableRows = rows.filter((row) => row.action !== "error"); return [ ...errorRows.slice(0, RESOURCE_IMPORT_PREVIEW_ROWS), ...importableRows.slice( 0, Math.max(0, RESOURCE_IMPORT_PREVIEW_ROWS - errorRows.length), ), ]; } function deferredEnrichmentCount(rows: ResourceImportRow[]) { return rows.filter( (row) => row.errors.length === 0 && row.profileUrl && resourceImportMissingFields(row).length > 0, ).length; } async function enrichImportedRowsInBackground(rows: ResourceImportRow[]) { const accounts = await loadAccounts(); const baselineRows = mergeExistingFields(rows, accounts.results); const missingRows = baselineRows.filter( (row) => row.errors.length === 0 && row.profileUrl && resourceImportMissingFields(row).length > 0, ); if (missingRows.length === 0) return; const enriched = await enrichRows(missingRows, accounts.results); const latestAccounts = await loadAccounts(); const analyzed = analyzeRows(enriched, latestAccounts.results).filter( (row) => row.action !== "error", ); await writeAnalyzedRows(analyzed); } export async function POST(request: Request) { if (!(await isManagerRequest(request))) return managerForbidden(); try { const form = await request.formData(); const file = form.get("file"); const mode = String(form.get("mode") ?? "preview"); if (!(file instanceof File)) { return Response.json({ error: "请选择需要导入的 Excel 文件" }, { status: 400 }); } if (file.size <= 0 || file.size > RESOURCE_IMPORT_MAX_BYTES) { return Response.json({ error: "文件不能为空,且不能超过 20MB" }, { status: 400 }); } const rows = parseResourceImportFile(file.name, new Uint8Array(await file.arrayBuffer())); const accounts = await loadAccounts(); const accountRows: AccountRow[] = accounts.results; const shouldEnrichSynchronously = rows.length <= RESOURCE_IMPORT_SYNC_ENRICH_ROWS; const preparedRows = shouldEnrichSynchronously ? await enrichRows(rows, accountRows) : mergeExistingFields(rows, accountRows); const analyzed = analyzeRows(preparedRows, accountRows); const summary = summarize(analyzed); const importableRows = analyzed.filter((row) => row.action !== "error"); const importableRowNumbers = new Set( importableRows.map((row) => row.rowNumber), ); const importablePreparedRows = preparedRows.filter((row) => importableRowNumbers.has(row.rowNumber), ); const deferredEnrichment = shouldEnrichSynchronously ? 0 : deferredEnrichmentCount(importablePreparedRows); if (mode !== "commit") { return Response.json({ summary, rows: previewAnalyzedRows(analyzed).map((row) => ({ rowNumber: row.rowNumber, platform: row.platform, nickname: row.nickname, publicAccountId: row.publicAccountId, profileUrl: row.profileUrl, ipLocation: row.ipLocation, followers: row.followers, gender: row.gender, bio: row.bio, tags: row.tags, cooperationSource: row.cooperationSource, action: row.action, errors: row.errors, })), truncated: analyzed.length > RESOURCE_IMPORT_PREVIEW_ROWS, deferredEnrichment, maxRows: RESOURCE_IMPORT_MAX_ROWS, }); } if (importableRows.length === 0) { return Response.json( { error: "没有可导入的有效账号,请修正异常数据后重新上传", summary }, { status: 400 }, ); } await writeAnalyzedRows(importableRows); if (deferredEnrichment > 0) { const importableSourceRows = rows.filter((row) => importableRowNumbers.has(row.rowNumber), ); runInBackground( enrichImportedRowsInBackground(importableSourceRows), "bulk resource profile enrichment", ); } return Response.json({ summary, deferredEnrichment, message: `已导入 ${summary.create} 个新账号,更新 ${summary.update} 个已有账号${ summary.error > 0 ? `;跳过 ${summary.error} 条异常数据` : "" }${ deferredEnrichment > 0 ? `;${deferredEnrichment} 个账号的缺失公开资料将在后台补全` : "" }`, }); } catch (error) { const message = error instanceof Error ? error.message : "导入失败"; return Response.json({ error: message }, { status: 400 }); } }