Skip to content

Commit a7569db

Browse files
committed
✨feat: 병합 충돌 해결
2 parents c882a64 + 6983912 commit a7569db

File tree

233 files changed

+10065
-226
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

233 files changed

+10065
-226
lines changed

build.gradle

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dependencies {
3838
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
3939

4040
// 응답 통일
41-
implementation 'org.namul:api-payload:0.5.1'
41+
implementation 'org.namul:api-payload:0.6.0'
4242

4343
// validation
4444
implementation 'org.springframework.boot:spring-boot-starter-validation'
@@ -49,6 +49,12 @@ dependencies {
4949
// redis
5050
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
5151

52+
// WebClient
53+
implementation 'org.springframework.boot:spring-boot-starter-webflux'
54+
55+
// Netty
56+
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64'
57+
5258
// Email
5359
implementation 'org.springframework.boot:spring-boot-starter-mail'
5460
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
@@ -58,12 +64,23 @@ dependencies {
5864
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
5965
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
6066
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
67+
// OAuth2
68+
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
6169

6270
// test
6371
testImplementation 'org.springframework.boot:spring-boot-starter-test'
6472
testImplementation 'org.springframework.security:spring-security-test'
6573
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
6674
testImplementation 'com.h2database:h2'
75+
76+
// MongoDB
77+
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
78+
79+
// firebase
80+
implementation 'com.google.firebase:firebase-admin:9.1.0'
81+
82+
// jackson LocalDateTime 직렬화
83+
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
6784
}
6885

6986
tasks.named('test') {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
11
package org.withtime.be.withtimebe;
22

3+
import java.util.TimeZone;
4+
35
import org.springframework.boot.SpringApplication;
46
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.cache.annotation.EnableCaching;
8+
import org.springframework.context.annotation.ComponentScan;
9+
import org.springframework.context.annotation.FilterType;
510
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
11+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
12+
import org.springframework.scheduling.annotation.EnableScheduling;
13+
14+
import jakarta.annotation.PostConstruct;
15+
616

17+
@EnableCaching
18+
@EnableScheduling
719
@EnableJpaAuditing
20+
@EnableJpaRepositories(
21+
basePackages = {"org.withtime.be"},
22+
excludeFilters = @ComponentScan.Filter(
23+
type = FilterType.REGEX,
24+
pattern = "org\\.withtime\\.be\\.withtimebe\\.domain\\.log\\..*"
25+
))
826
@SpringBootApplication
927
public class WithTimeBeApplication {
1028

1129
public static void main(String[] args) {
1230
SpringApplication.run(WithTimeBeApplication.class, args);
1331
}
1432

33+
@PostConstruct
34+
public void init() { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); } // JVM 기본 TimeZone 설정
1535
}

src/main/java/org/withtime/be/withtimebe/domain/auth/controller/AuthController.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class AuthController {
2424
private final AuthCommandService authCommandService;
2525
private final EmailCommandService emailCommandService;
2626

27-
@Operation(summary = "회원가입 API by 요시", description = "최초 회원가입 시 필요한 정보를 포함하여 회원가입 진행")
27+
@Operation(summary = "회원가입 API by 요시", description = "최초 회원가입 시 필요한 정보를 포함하여 회원가입 진행, 소셜 로그인인 경우에만 socialId 포함하고 아닌 경우 제거하거나 null")
2828
@ApiResponses({
2929
@ApiResponse(responseCode = "204", description = "회원가입 성공"),
3030
@ApiResponse(
@@ -33,6 +33,13 @@ public class AuthController {
3333
다음과 같은 이유로 실패할 수 있습니다:
3434
- AUTH400_1: 이미 존재하는 이메일입니다.
3535
"""
36+
),
37+
@ApiResponse(
38+
responseCode = "404",
39+
description = """
40+
다음과 같은 이유로 실패할 수 있습니다:
41+
- SOCIAL404_1: 소셜을 찾을 수 없습니다.
42+
"""
3643
)
3744
})
3845
@PostMapping("/sign-up")
@@ -117,4 +124,36 @@ public DefaultResponse<String> checkVerificationCode(@Valid @RequestBody EmailRe
117124
emailCommandService.checkEmail(request);
118125
return DefaultResponse.noContent();
119126
}
127+
128+
@Operation(summary = "비밀번호 찾기 API", description = "이메일 인증 이후 이메일과 새로운 비밀번호로 비밀번호 변경")
129+
@ApiResponses({
130+
@ApiResponse(responseCode = "204", description = "비밀번호 변경 성공"),
131+
@ApiResponse(
132+
responseCode = "400",
133+
description = """
134+
다음과 같은 이유로 실패할 수 있습니다:
135+
- AUTH400_2: 소셜 로그인으로 가입된 사용자입니다.
136+
- MEMBER400_1: 이전 비밀번호와 동일합니다.
137+
"""
138+
),
139+
@ApiResponse(
140+
responseCode = "401",
141+
description = """
142+
다음과 같은 이유로 실패할 수 있습니다:
143+
- EMAIL401_2: 비밀번호 재설정에 이메일 인증을 하지 않았습니다.
144+
"""
145+
),
146+
@ApiResponse(
147+
responseCode = "404",
148+
description = """
149+
다음과 같은 이유로 실패할 수 있습니다:
150+
- MEMBER404_1: 사용자를 찾지 못했습니다.
151+
"""
152+
),
153+
})
154+
@PostMapping("/passwords")
155+
public DefaultResponse<Void> findPassword(@RequestBody AuthRequestDTO.FindPassword request) {
156+
authCommandService.findPassword(request);
157+
return DefaultResponse.noContent();
158+
}
120159
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.withtime.be.withtimebe.domain.auth.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import jakarta.servlet.http.HttpServletRequest;
9+
import jakarta.servlet.http.HttpServletResponse;
10+
import lombok.RequiredArgsConstructor;
11+
import org.namul.api.payload.response.DefaultResponse;
12+
import org.springframework.web.bind.annotation.*;
13+
import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO;
14+
import org.withtime.be.withtimebe.domain.auth.service.command.OAuth2CommandService;
15+
16+
@RestController
17+
@RequestMapping("/api/v1/oauth2")
18+
@RequiredArgsConstructor
19+
@Tag(name = "소셜 로그인 API")
20+
public class OAuth2Controller {
21+
22+
private final OAuth2CommandService oAuth2CommandService;
23+
24+
@Operation(summary = "소셜 로그인 API by 요시", description = "/oauth2/authorization/{provider}로 서버에 요청을 보낸 뒤 리다이렉트된 URI의 코드를 사용하여 요청, 리다이렉트되는 URI 의 Endpoint는 해당 API와 동일합니다.")
25+
@ApiResponses({
26+
@ApiResponse(responseCode = "200",
27+
description = """
28+
소셜 로그인 성공
29+
- isFirst: true 시 최초 회원가입 필요, 이메일은 인증된 상태로 1시간 유효
30+
- isFirst: false 시 최초 회원가입 필요 X, 로그인 처리
31+
"""
32+
),
33+
@ApiResponse(
34+
responseCode = "400",
35+
description = """
36+
다음과 같은 이유로 실패할 수 있습니다:
37+
- AUTH400_1: 지원하지 않는 소셜 로그인입니다. provider가 잘못되었거나 지원하지 않는 provider입니다.
38+
"""
39+
),
40+
@ApiResponse(
41+
responseCode = "500",
42+
description = """
43+
다음과 같은 이유로 실패할 수 있습니다:
44+
- AUTH500_1: 사용자 정보를 가져오는데 실패했습니다. 인가코드가 잘못되었거나 OAuth2 인증 서버나 리소스 서버에 보낸 요청이 실패했습니다.
45+
"""
46+
),
47+
48+
})
49+
@GetMapping("/callback/{provider}")
50+
public DefaultResponse<OAuth2ResponseDTO.Login> loginWithOAuth2(HttpServletRequest request, HttpServletResponse response,
51+
@Parameter(description = "소셜 로그인 플랫폼(대소문자 상관 없음), [kakao, google, naver]", example = "kakao") @PathVariable String provider,
52+
@RequestParam String code) {
53+
return DefaultResponse.ok(oAuth2CommandService.login(request, response, provider, code));
54+
}
55+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.withtime.be.withtimebe.domain.auth.converter;
2+
3+
import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO;
4+
import org.withtime.be.withtimebe.domain.auth.factory.support.dto.GoogleOAuth2ResponseDTO;
5+
import org.withtime.be.withtimebe.domain.auth.factory.support.dto.KakaoOAuth2ResponseDTO;
6+
import org.withtime.be.withtimebe.domain.auth.factory.support.dto.NaverOAuth2ResponseDTO;
7+
import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType;
8+
9+
public class OAuth2Converter {
10+
public static OAuth2ResponseDTO.Login toLogin(String email, boolean isFirst, Long socialId) {
11+
return OAuth2ResponseDTO.Login.builder()
12+
.email(email)
13+
.socialId(socialId)
14+
.isFirst(isFirst)
15+
.build();
16+
}
17+
18+
public static OAuth2ResponseDTO.GetUserInfo toGetUserInfo(KakaoOAuth2ResponseDTO.KakaoProfile kakaoProfile) {
19+
return OAuth2ResponseDTO.GetUserInfo.builder()
20+
.email(kakaoProfile.kakao_account().email())
21+
.providerId(String.valueOf(kakaoProfile.id()))
22+
.socialType(SocialType.KAKAO)
23+
.build();
24+
}
25+
26+
public static OAuth2ResponseDTO.GetUserInfo toGetUserInfo(NaverOAuth2ResponseDTO.UserInfo.UserInfoData naver) {
27+
return OAuth2ResponseDTO.GetUserInfo.builder()
28+
.email(naver.email())
29+
.providerId(naver.id())
30+
.socialType(SocialType.NAVER)
31+
.build();
32+
}
33+
34+
public static OAuth2ResponseDTO.GetUserInfo toGetUserInfo(GoogleOAuth2ResponseDTO.UserInfo google) {
35+
return OAuth2ResponseDTO.GetUserInfo.builder()
36+
.email(google.email())
37+
.providerId(google.id())
38+
.socialType(SocialType.GOOGLE)
39+
.build();
40+
}
41+
}

src/main/java/org/withtime/be/withtimebe/domain/auth/dto/request/AuthRequestDTO.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ public record SignUp(
2222
String password,
2323
Gender gender,
2424
String phoneNumber,
25-
LocalDate birth
25+
LocalDate birth,
26+
Long socialId
27+
) {
28+
29+
}
30+
31+
public record FindPassword(
32+
String email,
33+
String newPassword
2634
) {
2735

2836
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.withtime.be.withtimebe.domain.auth.dto.response;
2+
3+
import lombok.Builder;
4+
import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType;
5+
6+
public record OAuth2ResponseDTO() {
7+
8+
@Builder
9+
public record Login(
10+
String email,
11+
Long socialId,
12+
boolean isFirst
13+
) {
14+
15+
}
16+
17+
@Builder
18+
public record GetUserInfo(
19+
String email,
20+
String providerId,
21+
SocialType socialType
22+
) {
23+
24+
}
25+
26+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.withtime.be.withtimebe.domain.auth.factory;
2+
3+
import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO;
4+
5+
public interface OAuth2UserLoader {
6+
OAuth2ResponseDTO.GetUserInfo loadUser(String code);
7+
String getSocialType();
8+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.withtime.be.withtimebe.domain.auth.factory;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
9+
@Component
10+
public class OAuth2UserLoaderFactory {
11+
12+
private final Map<String, OAuth2UserLoader> oAuth2UserLoaderMap = new ConcurrentHashMap<>();
13+
14+
public OAuth2UserLoaderFactory(List<OAuth2UserLoader> oAuth2UserLoaders) {
15+
oAuth2UserLoaders.forEach(
16+
oAuth2UserLoader -> {
17+
String socialType = oAuth2UserLoader.getSocialType().toLowerCase();
18+
if (oAuth2UserLoaderMap.get(socialType) != null) {
19+
throw new IllegalStateException("OAuth2UserLoader social type 중복: " + socialType);
20+
}
21+
oAuth2UserLoaderMap.put(socialType, oAuth2UserLoader);
22+
}
23+
);
24+
}
25+
26+
public OAuth2UserLoader getUserLoader(String provider) {
27+
return oAuth2UserLoaderMap.get(provider.toLowerCase());
28+
}
29+
}

0 commit comments

Comments
 (0)