Skip to content

Commit 4dda1da

Browse files
authored
Merge pull request #199 from solid-connection/chore/release-sync
[RELEASE] 250214 릴리즈
2 parents 2af7b77 + ce1d88b commit 4dda1da

File tree

218 files changed

+8127
-4495
lines changed

Some content is hidden

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

218 files changed

+8127
-4495
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
name: Refactor request
3+
about: Suggest an refactor for this project
4+
title: ''
5+
labels: refactor
6+
assignees: ''
7+
8+
---
9+
10+
## 어떤 부분을 리팩터링하려 하나요?
11+
12+
> 리팩터링하려는 부분에 대해 간결하게 설명해주세요
13+
14+
### AS-IS
15+
- as-is
16+
- as-is
17+
18+
### TO-BE
19+
- to-be
20+
- to-be
21+
22+
## 작업 상세 내용
23+
24+
- [ ] TODO
25+
- [ ] TODO
26+
- [ ] TODO
27+
28+
## 참고할만한 자료(선택)

build.gradle

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,24 @@ dependencies {//todo: 안쓰는 의존성이나 deprecated된 의존성 제거
3737
implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
3838
implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'
3939
implementation 'org.apache.commons:commons-lang3:3.12.0'
40-
testImplementation 'org.mockito:mockito-core:3.3.3'
4140
implementation 'org.springframework.boot:spring-boot-starter-actuator'
4241
implementation 'io.micrometer:micrometer-registry-prometheus'
42+
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
4343

44+
// Lombok
4445
compileOnly 'org.projectlombok:lombok:1.18.26'
4546
annotationProcessor 'org.projectlombok:lombok'
47+
48+
// Test
4649
testImplementation 'org.springframework.boot:spring-boot-starter-test'
47-
testImplementation 'com.h2database:h2:2.2.224'
50+
testImplementation 'org.mockito:mockito-core:3.3.3'
4851
testImplementation 'io.rest-assured:rest-assured:5.4.0'
4952

50-
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
53+
// Testcontainers
54+
testImplementation 'org.testcontainers:testcontainers'
55+
testImplementation 'org.testcontainers:junit-jupiter'
56+
testImplementation 'org.testcontainers:mysql'
57+
5158
annotationProcessor(
5259
'com.querydsl:querydsl-apt:5.0.0:jakarta',
5360
'jakarta.persistence:jakarta.persistence-api:3.1.0',

src/main/java/com/example/solidconnection/SolidConnectionApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
56
import org.springframework.cache.annotation.EnableCaching;
67
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
78
import org.springframework.scheduling.annotation.EnableScheduling;
89

10+
@ConfigurationPropertiesScan
911
@EnableScheduling
1012
@EnableJpaAuditing
1113
@EnableCaching

src/main/java/com/example/solidconnection/application/controller/ApplicationController.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import com.example.solidconnection.application.dto.ApplyRequest;
66
import com.example.solidconnection.application.service.ApplicationQueryService;
77
import com.example.solidconnection.application.service.ApplicationSubmissionService;
8+
import com.example.solidconnection.custom.resolver.AuthorizedUser;
9+
import com.example.solidconnection.siteuser.domain.SiteUser;
810
import jakarta.validation.Valid;
911
import lombok.RequiredArgsConstructor;
1012
import org.springframework.http.HttpStatus;
@@ -16,8 +18,6 @@
1618
import org.springframework.web.bind.annotation.RequestParam;
1719
import org.springframework.web.bind.annotation.RestController;
1820

19-
import java.security.Principal;
20-
2121
@RequiredArgsConstructor
2222
@RequestMapping("/application")
2323
@RestController
@@ -29,30 +29,33 @@ public class ApplicationController {
2929
// 지원서 제출하기 api
3030
@PostMapping()
3131
public ResponseEntity<ApplicationSubmissionResponse> apply(
32-
Principal principal,
33-
@Valid @RequestBody ApplyRequest applyRequest) {
34-
boolean result = applicationSubmissionService.apply(principal.getName(), applyRequest);
32+
@AuthorizedUser SiteUser siteUser,
33+
@Valid @RequestBody ApplyRequest applyRequest
34+
) {
35+
boolean result = applicationSubmissionService.apply(siteUser, applyRequest);
3536
return ResponseEntity
3637
.status(HttpStatus.OK)
3738
.body(new ApplicationSubmissionResponse(result));
3839
}
3940

4041
@GetMapping
4142
public ResponseEntity<ApplicationsResponse> getApplicants(
42-
Principal principal,
43+
@AuthorizedUser SiteUser siteUser,
4344
@RequestParam(required = false, defaultValue = "") String region,
44-
@RequestParam(required = false, defaultValue = "") String keyword) {
45-
applicationQueryService.validateSiteUserCanViewApplicants(principal.getName());
46-
ApplicationsResponse result = applicationQueryService.getApplicants(principal.getName(), region, keyword);
45+
@RequestParam(required = false, defaultValue = "") String keyword
46+
) {
47+
applicationQueryService.validateSiteUserCanViewApplicants(siteUser);
48+
ApplicationsResponse result = applicationQueryService.getApplicants(siteUser, region, keyword);
4749
return ResponseEntity
4850
.ok(result);
4951
}
5052

5153
@GetMapping("/competitors")
5254
public ResponseEntity<ApplicationsResponse> getApplicantsForUserCompetitors(
53-
Principal principal) {
54-
applicationQueryService.validateSiteUserCanViewApplicants(principal.getName());
55-
ApplicationsResponse result = applicationQueryService.getApplicantsByUserApplications(principal.getName());
55+
@AuthorizedUser SiteUser siteUser
56+
) {
57+
applicationQueryService.validateSiteUserCanViewApplicants(siteUser);
58+
ApplicationsResponse result = applicationQueryService.getApplicantsByUserApplications(siteUser);
5659
return ResponseEntity
5760
.ok(result);
5861
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package com.example.solidconnection.application.dto;
22

3+
import jakarta.validation.Valid;
34
import jakarta.validation.constraints.NotNull;
45

56
public record ApplyRequest(
7+
68
@NotNull(message = "gpa score id를 입력해주세요.")
79
Long gpaScoreId,
810

911
@NotNull(message = "language test score id를 입력해주세요.")
1012
Long languageTestScoreId,
1113

14+
@Valid
1215
UniversityChoiceRequest universityChoiceRequest
1316
) {
1417
}
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package com.example.solidconnection.application.dto;
22

3-
import jakarta.validation.constraints.NotNull;
3+
import com.example.solidconnection.custom.validation.annotation.ValidUniversityChoice;
44

5+
@ValidUniversityChoice
56
public record UniversityChoiceRequest(
6-
@NotNull(message = "1지망 대학교를 입력해주세요.")
77
Long firstChoiceUniversityId,
8-
98
Long secondChoiceUniversityId,
109
Long thirdChoiceUniversityId) {
1110
}

src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.example.solidconnection.cache.annotation.ThunderingHerdCaching;
99
import com.example.solidconnection.custom.exception.CustomException;
1010
import com.example.solidconnection.siteuser.domain.SiteUser;
11-
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
1211
import com.example.solidconnection.type.VerifyStatus;
1312
import com.example.solidconnection.university.domain.University;
1413
import com.example.solidconnection.university.domain.UniversityInfoForApply;
@@ -34,7 +33,6 @@ public class ApplicationQueryService {
3433

3534
private final ApplicationRepository applicationRepository;
3635
private final UniversityInfoForApplyRepository universityInfoForApplyRepository;
37-
private final SiteUserRepository siteUserRepository;
3836
private final UniversityFilterRepositoryImpl universityFilterRepository;
3937

4038
@Value("${university.term}")
@@ -49,9 +47,7 @@ public class ApplicationQueryService {
4947
* */
5048
@Transactional(readOnly = true)
5149
@ThunderingHerdCaching(key = "application:query:{1}:{2}", cacheManager = "customCacheManager", ttlSec = 86400)
52-
public ApplicationsResponse getApplicants(String email, String regionCode, String keyword) {
53-
SiteUser siteUser = siteUserRepository.getByEmail(email);
54-
50+
public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) {
5551
// 국가와 키워드와 지역을 통해 대학을 필터링한다.
5652
List<University> universities
5753
= universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword));
@@ -64,9 +60,7 @@ public ApplicationsResponse getApplicants(String email, String regionCode, Strin
6460
}
6561

6662
@Transactional(readOnly = true)
67-
public ApplicationsResponse getApplicantsByUserApplications(String email) {
68-
SiteUser siteUser = siteUserRepository.getByEmail(email);
69-
63+
public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) {
7064
Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term);
7165
List<University> userAppliedUniversities = Arrays.asList(
7266
Optional.ofNullable(userLatestApplication.getFirstChoiceUniversity())
@@ -91,8 +85,7 @@ public ApplicationsResponse getApplicantsByUserApplications(String email) {
9185
// 학기별로 상태가 관리된다.
9286
// 금학기에 지원이력이 있는 사용자만 지원정보를 확인할 수 있도록 한다.
9387
@Transactional(readOnly = true)
94-
public void validateSiteUserCanViewApplicants(String email) {
95-
SiteUser siteUser = siteUserRepository.getByEmail(email);
88+
public void validateSiteUserCanViewApplicants(SiteUser siteUser) {
9689
VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus();
9790
if (verifyStatus != VerifyStatus.APPROVED) {
9891
throw new CustomException(APPLICATION_NOT_APPROVED);

src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import com.example.solidconnection.score.repository.GpaScoreRepository;
1111
import com.example.solidconnection.score.repository.LanguageTestScoreRepository;
1212
import com.example.solidconnection.siteuser.domain.SiteUser;
13-
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
1413
import com.example.solidconnection.type.VerifyStatus;
1514
import com.example.solidconnection.university.domain.UniversityInfoForApply;
1615
import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository;
@@ -19,7 +18,9 @@
1918
import org.springframework.stereotype.Service;
2019
import org.springframework.transaction.annotation.Transactional;
2120

21+
import java.util.ArrayList;
2222
import java.util.HashSet;
23+
import java.util.List;
2324
import java.util.Optional;
2425
import java.util.Set;
2526

@@ -38,7 +39,6 @@ public class ApplicationSubmissionService {
3839

3940
private final ApplicationRepository applicationRepository;
4041
private final UniversityInfoForApplyRepository universityInfoForApplyRepository;
41-
private final SiteUserRepository siteUserRepository;
4242
private final GpaScoreRepository gpaScoreRepository;
4343
private final LanguageTestScoreRepository languageTestScoreRepository;
4444

@@ -48,10 +48,8 @@ public class ApplicationSubmissionService {
4848
// 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다.
4949
// 기존에 있던 status field 우선 APRROVED로 입력시킨다.
5050
@Transactional
51-
public boolean apply(String email, ApplyRequest applyRequest) {
52-
SiteUser siteUser = siteUserRepository.getByEmail(email);
51+
public boolean apply(SiteUser siteUser, ApplyRequest applyRequest) {
5352
UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest();
54-
validateUniversityChoices(universityChoiceRequest);
5553

5654
Long gpaScoreId = applyRequest.gpaScoreId();
5755
Long languageTestScoreId = applyRequest.languageTestScoreId();
@@ -119,23 +117,4 @@ private void validateUpdateLimitNotExceed(Application application) {
119117
throw new CustomException(APPLY_UPDATE_LIMIT_EXCEED);
120118
}
121119
}
122-
123-
// 입력값 유효성 검증
124-
private void validateUniversityChoices(UniversityChoiceRequest universityChoiceRequest) {
125-
Set<Long> uniqueUniversityIds = new HashSet<>();
126-
uniqueUniversityIds.add(universityChoiceRequest.firstChoiceUniversityId());
127-
if (universityChoiceRequest.secondChoiceUniversityId() != null) {
128-
addUniversityChoice(uniqueUniversityIds, universityChoiceRequest.secondChoiceUniversityId());
129-
}
130-
if (universityChoiceRequest.thirdChoiceUniversityId() != null) {
131-
addUniversityChoice(uniqueUniversityIds, universityChoiceRequest.thirdChoiceUniversityId());
132-
}
133-
}
134-
135-
private void addUniversityChoice(Set<Long> uniqueUniversityIds, Long universityId) {
136-
boolean notAdded = !uniqueUniversityIds.add(universityId);
137-
if (notAdded) {
138-
throw new CustomException(CANT_APPLY_FOR_SAME_UNIVERSITY);
139-
}
140-
}
141120
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.example.solidconnection.auth.client;
2+
3+
import com.example.solidconnection.auth.dto.oauth.AppleTokenDto;
4+
import com.example.solidconnection.auth.dto.oauth.AppleUserInfoDto;
5+
import com.example.solidconnection.config.client.AppleOAuthClientProperties;
6+
import com.example.solidconnection.custom.exception.CustomException;
7+
import io.jsonwebtoken.Jwts;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpEntity;
10+
import org.springframework.http.HttpHeaders;
11+
import org.springframework.http.HttpMethod;
12+
import org.springframework.http.MediaType;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.stereotype.Component;
15+
import org.springframework.util.LinkedMultiValueMap;
16+
import org.springframework.util.MultiValueMap;
17+
import org.springframework.web.client.RestTemplate;
18+
19+
import java.security.PublicKey;
20+
import java.util.Objects;
21+
22+
import static com.example.solidconnection.custom.exception.ErrorCode.APPLE_AUTHORIZATION_FAILED;
23+
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_APPLE_ID_TOKEN;
24+
25+
/*
26+
* 애플 인증을 위한 OAuth2 클라이언트
27+
* https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
28+
* */
29+
@Component
30+
@RequiredArgsConstructor
31+
public class AppleOAuthClient {
32+
33+
private final RestTemplate restTemplate;
34+
private final AppleOAuthClientProperties properties;
35+
private final AppleOAuthClientSecretProvider clientSecretProvider;
36+
private final ApplePublicKeyProvider publicKeyProvider;
37+
38+
public AppleUserInfoDto processOAuth(String code) {
39+
String idToken = requestIdToken(code);
40+
PublicKey applePublicKey = publicKeyProvider.getApplePublicKey(idToken);
41+
return new AppleUserInfoDto(parseEmailFromToken(applePublicKey, idToken));
42+
}
43+
44+
public String requestIdToken(String code) {
45+
HttpHeaders headers = new HttpHeaders();
46+
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
47+
MultiValueMap<String, String> formData = buildFormData(code);
48+
49+
try {
50+
ResponseEntity<AppleTokenDto> response = restTemplate.exchange(
51+
properties.tokenUrl(),
52+
HttpMethod.POST,
53+
new HttpEntity<>(formData, headers),
54+
AppleTokenDto.class
55+
);
56+
return Objects.requireNonNull(response.getBody()).idToken();
57+
} catch (Exception e) {
58+
throw new CustomException(APPLE_AUTHORIZATION_FAILED, e.getMessage());
59+
}
60+
}
61+
62+
private MultiValueMap<String, String> buildFormData(String code) {
63+
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
64+
formData.add("client_id", properties.clientId());
65+
formData.add("client_secret", clientSecretProvider.generateClientSecret());
66+
formData.add("code", code);
67+
formData.add("grant_type", "authorization_code");
68+
formData.add("redirect_uri", properties.redirectUrl());
69+
return formData;
70+
}
71+
72+
private String parseEmailFromToken(PublicKey applePublicKey, String idToken) {
73+
try {
74+
return Jwts.parser()
75+
.setSigningKey(applePublicKey)
76+
.parseClaimsJws(idToken)
77+
.getBody()
78+
.get("email", String.class);
79+
} catch (Exception e) {
80+
throw new CustomException(INVALID_APPLE_ID_TOKEN);
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)