Files
recipe-app/backend/src/receipt-import/receipt-import.controller.ts
T
Nils-Johan Gynther d5f903db98
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Failing after 3m41s
Test Suite / flutter-quality (push) Successful in 2m3s
chore(import): improve error handling and add flyer integration
- Replace BadRequestException with UnauthorizedException for authentication failures in flyer-import and flyer-selection controllers
- Add bulk selection endpoint in FlyerSelectionController for creating multiple selections in one request
- Update FlyerSelectionModule to include new FlyerSelectionMatcherService and FlyerSelectionSyncController
- Extend FlyerSelectionService with createMany method for bulk operations
- Add new DTOs for bulk selection and receipt matching functionality
- Update ReceiptImportService to accept FlyerSelectionService dependency and track successful rows
- Extend SaveReceiptResponse with flyerAutoSync field for receipt-to-flyer matching results
- Add new API paths for flyer import and selection endpoints
- Update Flutter UI to include Flyer import tab and adjust tab controller length
- Add new domain models and repository methods for flyer import functionality
- Update test files to include new FlyerSelectionService dependency
- Modify .kilo plan documentation to reflect current system architecture
2026-05-18 22:51:27 +02:00

114 lines
3.4 KiB
TypeScript

import {
Body,
Controller,
HttpCode,
Post,
Request,
UploadedFile,
UseGuards,
UseInterceptors,
BadRequestException,
UnauthorizedException,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { ReceiptImportService } from './receipt-import.service';
import { ParsedReceiptItem } from './dto/parsed-receipt-item.dto';
import { SaveReceiptDto } from './dto/save-receipt.dto';
import { SaveReceiptResponse } from './dto/save-receipt.response';
import { CreateUnitMappingDto } from './dto/create-unit-mapping.dto';
import { AuthGuard } from '@nestjs/passport';
const ALLOWED_MIMES = [
'image/jpeg',
'image/png',
'image/webp',
'image/heic',
'image/heif',
'application/pdf',
'application/octet-stream', // Flutter Web skickar detta för PDF-filer
];
@Controller('receipt-import')
export class ReceiptImportController {
constructor(private readonly receiptImportService: ReceiptImportService) {}
@HttpCode(200)
@Post()
@Throttle({ default: { ttl: 60_000, limit: 20 } })
@UseInterceptors(
FileInterceptor('file', {
storage: memoryStorage(),
limits: { fileSize: 15 * 1024 * 1024 }, // 15 MB
}),
)
async parseReceipt(
@UploadedFile() file?: Express.Multer.File,
@Request() req?: any,
): Promise<ParsedReceiptItem[]> {
if (!file?.buffer) {
throw new BadRequestException('Ingen fil skickades med.');
}
if (!ALLOWED_MIMES.includes(file.mimetype)) {
throw new BadRequestException(
'Otillåten filtyp. Använd JPEG, PNG, WebP eller PDF.',
);
}
const isPremium = req?.user?.isPremium === true || req?.user?.role === 'admin';
const userId = typeof req?.user?.id === 'number' ? req.user.id : undefined;
return this.receiptImportService.parseReceipt(file, isPremium, userId);
}
@Post('unit-mappings')
@UseGuards(AuthGuard('jwt'))
async upsertUnitMapping(
@Body() dto: CreateUnitMappingDto,
@Request() req?: any,
) {
const userId =
typeof req?.user?.id === 'number'
? req.user.id
: typeof req?.user?.userId === 'number'
? req.user.userId
: undefined;
if (!userId) {
throw new UnauthorizedException('Kunde inte identifiera användaren.');
}
return this.receiptImportService.upsertUnitMapping(
userId,
dto.productId,
dto.originalUnit,
dto.preferredUnit,
);
}
@HttpCode(200)
@Post('save')
@UseGuards(AuthGuard('jwt'))
@Throttle({ default: { ttl: 60_000, limit: 10 } })
async saveReceipt(
@Body() dto: SaveReceiptDto,
@Request() req?: any,
): Promise<SaveReceiptResponse> {
const userId =
typeof req?.user?.id === 'number'
? req.user.id
: typeof req?.user?.userId === 'number'
? req.user.userId
: undefined;
if (!userId) {
throw new UnauthorizedException('Kunde inte identifiera användaren.');
}
const isAdmin = req?.user?.role === 'admin';
if (dto.items.some((item) => item.learnAliasGlobally === true) && !isAdmin) {
throw new BadRequestException('Endast administratörer kan spara globala aliaser.');
}
return this.receiptImportService.saveReceipt(userId, dto);
}
}