Add canvas workflow and harden data import
This commit is contained in:
@@ -75,6 +75,7 @@ type ImportContext = {
|
||||
apiKeyIdMap: Map<string, string>;
|
||||
apiKeyOwnerIdMap: Map<string, string>;
|
||||
columnCache: Map<string, Set<string>>;
|
||||
defaultableColumnCache: Map<string, Set<string>>;
|
||||
};
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -136,11 +137,15 @@ async function importRows(
|
||||
let skipped = 0;
|
||||
const errors: string[] = [];
|
||||
const existingColumns = await getExistingColumns(client, table, context);
|
||||
const defaultableColumns = await getDefaultableColumns(client, table, context);
|
||||
const effectiveAllowedColumns = allowedColumns.filter(col => existingColumns.has(col));
|
||||
|
||||
for (const rawRow of rows) {
|
||||
const row = await normalizeImportRow(table, rawRow as Record<string, unknown>, context);
|
||||
const cols = Object.keys(row).filter(col => effectiveAllowedColumns.includes(col));
|
||||
const cols = Object.keys(row).filter(col => (
|
||||
effectiveAllowedColumns.includes(col)
|
||||
&& !(row[col] == null && defaultableColumns.has(col))
|
||||
));
|
||||
if (!cols.includes('id') || cols.length === 0) {
|
||||
skipped++;
|
||||
errors.push(`${table}: 缺少 id 或没有允许导入的字段`);
|
||||
@@ -235,7 +240,15 @@ async function buildImportContext(
|
||||
apiKeyIdMap.set(oldId, isUuid(oldId) ? oldId : crypto.randomUUID());
|
||||
}
|
||||
const ownerId = findImportedWorkUserId(row);
|
||||
const ownerByEmail = findUserIdByEmail(row, { userIdMap, workIdMap, emailUserIdMap, apiKeyIdMap, apiKeyOwnerIdMap, columnCache: new Map() });
|
||||
const ownerByEmail = findUserIdByEmail(row, {
|
||||
userIdMap,
|
||||
workIdMap,
|
||||
emailUserIdMap,
|
||||
apiKeyIdMap,
|
||||
apiKeyOwnerIdMap,
|
||||
columnCache: new Map(),
|
||||
defaultableColumnCache: new Map(),
|
||||
});
|
||||
const mappedOwnerId = ownerId
|
||||
? (userIdMap.get(ownerId) || ownerId)
|
||||
: ownerByEmail;
|
||||
@@ -266,7 +279,15 @@ async function buildImportContext(
|
||||
}
|
||||
}
|
||||
|
||||
return { userIdMap, workIdMap, emailUserIdMap, apiKeyIdMap, apiKeyOwnerIdMap, columnCache: new Map() };
|
||||
return {
|
||||
userIdMap,
|
||||
workIdMap,
|
||||
emailUserIdMap,
|
||||
apiKeyIdMap,
|
||||
apiKeyOwnerIdMap,
|
||||
columnCache: new Map(),
|
||||
defaultableColumnCache: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
async function normalizeImportRow(table: string, row: Record<string, unknown>, context: ImportContext): Promise<Record<string, unknown>> {
|
||||
@@ -331,6 +352,12 @@ async function normalizeImportRow(table: string, row: Record<string, unknown>, c
|
||||
}
|
||||
|
||||
if (table === 'user_api_keys') {
|
||||
if (typeof next.note !== 'string' || next.note.trim() === '') {
|
||||
next.note = '导入的 API Key';
|
||||
}
|
||||
if (typeof next.type !== 'string' || next.type.trim() === '') {
|
||||
next.type = 'image';
|
||||
}
|
||||
const rawEncrypted = typeof next.api_key_encrypted === 'string' ? next.api_key_encrypted.trim() : '';
|
||||
const rawApiKey = typeof next.apiKey === 'string' ? next.apiKey.trim() : '';
|
||||
const secret = rawApiKey || rawEncrypted;
|
||||
@@ -519,6 +546,29 @@ async function getExistingColumns(
|
||||
return columns;
|
||||
}
|
||||
|
||||
async function getDefaultableColumns(
|
||||
client: Awaited<ReturnType<typeof getDbClient>>,
|
||||
table: string,
|
||||
context: ImportContext,
|
||||
): Promise<Set<string>> {
|
||||
const cached = context.defaultableColumnCache.get(table);
|
||||
if (cached) return cached;
|
||||
|
||||
const [schemaName, tableName] = table.includes('.') ? table.split('.', 2) : ['public', table];
|
||||
const result = await client.query(
|
||||
`SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = $1
|
||||
AND table_name = $2
|
||||
AND is_nullable = 'NO'
|
||||
AND column_default IS NOT NULL`,
|
||||
[schemaName, tableName],
|
||||
);
|
||||
const columns = new Set((result.rows || []).map((row: Record<string, unknown>) => String(row.column_name)));
|
||||
context.defaultableColumnCache.set(table, columns);
|
||||
return columns;
|
||||
}
|
||||
|
||||
function seedUuidMap(map: Map<string, string>, value: unknown): void {
|
||||
if (typeof value === 'string' && value && !isUuid(value) && !map.has(value)) {
|
||||
map.set(value, crypto.randomUUID());
|
||||
|
||||
Reference in New Issue
Block a user