ShoppingItemController.java

package com.hwhub.backend.presentation.rest.shopping;

import com.hwhub.backend.application.service.ShoppingItemService;
import com.hwhub.backend.domain.model.ShoppingItemModel;
import com.hwhub.backend.presentation.rest.shopping.dto.*;
import com.hwhub.backend.security.CurrentUserId;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * 買い物アイテム(Shopping Item)の参照、作成、更新、ステータス管理を行うAPIコントローラです。
 *
 * <p>このコントローラ内の全てのエンドポイントは、認証(Authentication)と、 アイテムまたは世帯(Household)に対する認可(Authorization)が必要です。
 * ベースパス: /api/
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/")
public class ShoppingItemController {

  private final ShoppingItemService shoppingItemService;

  /**
   * 指定された世帯IDに紐づく買い物アイテムを取得します。<br>
   * GET /api/households/{householdId}/shopping-items
   *
   * @param householdId 世帯ID。パス変数として指定します。
   * @param userId 認証済みユーザーID
   * @return 指定された世帯IDの買い物アイテムリスト
   * @throws com.hwhub.backend.domain.exception.ResourceNotFoundException
   *     指定された世帯IDが存在しない、またはユーザーが所属していない場合(404/403)
   */
  @GetMapping("/households/{householdId}/shopping-items")
  public ShoppingItemListResponse getShoppingItems(
      @PathVariable("householdId") Long householdId, @CurrentUserId Long userId) {
    List<ShoppingItemModel> models = shoppingItemService.getShoppingItems(householdId, userId);
    return ShoppingItemListResponse.fromModelList(models);
  }

  /**
   * 指定された世帯IDに紐づくお気に入り買い物アイテムを取得します。<br>
   * GET /api/households/{householdId}/shopping-items/favorites
   *
   * @param householdId 世帯ID。パス変数として指定します。
   * @param userId 認証済みユーザーID
   * @return 指定された世帯IDのお気に入り買い物アイテムリスト
   * @throws com.hwhub.backend.domain.exception.ResourceNotFoundException
   *     指定された世帯IDが存在しない、またはユーザーが所属していない場合(404/403)
   */
  @GetMapping("/households/{householdId}/shopping-items/favorites")
  public ShoppingItemListResponse getFavorites(
      @PathVariable("householdId") Long householdId, @CurrentUserId Long userId) {
    List<ShoppingItemModel> models =
        shoppingItemService.getFavoriteShoppingItems(householdId, userId);
    return ShoppingItemListResponse.fromModelList(models);
  }

  /**
   * 買い物アイテムのお気に入り状態(お気に入り/お気に入り解除)を更新します。<br>
   * PATCH /api/shopping-items/{shoppingItemId}/favorite
   *
   * @param shoppingItemId 買い物アイテムのID
   * @param request お気に入り状態(True/False)を含むリクエストボディ
   * @param userId 認証済みユーザーID
   * @return HTTPステータス 204 No Content(更新成功)
   * @throws com.hwhub.backend.domain.exception.ResourceNotFoundException アイテムが存在しない場合(404)
   * @throws org.springframework.web.server.ResponseStatusException ユーザーに更新権限がない場合(403 Forbidden)
   */
  @PatchMapping("/shopping-items/{shoppingItemId}/favorite")
  public ResponseEntity<Void> updateFavorite(
      @PathVariable("shoppingItemId") Long shoppingItemId,
      @RequestBody @Valid UpdateFavoriteRequest request,
      @CurrentUserId Long userId) {
    shoppingItemService.updateFavorite(shoppingItemId, request.favorite(), userId);
    return ResponseEntity.noContent().build(); // 204
  }

  /**
   * 複数の買い物アイテムのステータスを一括更新します。<br>
   * PATCH /api/shopping-items/bulk-status
   *
   * @param request 更新するアイテムIDリストと更新後のステータスを含むリクエストボディ
   * @param userId 認証済みユーザーID
   * @return HTTPステータス 204 No Content(更新成功)
   */
  @PatchMapping("/shopping-items/bulk-status")
  public ResponseEntity<Void> bulkUpdateStatus(
      @RequestBody @Valid BulkUpdateStatusRequest request, @CurrentUserId Long userId) {
    shoppingItemService.bulkUpdateStatus(request.ids(), request.status(), userId);
    return ResponseEntity.noContent().build();
  }

  /**
   * 買い物アイテムのステータス(購入済み、未購入など)を更新します。<br>
   * PATCH /api/shopping-items/{shoppingItemId}/status
   *
   * @param shoppingItemId 買い物アイテムのID
   * @param request 更新後のステータスコードを含むリクエストボディ
   * @param userId 認証済みユーザーID
   * @return HTTPステータス 204 No Content(更新成功)
   * @throws com.hwhub.backend.domain.exception.ResourceNotFoundException アイテムが存在しない場合(404)
   */
  @PatchMapping("/shopping-items/{shoppingItemId}/status")
  public ResponseEntity<Void> updateStatus(
      @PathVariable("shoppingItemId") Long shoppingItemId,
      @RequestBody @Valid UpdateStatusRequest request,
      @CurrentUserId Long userId) {
    shoppingItemService.updateStatus(shoppingItemId, request.status(), userId);
    return ResponseEntity.noContent().build();
  }

  /**
   * 指定された世帯に新しい買い物アイテムを作成します。<br>
   * POST /api/households/{householdId}/shopping-items
   *
   * @param householdId アイテムを作成する世帯ID
   * @param request 作成するアイテムの情報(名前、数量など)を含むリクエストボディ
   * @param userId 認証済みユーザーID
   * @return HTTPステータス 201 Created と共に、作成されたアイテムのDTO
   */
  @PostMapping("/households/{householdId}/shopping-items")
  public ResponseEntity<ShoppingItemDto> create(
      @PathVariable("householdId") Long householdId,
      @RequestBody @Valid CreateShoppingItemRequest request,
      @CurrentUserId Long userId) {
    ShoppingItemModel inserted =
        shoppingItemService.create(
            ShoppingItemModel.create(
                householdId, request.getName(), request.getMemo(), request.getStoreType()),
            request.getSourceShoppingItemId(),
            userId);

    ShoppingItemDto dto = ShoppingItemDto.fromModel(inserted);
    return ResponseEntity.status(HttpStatus.CREATED).body(dto);
  }

  /**
   * 買い物アイテムを削除します(物理削除)。<br>
   * DELETE /api/shopping-items/{shoppingItemId}
   *
   * @param shoppingItemId 買い物アイテムのID
   * @param userId 認証済みユーザーID
   * @return HTTPステータス 204 No Content(削除成功)
   */
  @DeleteMapping("/shopping-items/{shoppingItemId}")
  public ResponseEntity<Void> delete(
      @PathVariable("shoppingItemId") Long shoppingItemId, @CurrentUserId Long userId) {
    shoppingItemService.delete(shoppingItemId, userId);
    return ResponseEntity.noContent().build();
  }

  /**
   * 既存の買い物アイテムの情報を全体的に更新します(PUT)。<br>
   * PUT /api/shopping-items/{shoppingItemId}
   *
   * @param shoppingItemId 更新する買い物アイテムのID
   * @param request 更新内容を含むリクエストボディ
   * @param userId 認証済みユーザーID
   * @return HTTPステータス 200 OK と共に、更新後のアイテムのDTO
   */
  @PutMapping("/shopping-items/{shoppingItemId}")
  public ResponseEntity<ShoppingItemDto> update(
      @PathVariable("shoppingItemId") Long shoppingItemId,
      @Valid @RequestBody UpdateShoppingItemRequest request,
      @CurrentUserId Long userId) {
    ShoppingItemModel updated =
        shoppingItemService.update(
            shoppingItemId, request.name(), request.memo(), request.storeType(), userId);

    ShoppingItemDto dto = ShoppingItemDto.fromModel(updated);
    return ResponseEntity.ok(dto);
  }
}