diff --git a/Dockerfile.backend b/Dockerfile.backend index 6229d9a..e866de5 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -5,6 +5,11 @@ COPY package.json package-lock.json* ./ COPY shared ./shared COPY backend ./backend +# Увеличенные таймауты для нестабильной сети +RUN npm config set fetch-retry-mintimeout 20000 && \ + npm config set fetch-retry-maxtimeout 120000 && \ + npm config set fetch-timeout 300000 + RUN npm install FROM node:20-alpine AS build @@ -22,6 +27,10 @@ FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production +# @napi-rs/canvas (dep of pdf-parse) ships a musl pre-built binary that +# needs these compatibility / font libraries at runtime. +RUN apk add --no-cache libc6-compat fontconfig freetype + COPY --from=build /app/backend/dist ./dist COPY --from=build /app/backend/package.json ./package.json COPY --from=build /app/node_modules ./node_modules diff --git a/Dockerfile.frontend b/Dockerfile.frontend index 0b3f1b1..3fdd469 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -9,6 +9,11 @@ COPY package.json package-lock.json* tsconfig.json* ./ COPY shared ./shared COPY frontend ./frontend +# Увеличиваем таймауты и повторы для нестабильной сети +RUN npm config set fetch-retry-mintimeout 20000 && \ + npm config set fetch-retry-maxtimeout 120000 && \ + npm config set fetch-timeout 300000 + # Устанавливаем зависимости из корня RUN npm install diff --git a/backend/src/db/pool.ts b/backend/src/db/pool.ts index 2d5895c..8d64f43 100644 --- a/backend/src/db/pool.ts +++ b/backend/src/db/pool.ts @@ -7,4 +7,5 @@ export const pool = new Pool({ database: config.db.database, user: config.db.user, password: config.db.password, + connectionTimeoutMillis: 5000, }); diff --git a/backend/src/services/pdfToStatement.ts b/backend/src/services/pdfToStatement.ts index def9b65..3f1e16a 100644 --- a/backend/src/services/pdfToStatement.ts +++ b/backend/src/services/pdfToStatement.ts @@ -1,4 +1,3 @@ -import { PDFParse } from 'pdf-parse'; import OpenAI from 'openai'; import { config } from '../config'; import type { StatementFile } from '@family-budget/shared'; @@ -64,6 +63,18 @@ export function isPdfConversionError(r: unknown): r is PdfConversionError { ); } +// Lazy-loaded to avoid crashing the app at startup — pdf-parse pulls in +// @napi-rs/canvas (native Skia binary) which may fail on Alpine. +let _PDFParse: typeof import('pdf-parse')['PDFParse'] | undefined; +function loadPDFParse() { + if (!_PDFParse) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const mod = require('pdf-parse') as typeof import('pdf-parse'); + _PDFParse = mod.PDFParse; + } + return _PDFParse; +} + export async function convertPdfToStatement( buffer: Buffer, ): Promise { @@ -77,6 +88,7 @@ export async function convertPdfToStatement( let text: string; try { + const PDFParse = loadPDFParse(); const parser = new PDFParse({ data: buffer }); const result = await parser.getText(); text = result.text || ''; diff --git a/frontend/src/components/ImportModal.tsx b/frontend/src/components/ImportModal.tsx index 8bc7208..107ffda 100644 --- a/frontend/src/components/ImportModal.tsx +++ b/frontend/src/components/ImportModal.tsx @@ -89,7 +89,7 @@ export function ImportModal({ onClose, onDone }: Props) { {result && (
-
+

Импорт завершён