From bce7dbc252e4dfe10c505eb4f377d5d1828d15a6 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Mon, 8 Dec 2025 10:50:37 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B2=B4=ED=81=AC=20(nickname=20unique),?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=EC=BD=94=EB=93=9C=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserController.java | 15 ++++++++++----- .../ita/tinybite/domain/user/entity/User.java | 6 +++--- .../tinybite/domain/user/service/UserService.java | 6 ++++++ .../global/exception/errorcode/AuthErrorCode.java | 6 +++++- .../global/sms/controller/SmsAuthController.java | 10 +++++----- .../global/sms/service/SmsAuthService.java | 13 ++++++++----- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/main/java/ita/tinybite/domain/user/controller/UserController.java b/src/main/java/ita/tinybite/domain/user/controller/UserController.java index 5e5ca01..2b93a13 100644 --- a/src/main/java/ita/tinybite/domain/user/controller/UserController.java +++ b/src/main/java/ita/tinybite/domain/user/controller/UserController.java @@ -9,7 +9,7 @@ import static ita.tinybite.global.response.APIResponse.success; @RestController -@RequestMapping("/api/v1/user/me") +@RequestMapping("/api/v1/user") public class UserController { private final UserService userService; @@ -18,27 +18,32 @@ public UserController(UserService userService) { this.userService = userService; } - @GetMapping public APIResponse getUser() { return success(userService.getUser()); } - @PatchMapping + @PatchMapping("/me") public APIResponse updateUser(@Valid @RequestBody UpdateUserReqDto req) { userService.updateUser(req); return success(); } - @PatchMapping("/location") + @PatchMapping("/me/location") public APIResponse updateLocation(@RequestParam(defaultValue = "37.3623504988728") String latitude, @RequestParam(defaultValue = "127.117057453619") String longitude) { userService.updateLocation(latitude, longitude); return success(); } - @DeleteMapping + @DeleteMapping("/me") public APIResponse deleteUser() { userService.deleteUser(); return success(); } + + @GetMapping("/nickname/check") + public APIResponse validateNickname(@RequestParam String nickname) { + userService.validateNickname(nickname); + return success(); + } } diff --git a/src/main/java/ita/tinybite/domain/user/entity/User.java b/src/main/java/ita/tinybite/domain/user/entity/User.java index f817069..1791de6 100644 --- a/src/main/java/ita/tinybite/domain/user/entity/User.java +++ b/src/main/java/ita/tinybite/domain/user/entity/User.java @@ -25,6 +25,9 @@ public class User extends BaseEntity { @Column(nullable = false, length = 50, unique = true) private String email; + @Column(nullable = false, length = 30, unique = true) + private String nickname; + @Column(length = 50) private String phone; @@ -36,9 +39,6 @@ public class User extends BaseEntity { @Column(nullable = false) private UserStatus status; - @Column(nullable = false, length = 100) - private String nickname; - @Column(nullable = false, length = 100) private String location; diff --git a/src/main/java/ita/tinybite/domain/user/service/UserService.java b/src/main/java/ita/tinybite/domain/user/service/UserService.java index 5b04ee6..a71130e 100644 --- a/src/main/java/ita/tinybite/domain/user/service/UserService.java +++ b/src/main/java/ita/tinybite/domain/user/service/UserService.java @@ -5,6 +5,8 @@ import ita.tinybite.domain.user.dto.res.UserResDto; import ita.tinybite.domain.user.entity.User; import ita.tinybite.domain.user.repository.UserRepository; +import ita.tinybite.global.exception.BusinessException; +import ita.tinybite.global.exception.errorcode.AuthErrorCode; import ita.tinybite.global.location.LocationService; import org.springframework.stereotype.Service; @@ -43,4 +45,8 @@ public void deleteUser() { userRepository.delete(securityProvider.getCurrentUser()); } + public void validateNickname(String nickname) { + if(userRepository.existsByNickname(nickname)) + throw BusinessException.of(AuthErrorCode.DUPLICATED_NICKNAME); + } } diff --git a/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java b/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java index 5618914..00c7180 100644 --- a/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java +++ b/src/main/java/ita/tinybite/global/exception/errorcode/AuthErrorCode.java @@ -7,7 +7,11 @@ public enum AuthErrorCode implements ErrorCode { INVALID_PHONE_NUMBER(HttpStatus.BAD_REQUEST, "INVALID_PHONE_NUMBER", "유효하지 않은 번호입니다."), - INVALID_AUTHCODE(HttpStatus.UNAUTHORIZED, "INVALID_AUTHCODE", "인증코드가 만료되었거나, 일치하지 않습니다.") + INVALID_AUTHCODE(HttpStatus.UNAUTHORIZED, "INVALID_AUTHCODE", "인증코드가 일치하지 않습니다."), + EXPIRED_AUTH_CODE(HttpStatus.BAD_REQUEST, "EXPIRED_AUTH_CODE", "인증시간이 만료되었습니다."), + + DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "DUPLICATED_NICKNAME", "중복된 닉네임입니다."), + ; private final HttpStatus httpStatus; diff --git a/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java b/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java index 57d84c9..eba0fa5 100644 --- a/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java +++ b/src/main/java/ita/tinybite/global/sms/controller/SmsAuthController.java @@ -1,7 +1,5 @@ package ita.tinybite.global.sms.controller; -import ita.tinybite.global.exception.errorcode.AuthErrorCode; -import ita.tinybite.global.exception.errorcode.TaskErrorCode; import ita.tinybite.global.response.APIResponse; import ita.tinybite.global.sms.dto.req.CheckReqDto; import ita.tinybite.global.sms.dto.req.SendReqDto; @@ -11,6 +9,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static ita.tinybite.global.response.APIResponse.success; + @RestController @RequestMapping("/api/v1/auth/sms") public class SmsAuthController { @@ -24,12 +24,12 @@ public SmsAuthController(SmsAuthService smsAuthService) { @PostMapping("/send") public APIResponse send(@RequestBody SendReqDto req) { smsAuthService.send(req.phone()); - return APIResponse.success(); + return success(); } @PostMapping("/check") public APIResponse check(@RequestBody CheckReqDto req) { - if(smsAuthService.check(req)) return APIResponse.success(); - return APIResponse.businessError(AuthErrorCode.INVALID_AUTHCODE); + smsAuthService.check(req); + return success(); } } diff --git a/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java b/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java index 781ecc0..66daf10 100644 --- a/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java +++ b/src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java @@ -27,7 +27,7 @@ public SmsAuthService(SmsService smsService, RedisTemplate redis /** * 1. 인증코드 생성
* 2. 주어진 폰번호로 인증코드 전송
- * 3. DB에 {번호, 인증코드}쌍으로 저장 or 메모리에 저장 (ttl 설정 고려하기)
+ * 3. redis에 {번호, 인증코드}쌍으로 저장 (ttl 설정 고려)
*/ public void send(String phone) { validatePhoneNumber(phone); @@ -40,14 +40,17 @@ public void send(String phone) { /** * req.phone으로 redis 조회
* 조회한 authCode와 요청받은 authcode를 비교
- * 같으면 true, 다르면 false
+ * 조회된 authCode가 없을 시, 만료 혹은 요청 X
*/ - public boolean check(CheckReqDto req) { + public void check(CheckReqDto req) { validatePhoneNumber(req.phone()); String authCode = redisTemplate.opsForValue().get(req.phone()); - if(authCode == null) return false; - return authCode.equals(req.authCode()); + if(authCode == null) + throw BusinessException.of(AuthErrorCode.EXPIRED_AUTH_CODE); + + if(!authCode.equals(req.authCode())) + throw BusinessException.of(AuthErrorCode.INVALID_AUTHCODE); } private void validatePhoneNumber(String phone) { From 3573613a1d704efb7121ee8e089cb89d3daa3c03 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Mon, 8 Dec 2025 13:18:00 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix=20:=20user=5Fid=20IDENTITY=20strategy?= =?UTF-8?q?=20&=20dev=20redis=20host=20=EC=9D=B4=EB=A6=84=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(localhost=20->=20redis)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ita/tinybite/domain/user/entity/User.java | 2 +- src/main/resources/application-dev.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ita/tinybite/domain/user/entity/User.java b/src/main/java/ita/tinybite/domain/user/entity/User.java index 1791de6..8672f0c 100644 --- a/src/main/java/ita/tinybite/domain/user/entity/User.java +++ b/src/main/java/ita/tinybite/domain/user/entity/User.java @@ -18,7 +18,7 @@ public class User extends BaseEntity { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Comment("uid") private Long userId; diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 580242a..f656fb0 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -16,7 +16,7 @@ spring: data: redis: - host: localhost + host: redis port: 6379 kakao: From 08cfb63c68280e10b4fe1cce7de2f701c9c7c304 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Mon, 8 Dec 2025 13:26:01 +0900 Subject: [PATCH 3/4] =?UTF-8?q?test=EC=BD=94=EB=93=9C=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20&=20swagger=20url=20=EC=82=AD=EC=A0=9C=20&=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=A4=91=EB=B3=B5=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tinybite/global/config/SwaggerConfig.java | 1 - src/main/resources/application-dev.yaml | 7 +- src/main/resources/application-local.yaml | 5 - .../tinybite/TinyBiteApplicationTests.java | 15 --- .../domain/user/service/UserServiceTest.java | 97 +++++++++++++++++++ .../service/fake/FakeLocationService.java | 11 +++ .../service/fake/FakeSecurityProvider.java | 23 +++++ .../global/sms/SmsAuthServiceTest.java | 8 ++ src/test/resources/application-test.yaml | 15 +-- 9 files changed, 149 insertions(+), 33 deletions(-) delete mode 100644 src/test/java/ita/tinybite/TinyBiteApplicationTests.java create mode 100644 src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java create mode 100644 src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java create mode 100644 src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java diff --git a/src/main/java/ita/tinybite/global/config/SwaggerConfig.java b/src/main/java/ita/tinybite/global/config/SwaggerConfig.java index a120ca4..6c6fdc8 100644 --- a/src/main/java/ita/tinybite/global/config/SwaggerConfig.java +++ b/src/main/java/ita/tinybite/global/config/SwaggerConfig.java @@ -18,7 +18,6 @@ public class SwaggerConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() - .servers(List.of(new Server().url("https://growinserver.shop"))) .addSecurityItem(new SecurityRequirement().addList("BearerAuth")) .components( new Components() diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index f656fb0..8410dca 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -25,9 +25,4 @@ kakao: naver: client-id: ${NAVER_CLIENT_ID} - secret: ${NAVER_CLIENT_SECRET} - -jwt: - secret: ${JWT_SECRET} - access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY} - refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY} + secret: ${NAVER_CLIENT_SECRET} \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index f607a37..9de545a 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -33,11 +33,6 @@ naver: client-id: ${NAVER_CLIENT_ID} secret: ${NAVER_CLIENT_SECRET} -jwt: - secret: ${JWT_SECRET} - access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY} - refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY} - logging: level: org.hibernate.SQL: debug \ No newline at end of file diff --git a/src/test/java/ita/tinybite/TinyBiteApplicationTests.java b/src/test/java/ita/tinybite/TinyBiteApplicationTests.java deleted file mode 100644 index 4030bf9..0000000 --- a/src/test/java/ita/tinybite/TinyBiteApplicationTests.java +++ /dev/null @@ -1,15 +0,0 @@ -package ita.tinybite; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("test") -class TinyBiteApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java b/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java new file mode 100644 index 0000000..ae14b5b --- /dev/null +++ b/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java @@ -0,0 +1,97 @@ +package ita.tinybite.domain.user.service; + +import ita.tinybite.domain.user.constant.LoginType; +import ita.tinybite.domain.user.constant.UserStatus; +import ita.tinybite.domain.user.dto.req.UpdateUserReqDto; +import ita.tinybite.domain.user.dto.res.UserResDto; +import ita.tinybite.domain.user.entity.User; +import ita.tinybite.domain.user.repository.UserRepository; +import ita.tinybite.domain.user.service.fake.FakeLocationService; +import ita.tinybite.domain.user.service.fake.FakeSecurityProvider; +import ita.tinybite.global.exception.BusinessException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.assertj.core.api.Assertions.*; + +@DataJpaTest +class UserServiceTest { + + @Autowired + private UserRepository userRepository; + + // Fake 객체 + private FakeSecurityProvider securityProvider; + private FakeLocationService locationService; + + // 테스트 객체 + private UserService userService; + + @BeforeEach + void setUp() { + securityProvider = new FakeSecurityProvider(userRepository); + locationService = new FakeLocationService(); + userService = new UserService(securityProvider, userRepository, locationService); + + User user = User.builder() + .email("yyytir777@gmail.com") + .nickname("임원재") + .location("분당구 정자동") + .status(UserStatus.ACTIVE) + .type(LoginType.KAKAO) + .build(); + + userRepository.save(user); + securityProvider.setCurrentUser(user); + } + + @Test + void getUser() { + UserResDto user = userService.getUser(); + assertThat(user).isNotNull(); + } + + @Test + void updateUser() { + // given + UpdateUserReqDto req = new UpdateUserReqDto("updated_nickname"); + + // when + userService.updateUser(req); + + // then + assertThat(securityProvider.getCurrentUser().getNickname()).isEqualTo("updated_nickname"); + } + + @Test + void updateLocation() { + // given + String latitude = "12.123145"; String longitude = "123.123123"; + + // when + userService.updateLocation(latitude, longitude); + + // then + assertThat(securityProvider.getCurrentUser().getLocation()).isEqualTo(locationService.getLocation(latitude, longitude)); + } + + @Test + void deleteUser() { + // when + User currentUser = securityProvider.getCurrentUser(); + userService.deleteUser(); + + // then + assertThat(userRepository.findById(currentUser.getUserId())).isEmpty(); + } + + @Test + void validateNickname() { + assertThatThrownBy(() -> userService.validateNickname("임원재")) + .isInstanceOf(BusinessException.class); + + assertThatNoException().isThrownBy(() -> userService.validateNickname("임원재1")); + } +} \ No newline at end of file diff --git a/src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java b/src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java new file mode 100644 index 0000000..d959117 --- /dev/null +++ b/src/test/java/ita/tinybite/domain/user/service/fake/FakeLocationService.java @@ -0,0 +1,11 @@ +package ita.tinybite.domain.user.service.fake; + +import ita.tinybite.global.location.LocationService; + +public class FakeLocationService extends LocationService { + + @Override + public String getLocation(String latitude, String longitude) { + return latitude + " " + longitude; + } +} diff --git a/src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java b/src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java new file mode 100644 index 0000000..e368543 --- /dev/null +++ b/src/test/java/ita/tinybite/domain/user/service/fake/FakeSecurityProvider.java @@ -0,0 +1,23 @@ +package ita.tinybite.domain.user.service.fake; + +import ita.tinybite.domain.auth.service.SecurityProvider; +import ita.tinybite.domain.user.entity.User; +import ita.tinybite.domain.user.repository.UserRepository; + +public class FakeSecurityProvider extends SecurityProvider { + + private User currentUser; + + public FakeSecurityProvider(UserRepository userRepository) { + super(userRepository); + } + + public void setCurrentUser(User user) { + currentUser = user; + } + + @Override + public User getCurrentUser() { + return currentUser; + } +} diff --git a/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java b/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java index 407877a..eb0a4bc 100644 --- a/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java +++ b/src/test/java/ita/tinybite/global/sms/SmsAuthServiceTest.java @@ -1,10 +1,12 @@ package ita.tinybite.global.sms; import ita.tinybite.global.exception.BusinessException; +import ita.tinybite.global.sms.dto.req.CheckReqDto; import ita.tinybite.global.sms.fake.FakeRedisTemplate; import ita.tinybite.global.sms.fake.FakeSmsService; import ita.tinybite.global.sms.service.SmsAuthService; import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.Assertions.*; @@ -35,4 +37,10 @@ void should_fail_when_smsAuth_send_with_invalid_phone() { .isInstanceOf(BusinessException.class); } } + + @Test + void should_fail_when_smsAuth_send_with_expired_auth_code() { + assertThatThrownBy(() -> smsAuthService.check(new CheckReqDto(SUCCESS_PHONE_NUMBER, "123456"))) + .isInstanceOf(BusinessException.class); + } } diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 9ade865..58872b9 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -3,9 +3,12 @@ spring: activate: on-profile: "test" -# h2 사용하기? -# datasource: -# driver-class-name: com.mysql.cj.jdbc.Driver -# url: ${TEST_DB_URL} -# username: ${TEST_DB_USERNAME} -# password: ${TEST_DB_PASSWORD} \ No newline at end of file + profiles: + active: "test" + +# h2 사용 + datasource: + url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1 + driver-class-name: org.h2.Driver + username: sa + password: \ No newline at end of file From ac96fa0716e8d0d5fce810f09c1338da10959a41 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Tue, 9 Dec 2025 10:26:21 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix=20:=20=EC=97=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ita/tinybite/domain/user/controller/UserController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ita/tinybite/domain/user/controller/UserController.java b/src/main/java/ita/tinybite/domain/user/controller/UserController.java index 2b93a13..d1d72a6 100644 --- a/src/main/java/ita/tinybite/domain/user/controller/UserController.java +++ b/src/main/java/ita/tinybite/domain/user/controller/UserController.java @@ -18,6 +18,7 @@ public UserController(UserService userService) { this.userService = userService; } + @GetMapping("/me") public APIResponse getUser() { return success(userService.getUser()); }