Parse large Excel imports asynchronously to avoid gateway 502 timeouts.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,11 +17,13 @@ import {
|
||||
type ExcelPreviewResult,
|
||||
} from '../common/excel';
|
||||
import {
|
||||
beginExcelImportPrepare,
|
||||
chunkImportMeta,
|
||||
createExcelImportSession,
|
||||
destroyExcelImportSession,
|
||||
getExcelImportPrepareStatus,
|
||||
getExcelImportSessionCaches,
|
||||
resolveExcelImportChunk,
|
||||
type ExcelImportSessionStatus,
|
||||
} from '../common/excel-import-session';
|
||||
|
||||
const ACTIVITY_COLUMNS = [
|
||||
@@ -161,49 +163,45 @@ export class ActivitiesExcelService {
|
||||
return previewExcel(file, ACTIVITY_ALIASES, excelFieldsFromColumns(ACTIVITY_COLUMNS, ['title']));
|
||||
}
|
||||
|
||||
/** فقط خواندن و جلسه؛ بدون درج — برای فایلهای خیلی بزرگ قبل از تکهتکه */
|
||||
async prepare(
|
||||
/**
|
||||
* آپلود فوری + پارس در پسزمینه (جلوگیری از ۵۰۲ nginx روی فایلهای بزرگ).
|
||||
* وضعیت را با prepareStatus پول کنید.
|
||||
*/
|
||||
prepare(
|
||||
actor: Actor,
|
||||
file: Express.Multer.File,
|
||||
mappingRaw?: string,
|
||||
): Promise<ImportContactsResult> {
|
||||
const result: ImportContactsResult = { created: 0, skipped: 0, errors: [] };
|
||||
let rows: Record<string, string>[];
|
||||
try {
|
||||
rows = await readMappedRows(
|
||||
file,
|
||||
ACTIVITY_ALIASES,
|
||||
parseExcelMapping(mappingRaw),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof BadRequestException) throw e;
|
||||
result.errors.push({
|
||||
row: 1,
|
||||
message: e instanceof Error ? e.message : 'خواندن فایل اکسل ناموفق بود',
|
||||
});
|
||||
return result;
|
||||
): ImportContactsResult {
|
||||
if (!file?.buffer?.length) {
|
||||
throw new BadRequestException('فایل ارسال نشده');
|
||||
}
|
||||
|
||||
const MAX_ROWS = 100_000;
|
||||
if (rows.length > MAX_ROWS) {
|
||||
result.errors.push({
|
||||
row: 1,
|
||||
message: `فایل بیش از ${MAX_ROWS} ردیف دارد. فایل را تقسیم کنید.`,
|
||||
});
|
||||
return { ...result, total: rows.length, processed: 0, hasMore: false };
|
||||
}
|
||||
|
||||
const sessionId = createExcelImportSession(actor.organizationId, rows);
|
||||
const mapping = parseExcelMapping(mappingRaw);
|
||||
const sessionId = beginExcelImportPrepare(
|
||||
actor.organizationId,
|
||||
file.buffer,
|
||||
(f) => readMappedRows(f, ACTIVITY_ALIASES, mapping),
|
||||
);
|
||||
return {
|
||||
...result,
|
||||
created: 0,
|
||||
skipped: 0,
|
||||
errors: [],
|
||||
sessionId,
|
||||
total: rows.length,
|
||||
status: 'parsing',
|
||||
total: 0,
|
||||
parsed: 0,
|
||||
processed: 0,
|
||||
hasMore: rows.length > 0,
|
||||
hasMore: true,
|
||||
nextOffset: 0,
|
||||
};
|
||||
}
|
||||
|
||||
prepareStatus(
|
||||
actor: Actor,
|
||||
sessionId: string,
|
||||
): ExcelImportSessionStatus {
|
||||
return getExcelImportPrepareStatus(sessionId, actor.organizationId);
|
||||
}
|
||||
|
||||
async import(
|
||||
actor: Actor,
|
||||
file: Express.Multer.File | undefined,
|
||||
|
||||
@@ -113,6 +113,14 @@ export class ActivitiesController {
|
||||
return this.excelService.prepare(user, file, mapping);
|
||||
}
|
||||
|
||||
@Get('import/prepare/:sessionId')
|
||||
importPrepareStatus(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('sessionId') sessionId: string,
|
||||
) {
|
||||
return this.excelService.prepareStatus(user, sessionId);
|
||||
}
|
||||
|
||||
@Post('import')
|
||||
@UseInterceptors(excelUpload)
|
||||
import(
|
||||
|
||||
Reference in New Issue
Block a user