-
Notifications
You must be signed in to change notification settings - Fork 2
[BOM-1157] feat: 마이페이지 월간 리포트 API 구현 #783
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "BOM-1157-\uB9C8\uC774\uD398\uC774\uC9C0-\uC6D4\uAC04-\uB9AC\uD3EC\uD2B8-api-\uAD6C\uD604"
Changes from 14 commits
4513373
d3f8f30
fe3c910
91c22c7
5ae8f2e
773403a
0b6b61a
b957e4b
fbdc6ed
bcc9f25
df35490
818d094
65e4cc1
479a930
8995127
9ee6d80
223f1c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| /** | ||
| * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) | ||
| * (7.10.0). https://openapi-generator.tech Do not edit the class manually. | ||
| */ | ||
| package me.bombom.openapi.monthlyreport.api; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.media.ArraySchema; | ||
| 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.security.SecurityRequirement; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.annotation.Generated; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import java.util.List; | ||
| import me.bombom.api.v1.common.resolver.LoginMember; | ||
| import me.bombom.api.v1.member.domain.Member; | ||
| import me.bombom.openapi.monthlyreport.model.MonthlyReportDashboardRequest; | ||
| import me.bombom.openapi.monthlyreport.model.MonthlyReportRequest; | ||
| import me.bombom.openapi.monthlyreport.model.ReadingCalendarDayResponse; | ||
| import me.bombom.openapi.monthlyreport.model.ReadingDashboardResponse; | ||
| import org.springdoc.core.annotations.ParameterObject; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @Validated | ||
| @Tag(name = "MonthlyReport", description = "월간 리포트 관련 API") | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
|
Comment on lines
+30
to
+32
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. k3기존 컨벤션인 애노테이션 길이 순 정렬을 고려했습니다.
따라서 |
||
| public interface MonthlyReportApi { | ||
|
|
||
| /** | ||
| * GET /api/v1/members/me/reading/calendar : 월간 읽기 캘린더 조회 로그인한 회원의 해당 연·월 일자별 읽기 현황을 조회합니다. | ||
| * | ||
| * @return 읽기 캘린더 조회 성공 (status code 200) or 잘못된 연·월 파라미터 요청 (status code 400) or 인증 실패 (status | ||
| * code 401) | ||
| */ | ||
| @Operation( | ||
| operationId = "getReadingCalendar", | ||
| summary = "월간 읽기 캘린더 조회", | ||
| description = "로그인한 회원의 해당 연·월 일자별 읽기 현황을 조회합니다.", | ||
| tags = {"MonthlyReport"}, | ||
| responses = { | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "읽기 캘린더 조회 성공", | ||
| content = { | ||
| @Content( | ||
| mediaType = "application/json", | ||
| array = | ||
| @ArraySchema( | ||
| schema = @Schema(implementation = ReadingCalendarDayResponse.class))) | ||
| }), | ||
| @ApiResponse(responseCode = "400", description = "잘못된 연·월 파라미터 요청"), | ||
| @ApiResponse(responseCode = "401", description = "인증 실패") | ||
| }, | ||
| security = {@SecurityRequirement(name = "sessionCookie")}) | ||
| @RequestMapping( | ||
| method = RequestMethod.GET, | ||
| value = "/api/v1/members/me/reading/calendar", | ||
| produces = {"application/json"}) | ||
| @ResponseStatus(HttpStatus.OK) | ||
| List<ReadingCalendarDayResponse> getReadingCalendar( | ||
| @Parameter(hidden = true) @LoginMember Member member, | ||
| @Valid @ModelAttribute @ParameterObject MonthlyReportRequest request | ||
| ); | ||
|
|
||
| /** | ||
| * GET /api/v1/members/me/reading/dashboard : 월간 읽기 대시보드 조회 로그인한 회원의 해당 연·월 읽기 통계를 조회합니다. 읽은 아티클 | ||
| * 수(지난 달 대비 증감 포함), 북마크 수, 자주 읽은 뉴스레터를 반환합니다. | ||
| * | ||
| * @return 읽기 대시보드 조회 성공 (status code 200) or 잘못된 연·월 파라미터 요청 (status code 400) or 인증 실패 (status | ||
| * code 401) | ||
| */ | ||
| @Operation( | ||
| operationId = "getReadingDashboard", | ||
| summary = "월간 읽기 대시보드 조회", | ||
| description = | ||
| "로그인한 회원의 해당 연·월 읽기 통계를 조회합니다. 읽은 아티클 수(지난 달 대비 증감 포함), 북마크 수, 자주 읽은 뉴스레터를 반환합니다.", | ||
| tags = {"MonthlyReport"}, | ||
| responses = { | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "읽기 대시보드 조회 성공", | ||
| content = { | ||
| @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ReadingDashboardResponse.class)) | ||
| }), | ||
| @ApiResponse(responseCode = "400", description = "잘못된 연·월 파라미터 요청"), | ||
| @ApiResponse(responseCode = "401", description = "인증 실패") | ||
| }, | ||
| security = {@SecurityRequirement(name = "sessionCookie")}) | ||
| @RequestMapping( | ||
| method = RequestMethod.GET, | ||
| value = "/api/v1/members/me/reading/dashboard", | ||
| produces = {"application/json"}) | ||
| @ResponseStatus(HttpStatus.OK) | ||
| ReadingDashboardResponse getReadingDashboard( | ||
| @Parameter(hidden = true) @LoginMember Member member, | ||
| @Valid @ModelAttribute @ParameterObject MonthlyReportDashboardRequest request | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package me.bombom.openapi.monthlyreport.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonValue; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
| import jakarta.annotation.Generated; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
|
|
||
| /** | ||
| * 증감 방향 | ||
| */ | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
| public enum ChangeDirection { | ||
|
|
||
| UP("UP"), | ||
| DOWN("DOWN"), | ||
| SAME("SAME"); | ||
|
Comment on lines
+18
to
+20
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2API 스펙 보면서 궁금했던 부분인데, 증감률(
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 증감률 계산 과정에서 서버가 이미 현재 값과 이전 값을 비교하고 있으므로, 그 결과로 나오는 증감 방향도 서버가 함께 내려주어 클라이언트가 증감률을 해석해 방향을 다시 계산하지 않아도 되도록 했습니다. 클라이언트 요청이기도 하구여,, 그리고 지금은 previousCount가 0일 경우 무조건 |
||
|
|
||
| private final String value; | ||
|
|
||
| ChangeDirection(String value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| @JsonValue | ||
| public String getValue() { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return String.valueOf(value); | ||
| } | ||
|
|
||
| @JsonCreator | ||
| public static ChangeDirection fromValue(String value) { | ||
| for (ChangeDirection b : ChangeDirection.values()) { | ||
| if (b.value.equals(value)) { | ||
| return b; | ||
| } | ||
| } | ||
| throw new IllegalArgumentException("Unexpected value '" + value + "'"); | ||
| } | ||
|
Comment on lines
+28
to
+46
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. K2이 부분은 OpenAPI codegen이 enum의 JSON 직렬화/역직렬화를 보장하기 위해 생성하는 코드입니다. 스펙에 정의된 enum 문자열 값과 Java enum 상수를 명시적으로 매핑해야 해서 직접 작성했다면 더 단순하게 만들 수도 있지만, codegen 결과물에서는 enum 값 매핑을 안전하게 처리하기 위해 생기는 어쩔 수 없는 부분입니다. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package me.bombom.openapi.monthlyreport.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
| import jakarta.annotation.Generated; | ||
|
|
||
| /** | ||
| * 자주 읽은 뉴스레터 정보 | ||
| */ | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
| public record FrequentReadNewsletterResponse( | ||
|
|
||
| @Schema(description = "순위 (1-base)", requiredMode = REQUIRED) | ||
| int rank, | ||
|
|
||
| @NotNull | ||
| @Schema(description = "뉴스레터 ID", requiredMode = REQUIRED) | ||
| Long newsletterId, | ||
|
|
||
| @NotNull | ||
| @Schema(description = "뉴스레터명", requiredMode = REQUIRED) | ||
| String name, | ||
|
|
||
| @Schema(description = "이번 달 읽은 아티클 수", requiredMode = REQUIRED) | ||
| long readCount | ||
| ) { | ||
|
|
||
| public static FrequentReadNewsletterResponse of( | ||
| int rank, | ||
| Long newsletterId, | ||
| String name, | ||
| long readCount | ||
| ) { | ||
| return new FrequentReadNewsletterResponse( | ||
| rank, | ||
| newsletterId, | ||
| name, | ||
| readCount | ||
| ); | ||
| } | ||
|
Comment on lines
+32
to
+44
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. K1OpenAPI codegen으로 자동 생성되는 응답 DTO는 매퍼/호출부에서 기존과 동일한 생성 방식을 유지하기 위해 codegen 템플릿 차원에서 필드 개수에 따라
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P4codegen 템플릿 차원에서 설정한 것이라면, 생성자 필드와 정적 팩터리 메서드 파라미터가 서로 동일할 것 같은데요 (맞나요?ㅎㅎ) 저는 기본 생성자와 똑같은 인자로 정적 팩터리 메서드를 만든 경험이 거의 없었어서, 조금 어색하게 느껴졌던 것 같아요!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 차이가 없는게 맞습니다,, |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package me.bombom.openapi.monthlyreport.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
| import jakarta.annotation.Generated; | ||
|
|
||
| /** | ||
| * 월간 리포트 대시보드 조회 조건 | ||
| */ | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
| public record MonthlyReportDashboardRequest( | ||
|
|
||
| @Min(1) | ||
| @Schema(description = "조회할 연도", requiredMode = REQUIRED) | ||
| int year, | ||
|
|
||
| @Min(1) | ||
| @Max(12) | ||
| @Schema(description = "조회할 월 (1-12)", requiredMode = REQUIRED) | ||
| int month, | ||
|
|
||
| @Min(1) | ||
| @Schema(description = "자주 읽은 뉴스레터 조회 개수", requiredMode = REQUIRED) | ||
| int limit | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package me.bombom.openapi.monthlyreport.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
| import jakarta.annotation.Generated; | ||
|
|
||
| /** | ||
| * 월간 리포트 조회 조건 | ||
| */ | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
| public record MonthlyReportRequest( | ||
|
|
||
| @Min(1) | ||
| @Schema(description = "조회할 연도", requiredMode = REQUIRED) | ||
| int year, | ||
|
|
||
| @Min(1) | ||
| @Max(12) | ||
| @Schema(description = "조회할 월 (1-12)", requiredMode = REQUIRED) | ||
| int month | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package me.bombom.openapi.monthlyreport.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import java.time.LocalDate; | ||
| import org.springframework.format.annotation.DateTimeFormat; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
| import jakarta.annotation.Generated; | ||
|
|
||
| /** | ||
| * 읽기 캘린더의 하루 정보 | ||
| */ | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
| public record ReadingCalendarDayResponse( | ||
|
|
||
| @NotNull | ||
| @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) | ||
| @Schema(description = "날짜 (yyyy-MM-dd)", requiredMode = REQUIRED) | ||
| LocalDate date, | ||
|
|
||
| @Schema(description = "해당 날짜에 아티클을 읽었는지 여부", requiredMode = REQUIRED) | ||
| boolean read, | ||
|
|
||
| @Schema(description = "해당 날짜에 읽은 아티클 수", requiredMode = REQUIRED) | ||
| long readCount | ||
| ) { | ||
|
|
||
| public static ReadingCalendarDayResponse of( | ||
| LocalDate date, | ||
| boolean read, | ||
| long readCount | ||
| ) { | ||
| return new ReadingCalendarDayResponse( | ||
| date, | ||
| read, | ||
| readCount | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package me.bombom.openapi.monthlyreport.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import com.fasterxml.jackson.annotation.JsonValue; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
| import jakarta.annotation.Generated; | ||
|
|
||
| /** | ||
| * 월간 읽기 대시보드 응답 | ||
| */ | ||
| @Generated("org.openapitools.codegen.languages.SpringCodegen") | ||
| public record ReadingDashboardResponse( | ||
|
|
||
| @Schema(description = "이번 달 읽은 아티클 수", requiredMode = REQUIRED) | ||
| long readArticleCount, | ||
|
|
||
| @Schema(description = "지난 달 대비 읽은 아티클 수 증감률 (%)", requiredMode = REQUIRED) | ||
| Double readArticleChangeRate, | ||
|
|
||
| ChangeDirection readArticleChangeDirection, | ||
|
|
||
| @Schema(description = "북마크 개수", requiredMode = REQUIRED) | ||
| long bookmarkCount, | ||
|
|
||
| @Valid | ||
| @NotNull | ||
| @Schema(description = "자주 읽은 뉴스레터 TOP", requiredMode = REQUIRED) | ||
| List<FrequentReadNewsletterResponse> frequentReadNewsletters | ||
| ) { | ||
|
|
||
| public static ReadingDashboardResponse of( | ||
| long readArticleCount, | ||
| Double readArticleChangeRate, | ||
| ChangeDirection readArticleChangeDirection, | ||
| long bookmarkCount, | ||
| List<FrequentReadNewsletterResponse> frequentReadNewsletters | ||
| ) { | ||
| return new ReadingDashboardResponse( | ||
| readArticleCount, | ||
| readArticleChangeRate, | ||
| readArticleChangeDirection, | ||
| bookmarkCount, | ||
| frequentReadNewsletters | ||
| ); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
K1 - 그냥 보세요
옆 파일 트리에서 보이는 것처럼
generated하위 파일들은 OpenAPI codegen으로 자동 생성된 코드입니다.API는 spec 레포의
~.tsp파일에 정의하고 있으며, 생성 코드가 한 패키지에 계속 쌓이지 않도록tsp파일명을 기준으로 하위 패키지가 분리되도록 설정했습니다.