-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 지역 목록(더보기·필터) API 와 필터칩 지역 수 #272
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
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
5770039
feat: 필터칩에 카테고리별 지역 수를 함께 내린다
sevineleven 880433f
feat: 인구감소지역 목록 API (GET /api/v1/regions)
sevineleven c3a0a27
test: 지역 목록 페이지 경계·외부 호출 0건·칩 개수 일치
sevineleven 49a379a
docs: api-spec 에 지역 목록 API·필터칩 regionCount 반영
sevineleven 59480ed
fix: 방문자 집계가 비면 목록 요청마다 관광빅데이터를 다시 부르던 것
sevineleven be5d7e6
test: 목록 조회의 외부 호출 0건을 두 외부 모두로 잠근다
sevineleven 2f277f1
Merge branch 'dev' into feat/266-region-list-api
sevineleven 32b0d6e
fix: 집계 중 콘텐츠가 갱신되면 옛 결과를 세우지 않는다
sevineleven 34e6bf5
docs: 필터칩 regionCount 가 예시임을 밝힌다
sevineleven 5271e48
Merge remote-tracking branch 'origin/feat/266-region-list-api' into f…
sevineleven eadadff
fix: 세대 대조와 스냅샷 대입 사이의 틈을 닫는다
sevineleven File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/main/java/com/offway/core/trip/controller/RegionListApi.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.offway.core.trip.controller; | ||
|
|
||
| import com.offway.core.common.response.ApiResponseBody; | ||
| import com.offway.core.trip.controller.dto.RegionListResponse; | ||
| import com.offway.core.trip.domain.Category; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
|
|
||
| /** 지역 목록 API 문서 계약(#266). 매핑은 구현체({@link RegionListController})가 소유한다. */ | ||
| @Tag(name = "지역 목록", description = "인구감소지역 89곳 페이지 조회") | ||
| public interface RegionListApi { | ||
|
|
||
| @Operation( | ||
| summary = "인구감소지역 목록", | ||
| description = """ | ||
| 인구감소지역 89곳을 **방문자 랭킹 내림차순**으로 페이지에 담아 준다. 홈이 주는 상위 6곳 | ||
| 너머를 보는 "더보기" 화면이 쓴다. 카드 재료(한산도·볼거리 수·대표 이미지·카테고리)는 | ||
| 홈 카드와 같다. | ||
|
|
||
| **외부 API 를 부르지 않는다.** 방문자 집계·지역 콘텐츠·관광사진이 모두 적재된 값이라, | ||
| 관광 API 한도가 소진되거나 포털이 점검 중이어도 목록은 그대로 나간다. 아직 콘텐츠가 | ||
| 적재되지 않은 지역은 목록에서 빠지지 않고 볼거리 0·이미지 없음으로 나간다. | ||
|
|
||
| **정렬 파라미터는 없다.** 정렬이 하나뿐이기 때문이다. 도달시간 순은 출발지 좌표가 있어야 | ||
| 정의되는데 이 엔드포인트는 그것을 받지 않는다 — 그쪽은 `POST /api/v1/regions/recommendations` | ||
| 가 소유한다. | ||
|
|
||
| 페이지 정보(`page`·`size`·`totalElements`·`totalPages`)는 응답 본문이 아니라 공통 래퍼의 | ||
| `pageResponse` 에 실린다. | ||
| """) | ||
| @ApiResponse(responseCode = "200", description = "조회 성공 (해당 카테고리에 지역이 없으면 빈 목록)") | ||
| @ApiResponse(responseCode = "400", description = "category 가 정의되지 않은 값 (ALL·SIGHT·STAY·EXPERIENCE·FOOD 외)") | ||
| @ApiResponse(responseCode = "401", description = "인증 필요") | ||
| ApiResponseBody<RegionListResponse> regions( | ||
| @Parameter(description = "필터칩으로 좁히기. 생략하거나 ALL 이면 전체. 칩별 지역 수는 GET /api/v1/categories 가 준다") | ||
| Category category, | ||
| @Parameter(description = "0부터 시작하는 페이지 번호. 기본 0. 음수는 0 으로 자른다") Integer page, | ||
| @Parameter(description = "페이지 크기. 기본 20, 최대 100. 범위를 벗어나면 잘라 준다(거절하지 않는다)") | ||
| Integer size); | ||
| } |
31 changes: 31 additions & 0 deletions
31
src/main/java/com/offway/core/trip/controller/RegionListController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.offway.core.trip.controller; | ||
|
|
||
| import com.offway.core.common.response.ApiResponseBody; | ||
| import com.offway.core.common.response.PageResponse; | ||
| import com.offway.core.trip.controller.dto.RegionListResponse; | ||
| import com.offway.core.trip.domain.Category; | ||
| import com.offway.core.trip.service.RegionListService; | ||
| import com.offway.core.trip.service.dto.RegionList; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/v1/regions") | ||
| @RequiredArgsConstructor | ||
| public class RegionListController implements RegionListApi { | ||
|
|
||
| private final RegionListService regionListService; | ||
|
|
||
| @Override | ||
| @GetMapping | ||
| public ApiResponseBody<RegionListResponse> regions( | ||
| @RequestParam(required = false) Category category, | ||
| @RequestParam(required = false) Integer page, | ||
| @RequestParam(required = false) Integer size) { | ||
| RegionList regions = regionListService.list(category, page, size); | ||
| return ApiResponseBody.ok(RegionListResponse.from(regions), PageResponse.of(regions)); | ||
| } | ||
| } |
17 changes: 12 additions & 5 deletions
17
src/main/java/com/offway/core/trip/controller/dto/CategoryResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,39 @@ | ||
| package com.offway.core.trip.controller.dto; | ||
|
|
||
| import com.offway.core.trip.domain.Category; | ||
| import com.offway.core.trip.domain.CategoryCounts; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
| * 필터칩 카테고리 목록 응답 — API 계약. | ||
| * | ||
| * <p>칩마다 <b>그 칩으로 좁혔을 때 나오는 지역 수</b>를 함께 낸다(#266). 없으면 화면이 개수를 지어내거나("전부 1건") 빈 칩을 그대로 | ||
| * 그린다. | ||
| * | ||
| * @param categories 노출 순서대로의 카테고리 칩 | ||
| */ | ||
| public record CategoryResponse(List<Item> categories) { | ||
|
|
||
| /** 도메인 {@link Category} 전부를 선언 순서대로 노출한다(ALL 이 맨 앞). */ | ||
| public static CategoryResponse of() { | ||
| return new CategoryResponse(Arrays.stream(Category.values()).map(Item::from).toList()); | ||
| public static CategoryResponse of(CategoryCounts counts) { | ||
| return new CategoryResponse( | ||
| Arrays.stream(Category.values()).map(category -> Item.from(category, counts)).toList()); | ||
| } | ||
|
|
||
| /** | ||
| * @param key enum 식별자 (ALL·SIGHT·STAY·EXPERIENCE·FOOD) | ||
| * @param label 한글 라벨 | ||
| * @param regionCount 이 칩으로 좁혔을 때 나오는 인구감소지역 수. {@code ALL} 은 전체 지역 수다 | ||
| */ | ||
| public record Item( | ||
| @Schema(example = "SIGHT") String key, | ||
| @Schema(example = "관광지") String label) { | ||
| @Schema(example = "관광지") String label, | ||
| @Schema(description = "이 칩으로 좁혔을 때 나오는 지역 수 (ALL 은 전체)", example = "61") int regionCount) { | ||
|
|
||
| static Item from(Category category) { | ||
| return new Item(category.name(), category.label()); | ||
| static Item from(Category category, CategoryCounts counts) { | ||
| return new Item(category.name(), category.label(), counts.of(category)); | ||
| } | ||
| } | ||
| } |
23 changes: 23 additions & 0 deletions
23
src/main/java/com/offway/core/trip/controller/dto/CategoryTagResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.offway.core.trip.controller.dto; | ||
|
|
||
| import com.offway.core.trip.domain.Category; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| /** | ||
| * 지역 카드에 붙는 볼거리 분류 태그 — "이 지역에 이런 것이 있다". | ||
| * | ||
| * <p><b>필터칩({@link CategoryResponse.Item})과 다른 타입이다.</b> 둘 다 {@code key}·{@code label} 을 갖지만 답하는 질문이 다르다 — | ||
| * 필터칩은 "이 칩으로 좁히면 몇 곳인가"({@code regionCount})까지 답하고, 태그는 그 지역 카드의 표시일 뿐이라 개수라는 개념이 없다. | ||
| * 한 타입으로 묶으면 지역 카드마다 전체 지역 수가 따라붙어 읽는 쪽이 그것을 그 지역의 수로 오해한다. | ||
| * | ||
| * @param key enum 식별자 (SIGHT·STAY·EXPERIENCE·FOOD) | ||
| * @param label 한글 라벨 | ||
| */ | ||
| public record CategoryTagResponse( | ||
| @Schema(example = "SIGHT") String key, | ||
| @Schema(example = "관광지") String label) { | ||
|
|
||
| public static CategoryTagResponse from(Category category) { | ||
| return new CategoryTagResponse(category.name(), category.label()); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
src/main/java/com/offway/core/trip/controller/dto/RegionListResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package com.offway.core.trip.controller.dto; | ||
|
|
||
| import com.offway.core.common.logging.LogSummaries; | ||
| import com.offway.core.common.logging.LogSummary; | ||
| import com.offway.core.trip.domain.CrowdLevel; | ||
| import com.offway.core.trip.service.dto.RegionList; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
| * 지역 목록 응답 — API 계약. 방문자 랭킹 내림차순. | ||
| * | ||
| * <p>페이지 메타({@code page}·{@code size}·{@code totalElements}·{@code totalPages})는 여기가 아니라 <b>공통 래퍼의 | ||
| * {@code pageResponse}</b> 로 나간다(api-convention). 목록 API 가 전부 같은 자리에서 페이지 정보를 주게 하려는 것이다. | ||
| * | ||
| * @param regions 이 페이지의 지역 | ||
| */ | ||
| public record RegionListResponse(List<Item> regions) implements LogSummary { | ||
|
|
||
| public static RegionListResponse from(RegionList regions) { | ||
| return new RegionListResponse(regions.regions().stream().map(Item::from).toList()); | ||
| } | ||
|
|
||
| @Override | ||
| public String logSummary() { | ||
| return LogSummaries.count("지역", regions); | ||
| } | ||
|
|
||
| /** | ||
| * @param regionId 지역 ID | ||
| * @param name 지역명 (시군구 · 시도) | ||
| * @param crowdLevel 한산도 뱃지 | ||
| * @param imageUrl 대표 이미지 URL (없으면 null) | ||
| * @param contentCount 볼거리 수 (인접 50km 병합 시 합산) | ||
| * @param categories 볼거리 카테고리 태그 | ||
| * @param neighborIncluded 볼거리 부족으로 인접 50km 지역이 포함됐는지 | ||
| */ | ||
| public record Item( | ||
| long regionId, | ||
| @Schema(example = "완도군 · 전라남도") String name, | ||
| CrowdLevel crowdLevel, | ||
| @Schema( | ||
| example = "http://tong.visitkorea.or.kr/cms/resource/83/1234583_image2_1.jpg", | ||
| nullable = true) | ||
| String imageUrl, | ||
| @Schema(example = "38") int contentCount, | ||
| List<CategoryTagResponse> categories, | ||
| @Schema(example = "false") boolean neighborIncluded) { | ||
|
|
||
| static Item from(RegionList.Item region) { | ||
| return new Item( | ||
| region.regionId(), | ||
| region.sigungu() + " · " + region.sido(), | ||
| region.crowdLevel(), | ||
| region.imageUrl(), | ||
| region.contentCount(), | ||
| region.categories().stream().map(CategoryTagResponse::from).toList(), | ||
| region.neighborIncluded()); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.