Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
public enum SightError implements ErrorCodeInterface {
INVALID_COORDINATE_RANGE("SIT001", "최소 위도/경도는 최대 위도/경도보다 작아야 합니다.", HttpStatus.BAD_REQUEST),
SIGHT_NOT_FOUND("SIT002", "해당 관광지를 찾을 수 없습니다", HttpStatus.NOT_FOUND),

CURATION_NOT_FOUND("CUR001", "큐레이션이 존재하지 않습니다.", HttpStatus.NOT_FOUND),
;

private final String status;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,100 @@
package com.earseo.sight.controller;

import com.earseo.sight.common.BaseResponse;
import com.earseo.sight.dto.request.CurationCreateRequest;
import com.earseo.sight.dto.request.CurationUpdateRequest;
import com.earseo.sight.dto.response.CurationDeleteResponse;
import com.earseo.sight.dto.response.CurationResponse;
import com.earseo.sight.service.CurationService;
import com.earseo.sight.service.InitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/api/admin/sight")
@RequiredArgsConstructor
public class SightAdminController {

private final InitService initService;
private final CurationService curationService;

@PostMapping("/init")
public ResponseEntity<BaseResponse<String>> initSight() {
initService.initSight();
initService.initDocent();
return ResponseEntity.ok(BaseResponse.ok(null));
}

@Operation(summary = "큐레이션 생성")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "생성 성공"),
@ApiResponse(responseCode = "400", description = "유효하지 않은 입력")
})
@PostMapping("/curation")
public ResponseEntity<BaseResponse<CurationResponse>> createCuration(
@RequestBody
@Valid
CurationCreateRequest request
){
return ResponseEntity.ok(BaseResponse.ok(curationService.createCuration(request)));
}

@PutMapping("/curation/{curationId}")
@Operation(
summary = "큐레이션 수정",
description = "큐레이션 정보를 수정합니다. 제공된 필드만 업데이트됩니다."
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "큐레이션 수정 성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CurationResponse.class)
)
),
@ApiResponse(
responseCode = "404",
description = "큐레이션을 찾을 수 없음"
)
})
public ResponseEntity<BaseResponse<CurationResponse>> updateCuration(
@Parameter(description = "큐레이션 ID", required = true)
@PathVariable Long curationId,
@RequestBody @Valid CurationUpdateRequest request
) {
CurationResponse response = curationService.updateCuration(curationId, request);
return ResponseEntity.ok(BaseResponse.ok(response));
}

@Operation(
summary = "큐레이션 삭제",
description = "큐레이션을 삭제합니다. 연관된 큐레이션-관광지 매핑도 함께 삭제됩니다."
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "큐레이션 삭제 성공"
),
@ApiResponse(
responseCode = "404",
description = "큐레이션을 찾을 수 없음"
)
})
@DeleteMapping("/curation/{curationId}")
public ResponseEntity<BaseResponse<CurationDeleteResponse>> deleteCuration(
@Parameter(description = "큐레이션 ID", required = true)
@PathVariable Long curationId
) {
return ResponseEntity.ok(BaseResponse.ok(curationService.deleteCuration(curationId)));
}
}
94 changes: 91 additions & 3 deletions src/main/java/com/earseo/sight/controller/SightController.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.earseo.sight.controller;

import com.earseo.sight.common.BaseResponse;
import com.earseo.sight.dto.response.DocentResponse;
import com.earseo.sight.dto.response.SightDetailInfoResponse;
import com.earseo.sight.dto.response.SightMapInfoList;
import com.earseo.sight.dto.response.*;
import com.earseo.sight.service.CurationService;
import com.earseo.sight.service.SightService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -29,6 +28,7 @@
public class SightController {

private final SightService sightService;
private final CurationService curationService;

@Operation(
summary = "지도 사각형 영역 내 관광지 조회",
Expand Down Expand Up @@ -354,4 +354,92 @@ public ResponseEntity<BaseResponse<DocentResponse>> getDocent(
) {
return ResponseEntity.ok(BaseResponse.ok(sightService.getDocent(sightId)));
}

@Operation(
summary = "큐레이션 목록 조회",
description = "큐레이션 목록을 조회합니다."
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "큐레이션 목록 조회 성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CurationList.class)
)
)
})
@GetMapping("/curation")
public ResponseEntity<BaseResponse<CurationList>> getCurationList() {
return ResponseEntity.ok(BaseResponse.ok(curationService.getCurationList()));
}

@Operation(
summary = "큐레이션에 포함된 관광지 목록 조회",
description = "지정된 큐레이션 ID에 포함된 관광지들을 중심점(위도/경도) 기준 거리순으로 반환합니다."
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content = @Content(
schema = @Schema(implementation = CurationSightList.class)
)
),
@ApiResponse(
responseCode = "400",
description = "파라미터 오류"
),
@ApiResponse(
responseCode = "404",
description = "큐레이션을 찾을 수 없음",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = @ExampleObject(
value = """
{
"status": "CURATION_NOT_FOUND",
"message": "큐레이션이 존재하지 않습니다.",
"data": null
}
"""
)
)
)
})
@GetMapping("/curation/{curationId}")
public ResponseEntity<BaseResponse<CurationSightList>> getCurationSight(
@Parameter(
description = "큐레이션 ID",
required = true,
example = "1"
)
@PathVariable
Long curationId,
@Parameter(
description = "중심점 경도",
required = true,
example = "126.9780",
schema = @Schema(minimum = "124", maximum = "133")
)
@RequestParam
@NotNull(message = "경도는 필수입니다")
@DecimalMin(value = "124", message = "경도는 124 이상이어야 합니다")
@DecimalMax(value = "133", message = "경도는 133 이하여야 합니다")
Double longitude,

@Parameter(
description = "중심점 위도",
required = true,
example = "37.5665",
schema = @Schema(minimum = "33.0", maximum = "39")
)
@RequestParam
@NotNull(message = "위도는 필수입니다")
@DecimalMin(value = "33.0", message = "위도는 33 이상이어야 합니다")
@DecimalMax(value = "39", message = "위도는 39 이하여야 합니다")
Double latitude
) {
return ResponseEntity.ok(BaseResponse.ok(curationService.getCurationSight(curationId, longitude, latitude)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.earseo.sight.dto.projection;

public record CurationSightItemDto(
String contentId,
String title,
String cat2,
Double distance,
String addr3
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.earseo.sight.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;

import java.util.List;

public record CurationCreateRequest(
@NotBlank(message = "제목은 필수입니다")
@Size(max = 100, message = "제목은 100자 이하여야 합니다")
String title,

@Size(max = 500, message = "설명은 500자 이하여야 합니다")
String subtitle,

String curationImgUrl,

@NotEmpty(message = "관광지 목록은 필수입니다")
List<String> contentIds
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.earseo.sight.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;

public record CurationUpdateRequest(
@Schema(description = "큐레이션 제목", example = "서울 핫플 여행")
String title,

@Schema(description = "큐레이션 설명", example = "서울의 인기 관광지를 모았습니다")
String description,

@Schema(description = "큐레이션 이미지 URL", example = "https://example.com/image.jpg")
String curationImgUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.earseo.sight.dto.response;

public record CurationDeleteResponse(
Long id
) {
}
11 changes: 11 additions & 0 deletions src/main/java/com/earseo/sight/dto/response/CurationList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.earseo.sight.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

public record CurationList(
@Schema(description = "큐레이션 목록")
List<CurationResponse> curationList
) {
}
18 changes: 18 additions & 0 deletions src/main/java/com/earseo/sight/dto/response/CurationResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.earseo.sight.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record CurationResponse(
@Schema(description = "큐레이션 ID", example = "1")
Long curationId,

@Schema(description = "큐레이션 제목", example = "시간이 머문 서울의 길을 걸어보세요")
String curationTitle,

@Schema(description = "큐레이션 세부 설명", example = "서울의 유산이 살아 숨쉬는 이야기로 안내합니다.")
String description,

@Schema(description = "큐레이션 이미지 URL", example = "https://example.com/image.jpg")
String curationImgUrl
) {
}
17 changes: 17 additions & 0 deletions src/main/java/com/earseo/sight/dto/response/CurationSightList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.earseo.sight.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

public record CurationSightList(
@Schema(description = "큐레이션 제목", example = "시간이 머문 서울의 길을 걸어보세요")
String curationTitle,

@Schema(description = "큐레이션 세부 설명", example = "서울의 유산이 살아 숨쉬는 이야기로 안내합니다.")
String description,

@Schema(description = "큐레이션 내 관광지 목록")
List<CurationSightResponse> curationSightList
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.earseo.sight.dto.response;

import com.earseo.sight.dto.projection.CurationSightItemDto;
import io.swagger.v3.oas.annotations.media.Schema;

public record CurationSightResponse(
@Schema(description = "관광지 고유 ID", example = "126508")
String sightId,

@Schema(description = "관광지 이름", example = "경복궁")
String title,

@Schema(description = "관광지 테마 (예: 문화시설, 자연관광지 등)", example = "인문")
String theme,

@Schema(description = "현재 위치로부터의 직선 거리 (미터)", example = "1234.56")
Double distance,

@Schema(description = "주소 요약 (구/동 단위)", example = "서울 종로구")
String address
) {
public static CurationSightResponse toDto(CurationSightItemDto dto) {
return new CurationSightResponse(
dto.contentId(),
dto.title(),
dto.cat2(),
Math.round(dto.distance()/1000 * 10.0) / 10.0,
dto.addr3()
);
}
}
Loading
Loading