first commit
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user