Skip to content

Commit 8c8b09a

Browse files
committed
chore : 병합 충돌 해결
2 parents 2593e0e + 0d64280 commit 8c8b09a

30 files changed

+862
-18
lines changed

build.gradle

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,23 @@ dependencies {
3838
testImplementation 'org.springframework.security:spring-security-test'
3939
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
4040
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
41-
testImplementation 'com.h2database:h2'
42-
implementation 'com.h2database:h2'
4341

42+
// h2
43+
testImplementation 'com.h2database:h2'
4444

45-
// Jwt
45+
// jwt
4646
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
4747
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
4848
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
4949

5050
// Validation
5151
implementation 'org.springframework.boot:spring-boot-starter-validation'
52+
53+
// mail
54+
implementation 'org.springframework.boot:spring-boot-starter-mail'
55+
56+
// Swagger
57+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14'
5258
}
5359

5460
tasks.named('test') {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.gpt.geumpumtabackend.global.config.mail;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.mail.javamail.JavaMailSender;
7+
import org.springframework.mail.javamail.JavaMailSenderImpl;
8+
9+
import java.util.Properties;
10+
11+
@Configuration
12+
public class MailConfig {
13+
@Value("${spring.mail.host}")
14+
private String host;
15+
16+
@Value("${spring.mail.port}")
17+
private int port;
18+
19+
@Value("${spring.mail.username}")
20+
private String username;
21+
22+
@Value("${spring.mail.password}")
23+
private String password;
24+
25+
@Value("${spring.mail.properties.mail.smtp.auth}")
26+
private boolean auth;
27+
28+
@Value("${spring.mail.properties.mail.smtp.connectiontimeout}")
29+
private int connectionTimeout;
30+
31+
@Value("${spring.mail.properties.mail.smtp.timeout}")
32+
private int timeout;
33+
34+
@Value("${spring.mail.properties.mail.smtp.writetimeout}")
35+
private int writeTimeout;
36+
37+
@Value("${spring.mail.properties.mail.smtp.ssl.enable}")
38+
private boolean sslEnable;
39+
40+
@Bean
41+
public JavaMailSender javaMailSender() {
42+
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
43+
mailSender.setHost(host);
44+
mailSender.setPort(port);
45+
mailSender.setUsername(username);
46+
mailSender.setPassword(password);
47+
mailSender.setDefaultEncoding("UTF-8");
48+
mailSender.setJavaMailProperties(getMailProperties());
49+
50+
return mailSender;
51+
}
52+
53+
private Properties getMailProperties() {
54+
Properties properties = new Properties();
55+
properties.put("mail.smtp.auth", auth);
56+
properties.put("mail.smtp.connectiontimeout", connectionTimeout);
57+
properties.put("mail.smtp.timeout", timeout);
58+
properties.put("mail.smtp.writetimeout", writeTimeout);
59+
properties.put("mail.smtp.ssl.enable", sslEnable);
60+
61+
return properties;
62+
}
63+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.gpt.geumpumtabackend.global.config.redis;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.data.redis.connection.RedisConnectionFactory;
7+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
8+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
9+
import org.springframework.data.redis.core.RedisTemplate;
10+
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
11+
import org.springframework.data.redis.serializer.StringRedisSerializer;
12+
13+
@Configuration
14+
@EnableRedisRepositories
15+
public class RedisConfig {
16+
@Value("${spring.data.redis.host}")
17+
private String host;
18+
19+
@Value("${spring.data.redis.port}")
20+
private int port;
21+
22+
@Value("${spring.data.redis.password:}")
23+
private String password;
24+
25+
@Bean
26+
public RedisConnectionFactory redisConnectionFactory() {
27+
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
28+
if(password != null) {
29+
config.setPassword(password);
30+
}
31+
return new LettuceConnectionFactory(config);
32+
}
33+
34+
@Bean
35+
public RedisTemplate<String, Object> redisTemplate() {
36+
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
37+
redisTemplate.setConnectionFactory(redisConnectionFactory());
38+
39+
// 일반
40+
redisTemplate.setKeySerializer(new StringRedisSerializer());
41+
redisTemplate.setValueSerializer(new StringRedisSerializer());
42+
43+
// Hash
44+
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
45+
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
46+
47+
// 모든 경우
48+
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
49+
50+
return redisTemplate;
51+
}
52+
}
53+
54+

src/main/java/com/gpt/geumpumtabackend/global/config/security/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public HttpSecurity defaultSecurity(HttpSecurity http) throws Exception {
6262
public RoleHierarchy roleHierarchy() {
6363
return RoleHierarchyImpl
6464
.withDefaultRolePrefix() // 기본 접두어 "ROLE_" 사용
65-
.role("FARMER")
65+
.role("ADMIN")
6666
.implies("USER")
6767
.build();
6868
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.gpt.geumpumtabackend.global.config.swagger;
2+
3+
4+
5+
import com.gpt.geumpumtabackend.global.exception.ExceptionType;
6+
7+
import java.lang.annotation.ElementType;
8+
import java.lang.annotation.Retention;
9+
import java.lang.annotation.RetentionPolicy;
10+
import java.lang.annotation.Target;
11+
12+
/**
13+
* 이 애너테이션은 API 호출이 실패했을 때의 응답 HTTP 상태 코드와 응답 본문에 대한
14+
* 스키마를 명시할 수 있습니다.
15+
*
16+
* @see com.gpt.geumpumtabackend.global.config.swagger.SwaggerApiResponses
17+
* @see ExceptionType
18+
*/
19+
@Target(ElementType.METHOD)
20+
@Retention(RetentionPolicy.RUNTIME)
21+
public @interface SwaggerApiFailedResponse {
22+
/**
23+
* {@link ExceptionType}에 정의된 예외 타입을 지정합니다.
24+
*/
25+
ExceptionType value();
26+
27+
/**
28+
* Swagger UI에 표시할 실패 응답 설명을 기재합니다.
29+
* <p>지정하지 않으면 {@link ExceptionType}의 기본 메시지가 사용됩니다.</p>
30+
*/
31+
String description() default "";
32+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.gpt.geumpumtabackend.global.config.swagger;
2+
3+
4+
5+
import com.gpt.geumpumtabackend.global.exception.ExceptionType;
6+
import com.gpt.geumpumtabackend.global.response.FailedResponseBody;
7+
import io.swagger.v3.oas.models.Operation;
8+
import io.swagger.v3.oas.models.examples.Example;
9+
import io.swagger.v3.oas.models.media.Content;
10+
import io.swagger.v3.oas.models.media.MediaType;
11+
import io.swagger.v3.oas.models.responses.ApiResponse;
12+
import io.swagger.v3.oas.models.responses.ApiResponses;
13+
import lombok.Builder;
14+
import lombok.Getter;
15+
import org.springframework.stereotype.Component;
16+
import org.springframework.web.method.HandlerMethod;
17+
18+
import java.util.Arrays;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
23+
@Component
24+
public class SwaggerApiFailedResponseHandler {
25+
public void handle(Operation operation, HandlerMethod handlerMethod) {
26+
// 스프링 빈 컨트롤러 메서드에 적용된 SwaggerApiResponses 애너테이션을 불러옴
27+
SwaggerApiResponses apiResponses = handlerMethod.getMethodAnnotation(SwaggerApiResponses.class);
28+
if (apiResponses == null) {
29+
return;
30+
}
31+
32+
ApiResponses responses = operation.getResponses();
33+
34+
List<SwaggerApiFailedResponse> apiFailedResponses = Arrays.asList(apiResponses.errors());
35+
36+
// SwaggerApiFailedResponse를 ExampleHolder 객체로 만들어 HTTP 응답 상태 코드별로 저장
37+
Map<Integer, List<ExampleHolder>> exampleHoldersGroupedByResponseCode = apiFailedResponses.stream()
38+
.map(this::createExampleHolder)
39+
.collect(Collectors.groupingBy(ExampleHolder::getResponseCode));
40+
41+
addExamplesToResponses(responses, exampleHoldersGroupedByResponseCode);
42+
}
43+
44+
private ExampleHolder createExampleHolder(SwaggerApiFailedResponse apiFailedResponse) {
45+
ExceptionType exceptionType = apiFailedResponse.value();
46+
String description = apiFailedResponse.description().isBlank() ? exceptionType.getMessage() : apiFailedResponse.description();
47+
48+
return ExampleHolder.builder()
49+
.responseCode(exceptionType.getStatus().value())
50+
.exceptionName(exceptionType.name())
51+
.exceptionCode(exceptionType.getCode())
52+
.description(description)
53+
.holder(createSwaggerExample(exceptionType, description))
54+
.build();
55+
}
56+
57+
private Example createSwaggerExample(ExceptionType exceptionType, String description) {
58+
// 공통 응답 스키마 구성
59+
FailedResponseBody failedResponseBody = new FailedResponseBody(exceptionType.getCode(), exceptionType.getMessage());
60+
ExampleFailedResponseBody failedResponseBodyExample = new ExampleFailedResponseBody("false", failedResponseBody);
61+
62+
Example example = new Example();
63+
example.setValue(failedResponseBodyExample);
64+
example.setDescription(description);
65+
66+
return example;
67+
}
68+
69+
private void addExamplesToResponses(
70+
ApiResponses responses,
71+
Map<Integer, List<ExampleHolder>> exampleHoldersGroupedByResponseCode
72+
) {
73+
// 각 HTTP 응답 상태 코드별로 탐색
74+
exampleHoldersGroupedByResponseCode.forEach((status, exampleHolders) -> {
75+
Content content = new Content();
76+
MediaType mediaType = new MediaType();
77+
ApiResponse apiResponse = new ApiResponse();
78+
79+
// 각 상태 코드별 예제 생성
80+
exampleHolders.forEach(
81+
exampleHolder -> mediaType.addExamples(exampleHolder.getExceptionName(), exampleHolder.getHolder())
82+
);
83+
84+
// 응답 객체 등록
85+
content.addMediaType("application/json", mediaType);
86+
apiResponse.setContent(content);
87+
88+
responses.addApiResponse(String.valueOf(status), apiResponse);
89+
});
90+
}
91+
92+
@Getter
93+
@Builder
94+
public static class ExampleHolder {
95+
private final int responseCode;
96+
private final String exceptionName;
97+
private final String exceptionCode;
98+
private final String description;
99+
private final Example holder;
100+
}
101+
102+
@Getter
103+
public static class ExampleFailedResponseBody {
104+
private final String success;
105+
private final String code;
106+
private final String message;
107+
108+
public ExampleFailedResponseBody(String success , FailedResponseBody failedResponseBody) {
109+
this.success = success;
110+
this.code = failedResponseBody.getCode();
111+
this.message = failedResponseBody.getMsg();
112+
}
113+
114+
}
115+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.gpt.geumpumtabackend.global.config.swagger;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* 이 애너테이션은 API 엔드 포인트에서의 성공 및 오류 응답에 대한 설명을 정의합니다.
10+
*
11+
* @see com.gpt.geumpumtabackend.global.config.swagger.SwaggerApiSuccessResponse
12+
* @see com.gpt.geumpumtabackend.global.config.swagger.SwaggerApiFailedResponse
13+
*/
14+
@Target(ElementType.METHOD)
15+
@Retention(RetentionPolicy.RUNTIME)
16+
public @interface SwaggerApiResponses {
17+
SwaggerApiSuccessResponse success() default @SwaggerApiSuccessResponse;
18+
19+
SwaggerApiFailedResponse[] errors() default { };
20+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.gpt.geumpumtabackend.global.config.swagger;
2+
3+
import org.springframework.http.HttpStatus;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* 이 애너테이션은 API 호출이 정상적으로 완료되었을 때의 응답 HTTP 상태 코드와 응답 본문에 대한
12+
* 스키마를 명시할 수 있습니다.
13+
*
14+
* @see com.gpt.geumpumtabackend.global.config.swagger.SwaggerApiResponses
15+
* @see HttpStatus
16+
*/
17+
@Target(ElementType.METHOD)
18+
@Retention(RetentionPolicy.RUNTIME)
19+
public @interface SwaggerApiSuccessResponse {
20+
/**
21+
* 반환할 HTTP 상태 코드를 지정합니다.
22+
*/
23+
HttpStatus status() default HttpStatus.OK;
24+
25+
/**
26+
* 단일 객체로 반환할 DTO 클래스 타입을 지정합니다.
27+
* <p><code>responsePage</code>와 함께 사용할 수 없습니다.</p>
28+
*/
29+
Class<?> response() default Void.class;
30+
31+
/**
32+
* 페이지네이션된 리스트 형태로 반환할 DTO 클래스 타입을 지정합니다.
33+
* <p><code>response</code>와 함께 사용할 수 없습니다.</p>
34+
*/
35+
Class<?> responsePage() default Void.class;
36+
37+
/**
38+
* Swagger UI에 표시할 응답 설명을 기재합니다.
39+
*/
40+
String description() default "";
41+
}

0 commit comments

Comments
 (0)