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
10 changes: 9 additions & 1 deletion src/main/java/com/umc/auth/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import com.umc.common.response.ApiResponse;
import com.umc.domain.user.entity.User;
import com.umc.domain.user.repository.UserRepository;
import com.umc.global.config.SwaggerConfig;
import com.umc.global.exception.BusinessException;
import com.umc.global.exception.ErrorCode;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -27,6 +30,11 @@ public class AuthController {
private final JwtProvider jwtProvider;

@Operation(summary = "로그인 (회원가입 없음)", description = "닉네임 + 비밀번호로 로그인 요청. 닉네임이 존재하지 않으면 자동으로 유저 생성 후 로그인합니다. 기존에 등록된 닉네임인 경우 비밀번호 검증 후 로그인합니다.")
@SwaggerConfig.ApiErrorExamples({
ErrorCode.DUPLICATE_NICKNAME,
ErrorCode.LOGIN_NICKNAME_EMPTY,
ErrorCode.LOGIN_PASSWORD_EMPTY
})
@PostMapping("/login")
public ResponseEntity<ApiResponse<TokenResponse>> login(@Valid @RequestBody LoginRequest request) {
User user = userRepository.findByNickname(request.nickname())
Expand All @@ -41,7 +49,7 @@ public ResponseEntity<ApiResponse<TokenResponse>> login(@Valid @RequestBody Logi

// 기존 유저인 경우 비밀번호 검증
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
throw new BusinessException(ErrorCode.DUPLICATE_NICKNAME);
}

// JWT 발급
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
import com.umc.domain.review.dto.ReviewResponseDTO;
import com.umc.domain.review.service.ReviewService;
import com.umc.domain.user.entity.User;
import com.umc.global.config.SwaggerConfig;
import com.umc.global.exception.ErrorCode;
import io.jsonwebtoken.JwtException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -34,6 +36,14 @@ public class ReviewController {
description = "특정 향수에 대한 리뷰를 작성합니다.",
security = @SecurityRequirement(name = "bearerAuth")
)
@SwaggerConfig.ApiErrorExamples({
ErrorCode.TOKEN_INVALID,
ErrorCode.PERFUME_NOT_FOUND,
ErrorCode.USER_NOT_FOUND,
ErrorCode.PERFUME_INVALID_INPUT_VALUE,
ErrorCode.REVIEW_DESCRIPTION_EMPTY,
ErrorCode.INTERNAL_SERVER_ERROR
})
public ResponseEntity<ApiResponse<ReviewResponseDTO.CreateReviewReponseDTO>> createReview(
@PathVariable Long perfumeId,
@RequestBody ReviewRequestDTO.CreateReviewRequestDTO request,
Expand All @@ -53,6 +63,11 @@ public ResponseEntity<ApiResponse<ReviewResponseDTO.CreateReviewReponseDTO>> cre

@GetMapping("/me")
@Operation(summary = "내가 작성한 리뷰 목록 조회", security = @SecurityRequirement(name = "bearerAuth"))
@SwaggerConfig.ApiErrorExamples({
ErrorCode.TOKEN_INVALID,
ErrorCode.USER_NOT_FOUND,
ErrorCode.INTERNAL_SERVER_ERROR
})
public ResponseEntity<ApiResponse<List<ReviewResponseDTO.MyReviewDTO>>> getMyReviews(HttpServletRequest httpRequest) {
String authorization = httpRequest.getHeader("Authorization");
log.info("내 리뷰 조회 요청 - Authorization: {}", authorization);
Expand All @@ -68,6 +83,12 @@ public ResponseEntity<ApiResponse<List<ReviewResponseDTO.MyReviewDTO>>> getMyRev

@GetMapping("/{perfumeId}")
@Operation(summary = "특정 향수 리뷰 목록 조회")
@SwaggerConfig.ApiErrorExamples({
ErrorCode.PERFUME_NOT_FOUND,
ErrorCode.USER_NOT_FOUND,
ErrorCode.PERFUME_INVALID_INPUT_VALUE,
ErrorCode.INTERNAL_SERVER_ERROR
})
public ResponseEntity<ApiResponse<List<ReviewResponseDTO.ReviewSimpleDTO>>> getReviewsByPerfume(
@PathVariable Long perfumeId) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.umc.domain.review.repository.ReviewRepository;
import com.umc.domain.user.entity.User;
import com.umc.domain.user.repository.UserRepository;
import com.umc.global.exception.BusinessException;
import com.umc.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -27,7 +29,11 @@ public class ReviewService {
@Transactional
public ReviewResponseDTO.CreateReviewReponseDTO createReview(Long perfumeId, Long userId, ReviewRequestDTO.CreateReviewRequestDTO request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

if (request.getDescription() == null || request.getDescription().trim().isEmpty()) {
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "리뷰 내용은 비어 있을 수 없습니다.");
}

Review review = ReviewConverter.toEntity(perfumeId, userId, request);
Review saved = reviewRepository.save(review);
Expand Down
48 changes: 46 additions & 2 deletions src/main/java/com/umc/domain/shop/controller/ShopController.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,68 @@
package com.umc.domain.shop.controller;

import com.umc.common.response.ApiResponse;
import com.umc.domain.shop.dto.ShopResponseDto;
import com.umc.domain.shop.service.ShopService;
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.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/shops")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "매장 API", description = "매장 정보 조회 API")
public class ShopController {

private final ShopService shopService;

@GetMapping("/nearby")
public List<ShopResponseDto> getNearbyShops(
@Operation(
summary = "근처 매장 조회",
description = "현재 위치(위도, 경도)를 기준으로 근처 매장 목록을 조회합니다."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "근처 매장 조회 성공",
content = @Content(schema = @Schema(implementation = ApiResponse.class))
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "400",
description = "잘못된 요청 (잘못된 위도/경도 값)"
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "500",
description = "서버 오류"
)
})
public ApiResponse<List<ShopResponseDto>> getNearbyShops(
@Parameter(description = "위도 (latitude)", required = true, example = "37.5665")
@RequestParam double lat,

@Parameter(description = "경도 (longitude)", required = true, example = "126.9780")
@RequestParam double lng
) {
return shopService.findNearbyShops(lat, lng);
try {
log.info("근처 매장 조회 요청 - lat: {}, lng: {}", lat, lng);

List<ShopResponseDto> response = shopService.findNearbyShops(lat, lng);

log.info("근처 매장 조회 성공 - 매장 개수: {}", response.size());

return ApiResponse.success("근처 매장이 성공적으로 조회되었습니다.", response);

} catch (Exception e) {
log.error("근처 매장 조회 실패 - 오류: {}", e.getMessage());
throw new RuntimeException("근처 매장 조회 중 오류가 발생했습니다.");
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/umc/domain/shop/dto/ShopResponseDto.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
package com.umc.domain.shop.dto;

import com.umc.domain.shop.entity.Shop;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@Schema(description = "매장 응답")
public class ShopResponseDto {

@Schema(description = "매장 ID", example = "1")
private Long id;

@Schema(description = "매장명", example = "향수 전문점")
private String title;

@Schema(description = "연락처", example = "02-1234-5678")
private String contact;

@Schema(description = "주소", example = "서울특별시 강남구 테헤란로 123")
private String address;

@Schema(description = "매장 URL", example = "https://example.com/shop")
private String shopUrl;

@Schema(description = "매장 설명", example = "다양한 향수를 취급하는 전문 매장입니다.")
private String description;

@Schema(description = "위도", example = "37.5665")
private Double latitude;

@Schema(description = "경도", example = "126.9780")
private Double longitude;

public static ShopResponseDto from(Shop shop) {
Expand Down
17 changes: 13 additions & 4 deletions src/main/java/com/umc/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@ public enum ErrorCode {
REQUIRED_FIELD_MISSING(HttpStatus.BAD_REQUEST, "VALIDATION_002", "필수 필드가 누락되었습니다."),

// 로그인 관련 에러
DUPLICATE_NICKNAME(HttpStatus.CONFLICT, "BUSINESS_002", "이미 존재하는 닉네임입니다. 비밀번호를 다시 입력해주세요."),
DUPLICATE_NICKNAME(HttpStatus.CONFLICT, "LOGIN_4001", "이미 존재하는 닉네임입니다. 비밀번호를 다시 입력해주세요."),
LOGIN_NICKNAME_EMPTY(HttpStatus.BAD_REQUEST, "LOGIN_4002", "닉네임을 입력해주세요."),
LOGIN_PASSWORD_EMPTY(HttpStatus.BAD_REQUEST, "LOGIN_4002", "비밀번호를 입력해주세요."),

// 토큰 관련 에러
TOKEN_INVALID(HttpStatus.UNAUTHORIZED, "AUTH_001", "유효하지 않은 토큰입니다."),
//토큰 관련 에러
TOKEN_INVALID(HttpStatus.UNAUTHORIZED, "TOKEN_4001", "토큰이 유효하지 않습니다."),

// 향수 관련 에러
PERFUME_NOT_FOUND(HttpStatus.NOT_FOUND, "PERFUME_001", "해당 향수를 찾을 수 없습니다.");
PERFUME_NOT_FOUND(HttpStatus.NOT_FOUND, "PERFUME_4001", "해당 향수를 찾을 수 없습니다."),
PERFUME_INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "PERFUME_4002", "perfumeId가 잘못된 형식입니다."),

// 리뷰 관련 에러
REVIEW_DESCRIPTION_EMPTY(HttpStatus.BAD_REQUEST, "REVIEW_4001", "리뷰 내용은 비어 있을 수 없습니다."),

// 유저 관련 에러
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_4001", "해당 사용자를 찾을 수 없습니다.");

// TODO: 비즈니스 로직 개발하면서 필요한 에러코드들 추가

Expand Down