diff --git a/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarMasterResponse.java b/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarMasterResponse.java index 091bd718..6ecf0de8 100644 --- a/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarMasterResponse.java +++ b/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarMasterResponse.java @@ -13,7 +13,7 @@ public class AvatarMasterResponse { public static AvatarMasterResponse from(AvatarMaster master) { return AvatarMasterResponse.builder() - .id(master.getId()) + .id(master.getId() - 2) .defaultImageUrl(master.getDefaultImageUrl()) .description(master.getDescription()) .build(); diff --git a/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarSimpleResponse.java b/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarSimpleResponse.java index 985a84fd..d641540a 100644 --- a/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarSimpleResponse.java +++ b/src/main/java/com/example/cp_main_be/domain/avatar/avatar/dto/response/AvatarSimpleResponse.java @@ -1,18 +1,21 @@ package com.example.cp_main_be.domain.avatar.avatar.dto.response; import com.example.cp_main_be.domain.avatar.avatar.domain.Avatar; +import lombok.Builder; import lombok.Getter; @Getter +@Builder public class AvatarSimpleResponse { private Long id; private String name; private String imageUrl; - // Avatar 엔티티를 AvatarDto로 변환하는 생성자 - public AvatarSimpleResponse(Avatar avatar) { - this.id = avatar.getId(); - this.name = avatar.getNickname(); - this.imageUrl = avatar.getAvatarMaster().getDefaultImageUrl(); + public static AvatarSimpleResponse from(Avatar avatar) { + return AvatarSimpleResponse.builder() + .id(avatar.getId()) + .name(avatar.getNickname()) + .imageUrl(avatar.getImageUrl()) + .build(); } } diff --git a/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/response/GardenListResponse.java b/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/response/GardenListResponse.java new file mode 100644 index 00000000..d249d1b6 --- /dev/null +++ b/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/response/GardenListResponse.java @@ -0,0 +1,7 @@ +package com.example.cp_main_be.domain.garden.garden.dto.response; + +import java.util.List; + +public class GardenListResponse { + List responses; +} diff --git a/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/GardenResponse.java b/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/response/GardenResponse.java similarity index 79% rename from src/main/java/com/example/cp_main_be/domain/garden/garden/dto/GardenResponse.java rename to src/main/java/com/example/cp_main_be/domain/garden/garden/dto/response/GardenResponse.java index 1be1246a..897e32ba 100644 --- a/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/GardenResponse.java +++ b/src/main/java/com/example/cp_main_be/domain/garden/garden/dto/response/GardenResponse.java @@ -1,5 +1,6 @@ -package com.example.cp_main_be.domain.garden.garden.dto; +package com.example.cp_main_be.domain.garden.garden.dto.response; +import com.example.cp_main_be.domain.avatar.avatar.dto.response.AvatarSimpleResponse; import com.example.cp_main_be.domain.garden.garden.domain.Garden; import java.time.LocalDateTime; import lombok.Builder; @@ -13,6 +14,7 @@ public class GardenResponse { private final Integer slotNumber; private final Integer waterCount; private final Integer sunlightCount; + private final AvatarSimpleResponse avatar; private final LocalDateTime createdAt; private final LocalDateTime updatedAt; @@ -23,6 +25,7 @@ private GardenResponse( Integer slotNumber, Integer waterCount, Integer sunlightCount, + AvatarSimpleResponse avatar, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; @@ -30,6 +33,7 @@ private GardenResponse( this.slotNumber = slotNumber; this.waterCount = waterCount; this.sunlightCount = sunlightCount; + this.avatar = avatar; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -41,6 +45,7 @@ public static GardenResponse from(Garden garden) { .slotNumber(garden.getSlotNumber()) .waterCount(garden.getWaterCount()) .sunlightCount(garden.getSunlightCount()) + .avatar(AvatarSimpleResponse.from(garden.getAvatar())) .createdAt(garden.getCreatedAt()) .updatedAt(garden.getUpdatedAt()) .build(); diff --git a/src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java b/src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java index 022f2f33..dc3c0982 100644 --- a/src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java +++ b/src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java @@ -1,7 +1,7 @@ package com.example.cp_main_be.domain.garden.garden.presentation; -import com.example.cp_main_be.domain.garden.garden.dto.GardenResponse; import com.example.cp_main_be.domain.garden.garden.dto.response.GardenBackgroundCandidateResponse; +import com.example.cp_main_be.domain.garden.garden.dto.response.GardenResponse; import com.example.cp_main_be.domain.garden.garden.service.GardenService; import com.example.cp_main_be.domain.member.user.domain.User; import com.example.cp_main_be.domain.member.user.service.UserService; diff --git a/src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java b/src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java index 29476690..54884abd 100644 --- a/src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java +++ b/src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java @@ -4,8 +4,8 @@ import com.example.cp_main_be.domain.garden.garden.domain.GardenBackground; import com.example.cp_main_be.domain.garden.garden.domain.repository.GardenBackgroundRepository; import com.example.cp_main_be.domain.garden.garden.domain.repository.GardenRepository; -import com.example.cp_main_be.domain.garden.garden.dto.GardenResponse; import com.example.cp_main_be.domain.garden.garden.dto.response.GardenBackgroundCandidateResponse; +import com.example.cp_main_be.domain.garden.garden.dto.response.GardenResponse; import com.example.cp_main_be.domain.garden.wateringlog.domain.FriendWateringLog; import com.example.cp_main_be.domain.garden.wateringlog.domain.repository.FriendWateringLogRepository; import com.example.cp_main_be.domain.member.user.domain.User; diff --git a/src/main/java/com/example/cp_main_be/domain/home/HomeResponseDto.java b/src/main/java/com/example/cp_main_be/domain/home/HomeResponseDto.java index 92ccda2f..547e490a 100644 --- a/src/main/java/com/example/cp_main_be/domain/home/HomeResponseDto.java +++ b/src/main/java/com/example/cp_main_be/domain/home/HomeResponseDto.java @@ -9,51 +9,56 @@ public class HomeResponseDto { private final UserInfo userInfo; - private final GardenInfo gardenInfo; + private final List gardenSummaries; // 본인의 모든 정원 목록 private final List todayMissions; - private final ActivityInfo activityInfo; + + // private final ActivityInfo activityInfo; // 주간 미션 상태 + + // --- 내부 DTO 클래스들 --- @Getter @Builder public static class UserInfo { - private String username; // [수정] nickname -> username + private Long id; + private String username; private int level; - private long currentExp; // [수정] int -> long (User 엔티티의 experience 타입과 일치) - private long requiredExpForNextLevel; // [수정] int -> long - private int unreadNotificationCount; + private long currentExp; + private long requiredExpForNextLevel; + private int unreadNotificationCount; // 읽지 않은 알림 수 (새 메시지 1) } + // 각 정원의 요약 정보 (홈 화면 슬라이드에 사용) @Getter @Builder - public static class GardenInfo { - private String plantName; - private String plantImageUrl; - private int waterCount; - private int maxWaterCount; - private int sunlightCount; - private int maxSunlightCount; - private String backgroundImageUrl; - private AvatarInfo avatar; // 아바타 정보를 정원 정보의 일부로 포함 + public static class GardenSummaryInfo { + private Long gardenId; // 정원 ID 추가 + private Integer gardenSlotNumber; + private AvatarInfo avatar; // 각 정원에 배치된 아바타 정보 + private boolean isOwnerWateringAble; // 본인 정원에 물주기 가능한지 여부 + private boolean isOwnerSunlightAble; // 본인 정원에 햇빛 주기 가능한지 여부 } @Getter @Builder public static class AvatarInfo { - private String characterImageUrl; + private Long avatarId; + private String avatarName; // 아바타 마스터 이름 (식물 이름) + private String avatarImageUrl; } @Getter @Builder public static class MissionInfo { + private Long missionId; // 미션 ID 추가 (프론트엔드에서 미션 클릭 시 사용) private String missionTitle; - private String missionType; // "QUIZ", "IMAGE_DIARY" 등 + private String missionType; private boolean isCompleted; + // private String description; // 필요시 미션 상세 설명 추가 가능 } - @Getter - @Builder - public static class ActivityInfo { - // 최근 7일간의 활동 기록 (true: 완료, false: 미완료) - private List weeklyMissionStatus; - } + // @Getter + // @Builder + // public static class ActivityInfo { + // private List weeklyMissionStatus; // 최근 7일간의 미션 완료 여부 + // } } diff --git a/src/main/java/com/example/cp_main_be/domain/home/service/HomeService.java b/src/main/java/com/example/cp_main_be/domain/home/service/HomeService.java index 5b292704..31e76f2f 100644 --- a/src/main/java/com/example/cp_main_be/domain/home/service/HomeService.java +++ b/src/main/java/com/example/cp_main_be/domain/home/service/HomeService.java @@ -1,19 +1,17 @@ package com.example.cp_main_be.domain.home.service; -import com.example.cp_main_be.domain.avatar.avatar.domain.Avatar; -import com.example.cp_main_be.domain.garden.garden.domain.Garden; import com.example.cp_main_be.domain.garden.garden.domain.repository.GardenRepository; import com.example.cp_main_be.domain.home.HomeResponseDto; import com.example.cp_main_be.domain.member.notification.domain.repository.NotificationRepository; import com.example.cp_main_be.domain.member.user.domain.User; import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository; -import com.example.cp_main_be.domain.mission.daily_mission_master.domain.DailyMissionMaster; import com.example.cp_main_be.domain.mission.user_daily_mission.domain.UserDailyMission; import com.example.cp_main_be.domain.mission.user_daily_mission.domain.repository.UserDailyMissionRepository; import com.example.cp_main_be.global.common.CustomApiException; import com.example.cp_main_be.global.common.ErrorCode; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -22,6 +20,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +// HomeService.java @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -32,38 +31,30 @@ public class HomeService { private final UserDailyMissionRepository userDailyMissionRepository; private final NotificationRepository notificationRepository; + // GardenService에서 가져오거나, 공통 유틸리티로 분리하면 더 좋습니다. + private LocalDateTime getStartOfCurrentWateringDay() { + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + LocalDateTime todayNoon = now.toLocalDate().atTime(12, 0); + return now.isBefore(todayNoon) ? todayNoon.minusDays(1) : todayNoon; + } + + private LocalDateTime getStartOfCurrentSunlightDay() { + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + LocalDateTime todaySixAM = now.toLocalDate().atTime(6, 0); + return now.isBefore(todaySixAM) ? todaySixAM.minusDays(1) : todaySixAM; + } + public HomeResponseDto getHomeScreenData(Long userId) { - // 1. 핵심 엔티티 조회 User user = userRepository .findById(userId) .orElseThrow(() -> new CustomApiException(ErrorCode.NOT_FOUND)); - Garden garden = - gardenRepository - .findByUserWithDetails(user) - .orElseThrow(() -> new CustomApiException(ErrorCode.NOT_FOUND)); - - Avatar avatar = garden.getAvatar(); - - // 2. 부가 정보 조회 + // 1. UserInfo 구성 int unreadNotificationCount = notificationRepository.countByReceiverAndIsReadFalse(user); - - LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = LocalDate.now().atTime(23, 59, 59); - - List todayMissions = - userDailyMissionRepository.findTodayMissionsWithMasterByUser(user, startOfDay, endOfDay); - - // TODO: Repository에 주간 활동 기록 조회 로직 구현 필요 - List weeklyStatus = createWeeklyMissionStatus(userId); - - HomeResponseDto.ActivityInfo activityInfo = - HomeResponseDto.ActivityInfo.builder().weeklyMissionStatus(weeklyStatus).build(); - - // 3. DTO 조립 HomeResponseDto.UserInfo userInfo = HomeResponseDto.UserInfo.builder() + .id(user.getId()) .username(user.getNickname()) .level(user.getLevel()) .currentExp(user.getExperience()) @@ -71,66 +62,91 @@ public HomeResponseDto getHomeScreenData(Long userId) { .unreadNotificationCount(unreadNotificationCount) .build(); - HomeResponseDto.AvatarInfo avatarInfo = - HomeResponseDto.AvatarInfo.builder() - .characterImageUrl(avatar.getAvatarMaster().getDefaultImageUrl()) - .build(); - - HomeResponseDto.GardenInfo gardenInfo = - HomeResponseDto.GardenInfo.builder() - .waterCount(garden.getWaterCount()) - .maxWaterCount(100) - .sunlightCount(garden.getSunlightCount()) - .maxSunlightCount(100) - .backgroundImageUrl(garden.getGardenBackground().getImageUrl()) - .avatar(avatarInfo) - .build(); - - List missionInfos = - todayMissions.stream() + // 2. GardenSummaries 구성 (모든 정원 순회) + List gardenSummaries = + user.getGardens().stream() .map( - mission -> { - DailyMissionMaster master = mission.getDailyMissionMaster(); - return HomeResponseDto.MissionInfo.builder() - .missionTitle(master.getTitle()) - .missionType(master.getMissionType().toString()) - .isCompleted(mission.isCompleted()) + garden -> { + // 각 정원의 물주기/햇빛주기 가능 여부 계산 (주인 본인이 주는 경우) + boolean isOwnerWateringAble = + garden.getLastWateredByOwnerAt() == null + || garden + .getLastWateredByOwnerAt() + .plusHours(8) + .isBefore(LocalDateTime.now()); + + boolean isOwnerSunlightAble = + garden.getLastSunlightReceivedAt() == null + || garden + .getLastSunlightReceivedAt() + .isBefore(getStartOfCurrentSunlightDay()); // 매일 06시 초기화 + + return HomeResponseDto.GardenSummaryInfo.builder() + .gardenId(garden.getId()) + .gardenSlotNumber(garden.getSlotNumber()) + .avatar( + HomeResponseDto.AvatarInfo.builder() + .avatarId(garden.getAvatar().getId()) + .avatarName(garden.getAvatar().getNickname()) + .avatarImageUrl( + garden.getAvatar().getAvatarMaster().getDefaultImageUrl()) + .build()) + .isOwnerWateringAble(isOwnerWateringAble) + .isOwnerSunlightAble(isOwnerSunlightAble) .build(); }) .collect(Collectors.toList()); - // 4. 최종 응답 반환 + // 3. TodayMissions 구성 + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = LocalDate.now().atTime(23, 59, 59); + List todayMissions = + userDailyMissionRepository + .findTodayMissionsWithMasterByUser(user, startOfDay, endOfDay) + .stream() + .map( + userDailyMission -> + HomeResponseDto.MissionInfo.builder() + .missionId(userDailyMission.getDailyMissionMaster().getId()) // 마스터 미션 ID + .missionTitle(userDailyMission.getDailyMissionMaster().getTitle()) + .missionType( + userDailyMission.getDailyMissionMaster().getMissionType().toString()) + .isCompleted(userDailyMission.isCompleted()) + .build()) + .collect(Collectors.toList()); + + // // 4. ActivityInfo 구성 + // List weeklyStatus = createWeeklyMissionStatus(userId); + // HomeResponseDto.ActivityInfo activityInfo = + // HomeResponseDto.ActivityInfo.builder().weeklyMissionStatus(weeklyStatus).build(); + + // 5. 최종 DTO 빌드 return HomeResponseDto.builder() .userInfo(userInfo) - .gardenInfo(gardenInfo) - .todayMissions(missionInfos) - .activityInfo(activityInfo) + .gardenSummaries(gardenSummaries) // 변경된 부분 + .todayMissions(todayMissions) .build(); } private long calculateRequiredExpForLevel(int level) { - return level * 100L; + return level * 100L; // 예시 로직 } private List createWeeklyMissionStatus(Long userId) { - // 1. 기준 날짜 설정 (오늘 포함 7일 전) + // 기존 로직과 동일 LocalDateTime sevenDaysAgo = LocalDate.now().minusDays(6).atStartOfDay(); - - // 2. Repository를 통해 7일간의 모든 미션 기록을 한번에 조회 List recentMissions = userDailyMissionRepository.findByUserIdAndCreatedAtAfter(userId, sevenDaysAgo); - // 3. '완료된' 미션들의 날짜만 Set으로 추출 (중복 제거) Set completedDates = recentMissions.stream() .filter(UserDailyMission::isCompleted) .map(mission -> mission.getCreatedAt().toLocalDate()) .collect(Collectors.toSet()); - // 4. 최근 7일(오늘-6일전 ~ 오늘)을 순회하며 완료 여부 리스트 생성 return Stream.iterate(LocalDate.now().minusDays(6), date -> date.plusDays(1)) .limit(7) - .map(completedDates::contains) // 해당 날짜가 completedDates Set에 포함되어 있는지 확인 + .map(completedDates::contains) .collect(Collectors.toList()); } } diff --git a/src/main/java/com/example/cp_main_be/domain/member/user/dto/response/UserGardenDetailResponse.java b/src/main/java/com/example/cp_main_be/domain/member/user/dto/response/UserGardenDetailResponse.java new file mode 100644 index 00000000..a20c70a9 --- /dev/null +++ b/src/main/java/com/example/cp_main_be/domain/member/user/dto/response/UserGardenDetailResponse.java @@ -0,0 +1,21 @@ +package com.example.cp_main_be.domain.member.user.dto.response; + +// GardenResponse를 +// 상속 또는 포함 +import com.example.cp_main_be.domain.home.HomeResponseDto; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +// 기존 GardenResponse 필드 포함 + @Builder로 추가 필드 설정 +@Getter +@Setter +@Builder +public class UserGardenDetailResponse { + private Long gardenId; + private int waterCount; + private int maxWaterCount; + private HomeResponseDto.AvatarInfo avatarInfo; // 해당 정원에 배치된 아바타의 상세 정보 + + private Boolean isWateringAbleByMe; // 현재 접속 유저가 이 정원에 물을 줄 수 있는지 여부 +} diff --git a/src/main/java/com/example/cp_main_be/domain/member/user/dto/response/UserProfileResponse.java b/src/main/java/com/example/cp_main_be/domain/member/user/dto/response/UserProfileResponse.java new file mode 100644 index 00000000..3e779018 --- /dev/null +++ b/src/main/java/com/example/cp_main_be/domain/member/user/dto/response/UserProfileResponse.java @@ -0,0 +1,27 @@ +package com.example.cp_main_be.domain.member.user.dto.response; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class UserProfileResponse { + private Long id; + private String userNickname; // 프로필 주인의 닉네임 + private String profileImageUrl; // 프로필 주인의 이미지 URL 추가 + + // 현재 접속 유저가 프로필 주인을 팔로우하는 상태 + // 0: 팔로우 안 함 (친구 추가 버튼 보임) + // 1: 팔로우 중 (팔로우 취소 버튼 보임) + // 2: 나를 팔로우 중 (맞팔로우 버튼 보임) - 이 상태는 isFriend가 false일 때만 의미 + private Integer followStatus; // 0: NOT_FOLLOWING, 1: FOLLOWING, 2: FOLLOW_BACK_POSSIBLE + + private int profileUserLevel; // 프로필 주인의 레벨 추가 + private Long leftWaterCountForOthers; // 오늘 남에게 물을 줄 수 있는 남은 횟수 (현재 접속 유저 기준) + + // 프로필 주인의 모든 정원 목록. 각 정원마다 물주기 가능 여부 포함 + private List userGardens; // GardenResponse 대신 상세 정보를 담는 새로운 DTO +} diff --git a/src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java b/src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java index 2c0b36f6..f06eec04 100644 --- a/src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java +++ b/src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java @@ -4,6 +4,7 @@ import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository; import com.example.cp_main_be.domain.member.user.dto.request.AvatarChangeRequest; import com.example.cp_main_be.domain.member.user.dto.request.NicknameChangeRequest; +import com.example.cp_main_be.domain.member.user.dto.response.UserProfileResponse; import com.example.cp_main_be.domain.member.user.dto.response.UserRegisterResponse; import com.example.cp_main_be.domain.member.user.service.UserService; import com.example.cp_main_be.global.common.ApiResponse; @@ -81,16 +82,13 @@ public ResponseEntity> @Operation(summary = "유저 정보 조회", description = "유저 정보를 조회합니다") @GetMapping("/{userId}") - public ResponseEntity> getUserInfo( + public ResponseEntity> getUserInfo( @PathParam("userId") Long userId) { // SecurityContextHolder에서 현재 인증된 사용자(UUID)를 가져옴 User user = userRepository .findById(userId) .orElseThrow(() -> new UserNotFoundException("해당 유저를 찾을 수 없습니다.")); - UserRegisterResponse userRegisterResponse = - new UserRegisterResponse( - user.getId(), user.getNickname(), user.getUuid(), null, null); // 토큰은 응답에 포함하지 않음 - return ResponseEntity.ok(ApiResponse.success(userRegisterResponse)); + return ResponseEntity.ok(ApiResponse.success(userService.getUserProfile(user.getId(), userId))); } } diff --git a/src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java b/src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java index e4b31b03..9f7e9fcc 100644 --- a/src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java +++ b/src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java @@ -2,15 +2,25 @@ import com.example.cp_main_be.domain.avatar.avatar.domain.Avatar; import com.example.cp_main_be.domain.avatar.avatar.domain.repository.AvatarRepository; +import com.example.cp_main_be.domain.garden.wateringlog.domain.repository.FriendWateringLogRepository; +import com.example.cp_main_be.domain.home.HomeResponseDto; import com.example.cp_main_be.domain.member.level.service.LevelService; import com.example.cp_main_be.domain.member.user.domain.User; import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository; import com.example.cp_main_be.domain.member.user.dto.request.AvatarChangeRequest; +import com.example.cp_main_be.domain.member.user.dto.response.UserGardenDetailResponse; +import com.example.cp_main_be.domain.member.user.dto.response.UserProfileResponse; import com.example.cp_main_be.domain.member.user.dto.response.UserRegisterResponse; +import com.example.cp_main_be.domain.social.follow.domain.repository.FollowRepository; +import com.example.cp_main_be.global.common.CustomApiException; +import com.example.cp_main_be.global.common.ErrorCode; import com.example.cp_main_be.global.exception.AvatarNotFoundException; import com.example.cp_main_be.global.exception.UserNotFoundException; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -25,6 +35,8 @@ public class UserService { private final UserRepository userRepository; private final LevelService levelService; private final AvatarRepository avatarRepository; + private final FriendWateringLogRepository friendWateringLogRepository; + private final FollowRepository followRepository; public void addExperience(Long actorId, int points) { User user = userRepository.findById(actorId).get(); @@ -110,4 +122,103 @@ public User getCurrentUser() { throw new IllegalArgumentException("인증 정보를 찾을 수 없습니다."); } + + /** + * 특정 유저의 프로필 정보를 조회합니다. + * + * @param currentUserId 현재 로그인한 유저(프로필을 보고 있는 사람)의 ID + * @param profileUserId 프로필의 주인 ID + * @return UserProfileResponse DTO + */ + public UserProfileResponse getUserProfile(Long currentUserId, Long profileUserId) { + User currentUser = + userRepository + .findById(currentUserId) + .orElseThrow(() -> new CustomApiException(ErrorCode.NOT_FOUND)); + User profileUser = + userRepository + .findById(profileUserId) + .orElseThrow(() -> new CustomApiException(ErrorCode.NOT_FOUND)); + + // 1. 팔로우 상태 확인 + // currentUserId -> profileUserId 팔로우 여부 + boolean isFollowing = followRepository.existsByFollowerAndFollowing(currentUser, profileUser); + // profileUserId -> currentUserId 팔로우 여부 (맞팔 여부 확인용) + boolean isFollowedByProfileUser = + followRepository.existsByFollowerAndFollowing(profileUser, currentUser); + + Integer followStatus; + if (isFollowing) { + followStatus = 1; // FOLLOWING (팔로우 중) + } else if (isFollowedByProfileUser) { + followStatus = 2; // FOLLOW_BACK_POSSIBLE (상대방이 나를 팔로우 중, 맞팔 가능) + } else { + followStatus = 0; // NOT_FOLLOWING (팔로우 안 함) + } + + // 2. 남에게 물 줄 수 있는 남은 횟수 계산 (현재 접속 유저 기준) + LocalDateTime startOfWateringDay = getStartOfCurrentWateringDay(); + int todayWateringCountForOthers = + friendWateringLogRepository.countByWaterGiverAndWateredAtAfter( + currentUser, startOfWateringDay); + Long leftWaterCountForOthers = + (long) (3 - todayWateringCountForOthers); // MAX_FRIEND_WATERING_PER_DAY = 3 + + // 3. 프로필 주인의 정원 목록 및 물주기 가능 여부 계산 + List userGardens = + profileUser.getGardens().stream() + .map( + garden -> { + // 현재 접속 유저가 이 정원에 오늘 물을 줄 수 있는지 여부 + boolean isWateringAbleByMe = false; + // 조건: 1) 아직 오늘 남에게 물 줄 수 있는 횟수가 남아있어야 하고 (leftWaterCountForOthers > 0) + // 2) 오늘 이 정원에 내가 물을 준 적이 없어야 한다. + if (leftWaterCountForOthers > 0) { + boolean alreadyWateredByMe = + friendWateringLogRepository + .existsByWaterGiverAndWateredGardenAndWateredAtAfter( + currentUser, garden, startOfWateringDay); + isWateringAbleByMe = !alreadyWateredByMe; + } + + HomeResponseDto.AvatarInfo avatarInfoForGarden = + HomeResponseDto.AvatarInfo.builder() + .avatarName(garden.getAvatar().getNickname()) + .avatarImageUrl(garden.getAvatar().getAvatarMaster().getDefaultImageUrl()) + .build(); + + // GardenResponse 대신 UserGardenDetailResponse를 빌드 + return UserGardenDetailResponse.builder() + .gardenId(garden.getId()) + .waterCount(garden.getWaterCount()) + .maxWaterCount(100) + .avatarInfo(avatarInfoForGarden) + .isWateringAbleByMe(isWateringAbleByMe) + .build(); + }) + .collect(Collectors.toList()); + + // 4. 최종 UserProfileResponse 빌드 + return UserProfileResponse.builder() + .id(profileUser.getId()) + .userNickname(profileUser.getNickname()) + .profileImageUrl(profileUser.getProfileImageUrl()) + .followStatus(followStatus) + .profileUserLevel(profileUser.getLevel()) + .leftWaterCountForOthers(leftWaterCountForOthers) + .userGardens(userGardens) + .build(); + } + + // GardenService에 있던 private 메서드를 가져오거나 공통 유틸 클래스로 분리해야 합니다. + private LocalDateTime getStartOfCurrentWateringDay() { + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + LocalDateTime todayNoon = now.toLocalDate().atTime(12, 0); + + if (now.isBefore(todayNoon)) { + return todayNoon.minusDays(1); + } else { + return todayNoon; + } + } } diff --git a/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/dto/MissionPanelResponse.java b/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/dto/MissionPanelResponse.java index 732bbd3e..2095ca36 100644 --- a/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/dto/MissionPanelResponse.java +++ b/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/dto/MissionPanelResponse.java @@ -8,6 +8,7 @@ @Builder public class MissionPanelResponse { + private final Integer todayMissionCount; private final List dailyMissions; private final WishTreeDto wishTree; diff --git a/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/service/UserDailyMissionService.java b/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/service/UserDailyMissionService.java index 5d8dccae..18c08b94 100644 --- a/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/service/UserDailyMissionService.java +++ b/src/main/java/com/example/cp_main_be/domain/mission/user_daily_mission/service/UserDailyMissionService.java @@ -120,6 +120,10 @@ public MissionPanelResponse getMissionPanelData(Long userId) { .isCompleted(mission.isCompleted()) .build()) .collect(Collectors.toList()); + Integer count = 0; + for (MissionPanelResponse.DailyMissionStatusDto mission : missionDtos) { + if (mission.isCompleted()) count++; + } // 2. 소망 나무 정보 조회 // TODO: WishTree 엔티티 및 Repository 구현 후 실제 데이터 조회 로직 필요 @@ -131,6 +135,10 @@ public MissionPanelResponse getMissionPanelData(Long userId) { .build(); // 3. 최종 응답 조립 - return MissionPanelResponse.builder().dailyMissions(missionDtos).wishTree(wishTreeDto).build(); + return MissionPanelResponse.builder() + .todayMissionCount(count) + .dailyMissions(missionDtos) + .wishTree(wishTreeDto) + .build(); } } diff --git a/src/main/java/com/example/cp_main_be/domain/realquiz/RealQuiz.java b/src/main/java/com/example/cp_main_be/domain/realquiz/RealQuiz.java index 89c09d6a..b5ab31aa 100644 --- a/src/main/java/com/example/cp_main_be/domain/realquiz/RealQuiz.java +++ b/src/main/java/com/example/cp_main_be/domain/realquiz/RealQuiz.java @@ -6,7 +6,6 @@ import lombok.*; import org.hibernate.annotations.CreationTimestamp; - @Entity @Getter @Setter @@ -36,5 +35,4 @@ public class RealQuiz { @Column private Boolean isCompleted; @Column @CreationTimestamp private LocalDateTime createdAt; - } diff --git a/src/main/java/com/example/cp_main_be/domain/realquiz/dto/response/RealQuizAnswerResponseDTO.java b/src/main/java/com/example/cp_main_be/domain/realquiz/dto/response/RealQuizAnswerResponseDTO.java index 962e2d6a..7067bdea 100644 --- a/src/main/java/com/example/cp_main_be/domain/realquiz/dto/response/RealQuizAnswerResponseDTO.java +++ b/src/main/java/com/example/cp_main_be/domain/realquiz/dto/response/RealQuizAnswerResponseDTO.java @@ -19,5 +19,4 @@ public class RealQuizAnswerResponseDTO { private String answerDescription; private Boolean isCorrect; // 사용자 답안 정답 여부 private Boolean isCompleted; - } diff --git a/src/main/java/com/example/cp_main_be/domain/realquiz/presentation/RealQuizController.java b/src/main/java/com/example/cp_main_be/domain/realquiz/presentation/RealQuizController.java index 9c8e8c86..76aa8da5 100644 --- a/src/main/java/com/example/cp_main_be/domain/realquiz/presentation/RealQuizController.java +++ b/src/main/java/com/example/cp_main_be/domain/realquiz/presentation/RealQuizController.java @@ -41,7 +41,6 @@ public ApiResponse createUserQuiz( return ApiResponse.success(responseDTO); } - @GetMapping @Operation(summary = "자신의 퀴즈 조회 API") public ApiResponse getRealQuiz( diff --git a/src/main/java/com/example/cp_main_be/domain/social/guestbook/dto/request/GuestbookResponse.java b/src/main/java/com/example/cp_main_be/domain/social/guestbook/dto/response/GuestbookResponse.java similarity index 77% rename from src/main/java/com/example/cp_main_be/domain/social/guestbook/dto/request/GuestbookResponse.java rename to src/main/java/com/example/cp_main_be/domain/social/guestbook/dto/response/GuestbookResponse.java index 4a3f3769..77c34efe 100644 --- a/src/main/java/com/example/cp_main_be/domain/social/guestbook/dto/request/GuestbookResponse.java +++ b/src/main/java/com/example/cp_main_be/domain/social/guestbook/dto/response/GuestbookResponse.java @@ -1,4 +1,4 @@ -package com.example.cp_main_be.domain.social.guestbook.dto.request; +package com.example.cp_main_be.domain.social.guestbook.dto.response; import java.time.LocalDateTime; import lombok.*; diff --git a/src/main/java/com/example/cp_main_be/domain/social/guestbook/presentation/GuestbookController.java b/src/main/java/com/example/cp_main_be/domain/social/guestbook/presentation/GuestbookController.java index b0dc9b8b..d11914c8 100644 --- a/src/main/java/com/example/cp_main_be/domain/social/guestbook/presentation/GuestbookController.java +++ b/src/main/java/com/example/cp_main_be/domain/social/guestbook/presentation/GuestbookController.java @@ -3,7 +3,7 @@ import com.example.cp_main_be.domain.member.user.domain.User; import com.example.cp_main_be.domain.member.user.service.UserService; import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookRequest; -import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookResponse; +import com.example.cp_main_be.domain.social.guestbook.dto.response.GuestbookResponse; import com.example.cp_main_be.domain.social.guestbook.service.GuestbookService; import com.example.cp_main_be.global.common.ApiResponse; import io.swagger.v3.oas.annotations.Operation; diff --git a/src/main/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookService.java b/src/main/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookService.java index 551df95c..efde8a8a 100644 --- a/src/main/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookService.java +++ b/src/main/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookService.java @@ -8,7 +8,7 @@ import com.example.cp_main_be.domain.social.guestbook.domain.Guestbook; import com.example.cp_main_be.domain.social.guestbook.domain.repository.GuestbookRepository; import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookRequest; -import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookResponse; +import com.example.cp_main_be.domain.social.guestbook.dto.response.GuestbookResponse; import com.example.cp_main_be.global.exception.UserNotFoundException; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/test/java/com/example/cp_main_be/domain/garden/service/GardenServiceTest.java b/src/test/java/com/example/cp_main_be/domain/garden/service/GardenServiceTest.java index e0a0e966..b80a4b82 100644 --- a/src/test/java/com/example/cp_main_be/domain/garden/service/GardenServiceTest.java +++ b/src/test/java/com/example/cp_main_be/domain/garden/service/GardenServiceTest.java @@ -6,7 +6,7 @@ // // import com.example.cp_main_be.domain.garden.garden.domain.Garden; // import com.example.cp_main_be.domain.garden.garden.domain.repository.GardenRepository; -// import com.example.cp_main_be.domain.garden.garden.dto.GardenResponse; +// import com.example.cp_main_be.domain.garden.garden.dto.response.GardenResponse; // import com.example.cp_main_be.domain.garden.garden.service.GardenService; // import com.example.cp_main_be.domain.member.user.domain.User; // import java.util.ArrayList; diff --git a/src/test/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookServiceTest.java b/src/test/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookServiceTest.java index 04f2b205..dd0f0a60 100644 --- a/src/test/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookServiceTest.java +++ b/src/test/java/com/example/cp_main_be/domain/social/guestbook/service/GuestbookServiceTest.java @@ -14,7 +14,7 @@ // import com.example.cp_main_be.domain.social.guestbook.domain.Guestbook; // import com.example.cp_main_be.domain.social.guestbook.domain.repository.GuestbookRepository; // import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookRequest; -// import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookResponse; +// import com.example.cp_main_be.domain.social.guestbook.dto.response.GuestbookResponse; // import com.example.cp_main_be.global.exception.UserNotFoundException; // import java.time.LocalDateTime; // import java.util.*; @@ -268,7 +268,7 @@ // import com.example.cp_main_be.domain.social.guestbook.domain.Guestbook; // import com.example.cp_main_be.domain.social.guestbook.domain.repository.GuestbookRepository; // import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookRequest; -// import com.example.cp_main_be.domain.social.guestbook.dto.request.GuestbookResponse; +// import com.example.cp_main_be.domain.social.guestbook.dto.response.GuestbookResponse; // import com.example.cp_main_be.global.exception.UserNotFoundException; // import java.time.LocalDateTime; // import java.util.*;