diff --git a/apps/api/src/app/cell-mapping/cell-mapping.controller.ts b/apps/api/src/app/cell-mapping/cell-mapping.controller.ts index b33c44688..f35205e47 100644 --- a/apps/api/src/app/cell-mapping/cell-mapping.controller.ts +++ b/apps/api/src/app/cell-mapping/cell-mapping.controller.ts @@ -14,7 +14,7 @@ import { AuthGuard } from '@nestjs/passport'; import { CellMappingService } from './cell-mapping.service'; -@Controller('api/v1/cell-mapping') +@Controller('cell-mapping') export class CellMappingController { public constructor( private readonly cellMappingService: CellMappingService diff --git a/apps/api/src/app/k1-import/k1-import.controller.ts b/apps/api/src/app/k1-import/k1-import.controller.ts index fe10d28b3..b9ab91c0b 100644 --- a/apps/api/src/app/k1-import/k1-import.controller.ts +++ b/apps/api/src/app/k1-import/k1-import.controller.ts @@ -26,7 +26,7 @@ import { ConfirmK1Dto } from './dto/confirm-k1.dto'; import { VerifyK1Dto } from './dto/verify-k1.dto'; import { K1ImportService } from './k1-import.service'; -@Controller('api/v1/k1-import') +@Controller('k1-import') export class K1ImportController { public constructor( private readonly k1ImportService: K1ImportService, diff --git a/apps/api/src/app/k1-import/k1-import.module.ts b/apps/api/src/app/k1-import/k1-import.module.ts index f75c2a643..995eeed35 100644 --- a/apps/api/src/app/k1-import/k1-import.module.ts +++ b/apps/api/src/app/k1-import/k1-import.module.ts @@ -2,6 +2,11 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration/conf import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { Module } from '@nestjs/common'; +import { MulterModule } from '@nestjs/platform-express'; +import { diskStorage } from 'multer'; +import { existsSync, mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { v4 as uuidv4 } from 'uuid'; import { CellMappingModule } from '../cell-mapping/cell-mapping.module'; import { UploadModule } from '../upload/upload.module'; @@ -15,10 +20,40 @@ import { AzureExtractor } from './extractors/azure-extractor'; import { PdfParseExtractor } from './extractors/pdf-parse-extractor'; import { TesseractExtractor } from './extractors/tesseract-extractor'; +const uploadDir = process.env.UPLOAD_DIR || join(process.cwd(), 'uploads'); + @Module({ controllers: [K1ImportController], exports: [K1ImportService], - imports: [CellMappingModule, ConfigurationModule, PrismaModule, UploadModule], + imports: [ + CellMappingModule, + ConfigurationModule, + MulterModule.register({ + limits: { + fileSize: 25 * 1024 * 1024 // 25 MB + }, + storage: diskStorage({ + destination: (_req, _file, cb) => { + const now = new Date(); + const yearDir = now.getFullYear().toString(); + const monthDir = (now.getMonth() + 1).toString().padStart(2, '0'); + const subDir = join(uploadDir, yearDir, monthDir); + + if (!existsSync(subDir)) { + mkdirSync(subDir, { recursive: true }); + } + + cb(null, subDir); + }, + filename: (_req, file, cb) => { + const ext = file.originalname.split('.').pop(); + cb(null, `${uuidv4()}.${ext}`); + } + }) + }), + PrismaModule, + UploadModule + ], providers: [ AzureExtractor, K1AggregationService, diff --git a/apps/api/src/app/k1-import/k1-import.service.ts b/apps/api/src/app/k1-import/k1-import.service.ts index 69e76c844..2fff6878a 100644 --- a/apps/api/src/app/k1-import/k1-import.service.ts +++ b/apps/api/src/app/k1-import/k1-import.service.ts @@ -476,16 +476,18 @@ export class K1ImportService { // Read file from disk and run extraction asynchronously const fs = await import('fs/promises'); - const filePath = (document as any).url || (document as any).filePath; + const relativePath = (document as any).filePath; - if (!filePath) { + if (!relativePath) { throw new HttpException( 'Cannot determine file path for stored document', StatusCodes.INTERNAL_SERVER_ERROR ); } - const fileBuffer = await fs.readFile(filePath); + const uploadDir = this.uploadService.getUploadDir(); + const fullPath = join(uploadDir, relativePath); + const fileBuffer = await fs.readFile(fullPath); const file = { buffer: fileBuffer, originalname: originalSession.fileName, diff --git a/apps/api/src/app/upload/upload.service.ts b/apps/api/src/app/upload/upload.service.ts index c95b0aca9..838a2f81a 100644 --- a/apps/api/src/app/upload/upload.service.ts +++ b/apps/api/src/app/upload/upload.service.ts @@ -4,8 +4,9 @@ import { HttpException, Injectable } from '@nestjs/common'; import { DocumentType } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { createReadStream, existsSync } from 'node:fs'; -import { mkdir } from 'node:fs/promises'; +import { mkdir, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class UploadService { @@ -51,7 +52,19 @@ export class UploadService { await mkdir(subDir, { recursive: true }); } - const relativePath = `${yearDir}/${monthDir}/${file.filename}`; + // Support both disk storage (file.filename set by multer) and memory storage (file.buffer) + let filename = file.filename; + + if (!filename) { + const ext = (file.originalname || 'file').split('.').pop(); + filename = `${uuidv4()}.${ext}`; + + if (file.buffer) { + await writeFile(join(subDir, filename), file.buffer); + } + } + + const relativePath = `/${yearDir}/${monthDir}/${filename}`; return this.prismaService.document.create({ data: {