Implement Recipe, Planning, Shopping, Pantry, and Admin domains

Outside-in TDD for all 5 remaining domains (128 tests total):
- Recipe: CRUD, ingredients autocomplete/patch, tags, categories (27 tests)
- Planning: week plans, slots, confirm, suggestions, variety score, cooking logs (24 tests)
- Shopping: generate from plan, publish, check/add/remove items (15 tests)
- Pantry: CRUD with expiry sorting (11 tests)
- Admin: user management, password reset, audit logging (13 tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 21:56:51 +02:00
parent 4f457303d8
commit 9ec703abcd
88 changed files with 5267 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
package com.recipeapp.planning;
import com.recipeapp.planning.dto.*;
import com.recipeapp.recipe.HouseholdResolver;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.time.LocalDate;
import java.util.UUID;
@RestController
@RequestMapping("/v1/week-plans")
public class WeekPlanController {
private final PlanningService planningService;
private final HouseholdResolver householdResolver;
public WeekPlanController(PlanningService planningService, HouseholdResolver householdResolver) {
this.planningService = planningService;
this.householdResolver = householdResolver;
}
@GetMapping
public WeekPlanResponse getWeekPlan(Principal principal, @RequestParam LocalDate weekStart) {
UUID householdId = householdResolver.resolve(principal.getName());
return planningService.getWeekPlan(householdId, weekStart);
}
@PostMapping
public ResponseEntity<WeekPlanResponse> createWeekPlan(
Principal principal,
@Valid @RequestBody CreateWeekPlanRequest request) {
UUID householdId = householdResolver.resolve(principal.getName());
WeekPlanResponse response = planningService.createWeekPlan(householdId, request.weekStart());
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@PostMapping("/{id}/slots")
public ResponseEntity<SlotResponse> addSlot(
Principal principal,
@PathVariable UUID id,
@Valid @RequestBody CreateSlotRequest request) {
UUID householdId = householdResolver.resolve(principal.getName());
SlotResponse response = planningService.addSlot(householdId, id, request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@PatchMapping("/{planId}/slots/{slotId}")
public SlotResponse updateSlot(
Principal principal,
@PathVariable UUID planId,
@PathVariable UUID slotId,
@Valid @RequestBody UpdateSlotRequest request) {
UUID householdId = householdResolver.resolve(principal.getName());
return planningService.updateSlot(householdId, planId, slotId, request);
}
@DeleteMapping("/{planId}/slots/{slotId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteSlot(
Principal principal,
@PathVariable UUID planId,
@PathVariable UUID slotId) {
UUID householdId = householdResolver.resolve(principal.getName());
planningService.deleteSlot(householdId, planId, slotId);
}
@PostMapping("/{id}/confirm")
public WeekPlanResponse confirmPlan(Principal principal, @PathVariable UUID id) {
UUID householdId = householdResolver.resolve(principal.getName());
return planningService.confirmPlan(householdId, id);
}
@GetMapping("/{id}/suggestions")
public SuggestionResponse getSuggestions(
Principal principal,
@PathVariable UUID id,
@RequestParam LocalDate slotDate) {
UUID householdId = householdResolver.resolve(principal.getName());
return planningService.getSuggestions(householdId, id, slotDate);
}
@GetMapping("/{id}/variety-score")
public VarietyScoreResponse getVarietyScore(Principal principal, @PathVariable UUID id) {
UUID householdId = householdResolver.resolve(principal.getName());
return planningService.getVarietyScore(householdId, id);
}
}