Skip to content

Commit

Permalink
Merge pull request #371 from Team-Sopetit/feature/sohyeon/#370
Browse files Browse the repository at this point in the history
[FEAT] 테마 달성도 관련 API 구현
  • Loading branch information
thguss authored Jan 9, 2025
2 parents c358eb6 + 4cc40f8 commit 28b3cb4
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 1 deletion.
46 changes: 46 additions & 0 deletions src/main/java/com/soptie/server/api/controller/AchievementApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.soptie.server.api.controller;

import java.security.Principal;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.soptie.server.api.controller.docs.AchievementApiDocs;
import com.soptie.server.api.controller.dto.response.SuccessResponse;
import com.soptie.server.api.controller.dto.response.achievement.AchievedThemeResponse;
import com.soptie.server.api.controller.dto.response.achievement.AchievedThemesResponse;
import com.soptie.server.api.controller.generic.SuccessMessage;
import com.soptie.server.domain.achievement.AchievedThemeService;

import lombok.RequiredArgsConstructor;
import lombok.val;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v3/achievement")
public class AchievementApi implements AchievementApiDocs {
private final AchievedThemeService achievedThemeService;

@ResponseStatus(HttpStatus.OK)
@GetMapping("/themes")
public SuccessResponse<AchievedThemesResponse> getAchievementThemes(Principal principal) {
val memberId = Long.parseLong(principal.getName());
val response = achievedThemeService.getAchievedThemes(memberId);
return SuccessResponse.success(SuccessMessage.GET_STATISTICS.getMessage(), response);
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/themes/{themeId}/routines")
public SuccessResponse<AchievedThemeResponse> getAchievementTheme(
Principal principal,
@PathVariable long themeId
) {
val memberId = Long.parseLong(principal.getName());
val response = achievedThemeService.getAchievementTheme(memberId, themeId);
return SuccessResponse.success(SuccessMessage.GET_STATISTICS.getMessage(), response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.soptie.server.api.controller.docs;

import java.security.Principal;

import com.soptie.server.api.controller.dto.response.ErrorResponse;
import com.soptie.server.api.controller.dto.response.SuccessResponse;
import com.soptie.server.api.controller.dto.response.achievement.AchievedThemeResponse;
import com.soptie.server.api.controller.dto.response.achievement.AchievedThemesResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "[Achievement] 달성도 API", description = "루틴 달성 통계 관련 api")
public interface AchievementApiDocs {

@Operation(
summary = "테마 달성도 조회",
description = "테마를 달성한 정보 리스트를 조회한다.",
responses = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(
responseCode = "4xx",
description = "클라이언트(요청) 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
SuccessResponse<AchievedThemesResponse> getAchievementThemes(
@Parameter(hidden = true) Principal principal
);

@Operation(
summary = "테마별 루틴 달성도 조회",
description = "테마 내 달성한 루틴 정보를 조회한다.",
responses = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(
responseCode = "4xx",
description = "클라이언트(요청) 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
SuccessResponse<AchievedThemeResponse> getAchievementTheme(
@Parameter(hidden = true) Principal principal,
@Parameter(
name = "themeId",
description = "조회할 테마 id",
in = ParameterIn.PATH,
required = true,
example = "1"
) long themeId
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.soptie.server.api.controller.dto.response.achievement;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

import com.soptie.server.domain.challenge.Challenge;
import com.soptie.server.domain.challenge.MemberChallenge;
import com.soptie.server.domain.memberroutine.MemberRoutine;
import com.soptie.server.domain.routine.Routine;
import com.soptie.server.domain.theme.Theme;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.val;

@Builder(access = AccessLevel.PRIVATE)
public record AchievedThemeResponse(
@Schema(description = "테마 id", example = "1")
long id,
@NotNull
@Schema(description = "테마 이름", example = "관계쌓기")
String name,
@NotNull
@Schema(description = "루틴 목록")
List<AchievedRoutine> routines,
@NotNull
@Schema(description = "챌린지 목록")
List<AchievedChallenge> challenges
) {

public static AchievedThemeResponse of(
Theme theme,
List<MemberRoutine> memberRoutines,
Map<Long, Routine> routineMapOfMember,
List<MemberChallenge> memberChallenges,
Map<Long, Challenge> challengeMapOfMember
) {
return AchievedThemeResponse.builder()
.id(theme.getId())
.name(theme.getName())
.routines(memberRoutines.stream()
.map(it -> AchievedRoutine.of(routineMapOfMember.get(it.getId()), it))
.sorted((a, b) -> {
val diff = b.achievedCount - a.achievedCount;
return diff != 0 ? diff : a.content.compareTo(b.content);
})
.toList())
.challenges(memberChallenges.stream()
.map(it -> AchievedChallenge.of(challengeMapOfMember.get(it.getId()), it))
.sorted((a, b) -> {
val diff = b.achievedCount - a.achievedCount;
return diff != 0 ? diff : a.content.compareTo(b.content);
})
.toList())
.build();
}

@Builder(access = AccessLevel.PRIVATE)
private record AchievedRoutine(
@NotNull
@Schema(description = "루틴 내용", example = "일찍 일어나기")
String content,
@Schema(description = "루틴 달성 횟수", example = "10")
int achievedCount,
@NotNull
@Schema(description = "루틴 시작일", example = "2024-01-01")
LocalDate startedAt
) {

private static AchievedRoutine of(Routine routine, MemberRoutine memberRoutine) {
return AchievedRoutine.builder()
.content(routine.getContent())
.achievedCount(memberRoutine.getAchievementCount())
.startedAt(memberRoutine.getCreatedAt())
.build();
}
}

@Builder(access = AccessLevel.PRIVATE)
private record AchievedChallenge(
@NotNull
@Schema(description = "챌린지 내용", example = "미라클 모닝")
String content,
@Schema(description = "챌린지 달성 횟수", example = "5")
int achievedCount,
@NotNull
@Schema(description = "챌린지 시작일", example = "2024-01-01")
LocalDate startedAt
) {

private static AchievedChallenge of(Challenge challenge, MemberChallenge memberChallenge) {
return AchievedChallenge.builder()
.content(challenge.getContent())
.achievedCount(memberChallenge.getAchievedCount())
.startedAt(memberChallenge.getCreatedAt())
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.soptie.server.api.controller.dto.response.achievement;

import java.util.List;
import java.util.Map;

import com.soptie.server.domain.theme.Theme;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.val;

@Builder(access = AccessLevel.PRIVATE)
public record AchievedThemesResponse(
@Schema(description = "전체 달성 횟수", example = "100")
int achievedCount,
@NotNull
@Schema(description = "테마 정보 목록")
List<AchievedTheme> themes
) {

public static AchievedThemesResponse of(
List<Theme> themes,
Map<Long, Integer> achievedCountsByTheme
) {
val achievedThemes = getAchievedThemes(themes, achievedCountsByTheme);
return AchievedThemesResponse.builder()
.achievedCount(achievedThemes.stream().mapToInt(it -> it.achievedCount).sum())
.themes(achievedThemes)
.build();
}

private static List<AchievedTheme> getAchievedThemes(
List<Theme> themes,
Map<Long, Integer> achievedCountsByTheme
) {
return themes.stream()
.map(it -> AchievedTheme.of(it, achievedCountsByTheme.get(it.getId())))
.sorted((a, b) -> {
val diff = b.achievedCount - a.achievedCount;
return diff != 0 ? diff : a.name.compareTo(b.name);
})
.toList();
}

@Builder(access = AccessLevel.PRIVATE)
private record AchievedTheme(
@Schema(description = "테마 id", example = "1")
long id,
@NotNull
@Schema(description = "테마 이름", example = "관계쌓기")
String name,
@Schema(description = "테마를 달성한 횟수", example = "50")
int achievedCount
) {

private static AchievedTheme of(Theme theme, int achievedCount) {
return AchievedTheme.builder()
.id(theme.getId())
.name(theme.getName())
.achievedCount(achievedCount)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ public enum SuccessMessage {
GET_CALENDAR("캘린더 조회 성공"),

/* version */
GET_VERSION("버전 조회 성공");
GET_VERSION("버전 조회 성공"),

/* statistics */
GET_STATISTICS("통계 조회 성공");

private final String message;
}
Loading

0 comments on commit 28b3cb4

Please sign in to comment.