From 12c2ff815bfc5da8f5066d093e2c40ba66d9c453 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Tue, 14 Jan 2025 16:10:40 +0900 Subject: [PATCH 01/55] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20gitignore=20=EC=A0=9C=EA=B1=B0=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.gitignore b/.gitignore index 059e2df..875adba 100644 --- a/.gitignore +++ b/.gitignore @@ -13,28 +13,6 @@ out/ !**/src/main/**/out/ !**/src/test/**/out/ -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - ### Mac OS ### .DS_Store From 3543137500d0ba63554d39c4a505081720ad47cc Mon Sep 17 00:00:00 2001 From: gahyuun Date: Tue, 14 Jan 2025 16:11:54 +0900 Subject: [PATCH 02/55] =?UTF-8?q?feat:=20login=20request=20dto=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acon/server/member/api/controller/.gitkeep | 0 .../com/acon/server/member/api/request/.gitkeep | 0 .../server/member/api/request/LoginRequest.java | 13 +++++++++++++ .../com/acon/server/member/api/response/.gitkeep | 0 4 files changed, 13 insertions(+) delete mode 100644 src/main/java/com/acon/server/member/api/controller/.gitkeep delete mode 100644 src/main/java/com/acon/server/member/api/request/.gitkeep create mode 100644 src/main/java/com/acon/server/member/api/request/LoginRequest.java delete mode 100644 src/main/java/com/acon/server/member/api/response/.gitkeep diff --git a/src/main/java/com/acon/server/member/api/controller/.gitkeep b/src/main/java/com/acon/server/member/api/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/acon/server/member/api/request/.gitkeep b/src/main/java/com/acon/server/member/api/request/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/acon/server/member/api/request/LoginRequest.java b/src/main/java/com/acon/server/member/api/request/LoginRequest.java new file mode 100644 index 0000000..1f6336a --- /dev/null +++ b/src/main/java/com/acon/server/member/api/request/LoginRequest.java @@ -0,0 +1,13 @@ +package com.acon.server.member.api.request; + +import com.acon.server.member.domain.enums.SocialType; +import jakarta.validation.constraints.NotNull; + +public record LoginRequest( + @NotNull + SocialType socialType, + @NotNull + String idToken +) { + +} diff --git a/src/main/java/com/acon/server/member/api/response/.gitkeep b/src/main/java/com/acon/server/member/api/response/.gitkeep deleted file mode 100644 index e69de29..0000000 From eade2815474fd4a78207395d040ebcec1290b42a Mon Sep 17 00:00:00 2001 From: gahyuun Date: Tue, 14 Jan 2025 16:12:02 +0900 Subject: [PATCH 03/55] =?UTF-8?q?feat:=20login=20response=20dto=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/member/api/response/LoginResponse.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/com/acon/server/member/api/response/LoginResponse.java diff --git a/src/main/java/com/acon/server/member/api/response/LoginResponse.java b/src/main/java/com/acon/server/member/api/response/LoginResponse.java new file mode 100644 index 0000000..2e0df99 --- /dev/null +++ b/src/main/java/com/acon/server/member/api/response/LoginResponse.java @@ -0,0 +1,8 @@ +package com.acon.server.member.api.response; + +public record LoginResponse( + String accessToken, + String refreshToken +) { + +} From ad04d45a4811b58454e445099f6c43df5e10071f Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:14:37 +0900 Subject: [PATCH 04/55] =?UTF-8?q?chore:=20gitkeep=20=EC=82=AD=EC=A0=9C=20(?= =?UTF-8?q?#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/member/application/service/.gitkeep | 0 src/main/java/com/acon/server/member/infra/external/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/java/com/acon/server/member/application/service/.gitkeep delete mode 100644 src/main/java/com/acon/server/member/infra/external/.gitkeep diff --git a/src/main/java/com/acon/server/member/application/service/.gitkeep b/src/main/java/com/acon/server/member/application/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/acon/server/member/infra/external/.gitkeep b/src/main/java/com/acon/server/member/infra/external/.gitkeep deleted file mode 100644 index e69de29..0000000 From 393a580a9c309207b07a67edf344d9f31eb9f3d9 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:15:11 +0900 Subject: [PATCH 05/55] =?UTF-8?q?setting:=20google,=20jwt=20dependency=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 10b41a1..dbce273 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,14 @@ dependencies { annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' + + // Google + implementation 'com.google.api-client:google-api-client:2.7.0' + + // JWT + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' } tasks.named('test') { From 1e10d1253fa58393887e5603e4990fa6bd4dee48 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:16:18 +0900 Subject: [PATCH 06/55] =?UTF-8?q?feat:=20Google=20oauth=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/global/exception/ErrorType.java | 4 ++ .../infra/external/GoogleSocialService.java | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java diff --git a/src/main/java/com/acon/server/global/exception/ErrorType.java b/src/main/java/com/acon/server/global/exception/ErrorType.java index e398598..9d26f1e 100644 --- a/src/main/java/com/acon/server/global/exception/ErrorType.java +++ b/src/main/java/com/acon/server/global/exception/ErrorType.java @@ -38,6 +38,10 @@ public enum ErrorType { INVALID_SPOT_TYPE_ERROR(HttpStatus.BAD_REQUEST, "40012", "유효하지 않은 spotType입니다."), INVALID_FAVORITE_SPOT_ERROR(HttpStatus.BAD_REQUEST, "40013", "유효하지 않은 favoriteSpot입니다."), INVALID_SPOT_STYLE_ERROR(HttpStatus.BAD_REQUEST, "40014", "유효하지 않은 spotStyle입니다."), + INVALID_ID_TOKEN_ERROR(HttpStatus.BAD_REQUEST, "40010", "ID 토큰의 서명이 올바르지 않습니다."), + + /* 500 Internal Server Error */ + FAILED_DOWNLOAD_GOOGLE_PUBLIC_KEY_ERROR(HttpStatus.BAD_REQUEST, "50002", "구글 공개키 다운로드에 실패하였습니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java b/src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java new file mode 100644 index 0000000..d121888 --- /dev/null +++ b/src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java @@ -0,0 +1,53 @@ +package com.acon.server.member.infra.external; + +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.json.gson.GsonFactory; +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Collections; +import org.springframework.beans.factory.annotation.Value; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import com.google.api.client.http.javanet.NetHttpTransport; + +@Service +@Slf4j +public class GoogleSocialService { + + @Value("${google.clientId}") + private String CLIENT_ID; + + private final NetHttpTransport transport = new NetHttpTransport(); + private final GsonFactory jsonFactory = GsonFactory.getDefaultInstance(); + private GoogleIdTokenVerifier verifier; + + @PostConstruct + void initVerifier() { + verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) + .setAudience(Collections.singletonList(CLIENT_ID)) + .build(); + } + + public String login(String unverifiedIdToken) { + GoogleIdToken idToken = verifyIdToken(unverifiedIdToken); + GoogleIdToken.Payload payload = idToken.getPayload(); + return payload.getSubject(); + } + + private GoogleIdToken verifyIdToken(String unverifiedIdToken) { + try { + GoogleIdToken idToken = verifier.verify(unverifiedIdToken); + if (idToken == null) { + throw new BusinessException(ErrorType.INVALID_ID_TOKEN_ERROR); + } + return idToken; + } catch (GeneralSecurityException | IOException error) { + throw new BusinessException(ErrorType.FAILED_DOWNLOAD_GOOGLE_PUBLIC_KEY_ERROR); + } + + } +} From 8263e6a2468d93956064981f4221bc60305149bc Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:16:46 +0900 Subject: [PATCH 07/55] =?UTF-8?q?chore:=20LoginRequest=20idToken=20NotBlan?= =?UTF-8?q?k=20=EC=B6=94=EA=B0=80=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/acon/server/member/api/request/LoginRequest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/acon/server/member/api/request/LoginRequest.java b/src/main/java/com/acon/server/member/api/request/LoginRequest.java index 1f6336a..80001b5 100644 --- a/src/main/java/com/acon/server/member/api/request/LoginRequest.java +++ b/src/main/java/com/acon/server/member/api/request/LoginRequest.java @@ -1,12 +1,14 @@ package com.acon.server.member.api.request; import com.acon.server.member.domain.enums.SocialType; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; public record LoginRequest( @NotNull SocialType socialType, @NotNull + @NotBlank String idToken ) { From c0865840521ab1cd3d9ba030a6fdce937269cf0d Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:17:23 +0900 Subject: [PATCH 08/55] =?UTF-8?q?refactor:=20LoginResponse=20=EC=A0=95?= =?UTF-8?q?=EC=A0=81=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acon/server/member/api/response/LoginResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/acon/server/member/api/response/LoginResponse.java b/src/main/java/com/acon/server/member/api/response/LoginResponse.java index 2e0df99..733ddb0 100644 --- a/src/main/java/com/acon/server/member/api/response/LoginResponse.java +++ b/src/main/java/com/acon/server/member/api/response/LoginResponse.java @@ -5,4 +5,11 @@ public record LoginResponse( String refreshToken ) { + public static LoginResponse of( + final String accessToken, + final String refreshToken + ) { + return new LoginResponse(accessToken, refreshToken); + } + } From 840491fa1aec7dc0b42bc5e14e3f405f04adb01b Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:17:57 +0900 Subject: [PATCH 09/55] =?UTF-8?q?feat:=20jwt=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/common/auth/jwt/JwtUtils.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java diff --git a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java new file mode 100644 index 0000000..e05680d --- /dev/null +++ b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java @@ -0,0 +1,46 @@ +package com.acon.server.common.auth.jwt; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import java.util.List; +import javax.crypto.SecretKey; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class JwtUtils { + + private final SecretKey secretKey; + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분 + private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 30L; // 30일 + private static final String JWT_KEY = "memberId"; + + public JwtUtils(@Value("${jwt.secret}") String secretKey) { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + this.secretKey = Keys.hmacShaKeyFor(keyBytes); + } + + public List createToken(Long memberId) { + long now = (new Date()).getTime(); + Date accessTokenExpiryTime = new Date(now + ACCESS_TOKEN_EXPIRE_TIME); + Date refreshTokenExpiryTime = new Date(now + REFRESH_TOKEN_EXPIRE_TIME); + + String accessToken = Jwts.builder() + .claim(JWT_KEY, memberId) + .setExpiration(accessTokenExpiryTime) + .signWith(secretKey, SignatureAlgorithm.HS512) + .compact(); + + String refreshToken = Jwts.builder() + .setExpiration(refreshTokenExpiryTime) + .signWith(secretKey, SignatureAlgorithm.HS512) + .compact(); + + return List.of(accessToken, refreshToken); + } +} From 510d48f60f53f1b2a613ee113e9af45c7a8c3212 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 02:19:17 +0900 Subject: [PATCH 10/55] =?UTF-8?q?feat:=20login=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/MemberController.java | 28 +++++++++ .../application/service/MemberService.java | 59 +++++++++++++++++++ .../infra/repository/MemberRepository.java | 12 ++++ 3 files changed, 99 insertions(+) create mode 100644 src/main/java/com/acon/server/member/api/controller/MemberController.java create mode 100644 src/main/java/com/acon/server/member/application/service/MemberService.java create mode 100644 src/main/java/com/acon/server/member/infra/repository/MemberRepository.java diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java new file mode 100644 index 0000000..e9743bd --- /dev/null +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -0,0 +1,28 @@ +package com.acon.server.member.api.controller; + +import com.acon.server.member.api.request.LoginRequest; +import com.acon.server.member.api.response.LoginResponse; +import com.acon.server.member.application.service.MemberService; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1") +public class MemberController { + + MemberService memberService; + + @PostMapping("/auth/login") + public ResponseEntity login(@Valid @RequestBody final LoginRequest request) { + return ResponseEntity.ok( + memberService.login(request) + ); + } + +} diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java new file mode 100644 index 0000000..d4eddbe --- /dev/null +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -0,0 +1,59 @@ +package com.acon.server.member.application.service; + +import com.acon.server.common.auth.jwt.JwtUtils; +import com.acon.server.member.api.request.LoginRequest; +import com.acon.server.member.api.response.LoginResponse; +import com.acon.server.member.application.mapper.MemberMapper; +import com.acon.server.member.domain.entity.Member; +import com.acon.server.member.domain.enums.SocialType; +import com.acon.server.member.infra.entity.MemberEntity; +import com.acon.server.member.infra.external.GoogleSocialService; +import com.acon.server.member.infra.repository.MemberRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final GoogleSocialService googleSocialService; + private final MemberRepository memberRepository; + private final MemberMapper memberMapper; + private final JwtUtils jwtUtils; + + @Transactional + public LoginResponse login(final LoginRequest request) { + String socialId = googleSocialService.login(request.idToken()); + Long memberId = fetchMemberId(request.socialType(), socialId); + List tokens = jwtUtils.createToken(memberId); + return LoginResponse.of(tokens.get(0), tokens.get(1)); + } + + private boolean isExistingMember( + final SocialType socialType, + final String socialId + ) { + return memberRepository.findBySocialTypeAndSocialId(socialType, socialId).isPresent(); + } + + protected Long fetchMemberId(final SocialType socialType, final String socialId) { + MemberEntity memberEntity; + if (isExistingMember(socialType, socialId)) { + memberEntity = memberRepository.findBySocialTypeAndSocialId( + socialType, + socialId + ).orElse(null); + } else { + memberEntity = memberRepository.save(MemberEntity.builder() + .socialType(socialType) + .socialId(socialId) + .leftAcornCount(25) + .build()); + } + Member member = memberMapper.toDomain(memberEntity); + return member.getId(); + } + +} diff --git a/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java b/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java new file mode 100644 index 0000000..0f94763 --- /dev/null +++ b/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java @@ -0,0 +1,12 @@ +package com.acon.server.member.infra.repository; + +import com.acon.server.member.domain.enums.SocialType; +import com.acon.server.member.infra.entity.MemberEntity; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); + +} From 68fa1a50b60a8caa472f0901efadd09490c5e0be Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 03:30:31 +0900 Subject: [PATCH 11/55] =?UTF-8?q?feat:=20configuration=20properties=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/acon/server/ServerApplication.java | 2 ++ .../com/acon/server/common/auth/jwt/JwtUtils.java | 5 +++-- .../server/common/auth/jwt/config/JwtConfig.java | 15 +++++++++++++++ .../member/application/service/MemberService.java | 2 +- .../{ => google}/GoogleSocialService.java | 2 +- .../external/google/config/googleConfig.java | 15 +++++++++++++++ 6 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/acon/server/common/auth/jwt/config/JwtConfig.java rename src/main/java/com/acon/server/member/infra/external/{ => google}/GoogleSocialService.java (97%) create mode 100644 src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java diff --git a/src/main/java/com/acon/server/ServerApplication.java b/src/main/java/com/acon/server/ServerApplication.java index 358ebad..1161c9a 100644 --- a/src/main/java/com/acon/server/ServerApplication.java +++ b/src/main/java/com/acon/server/ServerApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class ServerApplication { public static void main(String[] args) { diff --git a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java index e05680d..81a7272 100644 --- a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java +++ b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java @@ -1,5 +1,6 @@ package com.acon.server.common.auth.jwt; +import com.acon.server.common.auth.jwt.config.JwtConfig; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; @@ -20,8 +21,8 @@ public class JwtUtils { private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 30L; // 30일 private static final String JWT_KEY = "memberId"; - public JwtUtils(@Value("${jwt.secret}") String secretKey) { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); + public JwtUtils(JwtConfig jwtConfig) { + byte[] keyBytes = Decoders.BASE64.decode(jwtConfig.getSecretKey()); this.secretKey = Keys.hmacShaKeyFor(keyBytes); } diff --git a/src/main/java/com/acon/server/common/auth/jwt/config/JwtConfig.java b/src/main/java/com/acon/server/common/auth/jwt/config/JwtConfig.java new file mode 100644 index 0000000..f7012b6 --- /dev/null +++ b/src/main/java/com/acon/server/common/auth/jwt/config/JwtConfig.java @@ -0,0 +1,15 @@ +package com.acon.server.common.auth.jwt.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt") +@RequiredArgsConstructor +@Getter +@Setter +public class JwtConfig { + + private final String secretKey; +} \ No newline at end of file diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index d4eddbe..7026a7d 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -7,7 +7,7 @@ import com.acon.server.member.domain.entity.Member; import com.acon.server.member.domain.enums.SocialType; import com.acon.server.member.infra.entity.MemberEntity; -import com.acon.server.member.infra.external.GoogleSocialService; +import com.acon.server.member.infra.external.google.GoogleSocialService; import com.acon.server.member.infra.repository.MemberRepository; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java b/src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java similarity index 97% rename from src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java rename to src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java index d121888..0bc0e81 100644 --- a/src/main/java/com/acon/server/member/infra/external/GoogleSocialService.java +++ b/src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java @@ -1,4 +1,4 @@ -package com.acon.server.member.infra.external; +package com.acon.server.member.infra.external.google; import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; diff --git a/src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java b/src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java new file mode 100644 index 0000000..5682dc3 --- /dev/null +++ b/src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java @@ -0,0 +1,15 @@ +package com.acon.server.member.infra.external.google.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "google") +@RequiredArgsConstructor +@Getter +@Setter +public class googleConfig { + + private final String clientId; +} \ No newline at end of file From f25c348c2954450c2973fabca2cd9eefb1faa8c0 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 03:47:13 +0900 Subject: [PATCH 12/55] =?UTF-8?q?feat:=20configuration=20properties=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acon/server/common/auth/jwt/JwtUtils.java | 3 --- .../infra/external/google/GoogleSocialService.java | 14 ++++++++------ .../{googleConfig.java => GoogleConfig.java} | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) rename src/main/java/com/acon/server/member/infra/external/google/config/{googleConfig.java => GoogleConfig.java} (92%) diff --git a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java index 81a7272..3b17401 100644 --- a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java +++ b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java @@ -8,12 +8,9 @@ import java.util.Date; import java.util.List; import javax.crypto.SecretKey; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component -@Slf4j public class JwtUtils { private final SecretKey secretKey; diff --git a/src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java b/src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java index 0bc0e81..d5642e5 100644 --- a/src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java +++ b/src/main/java/com/acon/server/member/infra/external/google/GoogleSocialService.java @@ -2,29 +2,31 @@ import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; +import com.acon.server.member.infra.external.google.config.GoogleConfig; import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import jakarta.annotation.PostConstruct; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Collections; -import org.springframework.beans.factory.annotation.Value; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import com.google.api.client.http.javanet.NetHttpTransport; @Service -@Slf4j public class GoogleSocialService { - @Value("${google.clientId}") - private String CLIENT_ID; + private final String CLIENT_ID; private final NetHttpTransport transport = new NetHttpTransport(); private final GsonFactory jsonFactory = GsonFactory.getDefaultInstance(); private GoogleIdTokenVerifier verifier; + public GoogleSocialService(GoogleConfig googleConfig) { + CLIENT_ID = googleConfig.getClientId(); + + } + @PostConstruct void initVerifier() { verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) diff --git a/src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java b/src/main/java/com/acon/server/member/infra/external/google/config/GoogleConfig.java similarity index 92% rename from src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java rename to src/main/java/com/acon/server/member/infra/external/google/config/GoogleConfig.java index 5682dc3..c3d8360 100644 --- a/src/main/java/com/acon/server/member/infra/external/google/config/googleConfig.java +++ b/src/main/java/com/acon/server/member/infra/external/google/config/GoogleConfig.java @@ -9,7 +9,7 @@ @RequiredArgsConstructor @Getter @Setter -public class googleConfig { +public class GoogleConfig { private final String clientId; } \ No newline at end of file From 595194e8bcfa801901733efacdcd57f8fe5e3393 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 19:40:14 +0900 Subject: [PATCH 13/55] =?UTF-8?q?chore:=20gitignore=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 875adba..7eb922a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ out/ ### Mac OS ### .DS_Store -*.yml \ No newline at end of file +*.yml +application.properties \ No newline at end of file From 61a41ab438313949b90fc2d5f6c073a8f6b8e30e Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 19:43:52 +0900 Subject: [PATCH 14/55] =?UTF-8?q?chore:=20enum=20=EA=B0=92=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acon/server/member/domain/enums/FavoriteSpot.java | 6 ++++-- .../java/com/acon/server/member/domain/enums/SpotStyle.java | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/acon/server/member/domain/enums/FavoriteSpot.java b/src/main/java/com/acon/server/member/domain/enums/FavoriteSpot.java index ce98925..20e2ab4 100644 --- a/src/main/java/com/acon/server/member/domain/enums/FavoriteSpot.java +++ b/src/main/java/com/acon/server/member/domain/enums/FavoriteSpot.java @@ -12,8 +12,10 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum FavoriteSpot { - TRADITIONAL, - MODERN, + SENSE, + NEW_FOOD, + REASONABLE, + LUXURY, ; private static final Map FAVORITE_SPOT_MAP = new HashMap<>(); diff --git a/src/main/java/com/acon/server/member/domain/enums/SpotStyle.java b/src/main/java/com/acon/server/member/domain/enums/SpotStyle.java index e9ec881..98884bb 100644 --- a/src/main/java/com/acon/server/member/domain/enums/SpotStyle.java +++ b/src/main/java/com/acon/server/member/domain/enums/SpotStyle.java @@ -12,10 +12,8 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum SpotStyle { - SENSE, - NEW_FOOD, - REASONABLE, - LUXURY, + TRADITIONAL, + MODERN, ; private static final Map SPOT_STYLE_MAP = new HashMap<>(); From 5443754f81e3bcb6015bf9f43d91aaf480429ea0 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 19:44:15 +0900 Subject: [PATCH 15/55] =?UTF-8?q?feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/PreferenceController.java | 26 +++++++++++++++++++ .../member/api/request/PreferenceRequest.java | 18 +++++++++++++ .../service/PreferenceService.java | 25 ++++++++++++++++++ .../repository/PreferenceRepository.java | 8 ++++++ 4 files changed, 77 insertions(+) create mode 100644 src/main/java/com/acon/server/member/api/controller/PreferenceController.java create mode 100644 src/main/java/com/acon/server/member/api/request/PreferenceRequest.java create mode 100644 src/main/java/com/acon/server/member/application/service/PreferenceService.java create mode 100644 src/main/java/com/acon/server/member/infra/repository/PreferenceRepository.java diff --git a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java new file mode 100644 index 0000000..842fe5b --- /dev/null +++ b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java @@ -0,0 +1,26 @@ +package com.acon.server.member.api.controller; + +import com.acon.server.member.api.request.PreferenceRequest; +import com.acon.server.member.application.service.PreferenceService; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/preference") +public class PreferenceController { + + private final PreferenceService preferenceService; + + @PostMapping() + public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { + // 토큰으로 memberId 가져오기 + preferenceService.createPreference(request, 123L); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java new file mode 100644 index 0000000..53ea3af --- /dev/null +++ b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java @@ -0,0 +1,18 @@ +package com.acon.server.member.api.request; + +import com.acon.server.member.domain.enums.Cuisine; +import com.acon.server.member.domain.enums.DislikeFood; +import com.acon.server.member.domain.enums.FavoriteSpot; +import com.acon.server.member.domain.enums.SpotStyle; +import com.acon.server.member.domain.enums.SpotType; +import java.util.List; + +public record PreferenceRequest( + List dislikeFoodList, + List favoriteCuisineRank, + SpotType favoriteSpotType, + SpotStyle favoriteSpotStyle, + List favoriteSpotRank +) { + +} diff --git a/src/main/java/com/acon/server/member/application/service/PreferenceService.java b/src/main/java/com/acon/server/member/application/service/PreferenceService.java new file mode 100644 index 0000000..8e6428e --- /dev/null +++ b/src/main/java/com/acon/server/member/application/service/PreferenceService.java @@ -0,0 +1,25 @@ +package com.acon.server.member.application.service; + +import com.acon.server.member.api.request.PreferenceRequest; +import com.acon.server.member.application.mapper.PreferenceMapper; +import com.acon.server.member.domain.entity.Preference; +import com.acon.server.member.infra.repository.PreferenceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PreferenceService { + + private final PreferenceMapper preferenceMapper; + private final PreferenceRepository preferenceRepository; + + public void createPreference(final PreferenceRequest request, Long memberId) { + Preference preference = Preference.builder().memberId(memberId).dislikeFoodList(request.dislikeFoodList()) + .favoriteCuisineRank(request.favoriteCuisineRank()).favoriteSpotType(request.favoriteSpotType()) + .favoriteSpotStyle(request.favoriteSpotStyle()) + .favoriteSpotRank(request.favoriteSpotRank()).build(); + preferenceRepository.save(preferenceMapper.toEntity(preference)); + } + +} diff --git a/src/main/java/com/acon/server/member/infra/repository/PreferenceRepository.java b/src/main/java/com/acon/server/member/infra/repository/PreferenceRepository.java new file mode 100644 index 0000000..b819f4c --- /dev/null +++ b/src/main/java/com/acon/server/member/infra/repository/PreferenceRepository.java @@ -0,0 +1,8 @@ +package com.acon.server.member.infra.repository; + +import com.acon.server.member.infra.entity.PreferenceEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PreferenceRepository extends JpaRepository { + +} From 49b04a454c5e6b0edc36de51294b8e15fb44e9b0 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 21:19:42 +0900 Subject: [PATCH 16/55] =?UTF-8?q?feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20req?= =?UTF-8?q?uest=20body=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=20=EC=A7=84=ED=96=89=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/PreferenceController.java | 16 +++++++++++++--- .../member/api/request/PreferenceRequest.java | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java index 842fe5b..a99f522 100644 --- a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java +++ b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java @@ -2,6 +2,11 @@ import com.acon.server.member.api.request.PreferenceRequest; import com.acon.server.member.application.service.PreferenceService; +import com.acon.server.member.domain.enums.Cuisine; +import com.acon.server.member.domain.enums.DislikeFood; +import com.acon.server.member.domain.enums.FavoriteSpot; +import com.acon.server.member.domain.enums.SpotStyle; +import com.acon.server.member.domain.enums.SpotType; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -17,10 +22,15 @@ public class PreferenceController { private final PreferenceService preferenceService; - @PostMapping() + @PostMapping public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { - // 토큰으로 memberId 가져오기 - preferenceService.createPreference(request, 123L); + // ToDo 토큰으로 memberId 가져오기 + preferenceService.createPreference(request, 1L); + request.dislikeFoodList().forEach(dislikeFood -> DislikeFood.fromValue(String.valueOf(dislikeFood))); + request.favoriteCuisineRank().forEach(cuisine -> Cuisine.fromValue(String.valueOf(cuisine))); + SpotType.fromValue(String.valueOf(request.favoriteSpotType())); + SpotStyle.fromValue(String.valueOf(request.favoriteSpotStyle())); + request.favoriteSpotRank().forEach(favoriteSpot -> FavoriteSpot.fromValue(String.valueOf(favoriteSpot))); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java index 53ea3af..79a2c20 100644 --- a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java +++ b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java @@ -5,13 +5,17 @@ import com.acon.server.member.domain.enums.FavoriteSpot; import com.acon.server.member.domain.enums.SpotStyle; import com.acon.server.member.domain.enums.SpotType; +import jakarta.validation.constraints.NotNull; import java.util.List; public record PreferenceRequest( + @NotNull List dislikeFoodList, + @NotNull List favoriteCuisineRank, SpotType favoriteSpotType, SpotStyle favoriteSpotStyle, + @NotNull List favoriteSpotRank ) { From ec4c7b44e6a1b05b80a0299d71e6f861d2782f69 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 22:03:54 +0900 Subject: [PATCH 17/55] =?UTF-8?q?fix:=20dto=20enum=20->=20string=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/PreferenceController.java | 23 ++++++++++++++----- .../member/api/request/PreferenceRequest.java | 17 ++++++-------- .../service/PreferenceService.java | 19 ++++++++++----- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java index a99f522..70d7b68 100644 --- a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java +++ b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java @@ -1,5 +1,7 @@ package com.acon.server.member.api.controller; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.member.api.request.PreferenceRequest; import com.acon.server.member.application.service.PreferenceService; import com.acon.server.member.domain.enums.Cuisine; @@ -8,6 +10,7 @@ import com.acon.server.member.domain.enums.SpotStyle; import com.acon.server.member.domain.enums.SpotType; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -25,12 +28,20 @@ public class PreferenceController { @PostMapping public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { // ToDo 토큰으로 memberId 가져오기 - preferenceService.createPreference(request, 1L); - request.dislikeFoodList().forEach(dislikeFood -> DislikeFood.fromValue(String.valueOf(dislikeFood))); - request.favoriteCuisineRank().forEach(cuisine -> Cuisine.fromValue(String.valueOf(cuisine))); - SpotType.fromValue(String.valueOf(request.favoriteSpotType())); - SpotStyle.fromValue(String.valueOf(request.favoriteSpotStyle())); - request.favoriteSpotRank().forEach(favoriteSpot -> FavoriteSpot.fromValue(String.valueOf(favoriteSpot))); + List dislikeFoodList = request.dislikeFoodList().stream().map(DislikeFood::fromValue).toList(); + if (request.favoriteCuisineRank().size() != 3) { + throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); + } + List favoriteCuisineList = request.favoriteCuisineRank().stream().map(Cuisine::fromValue).toList(); + SpotType favoriteSpotType = SpotType.fromValue(request.favoriteSpotType()); + SpotStyle favoriteSpotStyle = SpotStyle.fromValue(request.favoriteSpotStyle()); + if (request.favoriteSpotRank().size() != 4) { + throw new BusinessException(ErrorType.INVALID_FAVORITE_SPOT_ERROR); + } + List favoriteSpotRank = request.favoriteSpotRank().stream().map(FavoriteSpot::fromValue).toList(); + + preferenceService.createPreference(dislikeFoodList, favoriteCuisineList, favoriteSpotType, favoriteSpotStyle, + favoriteSpotRank, 1L); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java index 79a2c20..4e91c88 100644 --- a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java +++ b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java @@ -1,22 +1,19 @@ package com.acon.server.member.api.request; -import com.acon.server.member.domain.enums.Cuisine; -import com.acon.server.member.domain.enums.DislikeFood; -import com.acon.server.member.domain.enums.FavoriteSpot; -import com.acon.server.member.domain.enums.SpotStyle; -import com.acon.server.member.domain.enums.SpotType; import jakarta.validation.constraints.NotNull; import java.util.List; public record PreferenceRequest( @NotNull - List dislikeFoodList, + List dislikeFoodList, @NotNull - List favoriteCuisineRank, - SpotType favoriteSpotType, - SpotStyle favoriteSpotStyle, + List favoriteCuisineRank, @NotNull - List favoriteSpotRank + String favoriteSpotType, + @NotNull + String favoriteSpotStyle, + @NotNull + List favoriteSpotRank ) { } diff --git a/src/main/java/com/acon/server/member/application/service/PreferenceService.java b/src/main/java/com/acon/server/member/application/service/PreferenceService.java index 8e6428e..54b9e78 100644 --- a/src/main/java/com/acon/server/member/application/service/PreferenceService.java +++ b/src/main/java/com/acon/server/member/application/service/PreferenceService.java @@ -1,9 +1,14 @@ package com.acon.server.member.application.service; -import com.acon.server.member.api.request.PreferenceRequest; import com.acon.server.member.application.mapper.PreferenceMapper; import com.acon.server.member.domain.entity.Preference; +import com.acon.server.member.domain.enums.Cuisine; +import com.acon.server.member.domain.enums.DislikeFood; +import com.acon.server.member.domain.enums.FavoriteSpot; +import com.acon.server.member.domain.enums.SpotStyle; +import com.acon.server.member.domain.enums.SpotType; import com.acon.server.member.infra.repository.PreferenceRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,11 +19,13 @@ public class PreferenceService { private final PreferenceMapper preferenceMapper; private final PreferenceRepository preferenceRepository; - public void createPreference(final PreferenceRequest request, Long memberId) { - Preference preference = Preference.builder().memberId(memberId).dislikeFoodList(request.dislikeFoodList()) - .favoriteCuisineRank(request.favoriteCuisineRank()).favoriteSpotType(request.favoriteSpotType()) - .favoriteSpotStyle(request.favoriteSpotStyle()) - .favoriteSpotRank(request.favoriteSpotRank()).build(); + public void createPreference(List dislikeFoodList, List favoriteCuisineList, + SpotType favoriteSpotType, + SpotStyle favoriteSpotStyle, List favoriteSpotRank, Long memberId) { + Preference preference = Preference.builder().memberId(memberId).dislikeFoodList(dislikeFoodList) + .favoriteCuisineRank(favoriteCuisineList).favoriteSpotType(favoriteSpotType) + .favoriteSpotStyle(favoriteSpotStyle) + .favoriteSpotRank(favoriteSpotRank).build(); preferenceRepository.save(preferenceMapper.toEntity(preference)); } From 3a3a77d4524b566a1ad2773e4864220d670ae4c7 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 22:06:50 +0900 Subject: [PATCH 18/55] =?UTF-8?q?refactor:=20rank=20size=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EC=B2=98=EB=A6=AC=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/member/api/controller/PreferenceController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java index 70d7b68..8b25ed3 100644 --- a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java +++ b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java @@ -24,18 +24,20 @@ public class PreferenceController { private final PreferenceService preferenceService; + private static final int FAVORITE_CUISINE_RANK_SIZE = 3; + private static final int FAVORITE_SPOT_RANK_SIZE = 4; @PostMapping public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { // ToDo 토큰으로 memberId 가져오기 List dislikeFoodList = request.dislikeFoodList().stream().map(DislikeFood::fromValue).toList(); - if (request.favoriteCuisineRank().size() != 3) { + if (request.favoriteCuisineRank().size() != FAVORITE_CUISINE_RANK_SIZE) { throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); } List favoriteCuisineList = request.favoriteCuisineRank().stream().map(Cuisine::fromValue).toList(); SpotType favoriteSpotType = SpotType.fromValue(request.favoriteSpotType()); SpotStyle favoriteSpotStyle = SpotStyle.fromValue(request.favoriteSpotStyle()); - if (request.favoriteSpotRank().size() != 4) { + if (request.favoriteSpotRank().size() != FAVORITE_SPOT_RANK_SIZE) { throw new BusinessException(ErrorType.INVALID_FAVORITE_SPOT_ERROR); } List favoriteSpotRank = request.favoriteSpotRank().stream().map(FavoriteSpot::fromValue).toList(); From 161a0869b5b6f0e9b50d375ffc6cdde97fc6d117 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 22:28:16 +0900 Subject: [PATCH 19/55] =?UTF-8?q?feat:=20=EB=8F=84=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/api/controller/MemberController.java | 7 +++++++ .../member/application/service/MemberService.java | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index e9743bd..5bbb6ad 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -1,6 +1,7 @@ package com.acon.server.member.api.controller; import com.acon.server.member.api.request.LoginRequest; +import com.acon.server.member.api.response.AcornCountResponse; import com.acon.server.member.api.response.LoginResponse; import com.acon.server.member.application.service.MemberService; import jakarta.validation.Valid; @@ -25,4 +26,10 @@ public ResponseEntity login(@Valid @RequestBody final LoginReques ); } + @PostMapping("/acorn") + public ResponseEntity getAcornCount() { + return ResponseEntity.ok( + memberService.fetchAcornCount(1L) + ); + } } diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 7026a7d..4de7d66 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -1,7 +1,10 @@ package com.acon.server.member.application.service; import com.acon.server.common.auth.jwt.JwtUtils; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.member.api.request.LoginRequest; +import com.acon.server.member.api.response.AcornCountResponse; import com.acon.server.member.api.response.LoginResponse; import com.acon.server.member.application.mapper.MemberMapper; import com.acon.server.member.domain.entity.Member; @@ -31,6 +34,14 @@ public LoginResponse login(final LoginRequest request) { return LoginResponse.of(tokens.get(0), tokens.get(1)); } + public AcornCountResponse fetchAcornCount(final Long memberId) { + MemberEntity memberEntity = memberRepository.findById(memberId).orElseThrow( + () -> new BusinessException(ErrorType.NOT_FOUND_MEMBER_ERROR) + ); + int acornCount = memberEntity.getLeftAcornCount(); + return new AcornCountResponse(acornCount); + } + private boolean isExistingMember( final SocialType socialType, final String socialId From a4ae52fdda8779c7de23e3d7a253fdf4907df12d Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 22:28:44 +0900 Subject: [PATCH 20/55] =?UTF-8?q?feat:=20=EB=8F=84=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20Response=20dto=20=EA=B5=AC=ED=98=84=20(#17?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/member/api/response/AcornCountResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/acon/server/member/api/response/AcornCountResponse.java diff --git a/src/main/java/com/acon/server/member/api/response/AcornCountResponse.java b/src/main/java/com/acon/server/member/api/response/AcornCountResponse.java new file mode 100644 index 0000000..d8b9369 --- /dev/null +++ b/src/main/java/com/acon/server/member/api/response/AcornCountResponse.java @@ -0,0 +1,7 @@ +package com.acon.server.member.api.response; + +public record AcornCountResponse( + int acornCount +) { + +} From 007cf35ab98b9baf24ae43906677c9bbfe5d0bc7 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Wed, 15 Jan 2025 22:33:53 +0900 Subject: [PATCH 21/55] =?UTF-8?q?fix:=20memberController=20memberService?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/member/api/controller/MemberController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index 5bbb6ad..2ab3634 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -7,6 +7,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,7 +18,7 @@ @RequestMapping("/api/v1") public class MemberController { - MemberService memberService; + private final MemberService memberService; @PostMapping("/auth/login") public ResponseEntity login(@Valid @RequestBody final LoginRequest request) { @@ -26,7 +27,7 @@ public ResponseEntity login(@Valid @RequestBody final LoginReques ); } - @PostMapping("/acorn") + @GetMapping("/acorn") public ResponseEntity getAcornCount() { return ResponseEntity.ok( memberService.fetchAcornCount(1L) From e9249bccdffaf72917ab6d7f266b56fcb50b80ca Mon Sep 17 00:00:00 2001 From: gahyuun Date: Thu, 16 Jan 2025 00:11:11 +0900 Subject: [PATCH 22/55] =?UTF-8?q?chore:=20TODO=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acon/server/member/api/controller/MemberController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index 2ab3634..b97e5bc 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -29,6 +29,7 @@ public ResponseEntity login(@Valid @RequestBody final LoginReques @GetMapping("/acorn") public ResponseEntity getAcornCount() { + // TODO 토큰으로 memberId 가져오기 return ResponseEntity.ok( memberService.fetchAcornCount(1L) ); From 5409fecc4778352f0fa5c9c532ca672c6705b4f6 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Thu, 16 Jan 2025 20:55:07 +0900 Subject: [PATCH 23/55] =?UTF-8?q?refactor:=20preference=20controller,=20se?= =?UTF-8?q?rvice=20=EC=82=AD=EC=A0=9C=20=ED=9B=84=20member=20controller,?= =?UTF-8?q?=20service=20=EC=83=9D=EC=84=B1=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/MemberController.java | 31 ++++++++++++ .../api/controller/PreferenceController.java | 49 ------------------- .../application/service/MemberService.java | 20 ++++++++ .../service/PreferenceService.java | 32 ------------ 4 files changed, 51 insertions(+), 81 deletions(-) delete mode 100644 src/main/java/com/acon/server/member/api/controller/PreferenceController.java delete mode 100644 src/main/java/com/acon/server/member/application/service/PreferenceService.java diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index e9743bd..801c6d6 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -1,9 +1,18 @@ package com.acon.server.member.api.controller; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.member.api.request.LoginRequest; +import com.acon.server.member.api.request.PreferenceRequest; import com.acon.server.member.api.response.LoginResponse; import com.acon.server.member.application.service.MemberService; +import com.acon.server.member.domain.enums.Cuisine; +import com.acon.server.member.domain.enums.DislikeFood; +import com.acon.server.member.domain.enums.FavoriteSpot; +import com.acon.server.member.domain.enums.SpotStyle; +import com.acon.server.member.domain.enums.SpotType; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -17,6 +26,28 @@ public class MemberController { MemberService memberService; + private static final int FAVORITE_CUISINE_RANK_SIZE = 3; + private static final int FAVORITE_SPOT_RANK_SIZE = 4; + + @PostMapping + public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { + // ToDo 토큰으로 memberId 가져오기 + List dislikeFoodList = request.dislikeFoodList().stream().map(DislikeFood::fromValue).toList(); + if (request.favoriteCuisineRank().size() != FAVORITE_CUISINE_RANK_SIZE) { + throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); + } + List favoriteCuisineList = request.favoriteCuisineRank().stream().map(Cuisine::fromValue).toList(); + SpotType favoriteSpotType = SpotType.fromValue(request.favoriteSpotType()); + SpotStyle favoriteSpotStyle = SpotStyle.fromValue(request.favoriteSpotStyle()); + if (request.favoriteSpotRank().size() != FAVORITE_SPOT_RANK_SIZE) { + throw new BusinessException(ErrorType.INVALID_FAVORITE_SPOT_ERROR); + } + List favoriteSpotRank = request.favoriteSpotRank().stream().map(FavoriteSpot::fromValue).toList(); + + memberService.createPreference(dislikeFoodList, favoriteCuisineList, favoriteSpotType, favoriteSpotStyle, + favoriteSpotRank, 1L); + return ResponseEntity.ok().build(); + } @PostMapping("/auth/login") public ResponseEntity login(@Valid @RequestBody final LoginRequest request) { diff --git a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java b/src/main/java/com/acon/server/member/api/controller/PreferenceController.java deleted file mode 100644 index 8b25ed3..0000000 --- a/src/main/java/com/acon/server/member/api/controller/PreferenceController.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.acon.server.member.api.controller; - -import com.acon.server.global.exception.BusinessException; -import com.acon.server.global.exception.ErrorType; -import com.acon.server.member.api.request.PreferenceRequest; -import com.acon.server.member.application.service.PreferenceService; -import com.acon.server.member.domain.enums.Cuisine; -import com.acon.server.member.domain.enums.DislikeFood; -import com.acon.server.member.domain.enums.FavoriteSpot; -import com.acon.server.member.domain.enums.SpotStyle; -import com.acon.server.member.domain.enums.SpotType; -import jakarta.validation.Valid; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/v1/preference") -public class PreferenceController { - - private final PreferenceService preferenceService; - private static final int FAVORITE_CUISINE_RANK_SIZE = 3; - private static final int FAVORITE_SPOT_RANK_SIZE = 4; - - @PostMapping - public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { - // ToDo 토큰으로 memberId 가져오기 - List dislikeFoodList = request.dislikeFoodList().stream().map(DislikeFood::fromValue).toList(); - if (request.favoriteCuisineRank().size() != FAVORITE_CUISINE_RANK_SIZE) { - throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); - } - List favoriteCuisineList = request.favoriteCuisineRank().stream().map(Cuisine::fromValue).toList(); - SpotType favoriteSpotType = SpotType.fromValue(request.favoriteSpotType()); - SpotStyle favoriteSpotStyle = SpotStyle.fromValue(request.favoriteSpotStyle()); - if (request.favoriteSpotRank().size() != FAVORITE_SPOT_RANK_SIZE) { - throw new BusinessException(ErrorType.INVALID_FAVORITE_SPOT_ERROR); - } - List favoriteSpotRank = request.favoriteSpotRank().stream().map(FavoriteSpot::fromValue).toList(); - - preferenceService.createPreference(dislikeFoodList, favoriteCuisineList, favoriteSpotType, favoriteSpotStyle, - favoriteSpotRank, 1L); - return ResponseEntity.ok().build(); - } -} diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 7026a7d..5fd5184 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -4,11 +4,19 @@ import com.acon.server.member.api.request.LoginRequest; import com.acon.server.member.api.response.LoginResponse; import com.acon.server.member.application.mapper.MemberMapper; +import com.acon.server.member.application.mapper.PreferenceMapper; import com.acon.server.member.domain.entity.Member; +import com.acon.server.member.domain.entity.Preference; +import com.acon.server.member.domain.enums.Cuisine; +import com.acon.server.member.domain.enums.DislikeFood; +import com.acon.server.member.domain.enums.FavoriteSpot; import com.acon.server.member.domain.enums.SocialType; +import com.acon.server.member.domain.enums.SpotStyle; +import com.acon.server.member.domain.enums.SpotType; import com.acon.server.member.infra.entity.MemberEntity; import com.acon.server.member.infra.external.google.GoogleSocialService; import com.acon.server.member.infra.repository.MemberRepository; +import com.acon.server.member.infra.repository.PreferenceRepository; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -22,6 +30,18 @@ public class MemberService { private final MemberRepository memberRepository; private final MemberMapper memberMapper; private final JwtUtils jwtUtils; + private final PreferenceMapper preferenceMapper; + private final PreferenceRepository preferenceRepository; + + public void createPreference(List dislikeFoodList, List favoriteCuisineList, + SpotType favoriteSpotType, + SpotStyle favoriteSpotStyle, List favoriteSpotRank, Long memberId) { + Preference preference = Preference.builder().memberId(memberId).dislikeFoodList(dislikeFoodList) + .favoriteCuisineRank(favoriteCuisineList).favoriteSpotType(favoriteSpotType) + .favoriteSpotStyle(favoriteSpotStyle) + .favoriteSpotRank(favoriteSpotRank).build(); + preferenceRepository.save(preferenceMapper.toEntity(preference)); + } @Transactional public LoginResponse login(final LoginRequest request) { diff --git a/src/main/java/com/acon/server/member/application/service/PreferenceService.java b/src/main/java/com/acon/server/member/application/service/PreferenceService.java deleted file mode 100644 index 54b9e78..0000000 --- a/src/main/java/com/acon/server/member/application/service/PreferenceService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.acon.server.member.application.service; - -import com.acon.server.member.application.mapper.PreferenceMapper; -import com.acon.server.member.domain.entity.Preference; -import com.acon.server.member.domain.enums.Cuisine; -import com.acon.server.member.domain.enums.DislikeFood; -import com.acon.server.member.domain.enums.FavoriteSpot; -import com.acon.server.member.domain.enums.SpotStyle; -import com.acon.server.member.domain.enums.SpotType; -import com.acon.server.member.infra.repository.PreferenceRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class PreferenceService { - - private final PreferenceMapper preferenceMapper; - private final PreferenceRepository preferenceRepository; - - public void createPreference(List dislikeFoodList, List favoriteCuisineList, - SpotType favoriteSpotType, - SpotStyle favoriteSpotStyle, List favoriteSpotRank, Long memberId) { - Preference preference = Preference.builder().memberId(memberId).dislikeFoodList(dislikeFoodList) - .favoriteCuisineRank(favoriteCuisineList).favoriteSpotType(favoriteSpotType) - .favoriteSpotStyle(favoriteSpotStyle) - .favoriteSpotRank(favoriteSpotRank).build(); - preferenceRepository.save(preferenceMapper.toEntity(preference)); - } - -} From 871d303277b089636815ddb439eded1839ddbd2b Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 02:27:08 +0900 Subject: [PATCH 24/55] =?UTF-8?q?refactor:=20preference=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/MemberController.java | 31 ++++++++----------- .../member/api/response/LoginResponse.java | 1 - .../application/service/MemberService.java | 22 +++++++++---- .../member/domain/entity/Preference.java | 19 ++++++++++++ 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index 801c6d6..dc1e246 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -1,7 +1,5 @@ package com.acon.server.member.api.controller; -import com.acon.server.global.exception.BusinessException; -import com.acon.server.global.exception.ErrorType; import com.acon.server.member.api.request.LoginRequest; import com.acon.server.member.api.request.PreferenceRequest; import com.acon.server.member.api.response.LoginResponse; @@ -14,46 +12,43 @@ import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RequiredArgsConstructor @RestController +@RequiredArgsConstructor @RequestMapping("/api/v1") public class MemberController { - MemberService memberService; - private static final int FAVORITE_CUISINE_RANK_SIZE = 3; - private static final int FAVORITE_SPOT_RANK_SIZE = 4; + private final MemberService memberService; - @PostMapping - public ResponseEntity postPreference(@Valid @RequestBody final PreferenceRequest request) { - // ToDo 토큰으로 memberId 가져오기 + @PostMapping(path = "/member/preference", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity postPreference( + @Valid @RequestBody final PreferenceRequest request + ) { + // TODO: 토큰으로 memberId 가져오기 List dislikeFoodList = request.dislikeFoodList().stream().map(DislikeFood::fromValue).toList(); - if (request.favoriteCuisineRank().size() != FAVORITE_CUISINE_RANK_SIZE) { - throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); - } List favoriteCuisineList = request.favoriteCuisineRank().stream().map(Cuisine::fromValue).toList(); SpotType favoriteSpotType = SpotType.fromValue(request.favoriteSpotType()); SpotStyle favoriteSpotStyle = SpotStyle.fromValue(request.favoriteSpotStyle()); - if (request.favoriteSpotRank().size() != FAVORITE_SPOT_RANK_SIZE) { - throw new BusinessException(ErrorType.INVALID_FAVORITE_SPOT_ERROR); - } List favoriteSpotRank = request.favoriteSpotRank().stream().map(FavoriteSpot::fromValue).toList(); memberService.createPreference(dislikeFoodList, favoriteCuisineList, favoriteSpotType, favoriteSpotStyle, favoriteSpotRank, 1L); + return ResponseEntity.ok().build(); } - @PostMapping("/auth/login") - public ResponseEntity login(@Valid @RequestBody final LoginRequest request) { + @PostMapping(path = "/auth/login", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity login( + @Valid @RequestBody final LoginRequest request + ) { return ResponseEntity.ok( memberService.login(request) ); } - } diff --git a/src/main/java/com/acon/server/member/api/response/LoginResponse.java b/src/main/java/com/acon/server/member/api/response/LoginResponse.java index 733ddb0..73adce7 100644 --- a/src/main/java/com/acon/server/member/api/response/LoginResponse.java +++ b/src/main/java/com/acon/server/member/api/response/LoginResponse.java @@ -11,5 +11,4 @@ public static LoginResponse of( ) { return new LoginResponse(accessToken, refreshToken); } - } diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 5fd5184..02510b8 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -33,13 +33,23 @@ public class MemberService { private final PreferenceMapper preferenceMapper; private final PreferenceRepository preferenceRepository; - public void createPreference(List dislikeFoodList, List favoriteCuisineList, - SpotType favoriteSpotType, - SpotStyle favoriteSpotStyle, List favoriteSpotRank, Long memberId) { - Preference preference = Preference.builder().memberId(memberId).dislikeFoodList(dislikeFoodList) - .favoriteCuisineRank(favoriteCuisineList).favoriteSpotType(favoriteSpotType) + public void createPreference( + List dislikeFoodList, + List favoriteCuisineList, + SpotType favoriteSpotType, + SpotStyle favoriteSpotStyle, + List favoriteSpotRank, + Long memberId + ) { + Preference preference = Preference.builder() + .memberId(memberId) + .dislikeFoodList(dislikeFoodList) + .favoriteCuisineRank(favoriteCuisineList) + .favoriteSpotType(favoriteSpotType) .favoriteSpotStyle(favoriteSpotStyle) - .favoriteSpotRank(favoriteSpotRank).build(); + .favoriteSpotRank(favoriteSpotRank) + .build(); + preferenceRepository.save(preferenceMapper.toEntity(preference)); } diff --git a/src/main/java/com/acon/server/member/domain/entity/Preference.java b/src/main/java/com/acon/server/member/domain/entity/Preference.java index 8d73386..09bcca4 100644 --- a/src/main/java/com/acon/server/member/domain/entity/Preference.java +++ b/src/main/java/com/acon/server/member/domain/entity/Preference.java @@ -1,5 +1,7 @@ package com.acon.server.member.domain.entity; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.member.domain.enums.Cuisine; import com.acon.server.member.domain.enums.DislikeFood; import com.acon.server.member.domain.enums.FavoriteSpot; @@ -14,6 +16,9 @@ @ToString public class Preference { + private static final int FAVORITE_CUISINE_RANK_SIZE = 3; + private static final int FAVORITE_SPOT_RANK_SIZE = 4; + private final Long memberId; private List dislikeFoodList; @@ -31,6 +36,8 @@ private Preference( SpotStyle favoriteSpotStyle, List favoriteSpotRank ) { + validateFavoriteSpotRank(favoriteSpotRank); + validateFavoriteCuisineRank(favoriteCuisineRank); this.memberId = memberId; this.dislikeFoodList = dislikeFoodList; this.favoriteCuisineRank = favoriteCuisineRank; @@ -38,4 +45,16 @@ private Preference( this.favoriteSpotStyle = favoriteSpotStyle; this.favoriteSpotRank = favoriteSpotRank; } + + private void validateFavoriteSpotRank(List favoriteSpotRank) { + if (favoriteSpotRank.size() != FAVORITE_SPOT_RANK_SIZE) { + throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); + } + } + + private void validateFavoriteCuisineRank(List favoriteCuisineRank) { + if (favoriteCuisineRank.size() != FAVORITE_CUISINE_RANK_SIZE) { + throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); + } + } } From a3fb53c0b8ec20377342165d84fec90463fcb757 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 02:42:53 +0900 Subject: [PATCH 25/55] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=84=B8=EB=B6=84=ED=99=94=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/global/exception/ErrorType.java | 2 ++ .../java/com/acon/server/member/domain/entity/Preference.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/acon/server/global/exception/ErrorType.java b/src/main/java/com/acon/server/global/exception/ErrorType.java index e80497d..99805b8 100644 --- a/src/main/java/com/acon/server/global/exception/ErrorType.java +++ b/src/main/java/com/acon/server/global/exception/ErrorType.java @@ -39,6 +39,8 @@ public enum ErrorType { INVALID_SPOT_STYLE_ERROR(HttpStatus.BAD_REQUEST, 40016, "유효하지 않은 spotStyle입니다."), INVALID_FAVORITE_SPOT_ERROR(HttpStatus.BAD_REQUEST, 40017, "유효하지 않은 favoriteSpot입니다."), INVALID_ID_TOKEN_ERROR(HttpStatus.BAD_REQUEST, 40010, "ID 토큰의 서명이 올바르지 않습니다."), + INVALID_FAVORITE_SPOT_RANK_SIZE_ERROR(HttpStatus.BAD_REQUEST, 40030, "favoriteSpotRank의 사이즈가 잘못되었습니다."), + INVALID_FAVORITE_CUISINE_RANK_SIZE_ERROR(HttpStatus.BAD_REQUEST, 40031, "favoriteCuisineRank의 사이즈가 잘못되었습니다."), /* 500 Internal Server Error */ FAILED_DOWNLOAD_GOOGLE_PUBLIC_KEY_ERROR(HttpStatus.BAD_REQUEST, 50002, "구글 공개키 다운로드에 실패하였습니다."); diff --git a/src/main/java/com/acon/server/member/domain/entity/Preference.java b/src/main/java/com/acon/server/member/domain/entity/Preference.java index 09bcca4..a30f5ef 100644 --- a/src/main/java/com/acon/server/member/domain/entity/Preference.java +++ b/src/main/java/com/acon/server/member/domain/entity/Preference.java @@ -48,13 +48,13 @@ private Preference( private void validateFavoriteSpotRank(List favoriteSpotRank) { if (favoriteSpotRank.size() != FAVORITE_SPOT_RANK_SIZE) { - throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); + throw new BusinessException(ErrorType.INVALID_FAVORITE_SPOT_RANK_SIZE_ERROR); } } private void validateFavoriteCuisineRank(List favoriteCuisineRank) { if (favoriteCuisineRank.size() != FAVORITE_CUISINE_RANK_SIZE) { - throw new BusinessException(ErrorType.INVALID_CUISINE_ERROR); + throw new BusinessException(ErrorType.INVALID_FAVORITE_CUISINE_RANK_SIZE_ERROR); } } } From 6a83b2cbe9b697547f222360c592588052049a2d Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 02:59:09 +0900 Subject: [PATCH 26/55] =?UTF-8?q?refactor:=20valid=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=84=B8=EB=B6=84=ED=99=94=20(#1?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/api/request/PreferenceRequest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java index 4e91c88..887823f 100644 --- a/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java +++ b/src/main/java/com/acon/server/member/api/request/PreferenceRequest.java @@ -1,18 +1,19 @@ package com.acon.server.member.api.request; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import java.util.List; public record PreferenceRequest( - @NotNull + @NotEmpty(message = "dislikeFoodList가 빈 값입니다.") List dislikeFoodList, - @NotNull + @NotEmpty(message = "favoriteCuisineRank가 빈 값입니다.") List favoriteCuisineRank, - @NotNull + @NotBlank(message = "favoriteSpotType가 빈 값입니다.") String favoriteSpotType, - @NotNull + @NotBlank(message = "favoriteSpotStyle가 빈 값입니다.") String favoriteSpotStyle, - @NotNull + @NotEmpty(message = "favoriteSpotRank가 빈 값입니다.") List favoriteSpotRank ) { From 48a024dd3385ed9529b3680b4772b08391558f65 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 03:53:51 +0900 Subject: [PATCH 27/55] =?UTF-8?q?refactor:=20orElseThrow=20repository?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/member/api/controller/MemberController.java | 4 ++-- .../server/member/application/service/MemberService.java | 7 ++----- .../server/member/infra/repository/MemberRepository.java | 6 ++++++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index b97e5bc..6e77209 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -27,9 +27,9 @@ public ResponseEntity login(@Valid @RequestBody final LoginReques ); } - @GetMapping("/acorn") + @GetMapping("/member/acorn") public ResponseEntity getAcornCount() { - // TODO 토큰으로 memberId 가져오기 + // TODO: 토큰으로 memberId 가져오기 return ResponseEntity.ok( memberService.fetchAcornCount(1L) ); diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 4de7d66..1b45066 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -1,8 +1,6 @@ package com.acon.server.member.application.service; import com.acon.server.common.auth.jwt.JwtUtils; -import com.acon.server.global.exception.BusinessException; -import com.acon.server.global.exception.ErrorType; import com.acon.server.member.api.request.LoginRequest; import com.acon.server.member.api.response.AcornCountResponse; import com.acon.server.member.api.response.LoginResponse; @@ -35,10 +33,9 @@ public LoginResponse login(final LoginRequest request) { } public AcornCountResponse fetchAcornCount(final Long memberId) { - MemberEntity memberEntity = memberRepository.findById(memberId).orElseThrow( - () -> new BusinessException(ErrorType.NOT_FOUND_MEMBER_ERROR) - ); + MemberEntity memberEntity = memberRepository.findByIdOrElseThrow(memberId); int acornCount = memberEntity.getLeftAcornCount(); + return new AcornCountResponse(acornCount); } diff --git a/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java b/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java index 0f94763..8736049 100644 --- a/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java +++ b/src/main/java/com/acon/server/member/infra/repository/MemberRepository.java @@ -1,5 +1,7 @@ package com.acon.server.member.infra.repository; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.member.domain.enums.SocialType; import com.acon.server.member.infra.entity.MemberEntity; import java.util.Optional; @@ -9,4 +11,8 @@ public interface MemberRepository extends JpaRepository { Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); + default MemberEntity findByIdOrElseThrow(Long id) { + return findById(id) + .orElseThrow(() -> new BusinessException(ErrorType.NOT_FOUND_MEMBER_ERROR)); + } } From 8fcd469e2cc45c1aad223f636b186a669b98d253 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 16:43:57 +0900 Subject: [PATCH 28/55] =?UTF-8?q?chore:=20import=20=EC=88=98=EC=A0=95=20(#?= =?UTF-8?q?11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/global/exception/ErrorType.java | 6 +++--- .../api/controller/MemberController.java | 13 +++++------- .../application/service/MemberService.java | 20 ++++++++----------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/acon/server/global/exception/ErrorType.java b/src/main/java/com/acon/server/global/exception/ErrorType.java index 895edd6..d65ca7a 100644 --- a/src/main/java/com/acon/server/global/exception/ErrorType.java +++ b/src/main/java/com/acon/server/global/exception/ErrorType.java @@ -41,14 +41,14 @@ public enum ErrorType { INVALID_FAVORITE_SPOT_ERROR(HttpStatus.BAD_REQUEST, 40017, "유효하지 않은 favoriteSpot입니다."), INVALID_FAVORITE_SPOT_RANK_SIZE_ERROR(HttpStatus.BAD_REQUEST, 40030, "favoriteSpotRank의 사이즈가 잘못되었습니다."), INVALID_FAVORITE_CUISINE_RANK_SIZE_ERROR(HttpStatus.BAD_REQUEST, 40031, "favoriteCuisineRank의 사이즈가 잘못되었습니다."), - + /* 500 Internal Server Error */ - FAILED_DOWNLOAD_GOOGLE_PUBLIC_KEY_ERROR(HttpStatus.BAD_REQUEST, 50002, "구글 공개키 다운로드에 실패하였습니다."); + FAILED_DOWNLOAD_GOOGLE_PUBLIC_KEY_ERROR(HttpStatus.BAD_REQUEST, 50002, "구글 공개키 다운로드에 실패하였습니다."), /* Spot Error */ /* 400 Bad Request */ INVALID_DAY_ERROR(HttpStatus.BAD_REQUEST, 40099, "유효하지 않은 day입니다."), - + /* 404 Not Found */ NOT_FOUND_SPOT_ERROR(HttpStatus.NOT_FOUND, 40402, "유효한 장소가 없습니다"), ; diff --git a/src/main/java/com/acon/server/member/api/controller/MemberController.java b/src/main/java/com/acon/server/member/api/controller/MemberController.java index c3e6914..74e4c04 100644 --- a/src/main/java/com/acon/server/member/api/controller/MemberController.java +++ b/src/main/java/com/acon/server/member/api/controller/MemberController.java @@ -1,26 +1,22 @@ package com.acon.server.member.api.controller; +import com.acon.server.member.api.request.GuidedSpotRequest; import com.acon.server.member.api.request.LoginRequest; -import com.acon.server.member.api.response.AcornCountResponse; import com.acon.server.member.api.request.PreferenceRequest; +import com.acon.server.member.api.response.AcornCountResponse; import com.acon.server.member.api.response.LoginResponse; import com.acon.server.member.application.service.MemberService; import com.acon.server.member.domain.enums.Cuisine; import com.acon.server.member.domain.enums.DislikeFood; import com.acon.server.member.domain.enums.FavoriteSpot; import com.acon.server.member.domain.enums.SpotStyle; -import com.acon.server.member.domain.enums.SpotType; +import com.acon.server.spot.domain.enums.SpotType; import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import com.acon.server.member.api.request.GuidedSpotRequest; -import com.acon.server.member.application.service.MemberService; -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.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -46,7 +42,7 @@ public ResponseEntity postPreference( memberService.createPreference(dislikeFoodList, favoriteCuisineList, favoriteSpotType, favoriteSpotStyle, favoriteSpotRank, 1L); - + return ResponseEntity.ok().build(); } @@ -66,6 +62,7 @@ public ResponseEntity getAcornCount() { memberService.fetchAcornCount(1L) ); } + @PostMapping("/member/guided-spot") public ResponseEntity postGuidedSpot( @Valid @RequestBody final GuidedSpotRequest request diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 2d43dfa..e66589e 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -1,6 +1,8 @@ package com.acon.server.member.application.service; import com.acon.server.common.auth.jwt.JwtUtils; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.member.api.request.LoginRequest; import com.acon.server.member.api.response.AcornCountResponse; import com.acon.server.member.api.response.LoginResponse; @@ -13,22 +15,18 @@ import com.acon.server.member.domain.enums.FavoriteSpot; import com.acon.server.member.domain.enums.SocialType; import com.acon.server.member.domain.enums.SpotStyle; -import com.acon.server.member.domain.enums.SpotType; import com.acon.server.member.infra.entity.MemberEntity; +import com.acon.server.member.infra.entity.RecentGuidedSpotEntity; import com.acon.server.member.infra.external.google.GoogleSocialService; import com.acon.server.member.infra.repository.MemberRepository; import com.acon.server.member.infra.repository.PreferenceRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import com.acon.server.global.exception.BusinessException; -import com.acon.server.global.exception.ErrorType; -import com.acon.server.member.infra.entity.RecentGuidedSpotEntity; import com.acon.server.member.infra.repository.RecentGuidedSpotRepository; +import com.acon.server.spot.domain.enums.SpotType; import com.acon.server.spot.infra.repository.SpotRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -38,13 +36,12 @@ public class MemberService { private final SpotRepository spotRepository; private final PreferenceRepository preferenceRepository; private final RecentGuidedSpotRepository recentGuidedSpotRepository; - + private final MemberMapper memberMapper; private final PreferenceMapper preferenceMapper; - + private final JwtUtils jwtUtils; private final GoogleSocialService googleSocialService; - public void createPreference( List dislikeFoodList, @@ -106,7 +103,6 @@ protected Long fetchMemberId(final SocialType socialType, final String socialId) return member.getId(); } - public void createGuidedSpot(final Long spotId, final Long memberId) { if (spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); From 6366b18c7014035b76a8e1e38d04f33ddb967f83 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 17:52:52 +0900 Subject: [PATCH 29/55] =?UTF-8?q?fix:token=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=94=94=EC=BD=94=EB=94=A9=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java | 3 +-- .../com/acon/server/spot/application/service/SpotService.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java index 3b17401..666e2cc 100644 --- a/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java +++ b/src/main/java/com/acon/server/common/auth/jwt/JwtUtils.java @@ -3,7 +3,6 @@ import com.acon.server.common.auth.jwt.config.JwtConfig; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import java.util.Date; import java.util.List; @@ -19,7 +18,7 @@ public class JwtUtils { private static final String JWT_KEY = "memberId"; public JwtUtils(JwtConfig jwtConfig) { - byte[] keyBytes = Decoders.BASE64.decode(jwtConfig.getSecretKey()); + byte[] keyBytes = jwtConfig.getSecretKey().getBytes(); this.secretKey = Keys.hmacShaKeyFor(keyBytes); } diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 597efe3..42f5c72 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -19,7 +19,7 @@ public class SpotService { private final MenuRepository menuRepository; public MenuListResponse fetchMenus(Long spotId) { - if (spotRepository.existsById(spotId)) { + if (!spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } List menuEntityList = menuRepository.findAllBySpotId(spotId); From 246ac8029a4ae1af8c674a6b4f63211ec41f7941 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 19:57:09 +0900 Subject: [PATCH 30/55] =?UTF-8?q?feat:=20search=20spot=20response=20dto=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/api/response/SearchSpotResponse.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/acon/server/spot/api/response/SearchSpotResponse.java diff --git a/src/main/java/com/acon/server/spot/api/response/SearchSpotResponse.java b/src/main/java/com/acon/server/spot/api/response/SearchSpotResponse.java new file mode 100644 index 0000000..712861f --- /dev/null +++ b/src/main/java/com/acon/server/spot/api/response/SearchSpotResponse.java @@ -0,0 +1,14 @@ +package com.acon.server.spot.api.response; + +import com.acon.server.spot.domain.enums.SpotType; +import lombok.Builder; + +@Builder +public record SearchSpotResponse( + Long spotId, + String name, + String address, + SpotType spotType +) { + +} From a4ee9876f67eb5c1f7614657ab56e6e7b54fd2d4 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 19:57:26 +0900 Subject: [PATCH 31/55] =?UTF-8?q?feat:=20search=20spot=20list=20response?= =?UTF-8?q?=20dto=20=EA=B5=AC=ED=98=84=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/spot/api/response/SearchSpotListResponse.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/com/acon/server/spot/api/response/SearchSpotListResponse.java diff --git a/src/main/java/com/acon/server/spot/api/response/SearchSpotListResponse.java b/src/main/java/com/acon/server/spot/api/response/SearchSpotListResponse.java new file mode 100644 index 0000000..7e3db8b --- /dev/null +++ b/src/main/java/com/acon/server/spot/api/response/SearchSpotListResponse.java @@ -0,0 +1,9 @@ +package com.acon.server.spot.api.response; + +import java.util.List; + +public record SearchSpotListResponse( + List spotList +) { + +} From 95f13a0f60ada541c1992a74d7b6b38ec26a77e0 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Fri, 17 Jan 2025 19:57:52 +0900 Subject: [PATCH 32/55] =?UTF-8?q?feat:=20=EC=9E=A5=EC=86=8C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20API=20=EA=B5=AC=ED=98=84=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/api/controller/SpotController.java | 11 +++++++++++ .../spot/application/service/SpotService.java | 18 ++++++++++++++++++ .../spot/infra/repository/SpotRepository.java | 2 ++ 3 files changed, 31 insertions(+) diff --git a/src/main/java/com/acon/server/spot/api/controller/SpotController.java b/src/main/java/com/acon/server/spot/api/controller/SpotController.java index 44a847b..0c6ae3f 100644 --- a/src/main/java/com/acon/server/spot/api/controller/SpotController.java +++ b/src/main/java/com/acon/server/spot/api/controller/SpotController.java @@ -1,6 +1,7 @@ package com.acon.server.spot.api.controller; import com.acon.server.spot.api.response.MenuListResponse; +import com.acon.server.spot.api.response.SearchSpotListResponse; import com.acon.server.spot.application.service.SpotService; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; @@ -9,6 +10,7 @@ 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -27,4 +29,13 @@ public ResponseEntity getMenus( spotService.fetchMenus(spotId) ); } + + @GetMapping("/spots/search") + public ResponseEntity searchSpot( + @RequestParam(value = "keyword", required = false) final String keyword + ) { + return ResponseEntity.ok( + spotService.searchSpot(keyword) + ); + } } diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 597efe3..39862e5 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -4,7 +4,10 @@ import com.acon.server.global.exception.ErrorType; import com.acon.server.spot.api.response.MenuListResponse; import com.acon.server.spot.api.response.MenuResponse; +import com.acon.server.spot.api.response.SearchSpotListResponse; +import com.acon.server.spot.api.response.SearchSpotResponse; import com.acon.server.spot.infra.entity.MenuEntity; +import com.acon.server.spot.infra.entity.SpotEntity; import com.acon.server.spot.infra.repository.MenuRepository; import com.acon.server.spot.infra.repository.SpotRepository; import java.util.List; @@ -34,4 +37,19 @@ public MenuListResponse fetchMenus(Long spotId) { return new MenuListResponse(menuList); } + + public SearchSpotListResponse searchSpot(final String keyword) { + List spotEntityList = spotRepository.findTop10ByNameContainsIgnoreCase(keyword); + List spotList = spotEntityList.stream() + .map(spotEntity -> SearchSpotResponse.builder() + .spotId(spotEntity.getId()) + .name(spotEntity.getName()) + .address(spotEntity.getAddress()) + .spotType(spotEntity.getSpotType()) + .build()) + .toList(); + + return new SearchSpotListResponse(spotList); + + } } diff --git a/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java b/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java index 5bcc573..1fc9935 100644 --- a/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java +++ b/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java @@ -1,8 +1,10 @@ package com.acon.server.spot.infra.repository; import com.acon.server.spot.infra.entity.SpotEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface SpotRepository extends JpaRepository { + List findTop10ByNameContainsIgnoreCase(String keyword); } From e5d520769a7022a2f74e2097cca26488e584d581 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 18:51:05 +0900 Subject: [PATCH 33/55] =?UTF-8?q?fix:=20index=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/infra/entity/RecentGuidedSpotEntity.java | 6 ++++-- .../member/infra/entity/RecentViewedSpotEntity.java | 6 ++++-- .../server/member/infra/entity/VerifiedAreaEntity.java | 6 ++++-- .../acon/server/spot/api/response/MenuListResponse.java | 1 - .../com/acon/server/spot/infra/entity/MenuEntity.java | 8 +++++--- .../acon/server/spot/infra/entity/SpotImageEntity.java | 6 ++++-- .../acon/server/spot/infra/entity/SpotOptionEntity.java | 6 ++++-- .../acon/server/spot/infra/repository/MenuRepository.java | 1 - 8 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java b/src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java index 9dd9847..2b894d6 100644 --- a/src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java +++ b/src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java @@ -17,8 +17,10 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "recent_guided_spot", uniqueConstraints = @UniqueConstraint( - name = "unique_recent_guided_spot_member_id_spot_id", columnNames = {"member_id", "spot_id"} + name = "recent_guided_spot", + uniqueConstraints = @UniqueConstraint( + name = "unique_recent_guided_spot_member_id_spot_id", + columnNames = {"member_id", "spot_id"} ) ) public class RecentGuidedSpotEntity extends BaseTimeEntity { diff --git a/src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java b/src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java index 72d801c..7cbbe43 100644 --- a/src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java +++ b/src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java @@ -17,8 +17,10 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "recent_viewed_spot", uniqueConstraints = @UniqueConstraint( - name = "unique_recent_viewed_spot_member_id_spot_id", columnNames = {"member_id", "spot_id"} + name = "recent_viewed_spot", + uniqueConstraints = @UniqueConstraint( + name = "unique_recent_viewed_spot_member_id_spot_id", + columnNames = {"member_id", "spot_id"} ) ) public class RecentViewedSpotEntity extends BaseTimeEntity { diff --git a/src/main/java/com/acon/server/member/infra/entity/VerifiedAreaEntity.java b/src/main/java/com/acon/server/member/infra/entity/VerifiedAreaEntity.java index 6817ae6..15de048 100644 --- a/src/main/java/com/acon/server/member/infra/entity/VerifiedAreaEntity.java +++ b/src/main/java/com/acon/server/member/infra/entity/VerifiedAreaEntity.java @@ -20,8 +20,10 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "verified_area", indexes = @Index( - name = "idx_member_id", columnList = "member_id" + name = "verified_area", + indexes = @Index( + name = "idx_verified_area_member_id", + columnList = "member_id" ) ) public class VerifiedAreaEntity { diff --git a/src/main/java/com/acon/server/spot/api/response/MenuListResponse.java b/src/main/java/com/acon/server/spot/api/response/MenuListResponse.java index 49ed580..9db8dda 100644 --- a/src/main/java/com/acon/server/spot/api/response/MenuListResponse.java +++ b/src/main/java/com/acon/server/spot/api/response/MenuListResponse.java @@ -7,4 +7,3 @@ public record MenuListResponse( ) { } - diff --git a/src/main/java/com/acon/server/spot/infra/entity/MenuEntity.java b/src/main/java/com/acon/server/spot/infra/entity/MenuEntity.java index 21cfc32..e803d76 100644 --- a/src/main/java/com/acon/server/spot/infra/entity/MenuEntity.java +++ b/src/main/java/com/acon/server/spot/infra/entity/MenuEntity.java @@ -16,9 +16,11 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "menu", indexes = @Index( - name = "idx_spot_id", columnList = "spot_id" -) + name = "menu", + indexes = @Index( + name = "idx_menu_spot_id", + columnList = "spot_id" + ) ) public class MenuEntity { diff --git a/src/main/java/com/acon/server/spot/infra/entity/SpotImageEntity.java b/src/main/java/com/acon/server/spot/infra/entity/SpotImageEntity.java index 4a1df92..7937d60 100644 --- a/src/main/java/com/acon/server/spot/infra/entity/SpotImageEntity.java +++ b/src/main/java/com/acon/server/spot/infra/entity/SpotImageEntity.java @@ -16,8 +16,10 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "spot_image", indexes = @Index( - name = "idx_spot_id", columnList = "spot_id" + name = "spot_image", + indexes = @Index( + name = "idx_spot_image_spot_id", + columnList = "spot_id" ) ) public class SpotImageEntity { diff --git a/src/main/java/com/acon/server/spot/infra/entity/SpotOptionEntity.java b/src/main/java/com/acon/server/spot/infra/entity/SpotOptionEntity.java index a981073..ac1dbed 100644 --- a/src/main/java/com/acon/server/spot/infra/entity/SpotOptionEntity.java +++ b/src/main/java/com/acon/server/spot/infra/entity/SpotOptionEntity.java @@ -16,8 +16,10 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "spot_option", uniqueConstraints = @UniqueConstraint( - name = "unique_spot_option_spot_id_option_id", columnNames = {"spot_id", "option_id"} + name = "spot_option", + uniqueConstraints = @UniqueConstraint( + name = "unique_spot_option_spot_id_option_id", + columnNames = {"spot_id", "option_id"} ) ) public class SpotOptionEntity { diff --git a/src/main/java/com/acon/server/spot/infra/repository/MenuRepository.java b/src/main/java/com/acon/server/spot/infra/repository/MenuRepository.java index 05a47ee..354581c 100644 --- a/src/main/java/com/acon/server/spot/infra/repository/MenuRepository.java +++ b/src/main/java/com/acon/server/spot/infra/repository/MenuRepository.java @@ -7,5 +7,4 @@ public interface MenuRepository extends JpaRepository { List findAllBySpotId(Long spotId); - } From 09aa8d0c251030ed5518b7ac86b2cc64e6c814b4 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 18:52:52 +0900 Subject: [PATCH 34/55] =?UTF-8?q?feat:=20Naver=20Maps=20Geocoding=20API=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ .../server/global/config/FeignConfig.java | 11 +++++++ .../properties/NaverMapsProperties.java | 16 ++++++++++ .../server/global/exception/ErrorType.java | 5 +++- .../global/external/GeoCodingResponse.java | 8 +++++ .../global/external/NaverMapsClient.java | 19 ++++++++++++ .../global/external/NaverMapsFeignConfig.java | 20 +++++++++++++ .../global/external/NaverMapsService.java | 29 +++++++++++++++++++ 8 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/acon/server/global/config/FeignConfig.java create mode 100644 src/main/java/com/acon/server/global/config/properties/NaverMapsProperties.java create mode 100644 src/main/java/com/acon/server/global/external/GeoCodingResponse.java create mode 100644 src/main/java/com/acon/server/global/external/NaverMapsClient.java create mode 100644 src/main/java/com/acon/server/global/external/NaverMapsFeignConfig.java create mode 100644 src/main/java/com/acon/server/global/external/NaverMapsService.java diff --git a/build.gradle b/build.gradle index 348d4c3..d65e0d5 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,9 @@ dependencies { implementation 'org.mapstruct:mapstruct:1.6.3' annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' + + // OpenFeign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.2.0' } tasks.named('test') { diff --git a/src/main/java/com/acon/server/global/config/FeignConfig.java b/src/main/java/com/acon/server/global/config/FeignConfig.java new file mode 100644 index 0000000..8c6a122 --- /dev/null +++ b/src/main/java/com/acon/server/global/config/FeignConfig.java @@ -0,0 +1,11 @@ +package com.acon.server.global.config; + +import com.acon.server.ServerApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableFeignClients(basePackageClasses = ServerApplication.class) +public class FeignConfig { + +} diff --git a/src/main/java/com/acon/server/global/config/properties/NaverMapsProperties.java b/src/main/java/com/acon/server/global/config/properties/NaverMapsProperties.java new file mode 100644 index 0000000..2181bcb --- /dev/null +++ b/src/main/java/com/acon/server/global/config/properties/NaverMapsProperties.java @@ -0,0 +1,16 @@ +package com.acon.server.global.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@Getter +@Setter +@ConfigurationProperties(prefix = "naver.maps") +public class NaverMapsProperties { + + private String clientId; + private String clientSecret; +} diff --git a/src/main/java/com/acon/server/global/exception/ErrorType.java b/src/main/java/com/acon/server/global/exception/ErrorType.java index 310d8df..d44a622 100644 --- a/src/main/java/com/acon/server/global/exception/ErrorType.java +++ b/src/main/java/com/acon/server/global/exception/ErrorType.java @@ -42,9 +42,12 @@ public enum ErrorType { /* Spot Error */ /* 400 Bad Request */ INVALID_DAY_ERROR(HttpStatus.BAD_REQUEST, 40099, "유효하지 않은 day입니다."), - + /* 404 Not Found */ NOT_FOUND_SPOT_ERROR(HttpStatus.NOT_FOUND, 40402, "유효한 장소가 없습니다"), + + /* 500 Internal Server Error */ + NAVER_MAPS_GEOCODING_API_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50003, "Naver Maps GeoCoding API 호출에 실패했습니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/acon/server/global/external/GeoCodingResponse.java b/src/main/java/com/acon/server/global/external/GeoCodingResponse.java new file mode 100644 index 0000000..8ca831e --- /dev/null +++ b/src/main/java/com/acon/server/global/external/GeoCodingResponse.java @@ -0,0 +1,8 @@ +package com.acon.server.global.external; + +public record GeoCodingResponse( + String latitude, + String longitude +) { + +} diff --git a/src/main/java/com/acon/server/global/external/NaverMapsClient.java b/src/main/java/com/acon/server/global/external/NaverMapsClient.java new file mode 100644 index 0000000..a402f03 --- /dev/null +++ b/src/main/java/com/acon/server/global/external/NaverMapsClient.java @@ -0,0 +1,19 @@ +package com.acon.server.global.external; + +import java.util.Map; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient( + name = "naverMapsClient", + url = "https://naveropenapi.apigw.ntruss.com", + configuration = NaverMapsFeignConfig.class +) +public interface NaverMapsClient { + + @GetMapping(value = "/map-geocode/v2/geocode") + Map getGeoCode( + @RequestParam("query") String query + ); +} diff --git a/src/main/java/com/acon/server/global/external/NaverMapsFeignConfig.java b/src/main/java/com/acon/server/global/external/NaverMapsFeignConfig.java new file mode 100644 index 0000000..b3c1ec0 --- /dev/null +++ b/src/main/java/com/acon/server/global/external/NaverMapsFeignConfig.java @@ -0,0 +1,20 @@ +package com.acon.server.global.external; + +import com.acon.server.global.config.properties.NaverMapsProperties; +import feign.RequestInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; + +@RequiredArgsConstructor +public class NaverMapsFeignConfig { + + private final NaverMapsProperties naverMapsProperties; + + @Bean + public RequestInterceptor requestInterceptor() { + return requestTemplate -> { + requestTemplate.header("X-NCP-APIGW-API-KEY-ID", naverMapsProperties.getClientId()); + requestTemplate.header("X-NCP-APIGW-API-KEY", naverMapsProperties.getClientSecret()); + }; + } +} diff --git a/src/main/java/com/acon/server/global/external/NaverMapsService.java b/src/main/java/com/acon/server/global/external/NaverMapsService.java new file mode 100644 index 0000000..2a366b8 --- /dev/null +++ b/src/main/java/com/acon/server/global/external/NaverMapsService.java @@ -0,0 +1,29 @@ +package com.acon.server.global.external; + +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NaverMapsService { + + private final NaverMapsClient naverMapsClient; + + public GeoCodingResponse getGeoCodingResult(String address) { + return Optional.ofNullable(naverMapsClient.getGeoCode(address)) + .map(response -> (List>) response.get("addresses")) + .filter(addresses -> !addresses.isEmpty()) + .map(addresses -> addresses.get(0)) + .map(firstAddress -> new GeoCodingResponse( + (String) firstAddress.get("x"), + (String) firstAddress.get("y") + )) + .orElseThrow( + () -> new BusinessException(ErrorType.NAVER_MAPS_GEOCODING_API_ERROR)); + } +} From 1f756026925f1503b0156b199cdab0c814a6d79a Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 19:21:39 +0900 Subject: [PATCH 35/55] =?UTF-8?q?chore:=20TODO=20=EC=A0=95=EB=A6=AC=20(#24?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/global/exception/ErrorType.java | 3 +++ .../com/acon/server/global/handler/GlobalExceptionHandler.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/acon/server/global/exception/ErrorType.java b/src/main/java/com/acon/server/global/exception/ErrorType.java index d44a622..f374af9 100644 --- a/src/main/java/com/acon/server/global/exception/ErrorType.java +++ b/src/main/java/com/acon/server/global/exception/ErrorType.java @@ -9,6 +9,8 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public enum ErrorType { + // TODO: ErrorType code 한 번 싹 정리 + /* Common Error */ /* 400 Bad Request */ INVALID_PATH_ERROR(HttpStatus.BAD_REQUEST, 40001, "요청 경로의 변수 값이 허용된 형식과 다릅니다."), @@ -19,6 +21,7 @@ public enum ErrorType { INVALID_REQUEST_BODY_ERROR(HttpStatus.BAD_REQUEST, 40006, "유효하지 않은 Request Body입니다. 요청 형식 또는 필드를 확인하세요."), DATA_INTEGRITY_VIOLATION_ERROR(HttpStatus.BAD_REQUEST, 40007, "데이터 무결성 제약 조건을 위반했습니다."), INVALID_ACCESS_TOKEN_ERROR(HttpStatus.BAD_REQUEST, 40008, "유효하지 않은 accessToken입니다."), + // TODO: NonNull 필드에 null 값이 입력되었을 때 발생하는 예외 처리 추가 /* 401 Unauthorized */ EXPIRED_ACCESS_TOKEN_ERROR(HttpStatus.UNAUTHORIZED, 40101, "만료된 accessToken입니다."), diff --git a/src/main/java/com/acon/server/global/handler/GlobalExceptionHandler.java b/src/main/java/com/acon/server/global/handler/GlobalExceptionHandler.java index 2904831..e2a62c2 100644 --- a/src/main/java/com/acon/server/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/acon/server/global/handler/GlobalExceptionHandler.java @@ -91,6 +91,9 @@ public ResponseEntity handleNoHandlerFoundException(NoHandlerFoun .body(ErrorResponse.fail(ErrorType.NOT_FOUND_PATH_ERROR, e.getRequestURL())); } + // TODO: NonNull 필드에 null 값이 입력되었을 때 발생하는 예외 처리 추가 + // TODO: 존재하지 않는 경로로 요청이 들어왔을 시 예외 처리 추가 + // 비즈니스 로직에서 발생하는 예외 처리 @ExceptionHandler(BusinessException.class) public ResponseEntity handleCustomException(BusinessException e) { From ce9fb160d713eaa4351d4bb43961ea2db5ef3614 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 19:22:56 +0900 Subject: [PATCH 36/55] =?UTF-8?q?feat:=20=EC=9E=A5=EC=86=8C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=9E=91=EC=84=B1=20(#2?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/spot/api/controller/SpotController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/acon/server/spot/api/controller/SpotController.java b/src/main/java/com/acon/server/spot/api/controller/SpotController.java index 44a847b..bfec1a8 100644 --- a/src/main/java/com/acon/server/spot/api/controller/SpotController.java +++ b/src/main/java/com/acon/server/spot/api/controller/SpotController.java @@ -1,5 +1,6 @@ package com.acon.server.spot.api.controller; +import com.acon.server.spot.api.response.MenuDetailResponse; import com.acon.server.spot.api.response.MenuListResponse; import com.acon.server.spot.application.service.SpotService; import jakarta.validation.constraints.Positive; @@ -18,6 +19,16 @@ public class SpotController { private final SpotService spotService; + @GetMapping("/spot/{spotId}") + public ResponseEntity getSpotDetail( + @Positive(message = "spotId는 양수여야 합니다.") + @Validated @PathVariable(name = "spotId") Long spotId + ) { + return ResponseEntity.ok( + spotService.fetchSpotDetail(spotId) + ); + } + @GetMapping("/spot/{spotId}/menus") public ResponseEntity getMenus( @Positive(message = "spotId는 양수여야 합니다.") From fcbc2c02540713ff1d833c4773f935f328abab49 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 19:23:36 +0900 Subject: [PATCH 37/55] =?UTF-8?q?feat:=20=EC=9E=A5=EC=86=8C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A0=95=EB=B3=B4=20DTO,=20Mapper=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/api/response/MenuDetailResponse.java | 30 +++++++++++++++++++ .../application/mapper/SpotDtoMapper.java | 13 ++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java create mode 100644 src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java diff --git a/src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java b/src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java new file mode 100644 index 0000000..fcbba78 --- /dev/null +++ b/src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java @@ -0,0 +1,30 @@ +package com.acon.server.spot.api.response; + +import com.acon.server.spot.domain.enums.SpotType; +import java.util.List; +import lombok.NonNull; + +public record MenuDetailResponse( + @NonNull + Long id, + @NonNull + String name, + @NonNull + SpotType spotType, + @NonNull + List imageList, + @NonNull + Boolean openStatus, + @NonNull + String address, + @NonNull + Integer localAcornCount, + @NonNull + Integer basicAcornCount, + @NonNull + Double latitude, + @NonNull + Double longitude +) { + +} diff --git a/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java b/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java new file mode 100644 index 0000000..dca2c72 --- /dev/null +++ b/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java @@ -0,0 +1,13 @@ +package com.acon.server.spot.application.mapper; + +import com.acon.server.spot.api.response.MenuDetailResponse; +import com.acon.server.spot.infra.entity.SpotEntity; +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface SpotDtoMapper { + + MenuDetailResponse toMenuDetailResponse(SpotEntity spotEntity, List imageList, boolean openStatus); +} From 0c3fd9dd85c7676c072c4e4d3ac66cf568763b26 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 20:14:20 +0900 Subject: [PATCH 38/55] =?UTF-8?q?refactor:=20OpeningHour=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=ED=95=84=EB=93=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/domain/entity/OpeningHour.java | 8 ++--- .../acon/server/spot/domain/enums/Day.java | 31 +++++++++++++------ .../spot/infra/entity/OpeningHourEntity.java | 20 ++++++------ 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java b/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java index e330a00..9da6a1d 100644 --- a/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java +++ b/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java @@ -1,6 +1,6 @@ package com.acon.server.spot.domain.entity; -import com.acon.server.spot.domain.enums.Day; +import java.time.DayOfWeek; import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; @@ -13,7 +13,7 @@ public class OpeningHour { private final Long id; private final Long spotId; - private Day day; + private DayOfWeek dayOfWeek; private LocalDateTime startTime; private LocalDateTime endTime; @@ -21,13 +21,13 @@ public class OpeningHour { public OpeningHour( Long id, Long spotId, - Day day, + DayOfWeek dayOfWeek, LocalDateTime startTime, LocalDateTime endTime ) { this.id = id; this.spotId = spotId; - this.day = day; + this.dayOfWeek = dayOfWeek; this.startTime = startTime; this.endTime = endTime; } diff --git a/src/main/java/com/acon/server/spot/domain/enums/Day.java b/src/main/java/com/acon/server/spot/domain/enums/Day.java index 1fd799b..97b3bf6 100644 --- a/src/main/java/com/acon/server/spot/domain/enums/Day.java +++ b/src/main/java/com/acon/server/spot/domain/enums/Day.java @@ -1,7 +1,16 @@ package com.acon.server.spot.domain.enums; +import static java.time.DayOfWeek.FRIDAY; +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.THURSDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.DayOfWeek.WEDNESDAY; + import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; +import java.time.DayOfWeek; import java.util.HashMap; import java.util.Map; import lombok.AccessLevel; @@ -12,15 +21,17 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum Day { - MON, - TUE, - WED, - THU, - FRI, - SAT, - SUN, + MON(MONDAY), + TUE(TUESDAY), + WED(WEDNESDAY), + THU(THURSDAY), + FRI(FRIDAY), + SAT(SATURDAY), + SUN(SUNDAY), ; + private final DayOfWeek dayOfWeek; + private static final Map DAY_MAP = new HashMap<>(); static { @@ -29,13 +40,13 @@ public enum Day { } } - public static Day fromValue(String value) { - Day day = DAY_MAP.get(value); + public static DayOfWeek getDayOfWeekFromValue(String value) { + Day day = DAY_MAP.get(value.toUpperCase()); if (day == null) { throw new BusinessException(ErrorType.INVALID_DAY_ERROR); } - return day; + return day.getDayOfWeek(); } } diff --git a/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java b/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java index 16c30a4..ced940d 100644 --- a/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java +++ b/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java @@ -1,6 +1,5 @@ package com.acon.server.spot.infra.entity; -import com.acon.server.spot.domain.enums.Day; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -10,7 +9,8 @@ import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.Table; -import java.time.LocalDateTime; +import java.time.DayOfWeek; +import java.time.LocalTime; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -34,26 +34,26 @@ public class OpeningHourEntity { private Long spotId; @Enumerated(EnumType.STRING) - @Column(name = "day", nullable = false) - private Day day; + @Column(name = "day_of_week", nullable = false) + private DayOfWeek dayOfWeek; @Column(name = "start_time", nullable = false) - private LocalDateTime startTime; + private LocalTime startTime; @Column(name = "end_time", nullable = false) - private LocalDateTime endTime; + private LocalTime endTime; @Builder public OpeningHourEntity( Long id, Long spotId, - Day day, - LocalDateTime startTime, - LocalDateTime endTime + DayOfWeek dayOfWeek, + LocalTime startTime, + LocalTime endTime ) { this.id = id; this.spotId = spotId; - this.day = day; + this.dayOfWeek = dayOfWeek; this.startTime = startTime; this.endTime = endTime; } From 8d254c21c11d603f3d9b0467e33c1238bfba8f29 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 20:22:28 +0900 Subject: [PATCH 39/55] =?UTF-8?q?feat:=20=EC=A2=8C=ED=91=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/spot/domain/entity/Spot.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/acon/server/spot/domain/entity/Spot.java b/src/main/java/com/acon/server/spot/domain/entity/Spot.java index 5ebdc71..d665100 100644 --- a/src/main/java/com/acon/server/spot/domain/entity/Spot.java +++ b/src/main/java/com/acon/server/spot/domain/entity/Spot.java @@ -49,4 +49,9 @@ public Spot( this.longitude = longitude; this.adminDong = adminDong; } + + public void updateCoordinates(Double latitude, Double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } } From 02d4715a69740856bcf2c64353d12d54d65e8b39 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 20:24:11 +0900 Subject: [PATCH 40/55] =?UTF-8?q?feat:=20=EC=9E=A5=EC=86=8C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A0=95=EB=B3=B4=20Repository=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20JPA=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/infra/repository/OpeningHourRepository.java | 11 +++++++++++ .../spot/infra/repository/SpotImageRepository.java | 10 ++++++++++ .../server/spot/infra/repository/SpotRepository.java | 7 +++++++ 3 files changed, 28 insertions(+) create mode 100644 src/main/java/com/acon/server/spot/infra/repository/OpeningHourRepository.java create mode 100644 src/main/java/com/acon/server/spot/infra/repository/SpotImageRepository.java diff --git a/src/main/java/com/acon/server/spot/infra/repository/OpeningHourRepository.java b/src/main/java/com/acon/server/spot/infra/repository/OpeningHourRepository.java new file mode 100644 index 0000000..fa0d51b --- /dev/null +++ b/src/main/java/com/acon/server/spot/infra/repository/OpeningHourRepository.java @@ -0,0 +1,11 @@ +package com.acon.server.spot.infra.repository; + +import com.acon.server.spot.infra.entity.OpeningHourEntity; +import java.time.DayOfWeek; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OpeningHourRepository extends JpaRepository { + + List findAllBySpotIdAndDayOfWeek(Long spotId, DayOfWeek dayOfWeek); +} diff --git a/src/main/java/com/acon/server/spot/infra/repository/SpotImageRepository.java b/src/main/java/com/acon/server/spot/infra/repository/SpotImageRepository.java new file mode 100644 index 0000000..a79e0b3 --- /dev/null +++ b/src/main/java/com/acon/server/spot/infra/repository/SpotImageRepository.java @@ -0,0 +1,10 @@ +package com.acon.server.spot.infra.repository; + +import com.acon.server.spot.infra.entity.SpotImageEntity; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SpotImageRepository extends JpaRepository { + + List findAllBySpotId(Long spotId); +} diff --git a/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java b/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java index 5bcc573..422f6b1 100644 --- a/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java +++ b/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java @@ -1,8 +1,15 @@ package com.acon.server.spot.infra.repository; +import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.exception.ErrorType; import com.acon.server.spot.infra.entity.SpotEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface SpotRepository extends JpaRepository { + default SpotEntity findByIdOrThrow(Long id) { + return findById(id).orElseThrow( + () -> new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR) + ); + } } From 91b2077dd47c71bc77602ae6e953b754a54424f6 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 20:24:50 +0900 Subject: [PATCH 41/55] =?UTF-8?q?feat:=20=EC=9E=A5=EC=86=8C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A0=95=EB=B3=B4=20=EB=B9=84=EC=A6=88=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/application/service/SpotService.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 597efe3..2eb9047 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -2,11 +2,25 @@ import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; +import com.acon.server.global.external.GeoCodingResponse; +import com.acon.server.global.external.NaverMapsService; +import com.acon.server.spot.api.response.MenuDetailResponse; import com.acon.server.spot.api.response.MenuListResponse; import com.acon.server.spot.api.response.MenuResponse; +import com.acon.server.spot.application.mapper.SpotDtoMapper; +import com.acon.server.spot.application.mapper.SpotMapper; +import com.acon.server.spot.domain.entity.Spot; import com.acon.server.spot.infra.entity.MenuEntity; +import com.acon.server.spot.infra.entity.OpeningHourEntity; +import com.acon.server.spot.infra.entity.SpotEntity; +import com.acon.server.spot.infra.entity.SpotImageEntity; import com.acon.server.spot.infra.repository.MenuRepository; +import com.acon.server.spot.infra.repository.OpeningHourRepository; +import com.acon.server.spot.infra.repository.SpotImageRepository; import com.acon.server.spot.infra.repository.SpotRepository; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,12 +31,87 @@ public class SpotService { private final SpotRepository spotRepository; private final MenuRepository menuRepository; + private final SpotImageRepository spotImageRepository; + private final OpeningHourRepository openingHourRepository; + + private final SpotMapper spotMapper; + private final SpotDtoMapper spotDtoMapper; + + private final NaverMapsService naverMapsService; + + public MenuDetailResponse fetchSpotDetail(Long spotId) { + SpotEntity spotEntity = spotRepository.findByIdOrThrow(spotId); + Spot spot = spotMapper.toDomain(spotEntity); + + List spotImageEntityList = spotImageRepository.findAllBySpotId(spotId); + List imageList = spotImageEntityList.stream() + .map(SpotImageEntity::getImage) + .toList(); + + if (spot.getLatitude() == null || spot.getLongitude() == null) { + updateSpotCoordinates(spot); + spotEntity = spotRepository.save(spotMapper.toEntity(spot)); + } + + return spotDtoMapper.toMenuDetailResponse(spotEntity, imageList, isSpotOpen(spotId)); + } + + private void updateSpotCoordinates(Spot spot) { + GeoCodingResponse geoCodingResponse = naverMapsService.getGeoCodingResult(spot.getAddress()); + + spot.updateCoordinates( + Double.parseDouble(geoCodingResponse.latitude()), + Double.parseDouble(geoCodingResponse.longitude()) + ); + } + + private boolean isSpotOpen(Long spotId) { + LocalDateTime now = LocalDateTime.now(); + DayOfWeek today = now.getDayOfWeek(); + DayOfWeek yesterday = today.minus(1); + LocalTime currentTime = now.toLocalTime(); + + // 1. 전날 영업 시간에 속하는지 확인 (자정이 지났을 때) + List yesterdayHours = openingHourRepository.findAllBySpotIdAndDayOfWeek(spotId, yesterday); + boolean isOpenFromYesterday = yesterdayHours.stream() + .anyMatch(openingHour -> isAfterMidnight(currentTime, openingHour)); + + if (isOpenFromYesterday) { + return true; + } + + // 2. 오늘 영업 시간에 속하는지 확인 (자정이 지나기 전) + List todayHours = openingHourRepository.findAllBySpotIdAndDayOfWeek(spotId, today); + + return todayHours.stream() + .anyMatch(openingHour -> isBeforeMidnight(currentTime, openingHour)); + } + + private boolean isAfterMidnight(LocalTime currentTime, OpeningHourEntity openingHour) { + LocalTime startTime = openingHour.getStartTime(); + LocalTime endTime = openingHour.getEndTime(); + + return endTime.isBefore(startTime) && currentTime.isAfter(LocalTime.MIDNIGHT) && currentTime.isBefore(endTime); + } + + private boolean isBeforeMidnight(LocalTime currentTime, OpeningHourEntity openingHour) { + LocalTime startTime = openingHour.getStartTime(); + LocalTime endTime = openingHour.getEndTime(); + + if (endTime.isBefore(startTime)) { + return currentTime.isAfter(startTime) && currentTime.isBefore(LocalTime.MIDNIGHT); + } + + return currentTime.isAfter(startTime) && currentTime.isBefore(endTime); + } public MenuListResponse fetchMenus(Long spotId) { if (spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } + List menuEntityList = menuRepository.findAllBySpotId(spotId); + List menuList = menuEntityList.stream() .map(menu -> MenuResponse.builder() .id(menu.getId()) From e15c8e3f85f8b9d461d61f524529fea8af8916eb Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 20:26:38 +0900 Subject: [PATCH 42/55] =?UTF-8?q?fix:=20OpeningHour=20=EB=B3=B5=ED=95=A9?= =?UTF-8?q?=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=EB=A1=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/spot/infra/entity/OpeningHourEntity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java b/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java index ced940d..6f143da 100644 --- a/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java +++ b/src/main/java/com/acon/server/spot/infra/entity/OpeningHourEntity.java @@ -20,8 +20,10 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "opening_hour", indexes = @Index( - name = "idx_spot_id", columnList = "spot_id" + name = "opening_hour", + indexes = @Index( + name = "idx_opening_hour_spot_id_day_of_week", + columnList = "spot_id, day_of_week" ) ) public class OpeningHourEntity { From d9c81b2d349c67da7eeca1a11ca6b6fc57675952 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Sun, 19 Jan 2025 20:57:18 +0900 Subject: [PATCH 43/55] =?UTF-8?q?fix:=20OpeningHour=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/spot/domain/entity/OpeningHour.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java b/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java index 9da6a1d..a8f16b7 100644 --- a/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java +++ b/src/main/java/com/acon/server/spot/domain/entity/OpeningHour.java @@ -1,7 +1,7 @@ package com.acon.server.spot.domain.entity; import java.time.DayOfWeek; -import java.time.LocalDateTime; +import java.time.LocalTime; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -14,16 +14,16 @@ public class OpeningHour { private final Long spotId; private DayOfWeek dayOfWeek; - private LocalDateTime startTime; - private LocalDateTime endTime; + private LocalTime startTime; + private LocalTime endTime; @Builder public OpeningHour( Long id, Long spotId, DayOfWeek dayOfWeek, - LocalDateTime startTime, - LocalDateTime endTime + LocalTime startTime, + LocalTime endTime ) { this.id = id; this.spotId = spotId; From f40d4f1d050d69302cb6e74de82ade14ca117164 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 09:06:03 +0900 Subject: [PATCH 44/55] =?UTF-8?q?chore:=20Naver=20Maps=20External=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{NaverMapsService.java => NaverMapsAdapter.java} | 3 ++- .../acon/server/spot/application/service/SpotService.java | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) rename src/main/java/com/acon/server/global/external/{NaverMapsService.java => NaverMapsAdapter.java} (93%) diff --git a/src/main/java/com/acon/server/global/external/NaverMapsService.java b/src/main/java/com/acon/server/global/external/NaverMapsAdapter.java similarity index 93% rename from src/main/java/com/acon/server/global/external/NaverMapsService.java rename to src/main/java/com/acon/server/global/external/NaverMapsAdapter.java index 2a366b8..967d24d 100644 --- a/src/main/java/com/acon/server/global/external/NaverMapsService.java +++ b/src/main/java/com/acon/server/global/external/NaverMapsAdapter.java @@ -10,11 +10,12 @@ @Service @RequiredArgsConstructor -public class NaverMapsService { +public class NaverMapsAdapter { private final NaverMapsClient naverMapsClient; public GeoCodingResponse getGeoCodingResult(String address) { + // TODO: try-catch로 감싸기 return Optional.ofNullable(naverMapsClient.getGeoCode(address)) .map(response -> (List>) response.get("addresses")) .filter(addresses -> !addresses.isEmpty()) diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 2eb9047..b4e19fb 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -3,7 +3,7 @@ import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; import com.acon.server.global.external.GeoCodingResponse; -import com.acon.server.global.external.NaverMapsService; +import com.acon.server.global.external.NaverMapsAdapter; import com.acon.server.spot.api.response.MenuDetailResponse; import com.acon.server.spot.api.response.MenuListResponse; import com.acon.server.spot.api.response.MenuResponse; @@ -37,7 +37,7 @@ public class SpotService { private final SpotMapper spotMapper; private final SpotDtoMapper spotDtoMapper; - private final NaverMapsService naverMapsService; + private final NaverMapsAdapter naverMapsAdapter; public MenuDetailResponse fetchSpotDetail(Long spotId) { SpotEntity spotEntity = spotRepository.findByIdOrThrow(spotId); @@ -56,8 +56,8 @@ public MenuDetailResponse fetchSpotDetail(Long spotId) { return spotDtoMapper.toMenuDetailResponse(spotEntity, imageList, isSpotOpen(spotId)); } - private void updateSpotCoordinates(Spot spot) { - GeoCodingResponse geoCodingResponse = naverMapsService.getGeoCodingResult(spot.getAddress()); + private void updateSpotCoordinates(final Spot spot) { + GeoCodingResponse geoCodingResponse = naverMapsAdapter.getGeoCodingResult(spot.getAddress()); spot.updateCoordinates( Double.parseDouble(geoCodingResponse.latitude()), From cb7a4cf5d8cff71b30d74da158766a3ad0d4a1f6 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 09:07:15 +0900 Subject: [PATCH 45/55] =?UTF-8?q?fix:=20=EB=B9=84=EC=A6=88=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=EC=97=90=20=ED=8A=B8=EB=9E=9C?= =?UTF-8?q?=EC=9E=AD=EC=85=98=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/member/application/service/MemberService.java | 2 ++ .../acon/server/spot/application/service/SpotService.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 04091f0..bc20d0f 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -7,6 +7,7 @@ import com.acon.server.spot.infra.repository.SpotRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -15,6 +16,7 @@ public class MemberService { private final RecentGuidedSpotRepository recentGuidedSpotRepository; private final SpotRepository spotRepository; + @Transactional public void createGuidedSpot(final Long spotId, final Long memberId) { if (spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index b4e19fb..8b42815 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -24,6 +24,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -39,7 +40,9 @@ public class SpotService { private final NaverMapsAdapter naverMapsAdapter; - public MenuDetailResponse fetchSpotDetail(Long spotId) { + // TODO: 트랜잭션 범위 고민하기 + @Transactional + public MenuDetailResponse fetchSpotDetail(final Long spotId) { SpotEntity spotEntity = spotRepository.findByIdOrThrow(spotId); Spot spot = spotMapper.toDomain(spotEntity); @@ -105,7 +108,7 @@ private boolean isBeforeMidnight(LocalTime currentTime, OpeningHourEntity openin return currentTime.isAfter(startTime) && currentTime.isBefore(endTime); } - public MenuListResponse fetchMenus(Long spotId) { + @Transactional(readOnly = true) if (spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } From 2804c6726bebc6705ec1c6666b0884d30a988064 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 09:07:46 +0900 Subject: [PATCH 46/55] =?UTF-8?q?fix:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20final=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/spot/api/controller/SpotController.java | 4 ++-- .../server/spot/application/service/SpotService.java | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/acon/server/spot/api/controller/SpotController.java b/src/main/java/com/acon/server/spot/api/controller/SpotController.java index bfec1a8..a06241b 100644 --- a/src/main/java/com/acon/server/spot/api/controller/SpotController.java +++ b/src/main/java/com/acon/server/spot/api/controller/SpotController.java @@ -22,7 +22,7 @@ public class SpotController { @GetMapping("/spot/{spotId}") public ResponseEntity getSpotDetail( @Positive(message = "spotId는 양수여야 합니다.") - @Validated @PathVariable(name = "spotId") Long spotId + @Validated @PathVariable(name = "spotId") final Long spotId ) { return ResponseEntity.ok( spotService.fetchSpotDetail(spotId) @@ -32,7 +32,7 @@ public ResponseEntity getSpotDetail( @GetMapping("/spot/{spotId}/menus") public ResponseEntity getMenus( @Positive(message = "spotId는 양수여야 합니다.") - @Validated @PathVariable(name = "spotId") Long spotId + @Validated @PathVariable(name = "spotId") final Long spotId ) { return ResponseEntity.ok( spotService.fetchMenus(spotId) diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 8b42815..819a535 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -68,7 +68,7 @@ private void updateSpotCoordinates(final Spot spot) { ); } - private boolean isSpotOpen(Long spotId) { + private boolean isSpotOpen(final Long spotId) { LocalDateTime now = LocalDateTime.now(); DayOfWeek today = now.getDayOfWeek(); DayOfWeek yesterday = today.minus(1); @@ -90,25 +90,26 @@ private boolean isSpotOpen(Long spotId) { .anyMatch(openingHour -> isBeforeMidnight(currentTime, openingHour)); } - private boolean isAfterMidnight(LocalTime currentTime, OpeningHourEntity openingHour) { + private boolean isAfterMidnight(final LocalTime currentTime, final OpeningHourEntity openingHour) { LocalTime startTime = openingHour.getStartTime(); LocalTime endTime = openingHour.getEndTime(); return endTime.isBefore(startTime) && currentTime.isAfter(LocalTime.MIDNIGHT) && currentTime.isBefore(endTime); } - private boolean isBeforeMidnight(LocalTime currentTime, OpeningHourEntity openingHour) { + private boolean isBeforeMidnight(final LocalTime currentTime, final OpeningHourEntity openingHour) { LocalTime startTime = openingHour.getStartTime(); LocalTime endTime = openingHour.getEndTime(); if (endTime.isBefore(startTime)) { return currentTime.isAfter(startTime) && currentTime.isBefore(LocalTime.MIDNIGHT); } - + return currentTime.isAfter(startTime) && currentTime.isBefore(endTime); } @Transactional(readOnly = true) + public MenuListResponse fetchMenus(final Long spotId) { if (spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } From a5f8c48036549f10744a12ea43b41bfe993a736d Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 10:52:59 +0900 Subject: [PATCH 47/55] =?UTF-8?q?feat:=20=EC=9C=84=EC=B9=98=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EA=B0=80=20=EC=97=86=EB=8A=94=20Spot=EB=93=A4?= =?UTF-8?q?=EC=9D=98=20=EC=A2=8C=ED=91=9C=EB=A5=BC=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/application/service/SpotService.java | 51 +++++++++++++++---- .../acon/server/spot/domain/entity/Spot.java | 2 +- .../spot/infra/repository/SpotRepository.java | 3 ++ 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 819a535..44b31c4 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -23,11 +23,13 @@ import java.time.LocalTime; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor +@Slf4j public class SpotService { private final SpotRepository spotRepository; @@ -40,7 +42,42 @@ public class SpotService { private final NaverMapsAdapter naverMapsAdapter; + // 메서드 설명: 위도, 경도 정보가 없는 Spot들의 좌표를 업데이트한다. + @Transactional + public void updateNullCoordinatesForSpots() { + List spotEntityList = spotRepository.findAllByLatitudeIsNullOrLongitudeIsNull(); + + if (spotEntityList.isEmpty()) { + log.info("위도 또는 경도 정보가 비어 있는 Spot 데이터가 없습니다."); + return; + } + + log.info("위도 또는 경도 정보가 비어 있는 Spot 데이터를 {}건 찾았습니다.", spotEntityList.size()); + + List updatedEntities = spotEntityList.stream() + .map(spotEntity -> { + Spot spot = spotMapper.toDomain(spotEntity); + updateSpotCoordinate(spot); + return spotMapper.toEntity(spot); + }) + .toList(); + spotRepository.saveAll(updatedEntities); + + log.info("위도 또는 경도 정보가 비어 있는 Spot 데이터 {}건을 업데이트 했습니다.", updatedEntities.size()); + } + + // 메서드 설명: spotId에 해당하는 Spot의 좌표를 업데이트한다. (주소 -> 좌표) + private void updateSpotCoordinate(final Spot spot) { + GeoCodingResponse geoCodingResponse = naverMapsAdapter.getGeoCodingResult(spot.getAddress()); + + spot.updateCoordinate( + Double.parseDouble(geoCodingResponse.latitude()), + Double.parseDouble(geoCodingResponse.longitude()) + ); + } + // TODO: 트랜잭션 범위 고민하기 + // 메서드 설명: spotId에 해당하는 Spot의 상세 정보를 조회한다. (메뉴, 이미지, 영업 여부 등) @Transactional public MenuDetailResponse fetchSpotDetail(final Long spotId) { SpotEntity spotEntity = spotRepository.findByIdOrThrow(spotId); @@ -52,22 +89,14 @@ public MenuDetailResponse fetchSpotDetail(final Long spotId) { .toList(); if (spot.getLatitude() == null || spot.getLongitude() == null) { - updateSpotCoordinates(spot); + updateSpotCoordinate(spot); spotEntity = spotRepository.save(spotMapper.toEntity(spot)); } return spotDtoMapper.toMenuDetailResponse(spotEntity, imageList, isSpotOpen(spotId)); } - private void updateSpotCoordinates(final Spot spot) { - GeoCodingResponse geoCodingResponse = naverMapsAdapter.getGeoCodingResult(spot.getAddress()); - - spot.updateCoordinates( - Double.parseDouble(geoCodingResponse.latitude()), - Double.parseDouble(geoCodingResponse.longitude()) - ); - } - + // 메서드 설명: spotId에 해당하는 Spot이 현재 영업 중인지 확인한다. (영업 시간에 속하는지) private boolean isSpotOpen(final Long spotId) { LocalDateTime now = LocalDateTime.now(); DayOfWeek today = now.getDayOfWeek(); @@ -90,6 +119,7 @@ private boolean isSpotOpen(final Long spotId) { .anyMatch(openingHour -> isBeforeMidnight(currentTime, openingHour)); } + // 메서드 설명: currentTime이 영업 시간에 속하는지 확인한다. (자정이 지난 후) private boolean isAfterMidnight(final LocalTime currentTime, final OpeningHourEntity openingHour) { LocalTime startTime = openingHour.getStartTime(); LocalTime endTime = openingHour.getEndTime(); @@ -97,6 +127,7 @@ private boolean isAfterMidnight(final LocalTime currentTime, final OpeningHourEn return endTime.isBefore(startTime) && currentTime.isAfter(LocalTime.MIDNIGHT) && currentTime.isBefore(endTime); } + // 메서드 설명: currentTime이 영업 시간에 속하는지 확인한다. (자정이 지나기 전) private boolean isBeforeMidnight(final LocalTime currentTime, final OpeningHourEntity openingHour) { LocalTime startTime = openingHour.getStartTime(); LocalTime endTime = openingHour.getEndTime(); diff --git a/src/main/java/com/acon/server/spot/domain/entity/Spot.java b/src/main/java/com/acon/server/spot/domain/entity/Spot.java index d665100..e26617a 100644 --- a/src/main/java/com/acon/server/spot/domain/entity/Spot.java +++ b/src/main/java/com/acon/server/spot/domain/entity/Spot.java @@ -50,7 +50,7 @@ public Spot( this.adminDong = adminDong; } - public void updateCoordinates(Double latitude, Double longitude) { + public void updateCoordinate(Double latitude, Double longitude) { this.latitude = latitude; this.longitude = longitude; } diff --git a/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java b/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java index 422f6b1..9178ec4 100644 --- a/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java +++ b/src/main/java/com/acon/server/spot/infra/repository/SpotRepository.java @@ -3,6 +3,7 @@ import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; import com.acon.server.spot.infra.entity.SpotEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface SpotRepository extends JpaRepository { @@ -12,4 +13,6 @@ default SpotEntity findByIdOrThrow(Long id) { () -> new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR) ); } + + List findAllByLatitudeIsNullOrLongitudeIsNull(); } From 23bba2e98882c828963815cf06513816725b7a13 Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 10:55:48 +0900 Subject: [PATCH 48/55] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=EB=A6=AC?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=85=98=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C=20?= =?UTF-8?q?Spot=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/AppStartupConfig.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/acon/server/global/config/AppStartupConfig.java diff --git a/src/main/java/com/acon/server/global/config/AppStartupConfig.java b/src/main/java/com/acon/server/global/config/AppStartupConfig.java new file mode 100644 index 0000000..9c76102 --- /dev/null +++ b/src/main/java/com/acon/server/global/config/AppStartupConfig.java @@ -0,0 +1,25 @@ +package com.acon.server.global.config; + +import com.acon.server.spot.application.service.SpotService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +@Slf4j +public class AppStartupConfig { + + private final SpotService spotService; + + @Bean + public ApplicationRunner applicationRunner() { + return args -> { + log.info("애플리케이션 시작 시 Spot 데이터를 업데이트하는 작업을 시작합니다."); + spotService.updateNullCoordinatesForSpots(); + log.info("Spot 데이터 업데이트 작업이 완료되었습니다."); + }; + } +} From e111611643d04fe7f631caa8a1be4a9fd2f5745e Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 10:56:25 +0900 Subject: [PATCH 49/55] =?UTF-8?q?feat:=20=EB=A7=A4=20=EC=8B=9C=20=EC=A0=95?= =?UTF-8?q?=EA=B0=81=EC=97=90=20Spot=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EA=B5=AC=ED=98=84=20(#2?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SchedulingConfig.java | 10 ++++++++ .../global/scheduler/SpotScheduler.java | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/main/java/com/acon/server/global/config/SchedulingConfig.java create mode 100644 src/main/java/com/acon/server/global/scheduler/SpotScheduler.java diff --git a/src/main/java/com/acon/server/global/config/SchedulingConfig.java b/src/main/java/com/acon/server/global/config/SchedulingConfig.java new file mode 100644 index 0000000..64b0e62 --- /dev/null +++ b/src/main/java/com/acon/server/global/config/SchedulingConfig.java @@ -0,0 +1,10 @@ +package com.acon.server.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulingConfig { + +} diff --git a/src/main/java/com/acon/server/global/scheduler/SpotScheduler.java b/src/main/java/com/acon/server/global/scheduler/SpotScheduler.java new file mode 100644 index 0000000..7d9f67a --- /dev/null +++ b/src/main/java/com/acon/server/global/scheduler/SpotScheduler.java @@ -0,0 +1,23 @@ +package com.acon.server.global.scheduler; + +import com.acon.server.spot.application.service.SpotService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class SpotScheduler { + + private final SpotService spotService; + + // 매 시 정각에 실행 + @Scheduled(cron = "0 0 * * * *") + public void scheduleUpdateCoordinates() { + log.info("스케줄링 작업: 위도 또는 경도 정보가 비어 있는 Spot 데이터 업데이트 시작"); + spotService.updateNullCoordinatesForSpots(); + log.info("스케줄링 작업: Spot 데이터 업데이트 완료"); + } +} From d7464315fd71c4df5664970949739dfbefa3a5ed Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 10:57:59 +0900 Subject: [PATCH 50/55] =?UTF-8?q?chore:=20OpenFeign=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/{FeignConfig.java => OpenFeignConfig.java} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/main/java/com/acon/server/global/config/{FeignConfig.java => OpenFeignConfig.java} (73%) diff --git a/src/main/java/com/acon/server/global/config/FeignConfig.java b/src/main/java/com/acon/server/global/config/OpenFeignConfig.java similarity index 73% rename from src/main/java/com/acon/server/global/config/FeignConfig.java rename to src/main/java/com/acon/server/global/config/OpenFeignConfig.java index 8c6a122..b0ac74d 100644 --- a/src/main/java/com/acon/server/global/config/FeignConfig.java +++ b/src/main/java/com/acon/server/global/config/OpenFeignConfig.java @@ -6,6 +6,7 @@ @Configuration @EnableFeignClients(basePackageClasses = ServerApplication.class) -public class FeignConfig { +public class OpenFeignConfig { + // TODO: 타임아웃 설정 추가, 서킷 브레이커 적용하기 } From 60bc4aaf043a041b3c021fb259797dadb9688d5d Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 11:07:40 +0900 Subject: [PATCH 51/55] =?UTF-8?q?chore:=20=EC=97=90=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/global/exception/ErrorType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/acon/server/global/exception/ErrorType.java b/src/main/java/com/acon/server/global/exception/ErrorType.java index f374af9..79d6104 100644 --- a/src/main/java/com/acon/server/global/exception/ErrorType.java +++ b/src/main/java/com/acon/server/global/exception/ErrorType.java @@ -47,7 +47,7 @@ public enum ErrorType { INVALID_DAY_ERROR(HttpStatus.BAD_REQUEST, 40099, "유효하지 않은 day입니다."), /* 404 Not Found */ - NOT_FOUND_SPOT_ERROR(HttpStatus.NOT_FOUND, 40402, "유효한 장소가 없습니다"), + NOT_FOUND_SPOT_ERROR(HttpStatus.NOT_FOUND, 40402, "존재하지 않는 장소입니다."), /* 500 Internal Server Error */ NAVER_MAPS_GEOCODING_API_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50003, "Naver Maps GeoCoding API 호출에 실패했습니다."), From c3f64f25ac0f37a5efec412b8d3db7c5f57150fa Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Mon, 20 Jan 2025 11:07:53 +0900 Subject: [PATCH 52/55] =?UTF-8?q?chore:=20TODO=20=EC=B6=94=EA=B0=80=20(#24?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/acon/server/spot/infra/entity/SpotEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/acon/server/spot/infra/entity/SpotEntity.java b/src/main/java/com/acon/server/spot/infra/entity/SpotEntity.java index 7368a06..2da0686 100644 --- a/src/main/java/com/acon/server/spot/infra/entity/SpotEntity.java +++ b/src/main/java/com/acon/server/spot/infra/entity/SpotEntity.java @@ -74,6 +74,7 @@ public SpotEntity( this.id = id; this.name = name; this.spotType = spotType; + // TODO: 영속성 엔티티에서 기본값을 설정하는 로직을 도메인 엔티티로 이동 this.localAcornCount = localAcornCount != null ? localAcornCount : 0; this.localAcornUpdatedAt = localAcornUpdatedAt; this.basicAcornCount = basicAcornCount != null ? basicAcornCount : 0; From 918a105097fcad33326aba465a47baded45b2b94 Mon Sep 17 00:00:00 2001 From: gahyuun Date: Mon, 20 Jan 2025 17:44:54 +0900 Subject: [PATCH 53/55] =?UTF-8?q?hotfix:=20prefix=20message=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/prepare-commit-msg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare-commit-msg b/scripts/prepare-commit-msg index 6779557..436c05b 100644 --- a/scripts/prepare-commit-msg +++ b/scripts/prepare-commit-msg @@ -7,7 +7,7 @@ CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) ISSUE_NUMBER=$(echo "$CURRENT_BRANCH_NAME" | grep -o '/#.*' | sed 's/\///') -if [[ "$DEFAULT_COMMIT_MSG" =~ ^[Mm]erge ]]; then +if [[ "$DEFAULT_COMMIT_MSG" =~ ^[Mm]erge || "$DEFAULT_COMMIT_MSG" =~ ^[Hh]otfix ]]; then SUFFIX="" else SUFFIX="($ISSUE_NUMBER)" From ae081dc8a348031a03f150646c77e3d7b9030d9a Mon Sep 17 00:00:00 2001 From: gahyuun Date: Mon, 20 Jan 2025 17:51:19 +0900 Subject: [PATCH 54/55] =?UTF-8?q?hotfix:=20=EC=9E=A5=EC=86=8C=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=20=EC=97=AC=EB=B6=80=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acon/server/member/application/service/MemberService.java | 2 +- .../com/acon/server/spot/application/service/SpotService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index 04091f0..7937bb1 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -16,7 +16,7 @@ public class MemberService { private final SpotRepository spotRepository; public void createGuidedSpot(final Long spotId, final Long memberId) { - if (spotRepository.existsById(spotId)) { + if (!spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 597efe3..42f5c72 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -19,7 +19,7 @@ public class SpotService { private final MenuRepository menuRepository; public MenuListResponse fetchMenus(Long spotId) { - if (spotRepository.existsById(spotId)) { + if (!spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } List menuEntityList = menuRepository.findAllBySpotId(spotId); From ced31a474e6b244e55ae3ea28722c6760ce3dcdd Mon Sep 17 00:00:00 2001 From: ckkim817 Date: Tue, 21 Jan 2025 00:28:57 +0900 Subject: [PATCH 55/55] =?UTF-8?q?fix:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/GuidedSpotMapper.java | 16 ++++++++++++++ .../mapper/RecentGuidedSpotMapper.java | 16 -------------- .../mapper/RecentViewedSpotMapper.java | 16 -------------- .../application/mapper/ViewedSpotMapper.java | 16 ++++++++++++++ .../application/service/MemberService.java | 10 ++++----- ...{RecentGuidedSpot.java => GuidedSpot.java} | 14 +++++++++--- .../server/member/domain/entity/Member.java | 9 +++++++- ...{RecentViewedSpot.java => ViewedSpot.java} | 14 +++++++++--- ...dSpotEntity.java => GuidedSpotEntity.java} | 8 +++---- ...dSpotEntity.java => ViewedSpotEntity.java} | 8 +++---- .../repository/GuidedSpotRepository.java | 8 +++++++ .../RecentGuidedSpotRepository.java | 8 ------- .../server/review/domain/entity/Review.java | 10 ++++++++- .../spot/api/controller/SpotController.java | 8 ++++--- ...lResponse.java => SpotDetailResponse.java} | 2 +- .../application/mapper/SpotDtoMapper.java | 4 ++-- .../spot/application/service/SpotService.java | 22 ++++++++++--------- .../acon/server/spot/domain/entity/Spot.java | 8 ++++++- 18 files changed, 119 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/acon/server/member/application/mapper/GuidedSpotMapper.java delete mode 100644 src/main/java/com/acon/server/member/application/mapper/RecentGuidedSpotMapper.java delete mode 100644 src/main/java/com/acon/server/member/application/mapper/RecentViewedSpotMapper.java create mode 100644 src/main/java/com/acon/server/member/application/mapper/ViewedSpotMapper.java rename src/main/java/com/acon/server/member/domain/entity/{RecentGuidedSpot.java => GuidedSpot.java} (54%) rename src/main/java/com/acon/server/member/domain/entity/{RecentViewedSpot.java => ViewedSpot.java} (54%) rename src/main/java/com/acon/server/member/infra/entity/{RecentGuidedSpotEntity.java => GuidedSpotEntity.java} (84%) rename src/main/java/com/acon/server/member/infra/entity/{RecentViewedSpotEntity.java => ViewedSpotEntity.java} (84%) create mode 100644 src/main/java/com/acon/server/member/infra/repository/GuidedSpotRepository.java delete mode 100644 src/main/java/com/acon/server/member/infra/repository/RecentGuidedSpotRepository.java rename src/main/java/com/acon/server/spot/api/response/{MenuDetailResponse.java => SpotDetailResponse.java} (94%) diff --git a/src/main/java/com/acon/server/member/application/mapper/GuidedSpotMapper.java b/src/main/java/com/acon/server/member/application/mapper/GuidedSpotMapper.java new file mode 100644 index 0000000..b9871a2 --- /dev/null +++ b/src/main/java/com/acon/server/member/application/mapper/GuidedSpotMapper.java @@ -0,0 +1,16 @@ +package com.acon.server.member.application.mapper; + +import com.acon.server.member.domain.entity.GuidedSpot; +import com.acon.server.member.infra.entity.GuidedSpotEntity; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface GuidedSpotMapper { + + // GuidedSpotEntity -> GuidedSpot + GuidedSpot toDomain(GuidedSpotEntity entity); + + // GuidedSpot -> GuidedSpotEntity + GuidedSpotEntity toEntity(GuidedSpot domain); +} diff --git a/src/main/java/com/acon/server/member/application/mapper/RecentGuidedSpotMapper.java b/src/main/java/com/acon/server/member/application/mapper/RecentGuidedSpotMapper.java deleted file mode 100644 index 7dd5747..0000000 --- a/src/main/java/com/acon/server/member/application/mapper/RecentGuidedSpotMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.acon.server.member.application.mapper; - -import com.acon.server.member.domain.entity.RecentGuidedSpot; -import com.acon.server.member.infra.entity.RecentGuidedSpotEntity; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR) -public interface RecentGuidedSpotMapper { - - // RecentGuidedSpotEntity -> RecentGuidedSpot - RecentGuidedSpot toDomain(RecentGuidedSpotEntity entity); - - // RecentGuidedSpot -> RecentGuidedSpotEntity - RecentGuidedSpotEntity toEntity(RecentGuidedSpot domain); -} diff --git a/src/main/java/com/acon/server/member/application/mapper/RecentViewedSpotMapper.java b/src/main/java/com/acon/server/member/application/mapper/RecentViewedSpotMapper.java deleted file mode 100644 index c2de80c..0000000 --- a/src/main/java/com/acon/server/member/application/mapper/RecentViewedSpotMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.acon.server.member.application.mapper; - -import com.acon.server.member.domain.entity.RecentViewedSpot; -import com.acon.server.member.infra.entity.RecentViewedSpotEntity; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR) -public interface RecentViewedSpotMapper { - - // RecentViewedSpotEntity -> RecentViewedSpot - RecentViewedSpot toDomain(RecentViewedSpotEntity entity); - - // RecentViewedSpot -> RecentViewedSpotEntity - RecentViewedSpotEntity toEntity(RecentViewedSpot domain); -} diff --git a/src/main/java/com/acon/server/member/application/mapper/ViewedSpotMapper.java b/src/main/java/com/acon/server/member/application/mapper/ViewedSpotMapper.java new file mode 100644 index 0000000..a1ef149 --- /dev/null +++ b/src/main/java/com/acon/server/member/application/mapper/ViewedSpotMapper.java @@ -0,0 +1,16 @@ +package com.acon.server.member.application.mapper; + +import com.acon.server.member.domain.entity.ViewedSpot; +import com.acon.server.member.infra.entity.ViewedSpotEntity; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface ViewedSpotMapper { + + // ViewedSpotEntity -> ViewedSpot + ViewedSpot toDomain(ViewedSpotEntity entity); + + // ViewedSpot -> ViewedSpotEntity + ViewedSpotEntity toEntity(ViewedSpot domain); +} diff --git a/src/main/java/com/acon/server/member/application/service/MemberService.java b/src/main/java/com/acon/server/member/application/service/MemberService.java index bc20d0f..0c6132d 100644 --- a/src/main/java/com/acon/server/member/application/service/MemberService.java +++ b/src/main/java/com/acon/server/member/application/service/MemberService.java @@ -2,8 +2,8 @@ import com.acon.server.global.exception.BusinessException; import com.acon.server.global.exception.ErrorType; -import com.acon.server.member.infra.entity.RecentGuidedSpotEntity; -import com.acon.server.member.infra.repository.RecentGuidedSpotRepository; +import com.acon.server.member.infra.entity.GuidedSpotEntity; +import com.acon.server.member.infra.repository.GuidedSpotRepository; import com.acon.server.spot.infra.repository.SpotRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -13,7 +13,7 @@ @RequiredArgsConstructor public class MemberService { - private final RecentGuidedSpotRepository recentGuidedSpotRepository; + private final GuidedSpotRepository guidedSpotRepository; private final SpotRepository spotRepository; @Transactional @@ -22,10 +22,10 @@ public void createGuidedSpot(final Long spotId, final Long memberId) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } - RecentGuidedSpotEntity recentGuidedSpotEntity = RecentGuidedSpotEntity.builder() + GuidedSpotEntity guidedSpotEntity = GuidedSpotEntity.builder() .memberId(memberId) .spotId(spotId) .build(); - recentGuidedSpotRepository.save(recentGuidedSpotEntity); + guidedSpotRepository.save(guidedSpotEntity); } } diff --git a/src/main/java/com/acon/server/member/domain/entity/RecentGuidedSpot.java b/src/main/java/com/acon/server/member/domain/entity/GuidedSpot.java similarity index 54% rename from src/main/java/com/acon/server/member/domain/entity/RecentGuidedSpot.java rename to src/main/java/com/acon/server/member/domain/entity/GuidedSpot.java index 2de9db1..7b5acd4 100644 --- a/src/main/java/com/acon/server/member/domain/entity/RecentGuidedSpot.java +++ b/src/main/java/com/acon/server/member/domain/entity/GuidedSpot.java @@ -1,25 +1,33 @@ package com.acon.server.member.domain.entity; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.ToString; @Getter @ToString -public class RecentGuidedSpot { +public class GuidedSpot { private final Long id; private final Long memberId; private final Long spotId; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + @Builder - public RecentGuidedSpot( + public GuidedSpot( Long id, Long memberId, - Long spotId + Long spotId, + LocalDateTime createdAt, + LocalDateTime updatedAt ) { this.id = id; this.memberId = memberId; this.spotId = spotId; + this.createdAt = createdAt; + this.updatedAt = updatedAt; } } diff --git a/src/main/java/com/acon/server/member/domain/entity/Member.java b/src/main/java/com/acon/server/member/domain/entity/Member.java index 89486b9..fa77e76 100644 --- a/src/main/java/com/acon/server/member/domain/entity/Member.java +++ b/src/main/java/com/acon/server/member/domain/entity/Member.java @@ -2,6 +2,7 @@ import com.acon.server.member.domain.enums.SocialType; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -21,6 +22,8 @@ public class Member { private LocalDate nicknameUpdatedAt; private LocalDate birthDate; private int leftAcornCount; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; @Builder public Member( @@ -33,7 +36,9 @@ public Member( String nickname, LocalDate nicknameUpdatedAt, LocalDate birthDate, - int leftAcornCount + int leftAcornCount, + LocalDateTime createdAt, + LocalDateTime updatedAt ) { this.id = id; this.socialType = socialType; @@ -45,5 +50,7 @@ public Member( this.nicknameUpdatedAt = nicknameUpdatedAt; this.birthDate = birthDate; this.leftAcornCount = leftAcornCount; + this.createdAt = createdAt; + this.updatedAt = updatedAt; } } diff --git a/src/main/java/com/acon/server/member/domain/entity/RecentViewedSpot.java b/src/main/java/com/acon/server/member/domain/entity/ViewedSpot.java similarity index 54% rename from src/main/java/com/acon/server/member/domain/entity/RecentViewedSpot.java rename to src/main/java/com/acon/server/member/domain/entity/ViewedSpot.java index 6af54b7..202f7fc 100644 --- a/src/main/java/com/acon/server/member/domain/entity/RecentViewedSpot.java +++ b/src/main/java/com/acon/server/member/domain/entity/ViewedSpot.java @@ -1,25 +1,33 @@ package com.acon.server.member.domain.entity; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.ToString; @Getter @ToString -public class RecentViewedSpot { +public class ViewedSpot { private final Long id; private final Long memberId; private final Long spotId; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + @Builder - public RecentViewedSpot( + public ViewedSpot( Long id, Long memberId, - Long spotId + Long spotId, + LocalDateTime createdAt, + LocalDateTime updatedAt ) { this.id = id; this.memberId = memberId; this.spotId = spotId; + this.createdAt = createdAt; + this.updatedAt = updatedAt; } } diff --git a/src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java b/src/main/java/com/acon/server/member/infra/entity/GuidedSpotEntity.java similarity index 84% rename from src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java rename to src/main/java/com/acon/server/member/infra/entity/GuidedSpotEntity.java index 2b894d6..cb89eec 100644 --- a/src/main/java/com/acon/server/member/infra/entity/RecentGuidedSpotEntity.java +++ b/src/main/java/com/acon/server/member/infra/entity/GuidedSpotEntity.java @@ -17,13 +17,13 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "recent_guided_spot", + name = "guided_spot", uniqueConstraints = @UniqueConstraint( - name = "unique_recent_guided_spot_member_id_spot_id", + name = "unique_guided_spot_member_id_spot_id", columnNames = {"member_id", "spot_id"} ) ) -public class RecentGuidedSpotEntity extends BaseTimeEntity { +public class GuidedSpotEntity extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -36,7 +36,7 @@ public class RecentGuidedSpotEntity extends BaseTimeEntity { private Long spotId; @Builder - public RecentGuidedSpotEntity( + public GuidedSpotEntity( Long id, Long memberId, Long spotId diff --git a/src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java b/src/main/java/com/acon/server/member/infra/entity/ViewedSpotEntity.java similarity index 84% rename from src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java rename to src/main/java/com/acon/server/member/infra/entity/ViewedSpotEntity.java index 7cbbe43..01eef91 100644 --- a/src/main/java/com/acon/server/member/infra/entity/RecentViewedSpotEntity.java +++ b/src/main/java/com/acon/server/member/infra/entity/ViewedSpotEntity.java @@ -17,13 +17,13 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table( - name = "recent_viewed_spot", + name = "viewed_spot", uniqueConstraints = @UniqueConstraint( - name = "unique_recent_viewed_spot_member_id_spot_id", + name = "unique_viewed_spot_member_id_spot_id", columnNames = {"member_id", "spot_id"} ) ) -public class RecentViewedSpotEntity extends BaseTimeEntity { +public class ViewedSpotEntity extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -36,7 +36,7 @@ public class RecentViewedSpotEntity extends BaseTimeEntity { private Long spotId; @Builder - public RecentViewedSpotEntity( + public ViewedSpotEntity( Long id, Long memberId, Long spotId diff --git a/src/main/java/com/acon/server/member/infra/repository/GuidedSpotRepository.java b/src/main/java/com/acon/server/member/infra/repository/GuidedSpotRepository.java new file mode 100644 index 0000000..ba5eb46 --- /dev/null +++ b/src/main/java/com/acon/server/member/infra/repository/GuidedSpotRepository.java @@ -0,0 +1,8 @@ +package com.acon.server.member.infra.repository; + +import com.acon.server.member.infra.entity.GuidedSpotEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GuidedSpotRepository extends JpaRepository { + +} diff --git a/src/main/java/com/acon/server/member/infra/repository/RecentGuidedSpotRepository.java b/src/main/java/com/acon/server/member/infra/repository/RecentGuidedSpotRepository.java deleted file mode 100644 index 7cd8272..0000000 --- a/src/main/java/com/acon/server/member/infra/repository/RecentGuidedSpotRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.acon.server.member.infra.repository; - -import com.acon.server.member.infra.entity.RecentGuidedSpotEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RecentGuidedSpotRepository extends JpaRepository { - -} diff --git a/src/main/java/com/acon/server/review/domain/entity/Review.java b/src/main/java/com/acon/server/review/domain/entity/Review.java index 36597cb..3f70496 100644 --- a/src/main/java/com/acon/server/review/domain/entity/Review.java +++ b/src/main/java/com/acon/server/review/domain/entity/Review.java @@ -1,5 +1,6 @@ package com.acon.server.review.domain.entity; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -14,18 +15,25 @@ public class Review { private final int acornCount; private final boolean localAcorn; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + @Builder public Review( Long id, Long spotId, Long memberId, int acornCount, - boolean localAcorn + boolean localAcorn, + LocalDateTime createdAt, + LocalDateTime updatedAt ) { this.id = id; this.spotId = spotId; this.memberId = memberId; this.acornCount = acornCount; this.localAcorn = localAcorn; + this.createdAt = createdAt; + this.updatedAt = updatedAt; } } diff --git a/src/main/java/com/acon/server/spot/api/controller/SpotController.java b/src/main/java/com/acon/server/spot/api/controller/SpotController.java index a06241b..be692e5 100644 --- a/src/main/java/com/acon/server/spot/api/controller/SpotController.java +++ b/src/main/java/com/acon/server/spot/api/controller/SpotController.java @@ -1,9 +1,11 @@ package com.acon.server.spot.api.controller; -import com.acon.server.spot.api.response.MenuDetailResponse; +import com.acon.server.spot.api.response.MenuResponse; import com.acon.server.spot.api.response.MenuListResponse; +import com.acon.server.spot.api.response.SpotDetailResponse; import com.acon.server.spot.application.service.SpotService; import jakarta.validation.constraints.Positive; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -20,7 +22,7 @@ public class SpotController { private final SpotService spotService; @GetMapping("/spot/{spotId}") - public ResponseEntity getSpotDetail( + public ResponseEntity getSpotDetail( @Positive(message = "spotId는 양수여야 합니다.") @Validated @PathVariable(name = "spotId") final Long spotId ) { @@ -30,7 +32,7 @@ public ResponseEntity getSpotDetail( } @GetMapping("/spot/{spotId}/menus") - public ResponseEntity getMenus( + public ResponseEntity> getMenus( @Positive(message = "spotId는 양수여야 합니다.") @Validated @PathVariable(name = "spotId") final Long spotId ) { diff --git a/src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java b/src/main/java/com/acon/server/spot/api/response/SpotDetailResponse.java similarity index 94% rename from src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java rename to src/main/java/com/acon/server/spot/api/response/SpotDetailResponse.java index fcbba78..8ccab8b 100644 --- a/src/main/java/com/acon/server/spot/api/response/MenuDetailResponse.java +++ b/src/main/java/com/acon/server/spot/api/response/SpotDetailResponse.java @@ -4,7 +4,7 @@ import java.util.List; import lombok.NonNull; -public record MenuDetailResponse( +public record SpotDetailResponse( @NonNull Long id, @NonNull diff --git a/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java b/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java index dca2c72..2daca85 100644 --- a/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java +++ b/src/main/java/com/acon/server/spot/application/mapper/SpotDtoMapper.java @@ -1,6 +1,6 @@ package com.acon.server.spot.application.mapper; -import com.acon.server.spot.api.response.MenuDetailResponse; +import com.acon.server.spot.api.response.SpotDetailResponse; import com.acon.server.spot.infra.entity.SpotEntity; import java.util.List; import org.mapstruct.Mapper; @@ -9,5 +9,5 @@ @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR) public interface SpotDtoMapper { - MenuDetailResponse toMenuDetailResponse(SpotEntity spotEntity, List imageList, boolean openStatus); + SpotDetailResponse toSpotDetailResponse(SpotEntity spotEntity, List imageList, boolean openStatus); } diff --git a/src/main/java/com/acon/server/spot/application/service/SpotService.java b/src/main/java/com/acon/server/spot/application/service/SpotService.java index 44b31c4..def551f 100644 --- a/src/main/java/com/acon/server/spot/application/service/SpotService.java +++ b/src/main/java/com/acon/server/spot/application/service/SpotService.java @@ -4,9 +4,9 @@ import com.acon.server.global.exception.ErrorType; import com.acon.server.global.external.GeoCodingResponse; import com.acon.server.global.external.NaverMapsAdapter; -import com.acon.server.spot.api.response.MenuDetailResponse; -import com.acon.server.spot.api.response.MenuListResponse; +import com.acon.server.member.infra.repository.GuidedSpotRepository; import com.acon.server.spot.api.response.MenuResponse; +import com.acon.server.spot.api.response.SpotDetailResponse; import com.acon.server.spot.application.mapper.SpotDtoMapper; import com.acon.server.spot.application.mapper.SpotMapper; import com.acon.server.spot.domain.entity.Spot; @@ -21,6 +21,7 @@ import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -32,13 +33,14 @@ @Slf4j public class SpotService { - private final SpotRepository spotRepository; + private final GuidedSpotRepository guidedSpotRepository; private final MenuRepository menuRepository; - private final SpotImageRepository spotImageRepository; private final OpeningHourRepository openingHourRepository; + private final SpotImageRepository spotImageRepository; + private final SpotRepository spotRepository; - private final SpotMapper spotMapper; private final SpotDtoMapper spotDtoMapper; + private final SpotMapper spotMapper; private final NaverMapsAdapter naverMapsAdapter; @@ -79,7 +81,7 @@ private void updateSpotCoordinate(final Spot spot) { // TODO: 트랜잭션 범위 고민하기 // 메서드 설명: spotId에 해당하는 Spot의 상세 정보를 조회한다. (메뉴, 이미지, 영업 여부 등) @Transactional - public MenuDetailResponse fetchSpotDetail(final Long spotId) { + public SpotDetailResponse fetchSpotDetail(final Long spotId) { SpotEntity spotEntity = spotRepository.findByIdOrThrow(spotId); Spot spot = spotMapper.toDomain(spotEntity); @@ -93,7 +95,7 @@ public MenuDetailResponse fetchSpotDetail(final Long spotId) { spotEntity = spotRepository.save(spotMapper.toEntity(spot)); } - return spotDtoMapper.toMenuDetailResponse(spotEntity, imageList, isSpotOpen(spotId)); + return spotDtoMapper.toSpotDetailResponse(spotEntity, imageList, isSpotOpen(spotId)); } // 메서드 설명: spotId에 해당하는 Spot이 현재 영업 중인지 확인한다. (영업 시간에 속하는지) @@ -140,14 +142,14 @@ private boolean isBeforeMidnight(final LocalTime currentTime, final OpeningHourE } @Transactional(readOnly = true) - public MenuListResponse fetchMenus(final Long spotId) { + public List fetchMenus(final Long spotId) { if (spotRepository.existsById(spotId)) { throw new BusinessException(ErrorType.NOT_FOUND_SPOT_ERROR); } List menuEntityList = menuRepository.findAllBySpotId(spotId); - List menuList = menuEntityList.stream() + return menuEntityList.stream() .map(menu -> MenuResponse.builder() .id(menu.getId()) .name(menu.getName()) @@ -155,7 +157,7 @@ public MenuListResponse fetchMenus(final Long spotId) { .image(menu.getImage()) .build()) .toList(); + } - return new MenuListResponse(menuList); } } diff --git a/src/main/java/com/acon/server/spot/domain/entity/Spot.java b/src/main/java/com/acon/server/spot/domain/entity/Spot.java index e26617a..89edc5e 100644 --- a/src/main/java/com/acon/server/spot/domain/entity/Spot.java +++ b/src/main/java/com/acon/server/spot/domain/entity/Spot.java @@ -22,6 +22,8 @@ public class Spot { private Double latitude; private Double longitude; private String adminDong; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; @Builder public Spot( @@ -35,7 +37,9 @@ public Spot( LocalDateTime basicAcornUpdatedAt, Double latitude, Double longitude, - String adminDong + String adminDong, + LocalDateTime createdAt, + LocalDateTime updatedAt ) { this.id = id; this.name = name; @@ -48,6 +52,8 @@ public Spot( this.latitude = latitude; this.longitude = longitude; this.adminDong = adminDong; + this.createdAt = createdAt; + this.updatedAt = updatedAt; } public void updateCoordinate(Double latitude, Double longitude) {