feat: чанкинг batch-create >100 задач + журнал ошибок API

B: BatchCreateDialog режет список на чанки ≤100 (лимит сервера), каждый
чанк со своим idempotency-key (requestId-N) — повтор после ошибки не
создаёт дублей; 10-минутный лок армируется только при полном успехе,
а не до запроса (раньше блокировал даже после 400).

D: таблица error_logs (миграция 0066), захват ответов >=400 в request
middleware (без 401/health/events, дедуп 60с), admin API GET/DELETE
/api/error-logs и вкладка «Журнал ошибок» в Настройках.
This commit is contained in:
2026-08-03 14:55:29 +03:00
parent 1bb6a53226
commit 1d5321ed0c
8 changed files with 406 additions and 21 deletions

View File

@@ -3698,6 +3698,27 @@ export const idempotencyKeys = pgTable("idempotency_keys", {
export type IdempotencyKey = typeof idempotencyKeys.$inferSelect;
export type InsertIdempotencyKey = typeof idempotencyKeys.$inferInsert;
// =====================
// Error Logs (серверный журнал ошибок API, просмотр админом в Настройках)
// =====================
export const errorLogs = pgTable("error_logs", {
id: serial("id").primaryKey(),
organizationId: integer("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
userId: integer("user_id"),
method: varchar("method", { length: 10 }).notNull(),
path: text("path").notNull(),
statusCode: integer("status_code").notNull(),
message: text("message"),
createdAt: timestamp("created_at").defaultNow(),
}, (table) => ({
orgCreatedIdx: index("error_logs_org_created_idx").on(table.organizationId, table.createdAt),
statusIdx: index("error_logs_status_idx").on(table.statusCode),
}));
export type ErrorLog = typeof errorLogs.$inferSelect;
export type InsertErrorLog = typeof errorLogs.$inferInsert;
export const FINANCE_COLORS = [
"#2563eb", "#e11d48", "#16a34a", "#f59e0b",
"#8b5cf6", "#06b6d4", "#ea580c", "#84cc16",