feat: add image handling to recipes
- Implemented image downloading and optimization in QuickImportService. - Added imageUrl field to CreateRecipeDto for recipe creation. - Created an endpoint in RecipesController to update recipe images. - Enhanced RecipesService to handle image URL updates and optimizations. - Updated Docker Compose to mount a volume for recipe images. - Refactored frontend to display images in recipe grids and detail views. - Added a new utility function for downloading and optimizing images. - Created a new API route for handling image uploads. - Introduced RecipeGrid component for better recipe display. - Updated RecipeDetailClient to manage image updates and display. - Added migration for new imageUrl column in the Recipe table.
This commit is contained in:
@@ -38,6 +38,10 @@ export class CreateRecipeDto {
|
||||
@IsString()
|
||||
instructions?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
imageUrl?: string;
|
||||
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, Param, ParseIntPipe, Post, Patch } from '@nestjs/common';
|
||||
import { IsString } from 'class-validator';
|
||||
import { RecipesService } from './recipes.service';
|
||||
import { CreateRecipeDto } from './dto/create-recipe.dto';
|
||||
import { ParseMarkdownDto } from './dto/parse-markdown.dto';
|
||||
|
||||
class UpdateImageDto {
|
||||
@IsString()
|
||||
sourceUrl!: string;
|
||||
}
|
||||
|
||||
@Controller('recipes')
|
||||
export class RecipesController {
|
||||
constructor(private readonly recipesService: RecipesService) {}
|
||||
@@ -45,4 +51,12 @@ export class RecipesController {
|
||||
async remove(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.recipesService.remove(id);
|
||||
}
|
||||
|
||||
@Post(':id/image')
|
||||
async updateImage(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateImageDto,
|
||||
) {
|
||||
return this.recipesService.updateImage(id, dto.sourceUrl);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateRecipeDto } from './dto/create-recipe.dto';
|
||||
import { ParseMarkdownDto } from './dto/parse-markdown.dto';
|
||||
import { downloadAndOptimizeImage } from '../common/utils/download-image';
|
||||
|
||||
const IMAGE_DEST_DIR = process.env.IMAGE_DEST_DIR || '/app/recipe-images';
|
||||
|
||||
// Lokala typdefiniitioner (tidigare från recipe-document-converter)
|
||||
interface ParsedIngredient {
|
||||
@@ -340,6 +343,7 @@ export class RecipesService {
|
||||
name: updateRecipeDto.name,
|
||||
description: updateRecipeDto.description || null,
|
||||
instructions: updateRecipeDto.instructions || null,
|
||||
...(updateRecipeDto.imageUrl !== undefined && { imageUrl: updateRecipeDto.imageUrl || null }),
|
||||
ingredients: {
|
||||
create: updateRecipeDto.ingredients.map((ingredient) => ({
|
||||
productId: ingredient.productId,
|
||||
@@ -374,12 +378,39 @@ export class RecipesService {
|
||||
await this.prisma.recipe.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async updateImage(id: number, sourceUrl: string) {
|
||||
const existingRecipe = await this.prisma.recipe.findUnique({ where: { id } });
|
||||
if (!existingRecipe) {
|
||||
throw new NotFoundException(`Recipe with id ${id} not found`);
|
||||
}
|
||||
|
||||
const imageUrl = await downloadAndOptimizeImage(sourceUrl, IMAGE_DEST_DIR);
|
||||
|
||||
return this.prisma.recipe.update({
|
||||
where: { id },
|
||||
data: { imageUrl },
|
||||
include: { ingredients: { include: { product: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
async create(createRecipeDto: CreateRecipeDto) {
|
||||
// Om imageUrl är en extern URL — ladda ner och optimera
|
||||
let imageUrl: string | null = createRecipeDto.imageUrl || null;
|
||||
if (imageUrl && imageUrl.startsWith('http')) {
|
||||
try {
|
||||
imageUrl = await downloadAndOptimizeImage(imageUrl, IMAGE_DEST_DIR);
|
||||
} catch (err) {
|
||||
console.warn('[RecipesService] Kunde inte ladda ner receptbild:', err);
|
||||
imageUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
const recipe = await this.prisma.recipe.create({
|
||||
data: {
|
||||
name: createRecipeDto.name,
|
||||
description: createRecipeDto.description || null,
|
||||
instructions: createRecipeDto.instructions || null,
|
||||
imageUrl,
|
||||
ingredients: {
|
||||
create: createRecipeDto.ingredients.map((ingredient) => ({
|
||||
productId: ingredient.productId,
|
||||
|
||||
Reference in New Issue
Block a user