Files
dailydish/app/Services/RecipeIngestService.php
T

294 lines
8.8 KiB
PHP
Raw Normal View History

2026-08-09 20:15:40 +03:00
<?php
namespace App\Services;
use App\Dtos\RecipeDto;
use App\Models\Ingredient;
use App\Models\Recipe;
use App\Models\RecipeImage;
use App\Models\RecipeNutrition;
use App\Models\RecipeStep;
use App\Models\Tag;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RecipeIngestService
{
/**
* Ingest a recipe from DTO - create or update
*
* @param RecipeDto $recipeDto
* @return Recipe
*/
public function ingest(RecipeDto $recipeDto): Recipe
{
return DB::transaction(function () use ($recipeDto) {
// Find existing recipe by slug or create new
$recipe = Recipe::where('slug', $recipeDto->slug)->first();
if ($recipe) {
// Update existing recipe
$this->updateRecipe($recipe, $recipeDto);
} else {
// Create new recipe
$recipe = $this->createRecipe($recipeDto);
}
// Sync related data
$this->syncIngredients($recipe, $recipeDto);
$this->syncSteps($recipe, $recipeDto);
$this->syncImages($recipe, $recipeDto);
$this->syncNutrition($recipe, $recipeDto);
$this->syncTags($recipe, $recipeDto);
return $recipe->load(['ingredients', 'steps', 'images', 'nutrition', 'tags']);
});
}
/**
* Create a new recipe
*
* @param RecipeDto $dto
* @return Recipe
*/
protected function createRecipe(RecipeDto $dto): Recipe
{
return Recipe::create([
'title' => $dto->title,
'slug' => $dto->slug,
'description' => $dto->description,
'prep_time' => $dto->prepTime,
'cook_time' => $dto->cookTime,
'total_time' => $dto->totalTime,
'servings' => $dto->servings,
'difficulty' => $dto->difficulty,
'cuisine' => $dto->cuisine,
'category' => $dto->category,
'calories' => $dto->calories,
'featured_image' => $dto->featuredImage,
'video_url' => $dto->videoUrl,
'status' => $dto->status ?? 0,
'views_count' => $dto->viewsCount ?? 0,
'rating_avg' => $dto->ratingAvg,
'rating_count' => $dto->ratingCount ?? 0,
]);
}
/**
* Update an existing recipe
*
* @param Recipe $recipe
* @param RecipeDto $dto
* @return void
*/
protected function updateRecipe(Recipe $recipe, RecipeDto $dto): void
{
$updates = [];
// Check each field for changes
if ($dto->title !== null && $dto->title !== $recipe->title) {
$updates['title'] = $dto->title;
}
if ($dto->description !== null && $dto->description !== $recipe->description) {
$updates['description'] = $dto->description;
}
if ($dto->prepTime !== null && $dto->prepTime !== $recipe->prep_time) {
$updates['prep_time'] = $dto->prepTime;
}
if ($dto->cookTime !== null && $dto->cookTime !== $recipe->cook_time) {
$updates['cook_time'] = $dto->cookTime;
}
if ($dto->totalTime !== null && $dto->totalTime !== $recipe->total_time) {
$updates['total_time'] = $dto->totalTime;
}
if ($dto->servings !== null && $dto->servings !== $recipe->servings) {
$updates['servings'] = $dto->servings;
}
if ($dto->difficulty !== null && $dto->difficulty !== $recipe->difficulty) {
$updates['difficulty'] = $dto->difficulty;
}
if ($dto->cuisine !== null && $dto->cuisine !== $recipe->cuisine) {
$updates['cuisine'] = $dto->cuisine;
}
if ($dto->category !== null && $dto->category !== $recipe->category) {
$updates['category'] = $dto->category;
}
if ($dto->calories !== null && $dto->calories !== $recipe->calories) {
$updates['calories'] = $dto->calories;
}
if ($dto->featuredImage !== null && $dto->featuredImage !== $recipe->featured_image) {
$updates['featured_image'] = $dto->featuredImage;
}
if ($dto->videoUrl !== null && $dto->videoUrl !== $recipe->video_url) {
$updates['video_url'] = $dto->videoUrl;
}
if ($dto->status !== null && $dto->status !== $recipe->status) {
$updates['status'] = $dto->status;
}
if (!empty($updates)) {
$recipe->update($updates);
}
}
/**
* Sync recipe ingredients
*
* @param Recipe $recipe
* @param RecipeDto $dto
* @return void
*/
protected function syncIngredients(Recipe $recipe, RecipeDto $dto): void
{
if (empty($dto->ingredients)) {
return;
}
$syncData = [];
foreach ($dto->ingredients as $ingredientDto) {
$slug = $ingredientDto->ingredientSlug;
if (!$slug) {
$slug = Str::slug($ingredientDto->ingredientName);
}
// Find or create ingredient
$ingredient = Ingredient::firstOrCreate(
['slug' => $slug],
[
'name' => $ingredientDto->ingredientName,
'category' => $ingredientDto->ingredientCategory,
]
);
// Prepare pivot data
$syncData[$ingredient->id] = [
'quantity' => $ingredientDto->quantity,
'unit' => $ingredientDto->unit,
'notes' => $ingredientDto->notes,
'sort_order' => $ingredientDto->sortOrder ?? 0,
];
}
$recipe->ingredients()->sync($syncData);
}
/**
* Sync recipe steps
*
* @param Recipe $recipe
* @param RecipeDto $dto
* @return void
*/
protected function syncSteps(Recipe $recipe, RecipeDto $dto): void
{
if (empty($dto->steps)) {
// Remove all steps if none provided
$recipe->steps()->delete();
return;
}
// Delete existing steps and recreate
$recipe->steps()->delete();
foreach ($dto->steps as $stepDto) {
RecipeStep::create([
'recipe_id' => $recipe->id,
'step_number' => $stepDto->stepNumber,
'title' => $stepDto->title,
'instruction' => $stepDto->instruction,
'image' => $stepDto->image,
'time_estimate' => $stepDto->timeEstimate,
'sort_order' => $stepDto->sortOrder ?? 0,
]);
}
}
/**
* Sync recipe images
*
* @param Recipe $recipe
* @param RecipeDto $dto
* @return void
*/
protected function syncImages(Recipe $recipe, RecipeDto $dto): void
{
if (empty($dto->images)) {
return;
}
// Delete existing images and recreate
$recipe->images()->delete();
foreach ($dto->images as $imageDto) {
RecipeImage::create([
'recipe_id' => $recipe->id,
'image_path' => $imageDto->imagePath,
'title' => $imageDto->title,
'description' => $imageDto->description,
'is_featured' => $imageDto->isFeatured ?? false,
'sort_order' => $imageDto->sortOrder ?? 0,
]);
}
}
/**
* Sync recipe nutrition
*
* @param Recipe $recipe
* @param RecipeDto $dto
* @return void
*/
protected function syncNutrition(Recipe $recipe, RecipeDto $dto): void
{
if ($dto->nutrition === null) {
return;
}
RecipeNutrition::updateOrCreate(
['recipe_id' => $recipe->id],
[
'calories' => $dto->nutrition->calories,
'protein' => $dto->nutrition->protein,
'carbohydrates' => $dto->nutrition->carbohydrates,
'fat' => $dto->nutrition->fat,
'saturated_fat' => $dto->nutrition->saturatedFat,
'fiber' => $dto->nutrition->fiber,
'sugar' => $dto->nutrition->sugar,
'sodium' => $dto->nutrition->sodium,
'cholesterol' => $dto->nutrition->cholesterol,
]
);
}
/**
* Sync recipe tags
*
* @param Recipe $recipe
* @param RecipeDto $dto
* @return void
*/
protected function syncTags(Recipe $recipe, RecipeDto $dto): void
{
if (empty($dto->tags)) {
return;
}
$tagIds = [];
foreach ($dto->tags as $tagDto) {
// Find or create tag
$tag = Tag::firstOrCreate(
['slug' => $tagDto->slug],
['name' => $tagDto->name]
);
$tagIds[] = $tag->id;
}
$recipe->tags()->sync($tagIds);
}
}