Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.cotato.itda.domain.garden.controller;

import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO;
import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO;
import com.cotato.itda.domain.garden.enums.SharedPlantStatus;
import com.cotato.itda.domain.garden.service.command.SharedPlantCommandService;
import com.cotato.itda.domain.garden.service.query.SharedPlantQueryService;
import com.cotato.itda.global.common.response.ApiResponse;
import com.cotato.itda.global.security.jwt.principal.JwtPrincipal;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
Expand All @@ -22,25 +20,40 @@ public class SharedPlantController implements SharedPlantControllerDocs {
private final SharedPlantCommandService sharedPlantCommandService;
private final SharedPlantQueryService sharedPlantQueryService;

// TODO: 식물 시듦 상태 변환 & 식물 함께 돌봄으로 변환 & 솔로 모드 자동 진입
// TODO: 식물 시듦 상태 변환 & 식물 함께 돌봄으로 변환

@GetMapping
public ApiResponse<SharedPlantResDTO.SharedPlantInfoListDTO> getSharedPlants(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@RequestParam(name = "status", required = false) List<SharedPlantStatus> statuses
) {
List<SharedPlantStatus> effectiveStatuses = (statuses == null || statuses.isEmpty())
? List.of(SharedPlantStatus.GROWING, SharedPlantStatus.WITHERED)
? List.of(SharedPlantStatus.GROWING)
: statuses;
return ApiResponse.success(sharedPlantQueryService.getSharedPlants(jwtPrincipal.memberId(), effectiveStatuses));
}

@PatchMapping("/{sharedPlantId}")
public ApiResponse<SharedPlantResDTO.WaterInfoResDTO> wateringPlant(
@PostMapping("/{sharedPlantId}/water")
public ApiResponse<SharedPlantResDTO.PlantActionResDTO> wateringPlant(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@PathVariable(name = "sharedPlantId") Long sharedPlantId,
@Valid @RequestBody SharedPlantReqDTO.WaterPlantReqDTO dto
@PathVariable(name = "sharedPlantId") Long sharedPlantId
) {
return ApiResponse.success(sharedPlantCommandService.waterPlant(sharedPlantId, dto, jwtPrincipal.memberId()));
return ApiResponse.success(sharedPlantCommandService.water(sharedPlantId, jwtPrincipal.memberId()));
}

@PostMapping("/{sharedPlantId}/nutrient")
public ApiResponse<SharedPlantResDTO.PlantActionResDTO> giveNutrient(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@PathVariable(name = "sharedPlantId") Long sharedPlantId
) {
return ApiResponse.success(sharedPlantCommandService.giveNutrient(sharedPlantId, jwtPrincipal.memberId()));
}

@PostMapping("/{sharedPlantId}/plant")
public ApiResponse<SharedPlantResDTO.PlantActionResDTO> plantSeed(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@PathVariable(name = "sharedPlantId") Long sharedPlantId
) {
return ApiResponse.success(sharedPlantCommandService.plantSeed(sharedPlantId, jwtPrincipal.memberId()));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.cotato.itda.domain.garden.controller;

import com.cotato.itda.domain.garden.dto.req.SharedPlantReqDTO;
import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO;
import com.cotato.itda.domain.garden.enums.SharedPlantStatus;
import com.cotato.itda.global.common.response.ApiResponse;
Expand All @@ -10,12 +9,10 @@
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
Expand All @@ -37,16 +34,67 @@ ApiResponse<SharedPlantResDTO.SharedPlantInfoListDTO> getSharedPlants(
@RequestParam(name = "status", required = false) List<SharedPlantStatus> statuses
);

@Operation(summary = "공유 식물 물주기 API By 정원", description = "공유 식물에 물을 줍니다.")
@Operation(summary = "공유 식물 물주기 API By 정원", description = """
공유 식물에 물을 줍니다.

**물주기 규칙:**
- 번갈아가며 물을 줘야 합니다 (연속 물주기 불가)
- 72시간 이상 경과 시 영양제를 먼저 줘야 합니다
- 영양제 투여 후에는 같은 사람이 물을 줘야 합니다

**상태별 동작:**
- WITHERED (48h~72h): 물주기 가능, 상태 회복
- NUTRITION_AVAILABLE (72h+): 영양제 필요 에러 반환
- AFTER_NUTRITION: 영양제 준 본인만 물주기 가능
""")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "씨앗이 심어지지 않음 / 연속 물주기 불가 / 영양제 필요 / 완료된 식물"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "참여자가 아님"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음")
})
@PostMapping("/{sharedPlantId}/water")
ApiResponse<SharedPlantResDTO.PlantActionResDTO> wateringPlant(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId
);

@Operation(summary = "영양제 주기 API By 정원", description = """
시든 식물에 영양제를 줍니다.

**영양제 규칙:**
- 72시간 이상 물을 주지 않은 경우에만 사용 가능
- 영양제 투여 후 같은 사람이 물을 줘야 식물이 회복됩니다
- 영양제 보유 개수가 1개 이상이어야 합니다

**상태 변화:**
- NUTRITION_AVAILABLE (72h+) → AFTER_NUTRITION
""")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "씨앗이 심어지지 않음 / 영양제를 줄 수 없음 / 영양제 없음 / 완료된 식물"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "참여자가 아님"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음")
})
@PostMapping("/{sharedPlantId}/nutrient")
ApiResponse<SharedPlantResDTO.PlantActionResDTO> giveNutrient(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId
);

@Operation(summary = "씨앗 심기 API By 정원", description = "초대 수락 후 씨앗을 심습니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "이미 심어진 식물 / 완료된 식물"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "참여자가 아님"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "공유 식물을 찾을 수 없음")
})
@PatchMapping("/{sharedPlantId}")
ApiResponse<SharedPlantResDTO.WaterInfoResDTO> wateringPlant(
@PostMapping("/{sharedPlantId}/plant")
ApiResponse<SharedPlantResDTO.PlantActionResDTO> plantSeed(
@AuthenticationPrincipal JwtPrincipal jwtPrincipal,
@Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId,
@Valid @RequestBody SharedPlantReqDTO.WaterPlantReqDTO dto
@Parameter(description = "공유 식물 ID") @PathVariable(name = "sharedPlantId") Long sharedPlantId
);
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
package com.cotato.itda.domain.garden.converter;

import com.cotato.itda.domain.friendship.entity.Friendship;
import com.cotato.itda.domain.garden.dto.SharedPlantWithFriendship;
import com.cotato.itda.domain.garden.dto.res.SharedPlantResDTO;
import com.cotato.itda.domain.garden.entity.SharedPlant;
import com.cotato.itda.domain.garden.entity.SharedPlantInvite;
import com.cotato.itda.domain.garden.enums.GardenState;
import com.cotato.itda.domain.member.entity.Member;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

public class SharedPlantConverter {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

public static SharedPlant toSharedPlant(SharedPlantInvite invite) {
Member inviter = invite.getInviter();
Member invitee = invite.getInvitee();
Expand All @@ -36,22 +32,20 @@ public static SharedPlantResDTO.AcceptedSharedPlantDTO toAcceptedSharedPlantDTO(
.sharedPlantId(sharedPlant.getId())
.plantId(sharedPlant.getPlant().getId())
.nickname(sharedPlant.getNickname())
.createdAt(sharedPlant.getCreatedAt().format(FORMATTER))
.createdAt(sharedPlant.getCreatedAt())
.build();
}

public static SharedPlantResDTO.SharedPlantInfoDTO toSharedPlantInfoDTO(
SharedPlant sharedPlant,
Friendship friendship,
Long currentMemberId,
LocalDateTime myLastWateredAt
GardenState gardenState,
int percentage
) {
SharedPlantResDTO.LastWateredByDTO lastWateredBy = null;
if (sharedPlant.getLastWateredBy() != null) {
lastWateredBy = SharedPlantResDTO.LastWateredByDTO.builder()
.memberId(sharedPlant.getLastWateredBy())
.isMe(sharedPlant.getLastWateredBy().equals(currentMemberId))
.build();
lastWateredBy = SharedPlantConverter.toLastWateredByDTO(sharedPlant, currentMemberId);
}

return SharedPlantResDTO.SharedPlantInfoDTO.builder()
Expand All @@ -61,55 +55,55 @@ public static SharedPlantResDTO.SharedPlantInfoDTO toSharedPlantInfoDTO(
.plantId(sharedPlant.getPlant().getId())
.nickname(sharedPlant.getNickname())
.growthValue(sharedPlant.getGrowthValue())
.percentage(percentage)
.growthStage(sharedPlant.getGrowthStage())
.gardenState(gardenState)
.status(sharedPlant.getStatus())
.isSoloMode(sharedPlant.getIsSoloMode())
.lastWateredBy(lastWateredBy)
.myLastWateredAt(myLastWateredAt != null ? myLastWateredAt.format(FORMATTER) : null)
.createdAt(sharedPlant.getCreatedAt().format(FORMATTER))
.lastWateredAt(sharedPlant.getLastWateredAt())
.createdAt(sharedPlant.getCreatedAt())
.build();
}

public static SharedPlantResDTO.SharedPlantInfoListDTO toSharedPlantInfoListDTO(
List<SharedPlantWithFriendship> sharedPlantsWithFriendships,
int nutrientCount,
Long currentMemberId,
Map<Long, LocalDateTime> myLastWateredAtMap
List<SharedPlantResDTO.SharedPlantInfoDTO> sharedPlantInfoDTOs,
int nutrientCount
) {
List<SharedPlantResDTO.SharedPlantInfoDTO> sharedPlantInfoDTOs = sharedPlantsWithFriendships.stream()
.filter(pair -> pair.friendship() != null)
.map(pair -> toSharedPlantInfoDTO(
pair.sharedPlant(),
pair.friendship(),
currentMemberId,
myLastWateredAtMap.get(pair.sharedPlant().getId())
))
.toList();

return SharedPlantResDTO.SharedPlantInfoListDTO.builder()
.totalCount(sharedPlantInfoDTOs.size())
.nutrientCount(nutrientCount)
.sharedPlants(sharedPlantInfoDTOs)
.build();
}

public static SharedPlantResDTO.LastWateredByDTO toLastWateredByDTO(Member member) {
public static SharedPlantResDTO.LastWateredByDTO toLastWateredByDTO(SharedPlant sharedPlant, Long currentMemberId) {
return SharedPlantResDTO.LastWateredByDTO.builder()
.memberId(member.getId())
.isMe(true)
.memberId(sharedPlant.getLastWateredBy())
.isMe(sharedPlant.getLastWateredBy().equals(currentMemberId))
.build();
}

public static SharedPlantResDTO.WaterInfoResDTO toWaterInfoResDTO(SharedPlant sharedPlant, Member currentMember) {
SharedPlantResDTO.LastWateredByDTO lastWateredBy = toLastWateredByDTO(currentMember);
public static SharedPlantResDTO.PlantActionResDTO toPlantActionResDTO(
SharedPlant sharedPlant,
Member currentMember,
GardenState gardenState,
int percentage
) {
SharedPlantResDTO.LastWateredByDTO lastWateredBy = null;
if (sharedPlant.getLastWateredBy() != null) {
lastWateredBy = SharedPlantConverter.toLastWateredByDTO(sharedPlant, currentMember.getId());
}

return SharedPlantResDTO.WaterInfoResDTO.builder()
return SharedPlantResDTO.PlantActionResDTO.builder()
.sharedPlantId(sharedPlant.getId())
.growthValue(sharedPlant.getGrowthValue())
.percentage(percentage)
.growthStage(sharedPlant.getGrowthStage())
.lastWateredBy(lastWateredBy)
.gardenState(gardenState)
.status(sharedPlant.getStatus())
.isSoloMode(sharedPlant.getIsSoloMode())
.lastWateredBy(lastWateredBy)
.nutrientCount(currentMember.getNutrientCount())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ public static SharedPlantLog toSharedPlantLog(
Long wateredBy,
boolean affectedGrowth,
int growthIncrement,
boolean usedNutrient
boolean usedNutrient,
boolean stageChanged
) {
return SharedPlantLog.builder()
.sharedPlant(sharedPlant)
.wateredBy(wateredBy)
.affectedGrowth(affectedGrowth)
.growthIncrement(growthIncrement)
.usedNutrient(usedNutrient)
.stageChanged(stageChanged)
.build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
package com.cotato.itda.domain.garden.dto.res;

import com.cotato.itda.domain.garden.enums.GardenState;
import com.cotato.itda.domain.garden.enums.PlantStage;
import com.cotato.itda.domain.garden.enums.SharedPlantStatus;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

public class SharedPlantResDTO {

@Builder
@Schema(description = "물 주기식물 정보 응답 DTO")
public record WaterInfoResDTO(
@Schema(description = "식물 액션공통 응답 DTO")
public record PlantActionResDTO(
@Schema(description = "공유 식물 ID", example = "1")
Long sharedPlantId,
@Schema(description = "성장 정도", example = "12")
Integer growthValue,
@Schema(description = "성장 퍼센티지", example = "45")
Integer percentage,
@Schema(description = "성장 단계", example = "SPROUT")
PlantStage growthStage,
@Schema(description = "마지막으로 물 준 회원 정보")
LastWateredByDTO lastWateredBy,
@Schema(description = "공유 식물의 현재 상태", example = "GROWING")
@Schema(description = "정원 상태", example = "WATERED_RECENTLY")
GardenState gardenState,
@Schema(description = "DB 상태", example = "GROWING")
SharedPlantStatus status,
@JsonProperty("isSoloMode")
@Schema(description = "혼자 돌봄 모드 여부", example = "false")
boolean isSoloMode,
@Schema(description = "마지막으로 물 준 회원 정보")
LastWateredByDTO lastWateredBy,
@Schema(description = "현재 회원이 보유한 영양제 총 개수", example = "10")
Integer nutrientCount
) {}
Expand All @@ -50,7 +56,7 @@ public record AcceptedSharedPlantDTO(
@Schema(description = "식물 닉네임", example = "두쫀쿠")
String nickname,
@Schema(description = "생성 일시", example = "2025-12-28 12:34")
String createdAt
LocalDateTime createdAt
) {}

@Builder
Expand All @@ -68,19 +74,23 @@ public record SharedPlantInfoDTO(
String nickname,
@Schema(description = "성장 정도", example = "0")
int growthValue,
@Schema(description = "성장 퍼센티지", example = "45")
int percentage,
@Schema(description = "성장 단계", example = "SEED")
PlantStage growthStage,
@Schema(description = "상태", example = "GROWING")
@Schema(description = "정원 상태", example = "WATERABLE")
GardenState gardenState,
@Schema(description = "DB 상태", example = "GROWING")
SharedPlantStatus status,
@JsonProperty("isSoloMode")
@Schema(description = "혼자 돌봄 모드 여부", example = "false")
boolean isSoloMode,
@Schema(description = "마지막으로 물 준 회원 정보")
LastWateredByDTO lastWateredBy,
@Schema(description = "내가 마지막으로 물 준 시간 (물을 준 적 없으면 null)", example = "2025-01-15 14:30")
String myLastWateredAt,
@Schema(description = "마지막으로 물 준 시간")
LocalDateTime lastWateredAt,
@Schema(description = "생성 일시", example = "2025-12-28 12:34")
String createdAt
LocalDateTime createdAt
) {}

@Builder
Expand Down
Loading