feat: complete mealplanner app (backend + frontend + deployment)
.NET 8 backend with Zitadel JWT auth, TheMealDB integration, weekly meal plan generation, shopping list aggregation. Vue 3 + Tailwind 4 frontend with dark emerald theme, manual OIDC PKCE auth, all views implemented. Multi-stage Dockerfile with nginx reverse proxy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
backend/Controllers/RecipeController.cs
Normal file
74
backend/Controllers/RecipeController.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MealPlanner.Models;
|
||||
using MealPlanner.Services;
|
||||
|
||||
namespace MealPlanner.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/recipes")]
|
||||
[Authorize]
|
||||
public class RecipeController(RecipeService recipeService) : ControllerBase
|
||||
{
|
||||
private string UserId => User.FindFirst("sub")?.Value ?? throw new UnauthorizedAccessException();
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetOwn()
|
||||
{
|
||||
var recipes = await recipeService.GetOwnRecipesAsync(UserId);
|
||||
return Ok(recipes);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var recipe = await recipeService.GetByIdOrFetchAsync(id);
|
||||
if (recipe is null) return NotFound();
|
||||
return Ok(recipe);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] Recipe recipe)
|
||||
{
|
||||
var created = await recipeService.CreateAsync(UserId, recipe);
|
||||
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] Recipe recipe)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await recipeService.UpdateAsync(id, UserId, recipe);
|
||||
if (updated is null) return NotFound();
|
||||
return Ok(updated);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleted = await recipeService.DeleteAsync(id, UserId);
|
||||
if (!deleted) return NotFound();
|
||||
return NoContent();
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> Search([FromQuery] string q)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(q)) return BadRequest("Query parameter 'q' is required.");
|
||||
var results = await recipeService.SearchAsync(q, UserId);
|
||||
return Ok(results);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user