From 5b893a5389b7bcebc47341a7f17c0d9204dbe558 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 4 Nov 2024 17:41:40 +0900 Subject: [PATCH 0001/1002] Chore : add basic spring application class --- .../main/java/inu/codin/codin/CodinApplication.java | 13 +++++++++++++ .../java/inu/codin/codin/CodinApplicationTests.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/CodinApplication.java create mode 100644 codin-core/src/test/java/inu/codin/codin/CodinApplicationTests.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java new file mode 100644 index 00000000..e394736d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -0,0 +1,13 @@ +package inu.codin.codin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CodinApplication { + + public static void main(String[] args) { + SpringApplication.run(CodinApplication.class, args); + } + +} diff --git a/codin-core/src/test/java/inu/codin/codin/CodinApplicationTests.java b/codin-core/src/test/java/inu/codin/codin/CodinApplicationTests.java new file mode 100644 index 00000000..65bcab2c --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/CodinApplicationTests.java @@ -0,0 +1,13 @@ +package inu.codin.codin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CodinApplicationTests { + + @Test + void contextLoads() { + } + +} From d2beac9c004b940e4cbdabbe4ce41b8e8eb7d57e Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 5 Nov 2024 00:06:48 +0900 Subject: [PATCH 0002/1002] =?UTF-8?q?Chore=20:=20Redis=20Config=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/RedisConfig.java | 35 +++++++++++++++++++ .../codin/infra/redis/RedisProperties.java | 16 +++++++++ 2 files changed, 51 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java new file mode 100644 index 00000000..2e03922c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java @@ -0,0 +1,35 @@ +package inu.codin.codin.infra.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +// Redis configuration +@Configuration +@EnableConfigurationProperties(RedisProperties.class) +@RequiredArgsConstructor +public class RedisConfig { + + private final RedisProperties redisProperties; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + // Lettuce는 비동기 방식을 지원하는 Redis 클라이언트 + // 성능상 이점이 있어 기본적으로 사용 + return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + return redisTemplate; + } +} + diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java new file mode 100644 index 00000000..5b65b4c1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java @@ -0,0 +1,16 @@ +package inu.codin.codin.infra.redis; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@AllArgsConstructor +@ConfigurationProperties("spring.data.redis") +public class RedisProperties { + + private String host; + + private int port; + +} \ No newline at end of file From d60087c2bba7e37f1f929184611e3b3860b8c373 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 5 Nov 2024 00:07:15 +0900 Subject: [PATCH 0003/1002] =?UTF-8?q?Test=20:=20Redis=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9E=91=EB=8F=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/infra/redis/RedisConfigTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java b/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java new file mode 100644 index 00000000..6fff996e --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java @@ -0,0 +1,31 @@ +package inu.codin.codin.infra.redis; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +class RedisConfigTest { + + @Autowired + private RedisProperties redisProperties; + + @Autowired + private RedisTemplate redisTemplate; + + @Test + void Redis연결설정테스트() { + assertEquals(redisProperties.getHost(), "localhost"); + assertEquals(redisProperties.getPort(), 6379); + } + + @Test + void redisTemplate작동확인테스트() { + redisTemplate.opsForValue().set("key", "value"); + assertEquals(redisTemplate.opsForValue().get("key"), "value"); + redisTemplate.delete("key"); + } +} \ No newline at end of file From 6250e322dca4e571caccdf5ce955437fc68d4498 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 5 Nov 2024 00:07:42 +0900 Subject: [PATCH 0004/1002] =?UTF-8?q?Chore=20:=20S3=20Config=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/infra/s3/S3Config.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/S3Config.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Config.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Config.java new file mode 100644 index 00000000..bef26a0a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Config.java @@ -0,0 +1,31 @@ +package inu.codin.codin.infra.s3; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials= new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} \ No newline at end of file From a0d9842cc15e6240ebc515254c1b91c81909360b Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 5 Nov 2024 22:49:48 +0900 Subject: [PATCH 0005/1002] =?UTF-8?q?Feat=20:=20Swagger=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SwaggerConfig.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java new file mode 100644 index 00000000..ab03fd79 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -0,0 +1,50 @@ +package inu.codin.codin.common.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.util.List; + +@Configuration +@EnableWebMvc +@RequiredArgsConstructor +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + + Info info = new Info() + .title("CODIN API Documentation") + .description("CODIN API 명세서") + .version("v1.0.0"); + + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .name("Authorization"); + + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList("bearerAuth"); + + return new OpenAPI() + .info(info) + .security(List.of(securityRequirement)) + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .servers(List.of( + new Server().url("http://localhost:8080").description("Local Server"), + new Server().url("http://www.codin.co.kr").description("Production Server") + )); + } + +} From 2f549a546fbeae4999f598e4bfcec15d544370da Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 5 Nov 2024 22:50:17 +0900 Subject: [PATCH 0006/1002] =?UTF-8?q?Test=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20Controller=20=EC=B6=94=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/TestController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/TestController.java diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java new file mode 100644 index 00000000..4b731e8f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -0,0 +1,16 @@ +package inu.codin.codin; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v3/api") +public class TestController { + + @GetMapping("/test") + public String test() { + return "test"; + } + +} From ac5230f2238fd311236840eedbe6f55f881a749b Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 17:40:40 +0900 Subject: [PATCH 0007/1002] =?UTF-8?q?Feat=20:=20SecurityConfig=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java new file mode 100644 index 00000000..eda54d1f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -0,0 +1,46 @@ +package inu.codin.codin.common.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + String[] PERMIT_ALL = { + "/**", + "/api/members/sign-up", + "/api/members/sign-in", + "/api/members/refresh-token", + "/swagger-ui/**", + "/swagger-ui.html", + "/v3/api-docs/**" + }; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http + .csrf(CsrfConfigurer::disable) // csrf 비활성화 + .cors(Customizer.withDefaults()) // cors 설정 + .formLogin(FormLoginConfigurer::disable) // form login 비활성화 + .httpBasic(HttpBasicConfigurer::disable) // basic auth 비활성화 + // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 + .authorizeHttpRequests((authorizeHttpRequests) -> + authorizeHttpRequests + .requestMatchers(PERMIT_ALL).permitAll() + ); + + return http.build(); + } + +} From 111417db94009396ee2757ef91e8ea1012ebec6b Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 17:40:55 +0900 Subject: [PATCH 0008/1002] =?UTF-8?q?Feat=20:=20BaseTimeEntity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/CodinApplication.java | 2 ++ .../codin/codin/common/BaseTimeEntity.java | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index e394736d..a74dca60 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.config.EnableMongoAuditing; @SpringBootApplication +@EnableMongoAuditing public class CodinApplication { public static void main(String[] args) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java new file mode 100644 index 00000000..62ebd48e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -0,0 +1,31 @@ +package inu.codin.codin.common; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.LocalDateTime; + +@Getter +public abstract class BaseTimeEntity { + + @CreatedDate + @Field("created_at") + private LocalDateTime createdAt; + + @LastModifiedDate + @Field("updated_at") + private LocalDateTime updatedAt; + + private LocalDateTime deletedAt; + + public void delete() { + this.deletedAt = LocalDateTime.now(); + } + + public void restore() { + this.deletedAt = null; + } + +} \ No newline at end of file From db4c77ac38e6f6225ded3d6cfdc87e40d2e15b4a Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 17:40:40 +0900 Subject: [PATCH 0009/1002] =?UTF-8?q?[SC-55]=20Feat=20:=20SecurityConfig?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java new file mode 100644 index 00000000..eda54d1f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -0,0 +1,46 @@ +package inu.codin.codin.common.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + String[] PERMIT_ALL = { + "/**", + "/api/members/sign-up", + "/api/members/sign-in", + "/api/members/refresh-token", + "/swagger-ui/**", + "/swagger-ui.html", + "/v3/api-docs/**" + }; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http + .csrf(CsrfConfigurer::disable) // csrf 비활성화 + .cors(Customizer.withDefaults()) // cors 설정 + .formLogin(FormLoginConfigurer::disable) // form login 비활성화 + .httpBasic(HttpBasicConfigurer::disable) // basic auth 비활성화 + // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 + .authorizeHttpRequests((authorizeHttpRequests) -> + authorizeHttpRequests + .requestMatchers(PERMIT_ALL).permitAll() + ); + + return http.build(); + } + +} From 0cadb0baa040b8829e67eae34fbe62ab6b02e365 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 17:40:55 +0900 Subject: [PATCH 0010/1002] =?UTF-8?q?[SC-55]=20Feat=20:=20BaseTimeEntity?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/CodinApplication.java | 2 ++ .../codin/codin/common/BaseTimeEntity.java | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index e394736d..a74dca60 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.config.EnableMongoAuditing; @SpringBootApplication +@EnableMongoAuditing public class CodinApplication { public static void main(String[] args) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java new file mode 100644 index 00000000..62ebd48e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -0,0 +1,31 @@ +package inu.codin.codin.common; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.LocalDateTime; + +@Getter +public abstract class BaseTimeEntity { + + @CreatedDate + @Field("created_at") + private LocalDateTime createdAt; + + @LastModifiedDate + @Field("updated_at") + private LocalDateTime updatedAt; + + private LocalDateTime deletedAt; + + public void delete() { + this.deletedAt = LocalDateTime.now(); + } + + public void restore() { + this.deletedAt = null; + } + +} \ No newline at end of file From 35b5fdd6045d208a93c38875708658b7be538328 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 20:47:16 +0900 Subject: [PATCH 0011/1002] =?UTF-8?q?[SC-58]=20Feat=20:=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/EmailAuthExistException.java | 19 ++++++ .../email/controller/EmailController.java | 43 ++++++++++++++ .../email/dto/JoinEmailCheckRequestDto.java | 22 +++++++ .../email/dto/JoinEmailSendRequestDto.java | 20 +++++++ .../domain/email/entity/EmailAuthEntity.java | 41 +++++++++++++ .../email/repository/EmailAuthRepository.java | 15 +++++ .../email/service/EmailAuthService.java | 58 +++++++++++++++++++ 7 files changed, 218 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java new file mode 100644 index 00000000..a94b2de8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java @@ -0,0 +1,19 @@ +package inu.codin.codin.common.exception; + +import lombok.Getter; + +@Getter +public class EmailAuthExistException extends RuntimeException { + + private final String email; + + public EmailAuthExistException(String email) { + super("이미 인증된 이메일입니다."); + this.email = email; + } + + public EmailAuthExistException(String message, String email) { + super(message); + this.email = email; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java new file mode 100644 index 00000000..816cab87 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -0,0 +1,43 @@ +package inu.codin.codin.domain.email.controller; + +import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.service.EmailAuthService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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; + +@RestController +@RequestMapping("/api/email") +@Tag(name = "Email API") +@RequiredArgsConstructor +public class EmailController { + + private final EmailAuthService emailAuthService; + + @Operation(summary = "이메일 인증 코드 전송 - 학교인증 X") + @PostMapping("/auth/send") + public ResponseEntity sendJoinAuthEmail( + @RequestBody @Validated JoinEmailSendRequestDto emailAuthRequestDto + ) { + emailAuthService.sendAuthEmail(emailAuthRequestDto); + return ResponseEntity.ok("Good : " + emailAuthRequestDto.getEmail()); + } + + @Operation(summary = "이메일 인증 코드 확인 - 학교인증 X") + @PostMapping("/auth/check") + public ResponseEntity checkAuthNum( + @Valid @RequestBody JoinEmailCheckRequestDto joinEmailCheckRequestDto + ) { + emailAuthService.checkAuthNum(joinEmailCheckRequestDto); + return ResponseEntity.ok("Good : " + joinEmailCheckRequestDto.getEmail() + " " + joinEmailCheckRequestDto.getAuthNum()); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java new file mode 100644 index 00000000..35af1cf6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.email.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 이메일 인증 코드 확인 요청 DTO + * 해당 이메일로 전송된 인증 코드를 확인한다. + */ +@Data +public class JoinEmailCheckRequestDto { + + @Schema(description = "이메일 주소", example = "example@inu.ac.kr") + @Email @NotBlank + private String email; + + @Schema(description = "인증 코드 (6자리)", example = "123456") + @NotBlank + private String authNum; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java new file mode 100644 index 00000000..d66af5c6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.email.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 이메일 인증 코드 전송 요청 DTO + * 해당 이메일로 인증 코드를 전송한다. + */ +@Data +public class JoinEmailSendRequestDto { + + @Schema(description = "이메일 주소", example = "example@inu.ac.kr") + @Email @NotNull @NotEmpty + private String email; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java new file mode 100644 index 00000000..c1fbe19d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.email.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "auth-emails") +@Getter +public class EmailAuthEntity extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + @NotBlank + private String email; + + @NotBlank + private String authNum; + + private boolean isVerified = false; + + private String userId = null; + + @Builder + public EmailAuthEntity(String email, String authNum, boolean isVerified, String userId) { + this.email = email; + this.authNum = authNum; + } + + public void verifyEmail() { + this.isVerified = true; + } + + public void setUserId(String userId) { + this.userId = userId; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java new file mode 100644 index 00000000..dcad7f54 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.email.repository; + +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface EmailAuthRepository extends MongoRepository { + + Boolean existsByEmail(String email); + + Optional findByEmailAndAuthNum(String email, String authNum); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java new file mode 100644 index 00000000..8d102b26 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -0,0 +1,58 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.common.exception.EmailAuthExistException; +import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EmailAuthService { + + private final EmailAuthRepository emailAuthRepository; + + // + 비동기 방식 고려 + public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { + + String email = joinEmailSendRequestDto.getEmail(); + log.info("[sendAuthEmail] email : {}", email); + + if (emailAuthRepository.existsByEmail(email)) + throw new EmailAuthExistException(email); + + EmailAuthEntity emailAuthEntity = EmailAuthEntity.builder() + .email(email) + .authNum(generateAuthNum()) + .build(); + + emailAuthRepository.save(emailAuthEntity); + + // + 이메일 전송 로직 추가 + } + + private String generateAuthNum() { + // 8자리 인증번호 생성 + return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + } + + public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { + + String email = joinEmailCheckRequestDto.getEmail(); + String authNum = joinEmailCheckRequestDto.getAuthNum(); + log.info("[checkAuthNum] email : {}, authNum : {}", email, authNum); + + // 이메일과 인증번호가 일치하는지 확인 + EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) + .orElseThrow(() -> new IllegalArgumentException("인증번호가 일치하지 않습니다.")); + + log.info("[checkAuthNum] Email Auth SUCCESS!!, email : {}", email); + emailAuthEntity.verifyEmail(); + } +} \ No newline at end of file From 1b8615bec34367082afe87e95e7b174b6a097e70 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 21:17:12 +0900 Subject: [PATCH 0012/1002] =?UTF-8?q?[SC-58]=20Feat=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=A0=84=EC=86=A1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - application-email.yml 추가 --- .../codin/codin/common/config/MailConfig.java | 60 +++++++++++++++++++ .../email/service/EmailAuthService.java | 2 + .../email/service/EmailSendService.java | 40 +++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/MailConfig.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/MailConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/MailConfig.java new file mode 100644 index 00000000..1123aef4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/MailConfig.java @@ -0,0 +1,60 @@ +package inu.codin.codin.common.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +@RequiredArgsConstructor +public class MailConfig { + + private static final String MAIL_SMTP_AUTH = "mail.smtp.auth"; + private static final String MAIL_DEBUG = "mail.smtp.debug"; + private static final String MAIL_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout"; + private static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable"; + + // SMTP 서버 + @Value("${spring.mail.host}") + private String host; + // 계정 + @Value("${spring.mail.username}") + private String username; + // 비밀번호 + @Value("${spring.mail.password}") + private String password; + // 포트번호 + @Value("${spring.mail.port}") + private int port; + + @Value("${spring.mail.properties.mail.smtp.auth}") + private boolean auth; + @Value("${spring.mail.properties.mail.smtp.debug}") + private boolean debug; + @Value("${spring.mail.properties.mail.smtp.connectiontimeout}") + private int connectionTimeout; + @Value("${spring.mail.properties.mail.starttls.enable}") + private boolean startTlsEnable; + + @Bean + public JavaMailSender javaMailSender() { + + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(host); + mailSender.setPort(port); + mailSender.setUsername(username); + mailSender.setPassword(password); + + Properties properties = mailSender.getJavaMailProperties(); + properties.put(MAIL_SMTP_AUTH, auth); + properties.put(MAIL_DEBUG, debug); + properties.put(MAIL_CONNECTION_TIMEOUT, connectionTimeout); + properties.put(MAIL_SMTP_STARTTLS_ENABLE, startTlsEnable); + + return mailSender; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 8d102b26..18a63403 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -17,6 +17,7 @@ public class EmailAuthService { private final EmailAuthRepository emailAuthRepository; + private final EmailSendService emailSendService; // + 비동기 방식 고려 public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { @@ -35,6 +36,7 @@ public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { emailAuthRepository.save(emailAuthEntity); // + 이메일 전송 로직 추가 + emailSendService.sendAuthEmail(email, emailAuthEntity.getAuthNum()); } private String generateAuthNum() { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java new file mode 100644 index 00000000..3ead7330 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java @@ -0,0 +1,40 @@ +package inu.codin.codin.domain.email.service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.spring6.SpringTemplateEngine; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class EmailSendService { + + private final JavaMailSender javaMailSender; + private final SpringTemplateEngine templateEngine; + + // 이메일 전송 로직 + // +템플릿 엔진으로 이메일 전송 추가 필요!! + @Async + public void sendAuthEmail(String email, String authNum) { + MimeMessage message = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); + helper.setTo(email); + helper.setSubject("[CODIN] 회원가입 인증번호입니다."); + helper.setText("인증번호 : " + authNum, true); + javaMailSender.send(message); + log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); + } catch (MessagingException e) { + log.error("[sendAuthEmail] 인증 이메일 전송 실패, email : {}", email); + throw new RuntimeException(e); + } + } +} From 90c6ee2649d6cbfb684ecda8008f8889f716c25b Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 21:59:45 +0900 Subject: [PATCH 0013/1002] [SC-58] Rename : EmailAuthFailException EmailAuthExistException -> EmailAuthFailException --- ...lAuthExistException.java => EmailAuthFailException.java} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename codin-core/src/main/java/inu/codin/codin/common/exception/{EmailAuthExistException.java => EmailAuthFailException.java} (58%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthFailException.java similarity index 58% rename from codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java rename to codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthFailException.java index a94b2de8..cdc87c9f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthExistException.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthFailException.java @@ -3,16 +3,16 @@ import lombok.Getter; @Getter -public class EmailAuthExistException extends RuntimeException { +public class EmailAuthFailException extends RuntimeException { private final String email; - public EmailAuthExistException(String email) { + public EmailAuthFailException(String email) { super("이미 인증된 이메일입니다."); this.email = email; } - public EmailAuthExistException(String message, String email) { + public EmailAuthFailException(String message, String email) { super(message); this.email = email; } From 106623821b0a6170fe919b1340c1e5408bd50df1 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 22:00:45 +0900 Subject: [PATCH 0014/1002] =?UTF-8?q?[SC-58]=20Feat=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=9E=AC=EC=9D=B8=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/repository/EmailAuthRepository.java | 3 +- .../email/service/EmailAuthService.java | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java index dcad7f54..5ca1a667 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java @@ -9,7 +9,8 @@ @Repository public interface EmailAuthRepository extends MongoRepository { - Boolean existsByEmail(String email); + Optional findByEmail(String email); Optional findByEmailAndAuthNum(String email, String authNum); + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 18a63403..6838c58c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.common.exception.EmailAuthExistException; +import inu.codin.codin.common.exception.EmailAuthFailException; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Optional; import java.util.UUID; @Service @@ -25,17 +26,29 @@ public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { String email = joinEmailSendRequestDto.getEmail(); log.info("[sendAuthEmail] email : {}", email); - if (emailAuthRepository.existsByEmail(email)) - throw new EmailAuthExistException(email); + Optional emailAuth = emailAuthRepository.findByEmail(email); + EmailAuthEntity emailAuthEntity; - EmailAuthEntity emailAuthEntity = EmailAuthEntity.builder() - .email(email) - .authNum(generateAuthNum()) - .build(); + // 재인증 로직 + if (emailAuth.isPresent()) { + emailAuthEntity = emailAuth.get(); + if (emailAuthEntity.isVerified()) { + throw new EmailAuthFailException("이미 인증된 이메일입니다.", email); + } + + emailAuthEntity.changeAuthNum(generateAuthNum()); + } + else { + // 인증 생성 로직 + emailAuthEntity = EmailAuthEntity.builder() + .email(email) + .authNum(generateAuthNum()) + .build(); + } emailAuthRepository.save(emailAuthEntity); - // + 이메일 전송 로직 추가 + // 이메일 전송 로직 emailSendService.sendAuthEmail(email, emailAuthEntity.getAuthNum()); } From 7cbfa1e2c345c11fa6b3761fe3a5b6b791733868 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 22:02:24 +0900 Subject: [PATCH 0015/1002] =?UTF-8?q?[SC-58]=20Remove=20:=20EmailAuthEntit?= =?UTF-8?q?y=20=EB=82=B4=EC=9D=98=20userID=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 유저엔티티와 연결성 있는 변수가 아직 필요 없는것 같아서 삭제 --- .../codin/codin/domain/email/entity/EmailAuthEntity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index c1fbe19d..6b835799 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -22,8 +22,6 @@ public class EmailAuthEntity extends BaseTimeEntity { private boolean isVerified = false; - private String userId = null; - @Builder public EmailAuthEntity(String email, String authNum, boolean isVerified, String userId) { this.email = email; @@ -34,8 +32,4 @@ public void verifyEmail() { this.isVerified = true; } - public void setUserId(String userId) { - this.userId = userId; - } - } From 797db392d371fc997dd16a66922e098d4c480761 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 22:03:03 +0900 Subject: [PATCH 0016/1002] =?UTF-8?q?[SC-58]=20Feat=20:=20EmailAuthEntity?= =?UTF-8?q?=20=EB=82=B4=EC=9D=98=20authNum=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/email/entity/EmailAuthEntity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 6b835799..90d67fe5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -28,6 +28,10 @@ public EmailAuthEntity(String email, String authNum, boolean isVerified, String this.authNum = authNum; } + public void changeAuthNum(String authNum) { + this.authNum = authNum; + } + public void verifyEmail() { this.isVerified = true; } From 4b935973c9375b874e4aebf1afb7d0999d3e91ef Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 22:17:17 +0900 Subject: [PATCH 0017/1002] =?UTF-8?q?[SC-58]=20Fix=20:=20EmailAuthEntity?= =?UTF-8?q?=20Mapping=20Error=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인증시 entity를 불러오는 과정에서 문제 발생 오류 메세지 ''' No property userId found on entity class inu.codin.codin.domain.email.entity.EmailAuthEntity to bind constructor parameter to ''' 해결 - 불필요한 생성자 파라미터 삭제 --- .../inu/codin/codin/domain/email/entity/EmailAuthEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 90d67fe5..4bd1c890 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -23,7 +23,7 @@ public class EmailAuthEntity extends BaseTimeEntity { private boolean isVerified = false; @Builder - public EmailAuthEntity(String email, String authNum, boolean isVerified, String userId) { + public EmailAuthEntity(String email, String authNum) { this.email = email; this.authNum = authNum; } From f94fa7e53347e28e3704fee856023939e2dfde45 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 22:18:44 +0900 Subject: [PATCH 0018/1002] =?UTF-8?q?[SC-58]=20Feat=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=EC=97=90=20Template=20=EC=97=94=EC=A7=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/email/service/EmailSendService.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java index 3ead7330..3c24b4da 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java @@ -9,6 +9,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.context.Context; import org.thymeleaf.spring6.SpringTemplateEngine; @Service @@ -27,9 +28,17 @@ public void sendAuthEmail(String email, String authNum) { MimeMessage message = javaMailSender.createMimeMessage(); try { MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); + + Context context = new Context(); + context.setVariable("authNum", authNum); + + // 템플릿 엔진을 사용하여 HTML 내용을 생성 + String htmlContent = templateEngine.process("auth-email", context); + helper.setTo(email); helper.setSubject("[CODIN] 회원가입 인증번호입니다."); - helper.setText("인증번호 : " + authNum, true); + helper.setText(htmlContent, true); + javaMailSender.send(message); log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); } catch (MessagingException e) { From 5eb16d6e4ebe4171068fd8420d6a0060aa345acf Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 7 Nov 2024 22:22:42 +0900 Subject: [PATCH 0019/1002] =?UTF-8?q?[SC-58]=20Perf=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/email/entity/EmailAuthEntity.java | 8 ++++++++ .../codin/domain/email/service/EmailAuthService.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 4bd1c890..d53fcb6d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -7,6 +7,8 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import java.time.LocalDateTime; + @Document(collection = "auth-emails") @Getter public class EmailAuthEntity extends BaseTimeEntity { @@ -36,4 +38,10 @@ public void verifyEmail() { this.isVerified = true; } + /** + * 10분 이상이면 만료됌 + */ + public boolean isExpired() { + return getCreatedAt().plusMinutes(10).isBefore(LocalDateTime.now()); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 6838c58c..abf4842a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -67,6 +67,11 @@ public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) .orElseThrow(() -> new IllegalArgumentException("인증번호가 일치하지 않습니다.")); + // 10분 이내에 인증하지 않을 시에 인증번호 만료 + if (emailAuthEntity.isExpired()) { + throw new EmailAuthFailException("인증번호가 만료되었습니다.", email); + } + log.info("[checkAuthNum] Email Auth SUCCESS!!, email : {}", email); emailAuthEntity.verifyEmail(); } From aa2c8a8e6f96d89a2ece6a62a51bbd4db360c70a Mon Sep 17 00:00:00 2001 From: kbm Date: Fri, 8 Nov 2024 21:51:25 +0900 Subject: [PATCH 0020/1002] =?UTF-8?q?[SC-56]=20Perf=20:=20EmailAuthFailExc?= =?UTF-8?q?eption=20=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 --- .../inu/codin/codin/domain/email/service/EmailAuthService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index abf4842a..0bf001be 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -65,7 +65,7 @@ public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { // 이메일과 인증번호가 일치하는지 확인 EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) - .orElseThrow(() -> new IllegalArgumentException("인증번호가 일치하지 않습니다.")); + .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", email)); // 10분 이내에 인증하지 않을 시에 인증번호 만료 if (emailAuthEntity.isExpired()) { From 8f9112f8f7f449a3a554469202549715fc0a28e1 Mon Sep 17 00:00:00 2001 From: kbm Date: Fri, 8 Nov 2024 21:51:51 +0900 Subject: [PATCH 0021/1002] =?UTF-8?q?[SC-56]=20Comment=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/email/service/EmailAuthService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 0bf001be..cca4a2c1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -20,7 +20,6 @@ public class EmailAuthService { private final EmailAuthRepository emailAuthRepository; private final EmailSendService emailSendService; - // + 비동기 방식 고려 public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { String email = joinEmailSendRequestDto.getEmail(); @@ -33,6 +32,7 @@ public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { if (emailAuth.isPresent()) { emailAuthEntity = emailAuth.get(); + // 이미 인증된 이메일 체크 if (emailAuthEntity.isVerified()) { throw new EmailAuthFailException("이미 인증된 이메일입니다.", email); } @@ -48,7 +48,7 @@ public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { } emailAuthRepository.save(emailAuthEntity); - // 이메일 전송 로직 + // 비동기 이메일 전송 로직 emailSendService.sendAuthEmail(email, emailAuthEntity.getAuthNum()); } From ee50972eeb00f04c2fc67eb891af1bb0ef301567 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 17:59:54 +0900 Subject: [PATCH 0022/1002] =?UTF-8?q?[SC-56]=20Feat=20:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/user/entity/Department.java | 18 +++++++ .../codin/domain/user/entity/UserEntity.java | 47 +++++++++++++++++++ .../codin/domain/user/entity/UserRole.java | 16 +++++++ .../codin/domain/user/entity/UserStatus.java | 16 +++++++ 4 files changed, 97 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserRole.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserStatus.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java new file mode 100644 index 00000000..1a654d04 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java @@ -0,0 +1,18 @@ +package inu.codin.codin.domain.user.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Department { + + IT_COLLEGE("정보기술대학"), + COMPUTER_SCI("컴퓨터공학과"), + INFO_COMM("정보통신공학과"), + EMBEDDED("임베디드시스템공학과"), + STAFF("교직원"); + + private final String description; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java new file mode 100644 index 00000000..7c10bfc9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -0,0 +1,47 @@ +package inu.codin.codin.domain.user.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "users") +@Getter +public class UserEntity extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + private String email; + + private String password; + + private String studentId; + + private String name; + + private String nickname; + + private String profileImageUrl; + + private Department department; + + private UserRole role; + + private UserStatus status; + + @Builder + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status) { + this.email = email; + this.password = password; + this.studentId = studentId; + this.name = name; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.department = department; + this.role = role; + this.status = status; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserRole.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserRole.java new file mode 100644 index 00000000..075d837b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserRole.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.user.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum UserRole { + + ADMIN("관리자"), + MANAGER("교직원"), + USER("사용자"); + + private final String description; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserStatus.java new file mode 100644 index 00000000..47d062f9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserStatus.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.user.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum UserStatus { + + ACTIVE("활성"), + DISABLED("비활성"), + SUSPENDED("정지"); + + private final String description; + +} From 541c30aa9e328c95a8604b35c79b8f50ef270f1e Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 18:00:18 +0900 Subject: [PATCH 0023/1002] =?UTF-8?q?[SC-56]=20Feat=20:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 6 ++ .../user/controller/UserController.java | 27 ++++++++ .../domain/user/dto/UserCreateRequestDto.java | 43 +++++++++++++ .../exception/UserCreateFailException.java | 7 +++ .../user/repository/UserRepository.java | 16 +++++ .../domain/user/service/UserService.java | 61 +++++++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserCreateFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index eda54d1f..e45f25de 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -9,6 +9,8 @@ import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @@ -43,4 +45,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java new file mode 100644 index 00000000..f83ac652 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.user.controller; + +import inu.codin.codin.domain.user.dto.UserCreateRequestDto; +import inu.codin.codin.domain.user.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/users") +@Tag(name = "User API") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @Operation(summary = "회원가입") + @PostMapping("/signup") + public ResponseEntity signUpUser( + @RequestBody @Valid UserCreateRequestDto userCreateRequestDto) { + userService.signUpUser(userCreateRequestDto); + return ResponseEntity.ok("회원가입 성공"); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java new file mode 100644 index 00000000..3b784e14 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java @@ -0,0 +1,43 @@ +package inu.codin.codin.domain.user.dto; + +import inu.codin.codin.domain.user.entity.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.Getter; + +@Data +@Getter +public class UserCreateRequestDto { + + // todo : 길이 관련 validation 추가 + + @Schema(description = "이메일 주소", example = "codin@inu.ac.kr") + @NotBlank + private String email; + + @Schema(description = "비밀번호", example = "password") + @NotBlank + private String password; + + @Schema(description = "학번", example = "20210000") + @NotBlank + private String studentId; + + @Schema(description = "이름", example = "홍길동") + @NotBlank + private String name; + + @Schema(description = "닉네임", example = "코딩") + @NotBlank + private String nickname; + + //todo 프로필 이미지 업로드 처리 ex) 이미지 크기 제한.. + @Schema(description = "프로필 이미지 URL", example = "https://avatars.githubusercontent.com/u/77490521?v=4") + @NotBlank + private String profileImageUrl; + + @Schema(description = "소속", example = "IT_COLLEGE") + private Department department; + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserCreateFailException.java new file mode 100644 index 00000000..6b6adbbf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserCreateFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.user.exception; + +public class UserCreateFailException extends RuntimeException { + public UserCreateFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java new file mode 100644 index 00000000..5868cc55 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.user.repository; + +import inu.codin.codin.domain.user.entity.UserEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends MongoRepository { + + Optional findByEmail(String email); + + Optional findByStudentId(String studentId); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java new file mode 100644 index 00000000..5acd5eaf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -0,0 +1,61 @@ +package inu.codin.codin.domain.user.service; + +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.user.dto.UserCreateRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserService { + + private final EmailAuthRepository emailAuthRepository; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + public void signUpUser(UserCreateRequestDto userCreateRequestDto) { + + String encodedPassword = passwordEncoder.encode(userCreateRequestDto.getPassword()); + + validateUserCreateRequest(userCreateRequestDto); + log.info("[signUpUser] UserCreateRequestDto : {}", userCreateRequestDto); + + // todo : 중복 이메일, 닉네임 체크, 유저 상태, 유저 역할 변경 기능 추가 + UserEntity user = UserEntity.builder() + .email(userCreateRequestDto.getEmail()) + .password(encodedPassword) + .studentId(userCreateRequestDto.getStudentId()) + .name(userCreateRequestDto.getName()) + .nickname(userCreateRequestDto.getNickname()) + .profileImageUrl(userCreateRequestDto.getProfileImageUrl()) + .department(userCreateRequestDto.getDepartment()) + .status(UserStatus.ACTIVE) + .role(UserRole.USER) + .build(); + + userRepository.save(user); + log.info("[signUpUser] SIGN UP SUCCESS!! : {}", user.getEmail()); + } + + // todo : 정책적으로 보안 위반 사항 확인 -> 에러 메세지를 통해서 유추 금지 + private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto) { + EmailAuthEntity emailAuth = emailAuthRepository.findByEmail(userCreateRequestDto.getEmail()).orElseThrow(() -> + new UserCreateFailException("이메일 인증을 먼저 진행해주세요.")); + if (!emailAuth.isVerified()) + throw new UserCreateFailException("이메일 인증을 먼저 진행해주세요."); + if (userRepository.findByEmail(userCreateRequestDto.getEmail()).isPresent()) + throw new UserCreateFailException("이미 존재하는 이메일입니다."); + if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) + throw new UserCreateFailException("이미 존재하는 학번입니다."); + } + +} From 7225896eec221d239d677e341b65fa79d84ea449 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 18:01:28 +0900 Subject: [PATCH 0024/1002] =?UTF-8?q?[SC-56]=20Fix=20:=20JoinEmailSend=20D?= =?UTF-8?q?TO=20Valid=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/email/dto/JoinEmailSendRequestDto.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java index d66af5c6..877198e9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java @@ -2,8 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -14,7 +13,7 @@ public class JoinEmailSendRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") - @Email @NotNull @NotEmpty + @Email @NotBlank private String email; } From 1fef0aa6e716f8c10b9b7edc560e9c8556354445 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 18:02:24 +0900 Subject: [PATCH 0025/1002] =?UTF-8?q?[SC-56]=20Fix=20:=20Email=20verify=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - verify 를 true로 바꿔놓고 저장을 안함. --- .../codin/codin/domain/email/service/EmailAuthService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index cca4a2c1..5dc598a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.common.exception.EmailAuthFailException; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; @@ -72,7 +72,8 @@ public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { throw new EmailAuthFailException("인증번호가 만료되었습니다.", email); } - log.info("[checkAuthNum] Email Auth SUCCESS!!, email : {}", email); emailAuthEntity.verifyEmail(); + emailAuthRepository.save(emailAuthEntity); + log.info("[checkAuthNum] Email AUTH SUCCESS!!, email : {}", email); } } \ No newline at end of file From e76f91db9ca9a898485726ea1cdb7c21017ebb8e Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 18:03:31 +0900 Subject: [PATCH 0026/1002] =?UTF-8?q?[SC-56]=20Fix=20:=20Email=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=EA=B0=84=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 - getUpdated 기준으로 만료시간 측정하도록 로직 변경 --- .../inu/codin/codin/domain/email/entity/EmailAuthEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index d53fcb6d..9a088990 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -42,6 +42,6 @@ public void verifyEmail() { * 10분 이상이면 만료됌 */ public boolean isExpired() { - return getCreatedAt().plusMinutes(10).isBefore(LocalDateTime.now()); + return getUpdatedAt().plusMinutes(10).isBefore(LocalDateTime.now()); } } From 9a7df3658a92296be6c9f30eb4ea17af23263eaa Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 18:04:55 +0900 Subject: [PATCH 0027/1002] =?UTF-8?q?[SC-56]=20Refactor=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Exception=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9C=84=EC=B9=98=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/email/controller/EmailController.java | 9 ++++----- .../email}/exception/EmailAuthFailException.java | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) rename codin-core/src/main/java/inu/codin/codin/{common => domain/email}/exception/EmailAuthFailException.java (89%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 816cab87..7d3af59b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -8,7 +8,6 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,19 +24,19 @@ public class EmailController { @Operation(summary = "이메일 인증 코드 전송 - 학교인증 X") @PostMapping("/auth/send") public ResponseEntity sendJoinAuthEmail( - @RequestBody @Validated JoinEmailSendRequestDto emailAuthRequestDto + @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { emailAuthService.sendAuthEmail(emailAuthRequestDto); - return ResponseEntity.ok("Good : " + emailAuthRequestDto.getEmail()); + return ResponseEntity.ok("이메일 인증 코드 전송 성공"); } @Operation(summary = "이메일 인증 코드 확인 - 학교인증 X") @PostMapping("/auth/check") public ResponseEntity checkAuthNum( - @Valid @RequestBody JoinEmailCheckRequestDto joinEmailCheckRequestDto + @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto ) { emailAuthService.checkAuthNum(joinEmailCheckRequestDto); - return ResponseEntity.ok("Good : " + joinEmailCheckRequestDto.getEmail() + " " + joinEmailCheckRequestDto.getAuthNum()); + return ResponseEntity.ok("이메일 인증성공 - 회원가입 가능"); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailAuthFailException.java similarity index 89% rename from codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailAuthFailException.java index cdc87c9f..eeba6f04 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/EmailAuthFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailAuthFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.exception; +package inu.codin.codin.domain.email.exception; import lombok.Getter; From 81770703b901f90bfa64ae6dc01103a70f2482de Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 10 Nov 2024 22:25:03 +0900 Subject: [PATCH 0028/1002] =?UTF-8?q?[SC-56]=20Fix=20:=20Response=20Messag?= =?UTF-8?q?e=20=EA=B8=80=EC=9E=90=20=EC=9D=B8=EC=BD=94=EB=94=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/email/controller/EmailController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 7d3af59b..dad89d03 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/email") +@RequestMapping(value = "/api/email", produces = "plain/text; charset=utf-8") @Tag(name = "Email API") @RequiredArgsConstructor public class EmailController { From a8275ab3c539d2d9f85db490b6daa343ce9061ac Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 11 Nov 2024 15:54:50 +0900 Subject: [PATCH 0029/1002] =?UTF-8?q?[SC-71]=20feat=20:=20Professor=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EA=B5=AC=EC=B6=95=20=EB=B0=8F=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/Department.java | 8 +++ .../domain/info/professor/Professor.java | 42 +++++++++++++++ .../info/professor/ProfessorController.java | 38 +++++++++++++ .../info/professor/ProfessorRepository.java | 11 ++++ .../info/professor/ProfessorService.java | 28 ++++++++++ .../professor/dto/ProfessorListResDTO.java | 20 +++++++ .../dto/ProfessorThumbnailResDTO.java | 54 +++++++++++++++++++ 7 files changed, 201 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/Department.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/Department.java b/codin-core/src/main/java/inu/codin/codin/common/Department.java new file mode 100644 index 00000000..7463422b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/Department.java @@ -0,0 +1,8 @@ +package inu.codin.codin.common; + +import lombok.Getter; + +@Getter +public enum Department { + CSE, ITE, ESE, COLLEGE +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java new file mode 100644 index 00000000..2feaa31c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.info.professor; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.lab.Lab; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "professor") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Professor { + @Id @NotBlank + private String id; + + @NotBlank + private Department department; + + @NotBlank + private String name; + + @NotBlank + private String image; + + @NotBlank + private String number; + + @NotBlank + private String email; + + private String site; + + @NotBlank + private String field; + + private String subject; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java new file mode 100644 index 00000000..15e410fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java @@ -0,0 +1,38 @@ +package inu.codin.codin.domain.info.professor; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; +import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/professor") +@Tag(name = "Professor Info API") +public class ProfessorController { + + private final ProfessorService professorService; + + @Operation(summary = "교수 목록 반환") + @GetMapping("/{department}") + public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ + return ResponseEntity.ok() + .body(professorService.getProfessorByDepartment(department)); + } + + @Operation(summary = "id값에 따른 교수 썸네일 반환") + @GetMapping("/thumbnail/{id}") + public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ + return ResponseEntity.ok() + .body(professorService.getProfessorThumbnail(id)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java new file mode 100644 index 00000000..e33e992c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.info.professor; + +import inu.codin.codin.common.Department; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface ProfessorRepository extends MongoRepository { + + List findAllByDepartment(Department department); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java new file mode 100644 index 00000000..fae0eb98 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.info.professor; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; +import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ProfessorService { + + private final ProfessorRepository professorRepository; + + public List getProfessorByDepartment(Department department){ + List professors = professorRepository.findAllByDepartment(department); + return professors.stream().map(ProfessorListResDTO::of).toList(); + } + + public ProfessorThumbnailResDTO getProfessorThumbnail(String id) { + Professor professor = professorRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("not found")); + return ProfessorThumbnailResDTO.of(professor); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java new file mode 100644 index 00000000..c6f94a69 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.info.professor.dto; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.Professor; +import lombok.Builder; + + +public record ProfessorListResDTO(String id, String name, Department department) { + @Builder + public ProfessorListResDTO { + } + + public static ProfessorListResDTO of(Professor professor) { + return ProfessorListResDTO.builder() + .id(professor.getId()) + .name(professor.getName()) + .department(professor.getDepartment()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java new file mode 100644 index 00000000..da7628ce --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java @@ -0,0 +1,54 @@ +package inu.codin.codin.domain.info.professor.dto; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.Professor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ProfessorThumbnailResDTO { + + private final Department department; + + private final String name; + + private final String image; + + private final String number; + + private final String email; + + private final String site; + + private final String field; + + private final String subject; + + + @Builder + public ProfessorThumbnailResDTO(Department department, String name, String image, String number, String email, String site, String field, String subject) { + this.department = department; + this.name = name; + this.image = image; + this.number = number; + this.email = email; + this.site = site; + this.field = field; + this.subject = subject; + } + + public static ProfessorThumbnailResDTO of(Professor professor){ + return ProfessorThumbnailResDTO.builder() + .department(professor.getDepartment()) + .name(professor.getName()) + .image(professor.getImage()) + .number(professor.getNumber()) + .email(professor.getEmail()) + .site(professor.getSite()) + .field(professor.getField()) + .subject(professor.getSubject()) + .build(); + } +} From 215e7330c50484a29b3b0face8847b0e62e1b455 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 11 Nov 2024 16:06:12 +0900 Subject: [PATCH 0030/1002] =?UTF-8?q?[SC-71]=20refactor=20:=20package=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/professor/{ => controller}/ProfessorController.java | 3 ++- .../codin/domain/info/professor/{ => domain}/Professor.java | 2 +- .../codin/domain/info/professor/dto/ProfessorListResDTO.java | 2 +- .../domain/info/professor/dto/ProfessorThumbnailResDTO.java | 2 +- .../info/professor/{ => repository}/ProfessorRepository.java | 3 ++- .../info/professor/{ => service}/ProfessorService.java | 5 +++-- 6 files changed, 10 insertions(+), 7 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/professor/{ => controller}/ProfessorController.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/professor/{ => domain}/Professor.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/info/professor/{ => repository}/ProfessorRepository.java (70%) rename codin-core/src/main/java/inu/codin/codin/domain/info/professor/{ => service}/ProfessorService.java (83%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java index 15e410fa..5bf0b184 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java @@ -1,6 +1,7 @@ -package inu.codin.codin.domain.info.professor; +package inu.codin.codin.domain.info.professor.controller; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.service.ProfessorService; import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/domain/Professor.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/professor/domain/Professor.java index 2feaa31c..481d9fda 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/domain/Professor.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.professor; +package inu.codin.codin.domain.info.professor.domain; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.lab.Lab; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java index c6f94a69..cb8e5673 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.dto; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.professor.Professor; +import inu.codin.codin.domain.info.professor.domain.Professor; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java index da7628ce..5ebab5f2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.dto; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.professor.Professor; +import inu.codin.codin.domain.info.professor.domain.Professor; import lombok.Builder; import lombok.Getter; import lombok.Setter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java index e33e992c..615b2f01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java @@ -1,6 +1,7 @@ -package inu.codin.codin.domain.info.professor; +package inu.codin.codin.domain.info.professor.repository; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.domain.Professor; import org.springframework.data.mongodb.repository.MongoRepository; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java similarity index 83% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java index fae0eb98..60557b4d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java @@ -1,10 +1,11 @@ -package inu.codin.codin.domain.info.professor; +package inu.codin.codin.domain.info.professor.service; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.professor.domain.Professor; import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; From 5326ef610af3000f9ac5b9976908290060f426ae Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 11 Nov 2024 16:47:56 +0900 Subject: [PATCH 0031/1002] =?UTF-8?q?[SC-66]=20Feat=20:=20Post=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/entity/PostCategory.java | 19 +++++++++ .../codin/domain/post/entity/PostEntity.java | 42 +++++++++++++++++++ .../codin/domain/post/entity/PostStatus.java | 17 ++++++++ 3 files changed, 78 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java new file mode 100644 index 00000000..4a028855 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.post.entity; + +import lombok.Getter; + +@Getter +public enum PostCategory { + REQUEST("구해요"), + COMMUNICATION("소통해요"), + EXTRACURRICULAR("비교과"), + INFO("정보대소개"), + USED_BOOK("중고책"); + + private final String description; + + PostCategory(String description) { + this.description = description; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java new file mode 100644 index 00000000..7cb5d4d3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.post.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import lombok.Builder; +import lombok.Getter; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "post") +@Getter +public class PostEntity extends BaseTimeEntity { + @Id + private String postId; + + private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 + + private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) + + private String title; + + private String content; + + private String postImageUrl; + + private boolean isAnonymous; + + private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) + + + @Builder + public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, String postImageUrl, PostStatus postStatus) { + this.postId = postId; + this.userId = userId; + this.postCategory = postCategory; + this.title = title; + this.content = content; + this.isAnonymous = isAnonymous; + this.postImageUrl = postImageUrl; + this.postStatus = postStatus; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java new file mode 100644 index 00000000..2de6a255 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.post.entity; + +import lombok.Getter; + +@Getter +public enum PostStatus { + ACTIVE("활성"), + DISABLED("비활성"), + SUSPENDED("정지"); + + private final String description; + + PostStatus(String description) { + this.description = description; + } + +} From b932b0e2d36bdbfb658bf122409b7f3728bcfb27 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 11 Nov 2024 16:48:37 +0900 Subject: [PATCH 0032/1002] =?UTF-8?q?[SC-66]=20Feat=20:=20Post=20DTO=20cre?= =?UTF-8?q?ate,=20update,=20response=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/PostAnonymousUpdateReqDTO.java | 12 ++++++ .../dto/request/PostContentUpdateReqDTO.java | 20 ++++++++++ .../post/dto/request/PostCreateReqDTO.java | 36 ++++++++++++++++++ .../dto/request/PostStatusUpdateReqDTO.java | 14 +++++++ .../post/dto/response/PostDetailResDTO.java | 38 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java new file mode 100644 index 00000000..45f401d5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.post.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PostAnonymousUpdateReqDTO { + @Schema(description = "익명 여부", example = "true") + @NotNull + private boolean isAnonymous; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java new file mode 100644 index 00000000..842fa8e9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.post.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PostContentUpdateReqDTO { + + @Schema(description = "게시물 제목", example = "Updated Title") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "Updated content") + @NotBlank + private String content; + + @Schema(description = "게시물 내 이미지 url", example = "example/updated_image.jpg") + private String postImageUrl; +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java new file mode 100644 index 00000000..8e68ddcf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.post.dto.request; + +import inu.codin.codin.domain.post.entity.PostCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PostCreateReqDTO { + + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "게시물 종류", example = "구해요") + @NotBlank + private PostCategory postCategory; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private String content; + + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") + private String postImageUrl; + + @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @NotBlank + private boolean isAnonymous; + + //STATUS 필드 - DEFAULT :: ACTIVE + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java new file mode 100644 index 00000000..22c18c5e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.post.dto.request; + +import inu.codin.codin.domain.post.entity.PostStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PostStatusUpdateReqDTO { + @Schema(description = "게시물 상태", example = "ACTIVE") + @NotNull + private PostStatus postStatus; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java new file mode 100644 index 00000000..f5f08c2c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java @@ -0,0 +1,38 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.entity.PostCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PostDetailResDTO { + + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "게시물 ID", example = "111111") + @NotBlank + private String postId; + + @Schema(description = "게시물 종류", example = "구해요") + @NotBlank + private PostCategory postCategory; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private String content; + + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") + private String postImageUrl; + + @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @NotBlank + private boolean isAnonymous; + +} From f4f0df15b26b9d95064b29858f684ac474a04502 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 11 Nov 2024 20:00:41 +0900 Subject: [PATCH 0033/1002] =?UTF-8?q?[SC-57]=20Fix=20:=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20Object=20->=20UserEntity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/user/repository/UserRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 5868cc55..6c477daf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -9,8 +9,8 @@ @Repository public interface UserRepository extends MongoRepository { - Optional findByEmail(String email); + Optional findByEmail(String email); - Optional findByStudentId(String studentId); + Optional findByStudentId(String studentId); } From 9feadab3c8b4239732b18651cceda398bdebdb1e Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 11 Nov 2024 20:01:05 +0900 Subject: [PATCH 0034/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20UserDetails=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/security/CustomUserDetails.java | 101 ++++++++++++++++++ .../security/CustomUserDetailsService.java | 32 ++++++ 2 files changed, 133 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java new file mode 100644 index 00000000..2905bb1e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -0,0 +1,101 @@ +package inu.codin.codin.domain.user.security; + +import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; +import lombok.Builder; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@Getter +public class CustomUserDetails implements UserDetails { + + private final String id; + private final String email; + private final String password; + private final String studentId; + private final String name; + private final String nickname; + private final String profileImageUrl; + private final Department department; + private final UserRole role; + private final UserStatus status; + + private final Collection authorities; + + @Builder + public CustomUserDetails(String id, String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status, Collection authorities) { + this.id = id; + this.email = email; + this.password = password; + this.studentId = studentId; + this.name = name; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.department = department; + this.role = role; + this.status = status; + this.authorities = authorities; + } + + public static CustomUserDetails from(UserEntity userEntity) { + return CustomUserDetails.builder() + .id(userEntity.getId()) + .email(userEntity.getEmail()) + .password(userEntity.getPassword()) + .studentId(userEntity.getStudentId()) + .name(userEntity.getName()) + .nickname(userEntity.getNickname()) + .profileImageUrl(userEntity.getProfileImageUrl()) + .department(userEntity.getDepartment()) + .role(userEntity.getRole()) + .status(userEntity.getStatus()) + .authorities(Collections.singletonList( + new SimpleGrantedAuthority("ROLE_" + userEntity.getRole().name()))) + .build(); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + // 계정 만료 여부 + return !status.equals(UserStatus.DISABLED); + } + + @Override + public boolean isAccountNonLocked() { + // 계정 잠금 여부 + return !status.equals(UserStatus.SUSPENDED); + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + // 계정 활성화 여부 + return status.equals(UserStatus.ACTIVE); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java new file mode 100644 index 00000000..e4a5d995 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.user.security; + +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserStatus; +import inu.codin.codin.domain.user.exception.UserDisabledException; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + + UserEntity user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음, email :" + email)); + + if (!UserStatus.ACTIVE.equals(user.getStatus())) { + throw new UserDisabledException("유저가 활성화되지 않았습니다"); + } + + return CustomUserDetails.from(user); + } + +} From 052b42b8be05d7ac1fcf5cf205c10421e39a04b7 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 11 Nov 2024 20:01:19 +0900 Subject: [PATCH 0035/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20UserDisabledExce?= =?UTF-8?q?ption=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/exception/UserDisabledException.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserDisabledException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserDisabledException.java b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserDisabledException.java new file mode 100644 index 00000000..af027a9b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserDisabledException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.user.exception; + +import org.springframework.security.authentication.AccountStatusException; + +public class UserDisabledException extends AccountStatusException { + public UserDisabledException(String message) { + super(message); + } + + public UserDisabledException(String message, Throwable cause) { + super(message, cause); + } +} From 93c92da371dc0aa2bcbecaea9ca721da4432d9e3 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 12 Nov 2024 10:45:13 +0900 Subject: [PATCH 0036/1002] =?UTF-8?q?[SC-66]=20Fix=20:=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20:=20@NotN?= =?UTF-8?q?ull=20->=20@NotBlank?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/dto/request/PostAnonymousUpdateReqDTO.java | 4 ++-- .../codin/domain/post/dto/request/PostStatusUpdateReqDTO.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java index 45f401d5..08e84d51 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java @@ -1,12 +1,12 @@ package inu.codin.codin.domain.post.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class PostAnonymousUpdateReqDTO { @Schema(description = "익명 여부", example = "true") - @NotNull + @NotBlank private boolean isAnonymous; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java index 22c18c5e..96ac66c1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java @@ -2,13 +2,14 @@ import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class PostStatusUpdateReqDTO { @Schema(description = "게시물 상태", example = "ACTIVE") - @NotNull + @NotBlank private PostStatus postStatus; } From b3afba166cafcd227e81fcd311ffd28c7515448c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 12 Nov 2024 13:21:26 +0900 Subject: [PATCH 0037/1002] [SC-67] Feat : post Create API --- .../post/controller/PostController.java | 28 ++++++++++++ .../exception/PostCreateFailException.java | 7 +++ .../post/repository/PostRepository.java | 12 ++++++ .../domain/post/service/PostService.java | 43 +++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java new file mode 100644 index 00000000..daf00f41 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.post.controller; + +import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; +import inu.codin.codin.domain.post.service.PostService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/posts") +public class PostController { + + private final PostService postService; + public PostController(PostService postService) { + this.postService = postService; + } + + @Operation(summary = "게시물 작성") + @PostMapping + public ResponseEntity createPost(@RequestBody @Valid PostCreateReqDTO postCreateReqDTO) { + postService.createPost(postCreateReqDTO); + //게시물 작성 성공시 상태코드 201 Created , 게시물 작성성공 메세지 반환. + return ResponseEntity.status(HttpStatus.CREATED).body("게시물 작성 성공"); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java new file mode 100644 index 00000000..105155d0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.exception; + +public class PostCreateFailException extends RuntimeException { + public PostCreateFailException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java new file mode 100644 index 00000000..12495481 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.post.repository; + +import inu.codin.codin.domain.post.entity.PostEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PostRepository extends MongoRepository { + Optional findByTitle(String title); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java new file mode 100644 index 00000000..47ddc7c9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -0,0 +1,43 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.exception.PostCreateFailException; +import inu.codin.codin.domain.post.repository.PostRepository; +import org.springframework.stereotype.Service; + +@Service +public class PostService { + private final PostRepository postRepository; + public PostService(PostRepository postRepository) { + this.postRepository = postRepository; + } + + + public void createPost(PostCreateReqDTO postCreateReqDTO) { + validateCreatePostRequest(postCreateReqDTO); + + PostEntity postEntity = PostEntity.builder() + .userId(postCreateReqDTO.getUserId()) + .content(postCreateReqDTO.getContent()) + .title(postCreateReqDTO.getTitle()) + .postCategory(postCreateReqDTO.getPostCategory()) + .postImageUrl(postCreateReqDTO.getPostImageUrl()) + .isAnonymous(postCreateReqDTO.isAnonymous()) + + //Default Status = Active + .postStatus(PostStatus.ACTIVE) + + .build(); + postRepository.save(postEntity); + } + + private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { + if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { + throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); + } + + } +} + From 2fb4b50e3d71945ad2d7cf4b9b2ac9e43cca4384 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 12 Nov 2024 13:35:41 +0900 Subject: [PATCH 0038/1002] =?UTF-8?q?[SC-67]=20Fix=20:=20NotBlank=20->=20N?= =?UTF-8?q?otNull=20Enum,boolean=20type=20=EC=9D=BC=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=20NotNull=20=EC=82=AC=EC=9A=A9=20NotBlank=20->=20String=20Type?= =?UTF-8?q?=20=EC=A0=84=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/dto/request/PostCreateReqDTO.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java index 8e68ddcf..51315c53 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data @@ -13,7 +14,7 @@ public class PostCreateReqDTO { private String userId; @Schema(description = "게시물 종류", example = "구해요") - @NotBlank + @NotNull private PostCategory postCategory; @Schema(description = "게시물 제목", example = "Example") @@ -28,7 +29,7 @@ public class PostCreateReqDTO { private String postImageUrl; @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") - @NotBlank + @NotNull private boolean isAnonymous; //STATUS 필드 - DEFAULT :: ACTIVE From fee7d49add9f36170991d1d431ab876ee02a8164 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 12 Nov 2024 13:38:18 +0900 Subject: [PATCH 0039/1002] =?UTF-8?q?[SC-67]=20Refactor=20:=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20:=20description=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=ED=99=9C=EC=9A=A9=ED=95=B4=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EB=A7=A4=ED=95=91=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(REQUEST=20->=20=EA=B5=AC=ED=95=B4=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/entity/PostCategory.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 4a028855..39dc270a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.post.entity; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; @Getter @@ -16,4 +18,19 @@ public enum PostCategory { this.description = description; } + @JsonValue + public String getDescription() { + return description; + } + + @JsonCreator + public static PostCategory fromDescription(String description) { + for (PostCategory category : PostCategory.values()) { + if (category.description.equals(description)) { + return category; + } + } + throw new IllegalArgumentException("Unknown description: " + description); + } + } From 20fa92db802f60d8c7ae5f292c42dd5852e24c11 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 12 Nov 2024 13:44:48 +0900 Subject: [PATCH 0040/1002] =?UTF-8?q?[SC-67]=20Refactor=20:=20@Schema=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/dto/request/PostCreateReqDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java index 51315c53..277df337 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java @@ -28,7 +28,7 @@ public class PostCreateReqDTO { @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") private String postImageUrl; - @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") @NotNull private boolean isAnonymous; From e825dc029cee0eb72b93cac4900a449b9303fe5b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 12 Nov 2024 17:19:07 +0900 Subject: [PATCH 0041/1002] =?UTF-8?q?[SC-71]=20docs=20:=20DTO=20@Scheme=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../professor/dto/ProfessorListResDTO.java | 14 ++++- .../dto/ProfessorThumbnailResDTO.java | 58 ++++++++----------- .../repository/ProfessorRepository.java | 2 +- .../professor/service/ProfessorService.java | 2 +- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java index cb8e5673..62b125b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java @@ -1,11 +1,21 @@ package inu.codin.codin.domain.info.professor.dto; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.professor.domain.Professor; +import inu.codin.codin.domain.info.professor.entity.Professor; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; -public record ProfessorListResDTO(String id, String name, Department department) { +public record ProfessorListResDTO( + @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") + String id, + + @NotBlank @Schema(description = "교수 성함", example = "홍길동") + String name, + + @NotBlank @Schema(description = "학과", example = "CSE") + Department department) { @Builder public ProfessorListResDTO { } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java index 5ebab5f2..50d619ba 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java @@ -1,45 +1,36 @@ package inu.codin.codin.domain.info.professor.dto; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.professor.domain.Professor; +import inu.codin.codin.domain.info.professor.entity.Professor; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ProfessorThumbnailResDTO { - - private final Department department; - - private final String name; - - private final String image; - - private final String number; - - private final String email; - - private final String site; - - private final String field; - - private final String subject; +public record ProfessorThumbnailResDTO( + @NotBlank @Schema(description = "학과", example = "CSE") + Department department, + @NotBlank @Schema(description = "성함", example = "홍길동") + String name, + @NotBlank @Schema(description = "프로필 사진", example = "https://~") + String image, + @NotBlank @Schema(description = "전화번호", example = "032-123-4567") + String number, + @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") + String email, + @Schema(description = "연구실 홈페이지", example = "https://~") + String site, + @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") + String field, + @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") + String subject, + @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") + String labId) { @Builder - public ProfessorThumbnailResDTO(Department department, String name, String image, String number, String email, String site, String field, String subject) { - this.department = department; - this.name = name; - this.image = image; - this.number = number; - this.email = email; - this.site = site; - this.field = field; - this.subject = subject; + public ProfessorThumbnailResDTO { } - public static ProfessorThumbnailResDTO of(Professor professor){ + public static ProfessorThumbnailResDTO of(Professor professor) { return ProfessorThumbnailResDTO.builder() .department(professor.getDepartment()) .name(professor.getName()) @@ -49,6 +40,7 @@ public static ProfessorThumbnailResDTO of(Professor professor){ .site(professor.getSite()) .field(professor.getField()) .subject(professor.getSubject()) + .labId(professor.getLab().getId()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java index 615b2f01..a38ac779 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.repository; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.professor.domain.Professor; +import inu.codin.codin.domain.info.professor.entity.Professor; import org.springframework.data.mongodb.repository.MongoRepository; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java index 60557b4d..b1781924 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.professor.service; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.professor.domain.Professor; import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.professor.entity.Professor; import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; From 905df608e1a000601c7b452d3463577002f6c571 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 12 Nov 2024 17:20:37 +0900 Subject: [PATCH 0042/1002] =?UTF-8?q?[SC-71]=20feat=20:=20Lab=20=EC=97=B0?= =?UTF-8?q?=EA=B5=AC=EC=8B=A4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B5=AC?= =?UTF-8?q?=EC=B6=95=20=EB=B0=8F=20=EB=B0=98=ED=99=98=20API=20=EC=84=A4?= =?UTF-8?q?=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/lab/controller/LabController.java | 32 +++++++++++++ .../info/lab/dto/LabThumbnailResDTO.java | 48 +++++++++++++++++++ .../codin/domain/info/lab/entity/Lab.java | 40 ++++++++++++++++ .../info/lab/repository/LabRepository.java | 7 +++ .../domain/info/lab/service/LabService.java | 44 +++++++++++++++++ .../{domain => entity}/Professor.java | 10 +++- 6 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java rename codin-core/src/main/java/inu/codin/codin/domain/info/professor/{domain => entity}/Professor.java (80%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java new file mode 100644 index 00000000..421fd56c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.info.lab.controller; + +import inu.codin.codin.domain.info.lab.service.LabService; +import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/lab") +public class LabController { + + private final LabService labService; + + @Operation(summary = "연구실 썸네일 반환") + @GetMapping("/thumbnail/{id}") + public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ + return ResponseEntity.ok() + .body(labService.getLabThumbnail(id)); + } + + //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 + @GetMapping("/") + public void joinLab(){ + labService.joinlab(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java new file mode 100644 index 00000000..15550ad7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java @@ -0,0 +1,48 @@ +package inu.codin.codin.domain.info.lab.dto; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.lab.entity.Lab; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + + +public record LabThumbnailResDTO( + @NotBlank @Schema(description = "학과", example = "CSE") + Department department, + @NotBlank @Schema(description = "연구실 이름", example = "땡땡연구실") + String title, + @Schema(description = "연구 내용", example = "이것저것 연구합니다.") + String content, + @NotBlank @Schema(description = "담당 교수", example = "홍길동") + String professor, + @Schema(description = "교수실 위치", example = "7호관 423호") + String professorLoc, + @Schema(description = "교수실 전화번호", example = "032-123-4567") + String professorNumber, + @Schema(description = "연구실 위치", example = "7호관 409호") + String labLoc, + @Schema(description = "연구실 전화번호", example = "032-987-0653") + String labNumber, + @Schema(description = "연구실 홈페이지", example = "http://~") + String site) { + + @Builder + public LabThumbnailResDTO { + } + + + public static LabThumbnailResDTO of(Lab lab) { + return LabThumbnailResDTO.builder() + .department(lab.getDepartment()) + .title(lab.getTitle()) + .content(lab.getContent()) + .professor(lab.getProfessor()) + .professorLoc(lab.getProfessorLoc()) + .professorNumber(lab.getProfessorNumber()) + .labLoc(lab.getLabLoc()) + .labNumber(lab.getLabNumber()) + .site(lab.getSite()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java new file mode 100644 index 00000000..53ad75ed --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java @@ -0,0 +1,40 @@ +package inu.codin.codin.domain.info.lab.entity; + +import inu.codin.codin.common.Department; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "lab") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Lab { + + @Id @NotBlank + private String id; + + @NotBlank + private Department department; + + @NotBlank + private String professor; + + @NotBlank + private String title; + + private String content; + + private String professorLoc; + + private String professorNumber; + + private String labLoc; + + private String labNumber; + + private String site; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java new file mode 100644 index 00000000..9612ddeb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.info.lab.repository; + +import inu.codin.codin.domain.info.lab.entity.Lab; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface LabRepository extends MongoRepository { +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java new file mode 100644 index 00000000..b6146c1b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java @@ -0,0 +1,44 @@ +package inu.codin.codin.domain.info.lab.service; + +import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; +import inu.codin.codin.domain.info.lab.entity.Lab; +import inu.codin.codin.domain.info.lab.repository.LabRepository; +import inu.codin.codin.domain.info.professor.entity.Professor; +import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class LabService { + + private final LabRepository labRepository; + private final ProfessorRepository professorRepository; + + public LabThumbnailResDTO getLabThumbnail(String id) { + Lab lab = labRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("not found")); + return LabThumbnailResDTO.of(lab); + } + + //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 + @Transactional + public List joinlab() { + List professors = professorRepository.findAll(); + List labs = labRepository.findAll(); + + for (Professor professor : professors) { + for (Lab lab : labs) { + if (professor.getName().equals(lab.getProfessor())) { + professor.updateLab(lab); + professorRepository.save(professor); + break; + } + } + } + return professors; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/domain/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java similarity index 80% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/domain/Professor.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java index 481d9fda..2df1fa98 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/domain/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.info.professor.domain; +package inu.codin.codin.domain.info.professor.entity; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.lab.Lab; +import inu.codin.codin.domain.info.lab.entity.Lab; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; @@ -39,4 +39,10 @@ public class Professor { private String subject; + @DBRef + private Lab lab; + + public void updateLab(Lab lab){ + this.lab = lab; + } } From 1db141e26440cdd69621d446ff87f65a712971f7 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 12 Nov 2024 20:06:12 +0900 Subject: [PATCH 0043/1002] =?UTF-8?q?[SC-57]=20Refactor=20:=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/user/controller/UserController.java | 2 +- .../java/inu/codin/codin/domain/user/service/UserService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index f83ac652..56852b3c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -21,7 +21,7 @@ public class UserController { @PostMapping("/signup") public ResponseEntity signUpUser( @RequestBody @Valid UserCreateRequestDto userCreateRequestDto) { - userService.signUpUser(userCreateRequestDto); + userService.createUser(userCreateRequestDto); return ResponseEntity.ok("회원가입 성공"); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 5acd5eaf..d79bc208 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -22,7 +22,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; - public void signUpUser(UserCreateRequestDto userCreateRequestDto) { + public void createUser(UserCreateRequestDto userCreateRequestDto) { String encodedPassword = passwordEncoder.encode(userCreateRequestDto.getPassword()); From 0516f371e6a90dd9b7d3bd1de76e9285aedf703c Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 12 Nov 2024 23:01:34 +0900 Subject: [PATCH 0044/1002] =?UTF-8?q?[SC-57]=20Feature=20:=20TimeZone=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/inu/codin/codin/CodinApplication.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index a74dca60..a041ed7e 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -4,12 +4,13 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; +import java.util.TimeZone; + @SpringBootApplication @EnableMongoAuditing public class CodinApplication { - public static void main(String[] args) { + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); SpringApplication.run(CodinApplication.class, args); } - -} +} \ No newline at end of file From 41ba140339e3ca596e46380130ad10506407f30a Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 12 Nov 2024 23:13:07 +0900 Subject: [PATCH 0045/1002] =?UTF-8?q?[SC-57]=20Feature=20:=20Redis=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/RedisStorageService.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java new file mode 100644 index 00000000..e391963f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java @@ -0,0 +1,39 @@ +package inu.codin.codin.common.security.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RedisStorageService { + + private final RedisTemplate redisTemplate; + + /** + * RefreshToken 저장 + * @param username + * @param refreshToken + * @param expiration + */ + public void saveRefreshToken(String username, String refreshToken, long expiration) { + redisTemplate.opsForValue().set("RT:" + username, refreshToken, expiration); + } + + /** + * RefreshToken 조회 + * @param username + * @return refreshToken + */ + public String getStoredRefreshToken(String username) { + return redisTemplate.opsForValue().get("RT:" + username); + } + + /** + * RefreshToken 삭제 + * @param username + */ + public void deleteRefreshToken(String username) { + redisTemplate.delete("RT:" + username); + } +} From f17dcc60ca07f8c173e4b3fe1c56e3825b5aabb8 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 12 Nov 2024 23:14:13 +0900 Subject: [PATCH 0046/1002] =?UTF-8?q?[SC-57]=20Feature=20:=20JWT=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=84=9C=EB=B9=84=EC=8A=A4=EC=99=80=20TokenProvide?= =?UTF-8?q?r=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/exception/JwtException.java | 19 ++ .../security/exception/SecurityErrorCode.java | 21 +++ .../common/security/jwt/JwtTokenProvider.java | 170 ++++++++++++++++++ .../common/security/service/JwtService.java | 69 +++++++ 4 files changed, 279 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java b/codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java new file mode 100644 index 00000000..9079c325 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java @@ -0,0 +1,19 @@ +package inu.codin.codin.common.security.exception; + +import lombok.Getter; + +@Getter +public class JwtException extends RuntimeException { + + private final SecurityErrorCode errorCode; + + public JwtException(SecurityErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public JwtException(SecurityErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java b/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java new file mode 100644 index 00000000..019968f7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java @@ -0,0 +1,21 @@ +package inu.codin.codin.common.security.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SecurityErrorCode { + + INVALID_TOKEN("SEC_001", "유효하지 않은 토큰입니다."), + EXPIRED_TOKEN("SEC_002", "만료된 토큰입니다."), + TOKEN_NOT_FOUND("SEC_003", "토큰이 존재하지 않습니다."), + INVALID_SIGNATURE("SEC_004", "잘못된 토큰 서명입니다."), + ACCESS_DENIED("SEC_005", "접근 권한이 없습니다."), + ACCOUNT_LOCKED("SEC_006", "계정이 잠겼습니다. 관리자에게 문의하세요."), + INVALID_CREDENTIALS("SEC_007", "잘못된 인증 정보입니다."); + + private final String errorCode; + private final String message; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java new file mode 100644 index 00000000..dfa9d80d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -0,0 +1,170 @@ +package inu.codin.codin.common.security.jwt; + +import inu.codin.codin.common.security.service.RedisStorageService; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +/** + * JWT 토큰 생성 및 유효성 검사 + * - 토큰 생성 + * - 토큰 유효성 검사 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtTokenProvider { + + @Value("${spring.jwt.secret}") + private String secret; + @Value("${spring.jwt.expiration.access}") + private String ACCESS_TOKEN_EXPIRATION; + @Value("${spring.jwt.expiration.refresh}") + private String REFRESH_TOKEN_EXPIRATION; + + /** + * bytes[], String 키는 deprecated 되었기 때문에 Key 타입으로 변경 + */ + private Key SECRET_KEY; + private final RedisStorageService redisStorageService; + + /** + * 양방향 대칭키 방식인 HS512로 사용 + */ + @PostConstruct + protected void init() { + SECRET_KEY = Keys.hmacShaKeyFor(secret.getBytes()); + } + + public TokenDto createToken(Authentication authentication) { + // 권한을 authorities에 담아서 String으로 변환 + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .reduce((auth1, auth2) -> auth1 + "," + auth2) + .orElse(""); + + // 토큰 만료시간 설정 + Date now = new Date(); + Date accessTokenExpiration = new Date(now.getTime() + Long.parseLong(this.ACCESS_TOKEN_EXPIRATION)); + Date refreshTokenExpiration = new Date(now.getTime() + Long.parseLong(this.REFRESH_TOKEN_EXPIRATION)); + + // 토큰 생성 + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setIssuedAt(now) + .setExpiration(accessTokenExpiration) + .signWith(SECRET_KEY, SignatureAlgorithm.HS512) + .compact(); + + String refreshToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setIssuedAt(now) + .setExpiration(refreshTokenExpiration) + .signWith(SECRET_KEY, SignatureAlgorithm.HS512) + .compact(); + + // Redis에 기존에 저장된 RefreshToken 삭제 + String beforeRefreshToken = redisStorageService.getStoredRefreshToken(authentication.getName()); + if (beforeRefreshToken != null) { + redisStorageService.deleteRefreshToken(authentication.getName()); + } + + // Redis에 RefreshToken 저장 + redisStorageService.saveRefreshToken( + authentication.getName(), + refreshToken, + Long.parseLong(this.REFRESH_TOKEN_EXPIRATION) + ); + + + return TokenDto.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + /** + * 토큰 유효성 검사 (토큰 변조, 만료) + * @param accessToken + * @return true: 유효한 토큰, false: 유효하지 않은 토큰 + */ + public boolean validateAccessToken(String accessToken) { + try { + Jwts.parserBuilder() + .setSigningKey(SECRET_KEY) + .build() + .parseClaimsJws(accessToken); + return true; + } catch (ExpiredJwtException e) { // 토큰 만료 + log.error("[validateAccessToken] 토큰 만료 : {}", e.getMessage()); + return false; + } catch (Exception e) { // 토큰 변조 + log.error("[validateAccessToken] 유효하지 않은 토큰 : {}", e.getMessage()); + return false; + } + } + + /** + * Refresh Token 유효성 검사 : Redis에 저장된 Refresh Token과 비교 + * @param refreshToken + * @return true: 유효한 토큰, false: 유효하지 않은 토큰 + */ + public boolean validateRefreshToken(String refreshToken) { + try { + // Redis에 저장된 Refresh Token과 비교 + String storedRefreshToken = redisStorageService.getStoredRefreshToken(getClaims(refreshToken).getSubject()); + if (!refreshToken.equals(storedRefreshToken)) { + log.error("[validateRefreshToken] 저장된 Refresh Token과 요청된 Refresh Token이 일치하지 않음"); + return false; + } + return true; + } catch (ExpiredJwtException e) { // 토큰 만료 + log.error("[validateRefreshToken] 토큰 만료 : {}", e.getMessage()); + return false; + } catch (Exception e) { // 토큰 변조 + log.error("[validateRefreshToken] 유효하지 않은 토큰 : {}", e.getMessage()); + return false; + } + } + + /** 토큰에서 username 추출 */ + public String getUsername(String token) { + return getClaims(token).getSubject(); + } + + @Getter + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class TokenDto { + + private String accessToken; + private String refreshToken; + + @Builder + public TokenDto(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + } + + private Claims getClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(SECRET_KEY) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java new file mode 100644 index 00000000..c512c27c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -0,0 +1,69 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +/** + * JWT 토큰 관련 비즈니스 로직을 처리하는 서비스 + * + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class JwtService { + + private final JwtTokenProvider jwtTokenProvider; + private final RedisStorageService redisStorageService; + + /** + * 최초 로그인 시 Access Token, Refresh Token 발급 + * @param response + */ + public void reissueToken(HttpServletResponse response) { + createBothToken(response); + } + + /** + * Reissue : Refresh Token을 이용하여 Access Token, Refresh Token 재발급 + */ + public void reissueToken(String refreshToken, HttpServletResponse response) { + if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 유효하지 않습니다."); + } + createBothToken(response); + } + + /** + * Access Token, Refresh Token 생성 + */ + private void createBothToken(HttpServletResponse response) { + // 새로운 Access Token 발급 + var authentication = SecurityContextHolder.getContext().getAuthentication(); + JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); + + // 응답 헤더에 Access Token 추가 + response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); + + // 쿠키에 새로운 Refresh Token 추가 + Cookie refreshTokenCookie = new Cookie("RefreshToken", newToken.getRefreshToken()); + refreshTokenCookie.setHttpOnly(true); + refreshTokenCookie.setPath("/"); + response.addCookie(refreshTokenCookie); + } + + /** + * 로그아웃 - Refresh Token 삭제 + */ + public void deleteToken() { + var authentication = SecurityContextHolder.getContext().getAuthentication(); + redisStorageService.deleteRefreshToken(authentication.getName()); + } + +} \ No newline at end of file From e2bc3002b4a175fadf8489a1105535cda1994086 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 12 Nov 2024 23:14:57 +0900 Subject: [PATCH 0047/1002] =?UTF-8?q?[SC-57]=20Feature=20:=20JWT=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 13 +++ .../filter/JwtAuthenticationFilter.java | 83 +++++++++++++++++++ .../security/jwt/JwtAuthenticationToken.java | 28 +++++++ 3 files changed, 124 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index e45f25de..5e350c5f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -1,5 +1,8 @@ package inu.codin.codin.common.config; +import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; +import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,15 +12,21 @@ import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + private final UserDetailsService userDetailsService; + private final JwtService jwtService; + String[] PERMIT_ALL = { "/**", "/api/members/sign-up", @@ -40,6 +49,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PERMIT_ALL).permitAll() + ) + .addFilterBefore( + new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtService), + UsernamePasswordAuthenticationFilter.class ); return http.build(); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 00000000..5cb60ac0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,83 @@ +package inu.codin.codin.common.security.filter; + +import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; +import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * JWT 토큰을 검증하여 인증하는 필터 + */ +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + private final UserDetailsService userDetailsService; + private final JwtService jwtService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + String accessToken = getTokenFromHeader(request); + String refreshToken = getTokenFromCookie(request); + + // Access Token이 있는 경우 + if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { + String username = jwtTokenProvider.getUsername(accessToken); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + // Refresh Token이 있는 경우 (Access Token 만료) Access Token, Refresh Token 재발급 + else if (refreshToken != null && jwtTokenProvider.validateRefreshToken(refreshToken)) { + jwtService.reissueToken(refreshToken, response); + } + + filterChain.doFilter(request, response); + } + + /** + * 헤더에서 Access 토큰 추출 + * HTTP Header : "Authorization" : "Bearer ..." + * @return (null, 빈 문자열, "Bearer ")로 시작하지 않는 경우 null 반환 + */ + private String getTokenFromHeader(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + + /** + * 쿠키에서 Refresh 토큰 추출 + * @return 쿠키에 RefreshToken이 없는 경우 null 반환 + */ + private String getTokenFromCookie(HttpServletRequest request) { + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if (cookie.getName().equals("RefreshToken")) { + return cookie.getValue(); + } + } + } + return null; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java new file mode 100644 index 00000000..fa82aab1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java @@ -0,0 +1,28 @@ +package inu.codin.codin.common.security.jwt; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +public class JwtAuthenticationToken extends UsernamePasswordAuthenticationToken { + + public JwtAuthenticationToken(Object principal, Object credentials) { + super(principal, credentials); + } + + public JwtAuthenticationToken(UserDetails userDetails, Collection authorities) { + super(userDetails, null, authorities); + } + + @Override + public Object getPrincipal() { + return (UserDetails) super.getPrincipal(); + } + + @Override + public Object getCredentials() { + return null; + } +} From ff0ed8ad0369b99597ce47d1d0aef86887335511 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 12 Nov 2024 23:15:29 +0900 Subject: [PATCH 0048/1002] =?UTF-8?q?[SC-57]=20Comment=20:=20username=20?= =?UTF-8?q?=3D=20email=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/user/security/CustomUserDetails.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index 2905bb1e..b3243e6f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -71,6 +71,9 @@ public String getPassword() { return password; } + /** + * Spring Security에서 사용하는 유저네임은 email로 사용 + */ @Override public String getUsername() { return email; From 1fca3697395f9105d382ca0f903de7b933c0b125 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:40:34 +0900 Subject: [PATCH 0049/1002] =?UTF-8?q?[SC-57]=20Fix=20:=20Swagger=20?= =?UTF-8?q?=ED=95=9C=EA=B8=80=EB=B0=98=ED=99=98=EB=A9=94=EC=84=B8=EC=A7=80?= =?UTF-8?q?=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EB=AC=B8=EC=A0=9C=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 --- .../inu/codin/codin/domain/user/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 56852b3c..8b737cf3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/users") +@RequestMapping(value = "/api/users", produces = "plain/text; charset=utf-8") @Tag(name = "User API") @RequiredArgsConstructor public class UserController { From c71b4e299e8e7215b50e5c141c146a2536e013a7 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:41:07 +0900 Subject: [PATCH 0050/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20RedisConfig=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/RedisConfig.java | 18 +++++++++++++++--- .../codin/infra/redis/RedisProperties.java | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java index 2e03922c..6af6e9b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java @@ -5,8 +5,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; // Redis configuration @@ -19,16 +21,26 @@ public class RedisConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { + + RedisStandaloneConfiguration redisStandAloneConfiguration = new RedisStandaloneConfiguration(); + redisStandAloneConfiguration.setPort(redisProperties.getPort()); + redisStandAloneConfiguration.setHostName(redisProperties.getHost()); + redisStandAloneConfiguration.setPassword(redisProperties.getPassword()); + redisStandAloneConfiguration.setDatabase(0); + // Lettuce는 비동기 방식을 지원하는 Redis 클라이언트 // 성능상 이점이 있어 기본적으로 사용 - return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); + return new LettuceConnectionFactory(redisStandAloneConfiguration); } @Bean - public RedisTemplate redisTemplate() { + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setDefaultSerializer(RedisSerializer.string()); + redisTemplate.setEnableTransactionSupport(true); return redisTemplate; } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java index 5b65b4c1..e1fe77ef 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java @@ -13,4 +13,6 @@ public class RedisProperties { private int port; + private String password; + } \ No newline at end of file From 13bf0fe35a3ddaf07b630b93642adac92d421602 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:41:28 +0900 Subject: [PATCH 0051/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20Redis=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=8B=9C=20null=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/RedisStorageService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java index e391963f..536557e5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java @@ -1,11 +1,18 @@ package inu.codin.codin.common.security.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Slf4j @Service @RequiredArgsConstructor +@Transactional public class RedisStorageService { private final RedisTemplate redisTemplate; @@ -17,7 +24,15 @@ public class RedisStorageService { * @param expiration */ public void saveRefreshToken(String username, String refreshToken, long expiration) { - redisTemplate.opsForValue().set("RT:" + username, refreshToken, expiration); + log.info("[RedisStorageService] saveRefreshToken : username = {}, refreshToken = {}, expiration = {}", username, refreshToken, expiration); + + // null check validation + if (refreshToken != null && refreshToken.contains("\u0000")) { + log.warn("[RedisStorageService] refreshToken contains null character!"); + refreshToken = refreshToken.replace("\u0000", ""); + } + + redisTemplate.opsForValue().set("RT:" + username, Objects.requireNonNull(refreshToken), expiration, TimeUnit.SECONDS); } /** From 4fb8426848a431927503237bd8e498d53b9ee07f Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:48:01 +0900 Subject: [PATCH 0052/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8,=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83,=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=A6=AC=EC=9D=B4=EC=8A=88=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 58 +++++++++++++++++ .../common/security/dto/LoginRequestDto.java | 18 ++++++ .../filter/JwtAuthenticationFilter.java | 62 ++++++------------- .../codin/common/security/jwt/JwtUtils.java | 39 ++++++++++++ .../common/security/service/JwtService.java | 37 +++++++++-- 5 files changed, 168 insertions(+), 46 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java new file mode 100644 index 00000000..b52885f3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -0,0 +1,58 @@ +package inu.codin.codin.common.security.controller; + +import inu.codin.codin.common.security.dto.LoginRequestDto; +import inu.codin.codin.common.security.service.JwtService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +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; + +@RestController +@RequestMapping(value = "/auth" , produces = "plain/text; charset=utf-8") +@Tag(name = "Auth API") +@RequiredArgsConstructor +public class AuthController { + + private final AuthenticationManager authenticationManager; + private final JwtService jwtService; + + @Operation(summary = "로그인") + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) { + + UsernamePasswordAuthenticationToken authenticationToken + = new UsernamePasswordAuthenticationToken(loginRequestDto.getEmail(), loginRequestDto.getPassword()); + + Authentication authentication = authenticationManager.authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + + jwtService.createToken(response); + + return ResponseEntity.ok("로그인 성공 / 토큰 발급 완료"); + } + + @Operation(summary = "로그아웃") + @PostMapping("/logout") + public ResponseEntity logout() { + jwtService.deleteToken(); + return ResponseEntity.ok("로그아웃 성공 / 토큰 삭제 완료"); + } + + @Operation(summary = "토큰 재발급") + @PostMapping("/reissue") + public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { + jwtService.reissueToken(request, response); + return ResponseEntity.ok("토큰 재발급 완료"); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java new file mode 100644 index 00000000..0766b432 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java @@ -0,0 +1,18 @@ +package inu.codin.codin.common.security.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginRequestDto { + + @Schema(description = "이메일 주소", example = "codin@gmail.com") + @NotBlank + private String email; + + @Schema(description = "비밀번호", example = "1234") + @NotBlank + private String password; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 5cb60ac0..1fa843e9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -1,18 +1,17 @@ package inu.codin.codin.common.security.filter; import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; -import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.common.security.service.JwtService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -26,58 +25,37 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; private final JwtService jwtService; + private final JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String accessToken = getTokenFromHeader(request); - String refreshToken = getTokenFromCookie(request); + String accessToken = jwtUtils.getTokenFromHeader(request); + String refreshToken = jwtUtils.getTokenFromCookie(request); // Access Token이 있는 경우 - if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { - String username = jwtTokenProvider.getUsername(accessToken); - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); + if (accessToken != null) { + if (jwtTokenProvider.validateAccessToken(accessToken)) { + String username = jwtTokenProvider.getUsername(accessToken); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } } // Refresh Token이 있는 경우 (Access Token 만료) Access Token, Refresh Token 재발급 - else if (refreshToken != null && jwtTokenProvider.validateRefreshToken(refreshToken)) { - jwtService.reissueToken(refreshToken, response); + else if (refreshToken != null) { + if (jwtTokenProvider.validateRefreshToken(refreshToken)) { + jwtService.reissueToken(refreshToken, response); + } } filterChain.doFilter(request, response); } - /** - * 헤더에서 Access 토큰 추출 - * HTTP Header : "Authorization" : "Bearer ..." - * @return (null, 빈 문자열, "Bearer ")로 시작하지 않는 경우 null 반환 - */ - private String getTokenFromHeader(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } - /** - * 쿠키에서 Refresh 토큰 추출 - * @return 쿠키에 RefreshToken이 없는 경우 null 반환 - */ - private String getTokenFromCookie(HttpServletRequest request) { - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("RefreshToken")) { - return cookie.getValue(); - } - } - } - return null; - } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java new file mode 100644 index 00000000..ac21862b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -0,0 +1,39 @@ +package inu.codin.codin.common.security.jwt; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class JwtUtils { + + /** + * 헤더에서 Access 토큰 추출 + * HTTP Header : "Authorization" : "Bearer ..." + * @return (null, 빈 문자열, "Bearer ")로 시작하지 않는 경우 null 반환 + */ + public String getTokenFromHeader(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + + /** + * 쿠키에서 Refresh 토큰 추출 + * @return 쿠키에 RefreshToken이 없는 경우 null 반환 + */ + public String getTokenFromCookie(HttpServletRequest request) { + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if (cookie.getName().equals("RefreshToken")) { + return cookie.getValue(); + } + } + } + return null; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index c512c27c..3d0c531b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -3,7 +3,9 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import inu.codin.codin.common.security.jwt.JwtUtils; import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,25 +21,48 @@ @RequiredArgsConstructor public class JwtService { - private final JwtTokenProvider jwtTokenProvider; private final RedisStorageService redisStorageService; + private final JwtTokenProvider jwtTokenProvider; + private final JwtUtils jwtUtils; /** * 최초 로그인 시 Access Token, Refresh Token 발급 * @param response */ - public void reissueToken(HttpServletResponse response) { + public void createToken(HttpServletResponse response) { createBothToken(response); + log.info("[createToken] Access Token, Refresh Token 발급 완료"); + } + + /** + * Refresh Token을 이용하여 Access Token, Refresh Token 재발급 + * @param request + * @param response + */ + public void reissueToken(HttpServletRequest request, HttpServletResponse response) { + String refreshToken = jwtUtils.getTokenFromCookie(request); + + if (refreshToken == null) { + log.error("[reissueToken] Refresh Token이 없습니다."); + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 없습니다."); + } + + reissueToken(refreshToken, response); } /** - * Reissue : Refresh Token을 이용하여 Access Token, Refresh Token 재발급 + * Refresh Token을 이용하여 Access Token, Refresh Token 재발급 + * @param refreshToken + * @param response */ public void reissueToken(String refreshToken, HttpServletResponse response) { if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { + log.error("[reissueToken] Refresh Token이 유효하지 않습니다. : {}", refreshToken); throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 유효하지 않습니다."); } + createBothToken(response); + log.info("[reissueToken] Access Token, Refresh Token 재발급 완료"); } /** @@ -56,14 +81,18 @@ private void createBothToken(HttpServletResponse response) { refreshTokenCookie.setHttpOnly(true); refreshTokenCookie.setPath("/"); response.addCookie(refreshTokenCookie); + + log.info("[createBothToken] Access Token, Refresh Token 발급 완료 Refresh : {}", newToken.getRefreshToken()); } /** * 로그아웃 - Refresh Token 삭제 */ public void deleteToken() { + // 어차피 JwtAuthenticationFilter 단에서 토큰을 검증하여 인증을 처리하므로 + // SecurityContext에 Authentication 객체가 없는 경우는 없다. var authentication = SecurityContextHolder.getContext().getAuthentication(); redisStorageService.deleteRefreshToken(authentication.getName()); + log.info("[deleteToken] Refresh Token 삭제 완료"); } - } \ No newline at end of file From 9c86acf08ab9c3363a0dc1042094c0478bc3b343 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:48:24 +0900 Subject: [PATCH 0053/1002] =?UTF-8?q?[SC-57]=20Test=20:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=BB=A8=ED=85=8D=EC=8A=A4=ED=8A=B8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/TestController.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index 4b731e8f..d7e1b5ce 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -1,5 +1,7 @@ package inu.codin.codin; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -8,9 +10,21 @@ @RequestMapping("/v3/api") public class TestController { + @Operation(summary = "기본 접근 테스트") @GetMapping("/test") - public String test() { + public String test1() { return "test"; } + @Operation(summary = "기본 접근 테스트 - 로그인 데이터 확인") + @GetMapping("/test2") + public String test2() { + // SecurityContextHolder를 이용하여 현재 사용자의 정보를 가져올 수 있습니다. + String username = SecurityContextHolder // SecurityContextHolder를 이용하여 현재 사용자의 정보를 가져올 수 있습니다. + .getContext() + .getAuthentication() + .getName(); + return "유저 이름: " + username; + } + } From 86627a5ae79945fc0a479f60ff1a0c40ae9b014c Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:49:02 +0900 Subject: [PATCH 0054/1002] =?UTF-8?q?[SC-57]=20Fix=20:=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EC=8B=9C=EA=B0=84=20=EB=B0=80=EB=A6=AC=EC=84=B8?= =?UTF-8?q?=EC=BB=A8=EB=93=9C=20=EB=8B=A8=EC=9C=84=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/jwt/JwtTokenProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index dfa9d80d..b9f85cd5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -57,8 +57,8 @@ public TokenDto createToken(Authentication authentication) { // 토큰 만료시간 설정 Date now = new Date(); - Date accessTokenExpiration = new Date(now.getTime() + Long.parseLong(this.ACCESS_TOKEN_EXPIRATION)); - Date refreshTokenExpiration = new Date(now.getTime() + Long.parseLong(this.REFRESH_TOKEN_EXPIRATION)); + Date accessTokenExpiration = new Date(now.getTime() + Long.parseLong(this.ACCESS_TOKEN_EXPIRATION) * 1000); + Date refreshTokenExpiration = new Date(now.getTime() + Long.parseLong(this.REFRESH_TOKEN_EXPIRATION) * 1000); // 토큰 생성 String accessToken = Jwts.builder() @@ -106,6 +106,7 @@ public boolean validateAccessToken(String accessToken) { try { Jwts.parserBuilder() .setSigningKey(SECRET_KEY) + .setAllowedClockSkewSeconds(60) .build() .parseClaimsJws(accessToken); return true; @@ -128,7 +129,7 @@ public boolean validateRefreshToken(String refreshToken) { // Redis에 저장된 Refresh Token과 비교 String storedRefreshToken = redisStorageService.getStoredRefreshToken(getClaims(refreshToken).getSubject()); if (!refreshToken.equals(storedRefreshToken)) { - log.error("[validateRefreshToken] 저장된 Refresh Token과 요청된 Refresh Token이 일치하지 않음"); + log.warn("[validateRefreshToken] 저장된 Refresh Token과 요청된 Refresh Token이 일치하지 않음"); return false; } return true; From b07e1360af129846716725f94b3e66cd6d6358b2 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:51:08 +0900 Subject: [PATCH 0055/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20Security=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/ExceptionHandlerFilter.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java new file mode 100644 index 00000000..b89430db --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java @@ -0,0 +1,78 @@ +package inu.codin.codin.common.security.filter; + +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * 예외 처리 필터 + * - 예외 발생 시, 클라이언트에게 응답을 보내는 필터 + * - JwtException 발생 시, INVALID_TOKEN 응답 + * - 그 외 예외 발생 시, INVALID_TOKEN 응답 + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class ExceptionHandlerFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } catch (Exception e) { + log.error("[doFilterInternal] Exception in ExceptionHandlerFilter: ", e); + sendErrorResponse(response, SecurityErrorCode.INVALID_TOKEN); + } + } + + private void sendErrorResponse(HttpServletResponse response, SecurityErrorCode errorCode) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + ErrorResponse errorResponse = ErrorResponse.builder() + .status(HttpStatus.UNAUTHORIZED.value()) + .error(errorCode.name()) + .code(errorCode.getErrorCode()) + .message(errorCode.getMessage()) + .build(); + + try { + response.getWriter().write(errorResponse.toString()); + } catch (IOException e) { + log.error("Error writing error response", e); + } + } + + @Getter + public static class ErrorResponse { + private int status; + private String error; + private String code; + private String message; + private LocalDateTime timestamp; + + @Builder + public ErrorResponse(int status, String error, String code, String message) { + this.status = status; + this.error = error; + this.code = code; + this.message = message; + this.timestamp = LocalDateTime.now(); + } + } + +} From 71edbd3c08ef6aa82ac3faed719bb4044ad879f0 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:52:27 +0900 Subject: [PATCH 0056/1002] =?UTF-8?q?[SC-57]=20Feat=20:=20SecurityConfig?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 5e350c5f..930a2db3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -1,12 +1,16 @@ package inu.codin.codin.common.config; +import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; +import inu.codin.codin.common.security.jwt.JwtUtils; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; @@ -26,15 +30,20 @@ public class SecurityConfig { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; private final JwtService jwtService; + private final JwtUtils jwtUtils; String[] PERMIT_ALL = { - "/**", + "/api/login", + "/api/sign-up", + "/api/reissue-token", + "/api/logout", "/api/members/sign-up", "/api/members/sign-in", "/api/members/refresh-token", - "/swagger-ui/**", - "/swagger-ui.html", - "/v3/api-docs/**" + "/api/swagger-ui/**", + "/api/swagger-ui.html", + "/api/v3/api-docs/**", + "/**" }; @Bean @@ -50,14 +59,24 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { authorizeHttpRequests .requestMatchers(PERMIT_ALL).permitAll() ) + // JwtAuthenticationFilter 추가 .addFilterBefore( - new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtService), + new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtService, jwtUtils), UsernamePasswordAuthenticationFilter.class - ); + ) + // 예외 처리 필터 추가 + .addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class); return http.build(); } + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + return authenticationManagerBuilder.build(); + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); From 44b6708c72b8e322f57b5dbeaeca5e8edabc6751 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 15:59:43 +0900 Subject: [PATCH 0057/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20Redis=20TTL=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=ED=9B=84=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20nul?= =?UTF-8?q?l=20=EB=B0=98=ED=99=98=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/jwt/JwtTokenProvider.java | 4 ++++ .../codin/common/security/service/RedisStorageService.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index b9f85cd5..5ee22f30 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -128,6 +128,10 @@ public boolean validateRefreshToken(String refreshToken) { try { // Redis에 저장된 Refresh Token과 비교 String storedRefreshToken = redisStorageService.getStoredRefreshToken(getClaims(refreshToken).getSubject()); + if (storedRefreshToken == null) { + log.warn("[validateRefreshToken] 저장된 Refresh Token이 없음"); + return false; + } if (!refreshToken.equals(storedRefreshToken)) { log.warn("[validateRefreshToken] 저장된 Refresh Token과 요청된 Refresh Token이 일치하지 않음"); return false; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java index 536557e5..9e9644bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java @@ -38,7 +38,7 @@ public void saveRefreshToken(String username, String refreshToken, long expirati /** * RefreshToken 조회 * @param username - * @return refreshToken + * @return refreshToken or null */ public String getStoredRefreshToken(String username) { return redisTemplate.opsForValue().get("RT:" + username); From fd3a4f1e9482c21744cab1970f94578ac9919a80 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 16:00:41 +0900 Subject: [PATCH 0058/1002] =?UTF-8?q?[SC-57]=20Fix=20:=20=EC=9D=B8?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/java/inu/codin/codin/TestController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index d7e1b5ce..e9cb80b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/v3/api") +@RequestMapping(value = "/v3/api", produces = "plain/text; charset=utf-8") public class TestController { @Operation(summary = "기본 접근 테스트") From d76664d81d54682373308735e4f315a544059994 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 13 Nov 2024 16:51:20 +0900 Subject: [PATCH 0059/1002] =?UTF-8?q?[SC-71]=20feat=20:=20=ED=95=99?= =?UTF-8?q?=EA=B3=BC=EC=82=AC=EB=AC=B4=EC=8B=A4=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B0=8F=20=EB=B0=98=ED=99=98=20API=20=EA=B5=AC?= =?UTF-8?q?=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../office/controller/OfficeController.java | 31 +++++++++++++ .../info/office/dto/OfficeListResDTO.java | 44 +++++++++++++++++++ .../domain/info/office/dto/OfficeMember.java | 26 +++++++++++ .../info/office/dto/OfficeMemberResDTO.java | 32 ++++++++++++++ .../domain/info/office/entity/Office.java | 42 ++++++++++++++++++ .../office/repository/OfficeRepository.java | 13 ++++++ .../info/office/service/OfficeService.java | 27 ++++++++++++ 7 files changed, 215 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java new file mode 100644 index 00000000..68288d5d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.info.office.controller; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.office.service.OfficeService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/office") +public class OfficeController { + + private final OfficeService officeService; + + @GetMapping("/{department}") + public ResponseEntity getOfficeByDepartment(@PathVariable("department") Department department){ + return ResponseEntity.ok() + .body(officeService.getOfficeByDepartment(department)); + } + + + @GetMapping + public ResponseEntity getAllOffice(){ + return ResponseEntity.ok() + .body(officeService.getAllOffice()); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java new file mode 100644 index 00000000..4738e330 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java @@ -0,0 +1,44 @@ +package inu.codin.codin.domain.info.office.dto; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.office.entity.Office; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OfficeListResDTO{ + private final Department department; + + private final String location; + + private final String open; + + private final String vacation; + + private final String office_number; + + private final String fax; + + @Builder + public OfficeListResDTO(Department department, String location, String open, String vacation, String office_number, String fax) { + this.department = department; + this.location = location; + this.open = open; + this.vacation = vacation; + this.office_number = office_number; + this.fax = fax; + } + + public static OfficeListResDTO of(Office office){ + return OfficeListResDTO.builder() + .department(office.getDepartment()) + .location(office.getLocation()) + .open(office.getOpen()) + .vacation(office.getVacation()) + .office_number(office.getOffice_number()) + .fax(office.getOffice_number()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java new file mode 100644 index 00000000..8f486d46 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.info.office.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class OfficeMember { + @NotBlank + @Schema(description = "성명", example = "홍길동") + private String name; + @NotBlank + @Schema(description = "직위", example = "조교") + private String position; + + @Schema(description = "담당 업무", example = "학과사무실 업무") + private String role; + + @NotBlank + @Schema(description = "연락처", example = "032-123-2345") + private String number; + + @NotBlank + @Schema(description = "이메일", example = "test@inu.ac.kr") + private String email; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java new file mode 100644 index 00000000..a174e891 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.info.office.dto; + +import inu.codin.codin.domain.info.office.entity.Office; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class OfficeMemberResDTO { + @Schema(description = "사무실 평면도", example = "https://") + private String img; + + @Schema(description = "학과사무실 직원") + private List member; + + @Builder + public OfficeMemberResDTO(String img, List member) { + this.img = img; + this.member = member; + } + + public static OfficeMemberResDTO of(Office office) { + return OfficeMemberResDTO.builder() + .img(office.getImg()) + .member(office.getMember()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java new file mode 100644 index 00000000..3a450290 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.info.office.entity; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.office.dto.OfficeMember; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.List; + +@Document(collection = "department_office") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Office { + @Id @NotBlank + private String id; + + @NotBlank + private Department department; + + @NotBlank + private String location; + + @NotBlank + private String open; + + @NotBlank + private String vacation; + + private String img; + + private List member; + + @NotBlank + private String office_number; + + @NotBlank + private String fax; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java new file mode 100644 index 00000000..89525e4f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.info.office.repository; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.office.entity.Office; +import jakarta.validation.constraints.NotBlank; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface OfficeRepository extends MongoRepository { + + List findAllByDepartment(@NotBlank Department department); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java new file mode 100644 index 00000000..ffbb3ca0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.info.office.service; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; +import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; +import inu.codin.codin.domain.info.office.entity.Office; +import inu.codin.codin.domain.info.office.repository.OfficeRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class OfficeService { + + private final OfficeRepository officeRepository; + public List getAllOffice() { + List offices = officeRepository.findAll(); + return offices.stream().map(OfficeListResDTO::of).toList(); + } + + public List getOfficeByDepartment(Department department) { + List offices = officeRepository.findAllByDepartment(department); + return offices.stream().map(OfficeMemberResDTO::of).toList(); + } +} From c635defdb2b14fd5f92fc3bd89c7625c39764d65 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 13 Nov 2024 17:06:49 +0900 Subject: [PATCH 0060/1002] =?UTF-8?q?[SC-71]=20feat=20:=20=EC=97=B0?= =?UTF-8?q?=EA=B5=AC=EC=8B=A4=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20API=20=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/lab/controller/LabController.java | 10 +++ .../info/lab/dto/LabThumbnailResDTO.java | 65 +++++++++++++------ .../domain/info/lab/service/LabService.java | 6 ++ 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java index 421fd56c..a386a9f6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.lab.controller; +import inu.codin.codin.domain.info.lab.dto.LabListResDTO; import inu.codin.codin.domain.info.lab.service.LabService; import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; import io.swagger.v3.oas.annotations.Operation; @@ -10,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/api/lab") @@ -24,6 +27,13 @@ public ResponseEntity getLabThumbnail(@PathVariable("id") St .body(labService.getLabThumbnail(id)); } + @Operation(summary = "연구실 리스트 반환") + @GetMapping + public ResponseEntity> getAllLab(){ + return ResponseEntity.ok() + .body(labService.getAllLab()); + } + //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 @GetMapping("/") public void joinLab(){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java index 15550ad7..f3b297ba 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java @@ -5,33 +5,56 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; +import lombok.Data; +@Data +public class LabThumbnailResDTO { -public record LabThumbnailResDTO( - @NotBlank @Schema(description = "학과", example = "CSE") - Department department, - @NotBlank @Schema(description = "연구실 이름", example = "땡땡연구실") - String title, - @Schema(description = "연구 내용", example = "이것저것 연구합니다.") - String content, - @NotBlank @Schema(description = "담당 교수", example = "홍길동") - String professor, - @Schema(description = "교수실 위치", example = "7호관 423호") - String professorLoc, - @Schema(description = "교수실 전화번호", example = "032-123-4567") - String professorNumber, - @Schema(description = "연구실 위치", example = "7호관 409호") - String labLoc, - @Schema(description = "연구실 전화번호", example = "032-987-0653") - String labNumber, - @Schema(description = "연구실 홈페이지", example = "http://~") - String site) { + @NotBlank + @Schema(description = "학과", example = "CSE") + private Department department; + + @NotBlank + @Schema(description = "연구실 이름", example = "땡땡연구실") + private String title; + + @Schema(description = "연구 내용", example = "이것저것 연구합니다.") + private String content; + + @NotBlank + @Schema(description = "담당 교수", example = "홍길동") + private String professor; + + @Schema(description = "교수실 위치", example = "7호관 423호") + private String professorLoc; + + @Schema(description = "교수실 전화번호", example = "032-123-4567") + private String professorNumber; + + @Schema(description = "연구실 위치", example = "7호관 409호") + private String labLoc; + + @Schema(description = "연구실 전화번호", example = "032-987-0653") + private String labNumber; + + @Schema(description = "연구실 홈페이지", example = "http://~") + private String site; @Builder - public LabThumbnailResDTO { + public LabThumbnailResDTO(Department department, String title, String content, String professor, + String professorLoc, String professorNumber, String labLoc, + String labNumber, String site) { + this.department = department; + this.title = title; + this.content = content; + this.professor = professor; + this.professorLoc = professorLoc; + this.professorNumber = professorNumber; + this.labLoc = labLoc; + this.labNumber = labNumber; + this.site = site; } - public static LabThumbnailResDTO of(Lab lab) { return LabThumbnailResDTO.builder() .department(lab.getDepartment()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java index b6146c1b..3979a5be 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.lab.service; +import inu.codin.codin.domain.info.lab.dto.LabListResDTO; import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; import inu.codin.codin.domain.info.lab.entity.Lab; import inu.codin.codin.domain.info.lab.repository.LabRepository; @@ -41,4 +42,9 @@ public List joinlab() { } return professors; } + + public List getAllLab() { + List labs = labRepository.findAll(); + return labs.stream().map(LabListResDTO::of).toList(); + } } From 9279caafb55338985e62dff786cb7ad5b8282c32 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 13 Nov 2024 17:06:58 +0900 Subject: [PATCH 0061/1002] =?UTF-8?q?[SC-71]=20feat=20:=20=EC=97=B0?= =?UTF-8?q?=EA=B5=AC=EC=8B=A4=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20API=20=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/lab/dto/LabListResDTO.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java new file mode 100644 index 00000000..7a3633c1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.info.lab.dto; + +import inu.codin.codin.domain.info.lab.entity.Lab; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LabListResDTO { + private final String id; + private final String title; + private final String content; + private final String professor; + + @Builder + public LabListResDTO(String id, String title, String content, String professor) { + this.id = id; + this.title = title; + this.content = content; + this.professor = professor; + } + + public static LabListResDTO of(Lab lab){ + return LabListResDTO.builder() + .id(lab.getId()) + .title(lab.getTitle()) + .content(lab.getContent()) + .professor(lab.getProfessor()) + .build(); + } +} From 432a1d238910f159fe358340951327316afe216e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 13 Nov 2024 17:08:26 +0900 Subject: [PATCH 0062/1002] =?UTF-8?q?[SC-71]=20docs=20:=20@Operation=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/info/office/controller/OfficeController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java index 68288d5d..14635a57 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.office.service.OfficeService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -16,13 +17,14 @@ public class OfficeController { private final OfficeService officeService; + @Operation(summary = "학과별 사무실 직원 정보 반환") @GetMapping("/{department}") public ResponseEntity getOfficeByDepartment(@PathVariable("department") Department department){ return ResponseEntity.ok() .body(officeService.getOfficeByDepartment(department)); } - + @Operation(summary = "학과사무실 리스트 반환") @GetMapping public ResponseEntity getAllOffice(){ return ResponseEntity.ok() From 35791b1527e5f84649b0994de1f90d3facd9cb8d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 13 Nov 2024 17:11:48 +0900 Subject: [PATCH 0063/1002] =?UTF-8?q?[SC-71]=20feat=20:=20Lab,=20Professor?= =?UTF-8?q?=20NotFoundException=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/lab/exception/LabNotFoundException.java | 8 ++++++++ .../codin/codin/domain/info/lab/service/LabService.java | 3 ++- .../professor/exception/ProfessorNotFoundException.java | 7 +++++++ .../domain/info/professor/service/ProfessorService.java | 3 ++- 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java new file mode 100644 index 00000000..95982fd8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java @@ -0,0 +1,8 @@ +package inu.codin.codin.domain.info.lab.exception; + +public class LabNotFoundException extends RuntimeException{ + + public LabNotFoundException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java index 3979a5be..f53f5627 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.info.lab.dto.LabListResDTO; import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; import inu.codin.codin.domain.info.lab.entity.Lab; +import inu.codin.codin.domain.info.lab.exception.LabNotFoundException; import inu.codin.codin.domain.info.lab.repository.LabRepository; import inu.codin.codin.domain.info.professor.entity.Professor; import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; @@ -21,7 +22,7 @@ public class LabService { public LabThumbnailResDTO getLabThumbnail(String id) { Lab lab = labRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("not found")); + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); return LabThumbnailResDTO.of(lab); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java new file mode 100644 index 00000000..766dd62b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.info.professor.exception; + +public class ProfessorNotFoundException extends RuntimeException{ + public ProfessorNotFoundException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java index b1781924..8facf6c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; import inu.codin.codin.domain.info.professor.entity.Professor; +import inu.codin.codin.domain.info.professor.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,7 +24,7 @@ public List getProfessorByDepartment(Department department) public ProfessorThumbnailResDTO getProfessorThumbnail(String id) { Professor professor = professorRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("not found")); + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); return ProfessorThumbnailResDTO.of(professor); } } From 1511ac1f75669b370f100ad03568568617cf215e Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 20:54:24 +0900 Subject: [PATCH 0064/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20=EC=BF=A0?= =?UTF-8?q?=ED=82=A4=20=ED=86=A0=ED=81=B0=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/security/jwt/JwtUtils.java | 2 +- .../inu/codin/codin/common/security/service/JwtService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index ac21862b..7bd16313 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -28,7 +28,7 @@ public String getTokenFromHeader(HttpServletRequest request) { public String getTokenFromCookie(HttpServletRequest request) { if (request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("RefreshToken")) { + if (cookie.getName().equals("RT")) { return cookie.getValue(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 3d0c531b..2e65f045 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -77,7 +77,7 @@ private void createBothToken(HttpServletResponse response) { response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); // 쿠키에 새로운 Refresh Token 추가 - Cookie refreshTokenCookie = new Cookie("RefreshToken", newToken.getRefreshToken()); + Cookie refreshTokenCookie = new Cookie("RT", newToken.getRefreshToken()); refreshTokenCookie.setHttpOnly(true); refreshTokenCookie.setPath("/"); response.addCookie(refreshTokenCookie); From 8ffbad9a355c0621505e1c8c31da47c5c9f0cca1 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 20:55:33 +0900 Subject: [PATCH 0065/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20SecurityConfig?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SessionCreationPolicy -> STATLESS - RoleHierarchy 빈 추가 --- .../codin/common/config/SecurityConfig.java | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 930a2db3..132dca8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -2,12 +2,14 @@ import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; import inu.codin.codin.common.security.service.JwtService; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -16,11 +18,13 @@ import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; @Configuration @EnableWebSecurity @@ -32,20 +36,6 @@ public class SecurityConfig { private final JwtService jwtService; private final JwtUtils jwtUtils; - String[] PERMIT_ALL = { - "/api/login", - "/api/sign-up", - "/api/reissue-token", - "/api/logout", - "/api/members/sign-up", - "/api/members/sign-in", - "/api/members/refresh-token", - "/api/swagger-ui/**", - "/api/swagger-ui.html", - "/api/v3/api-docs/**", - "/**" - }; - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -54,10 +44,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .cors(Customizer.withDefaults()) // cors 설정 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 .httpBasic(HttpBasicConfigurer::disable) // basic auth 비활성화 + .sessionManagement(sessionManagement -> sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 + ) // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PERMIT_ALL).permitAll() + .requestMatchers(SWAGGER_AUTH_PATHS).permitAll() //.hasRole("ADMIN") + .anyRequest().hasRole("USER") ) // JwtAuthenticationFilter 추가 .addFilterBefore( @@ -65,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { UsernamePasswordAuthenticationFilter.class ) // 예외 처리 필터 추가 - .addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class); + .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class); return http.build(); } @@ -77,8 +72,39 @@ public AuthenticationManager authenticationManager(HttpSecurity http) throws Exc return authenticationManagerBuilder.build(); } + @Bean + public RoleHierarchy roleHierarchy() { + return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_MANGER > ROLE_USER"); + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + private static final String[] PERMIT_ALL = { + // Production Level + "/api/auth/login", + "/api/auth/reissue", + "/api/auth/logout", + "/api/users/sign-up", + "/api/email/auth/check", + "/api/email/auth/send", + + // Local Test Level + "/auth/login", + "/auth/reissue", + "/auth/logout", + "/users/sign-up", + "/email/auth/check", + "/email/auth/send" + }; + + private static final String[] SWAGGER_AUTH_PATHS = { + "/swagger-ui/**", + "/v3/api-docs/**", + "/v3/api-docs", + "/swagger-resources/**", + }; + } From aec45d529be5f801d33750da9cc80e622779be8b Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 20:56:04 +0900 Subject: [PATCH 0066/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20Swagger=20Produc?= =?UTF-8?q?tion=20Server=20HTTPS=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SwaggerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index ab03fd79..e3fbea6e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -43,7 +43,7 @@ public OpenAPI customOpenAPI() { .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), - new Server().url("http://www.codin.co.kr").description("Production Server") + new Server().url("https://www.codin.co.kr").description("Production Server") )); } From 2ebceafbfe680000dddc93b31c01ea259ccecb8b Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 13 Nov 2024 20:57:19 +0900 Subject: [PATCH 0067/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20RefreshToken=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RefreshToken을 일반적인 상황에서는 사용하지 않는데, 모든 트래픽에 대해서 검증하려하는 상황이 일어나서 제거함. --- .../filter/JwtAuthenticationFilter.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 1fa843e9..8bab1bb4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -34,28 +34,31 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String refreshToken = jwtUtils.getTokenFromCookie(request); // Access Token이 있는 경우 - if (accessToken != null) { - if (jwtTokenProvider.validateAccessToken(accessToken)) { - String username = jwtTokenProvider.getUsername(accessToken); - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } + if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { + setAuthentication(accessToken); } // Refresh Token이 있는 경우 (Access Token 만료) Access Token, Refresh Token 재발급 - else if (refreshToken != null) { - if (jwtTokenProvider.validateRefreshToken(refreshToken)) { - jwtService.reissueToken(refreshToken, response); - } - } +// else if (refreshToken != null) { +// if (jwtTokenProvider.validateRefreshToken(refreshToken)) { +// setAuthentication(refreshToken); +// jwtService.reissueToken(refreshToken, response); +// } +// } filterChain.doFilter(request, response); } + private void setAuthentication(String token) { + String username = jwtTokenProvider.getUsername(token); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } From e315012c31ba81cb915a6c113501dcbc01147660 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 14 Nov 2024 00:07:04 +0900 Subject: [PATCH 0068/1002] =?UTF-8?q?[SC-71]=20docs=20:=20@Tag=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20url=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/info/lab/controller/LabController.java | 2 ++ .../codin/domain/info/office/controller/OfficeController.java | 2 ++ .../domain/info/professor/controller/ProfessorController.java | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java index a386a9f6..102477ad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.info.lab.service.LabService; import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -16,6 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/lab") +@Tag(name = "Lab Info API") public class LabController { private final LabService labService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java index 14635a57..e124bcca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.office.service.OfficeService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -13,6 +14,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/office") +@Tag(name = "Department Office Info API") public class OfficeController { private final OfficeService officeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java index 5bf0b184..3f6796d5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java @@ -31,7 +31,7 @@ public ResponseEntity> getProfessorList(@PathVariable( } @Operation(summary = "id값에 따른 교수 썸네일 반환") - @GetMapping("/thumbnail/{id}") + @GetMapping("/detail/{id}") public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ return ResponseEntity.ok() .body(professorService.getProfessorThumbnail(id)); From 489c3d0600fa754db54c1123b352d873d7e0bd6e Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 14 Nov 2024 14:28:47 +0900 Subject: [PATCH 0069/1002] =?UTF-8?q?[SC-57]=20Fix=20:=20Refresh=20?= =?UTF-8?q?=EB=A6=AC=EC=9D=B4=EC=8A=88=EC=8B=9C=20anonymous=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=97=90=EA=B2=8C=20=EB=B0=9C=EA=B8=89=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/filter/JwtAuthenticationFilter.java | 10 +++++----- .../codin/common/security/service/JwtService.java | 2 +- .../common/security/service/RedisStorageService.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 8bab1bb4..e8aa4c47 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -38,12 +38,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse setAuthentication(accessToken); } // Refresh Token이 있는 경우 (Access Token 만료) Access Token, Refresh Token 재발급 -// else if (refreshToken != null) { -// if (jwtTokenProvider.validateRefreshToken(refreshToken)) { -// setAuthentication(refreshToken); + else if (refreshToken != null) { + if (jwtTokenProvider.validateRefreshToken(refreshToken)) { + setAuthentication(refreshToken); // jwtService.reissueToken(refreshToken, response); -// } -// } + } + } filterChain.doFilter(request, response); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 2e65f045..5bc422dc 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -82,7 +82,7 @@ private void createBothToken(HttpServletResponse response) { refreshTokenCookie.setPath("/"); response.addCookie(refreshTokenCookie); - log.info("[createBothToken] Access Token, Refresh Token 발급 완료 Refresh : {}", newToken.getRefreshToken()); + log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Refresh : {}",authentication.getName(), newToken.getRefreshToken()); } /** diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java index 9e9644bf..05c2bc7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java @@ -24,7 +24,7 @@ public class RedisStorageService { * @param expiration */ public void saveRefreshToken(String username, String refreshToken, long expiration) { - log.info("[RedisStorageService] saveRefreshToken : username = {}, refreshToken = {}, expiration = {}", username, refreshToken, expiration); + log.debug("[RedisStorageService] saveRefreshToken : username = {}, refreshToken = {}, expiration = {}", username, refreshToken, expiration); // null check validation if (refreshToken != null && refreshToken.contains("\u0000")) { From 8f2379ac761af030f87c8219ec9267cd9453e407 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 14 Nov 2024 18:04:59 +0900 Subject: [PATCH 0070/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20Swagger=20BasicS?= =?UTF-8?q?ecurity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 35 ++++++++++++++++--- .../user/controller/UserController.java | 2 +- .../domain/user/service/UserService.java | 1 - 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 132dca8c..b40a925c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -17,7 +17,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; -import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -43,15 +42,18 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .csrf(CsrfConfigurer::disable) // csrf 비활성화 .cors(Customizer.withDefaults()) // cors 설정 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 - .httpBasic(HttpBasicConfigurer::disable) // basic auth 비활성화 .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 ) + .httpBasic(Customizer.withDefaults()) // httpBasic 활성화 // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PERMIT_ALL).permitAll() - .requestMatchers(SWAGGER_AUTH_PATHS).permitAll() //.hasRole("ADMIN") + .requestMatchers(SWAGGER_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") + .requestMatchers(USER_AUTH_PATHS).hasRole("USER") .anyRequest().hasRole("USER") ) // JwtAuthenticationFilter 추가 @@ -62,6 +64,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 예외 처리 필터 추가 .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class); + http.setSharedObject(AuthenticationManager.class, authenticationManager(http)); + http.setSharedObject(RoleHierarchy.class, roleHierarchy()); + return http.build(); } @@ -90,6 +95,7 @@ public PasswordEncoder passwordEncoder() { "/api/users/sign-up", "/api/email/auth/check", "/api/email/auth/send", + "/api/v3/api/test1", // Local Test Level "/auth/login", @@ -97,7 +103,8 @@ public PasswordEncoder passwordEncoder() { "/auth/logout", "/users/sign-up", "/email/auth/check", - "/email/auth/send" + "/email/auth/send", + "/v3/api/test1", }; private static final String[] SWAGGER_AUTH_PATHS = { @@ -107,4 +114,24 @@ public PasswordEncoder passwordEncoder() { "/swagger-resources/**", }; + private static final String[] USER_AUTH_PATHS = { + "/api/v3/api/test2", + "/api/v3/api/test3", + + "/v3/api/test2", + "/v3/api/test3", + }; + + private static final String[] ADMIN_AUTH_PATHS = { + "/api/v3/api/test4", + + "/v3/api/test4", + }; + + private static final String[] MANAGER_AUTH_PATHS = { + "/api/v3/api/test5", + + "/v3/api/test5", + }; + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 8b737cf3..57f11e7d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -24,4 +24,4 @@ public ResponseEntity signUpUser( userService.createUser(userCreateRequestDto); return ResponseEntity.ok("회원가입 성공"); } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index d79bc208..1a739d47 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -57,5 +57,4 @@ private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) throw new UserCreateFailException("이미 존재하는 학번입니다."); } - } From 8bfa9f3cdb3089ed57e209e036bd95a7b71ff6ab Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 14 Nov 2024 18:06:03 +0900 Subject: [PATCH 0071/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20AccessToken?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EB=8A=94=EA=B2=BD=EC=9A=B0=20Context=20Ho?= =?UTF-8?q?lder=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/filter/JwtAuthenticationFilter.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index e8aa4c47..8058a6f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -31,18 +31,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String accessToken = jwtUtils.getTokenFromHeader(request); - String refreshToken = jwtUtils.getTokenFromCookie(request); // Access Token이 있는 경우 if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { setAuthentication(accessToken); - } - // Refresh Token이 있는 경우 (Access Token 만료) Access Token, Refresh Token 재발급 - else if (refreshToken != null) { - if (jwtTokenProvider.validateRefreshToken(refreshToken)) { - setAuthentication(refreshToken); -// jwtService.reissueToken(refreshToken, response); - } + } else { + SecurityContextHolder.clearContext(); } filterChain.doFilter(request, response); From 9db14d6f217e2cd9a7e01971f63d31af7ebf1538 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 14 Nov 2024 18:06:35 +0900 Subject: [PATCH 0072/1002] =?UTF-8?q?[SC-57]=20Perf=20:=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=A6=AC=EC=9D=B4=EC=8A=88=EC=8B=9C=20Refresh=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=ED=99=95=EC=9D=B8=20=ED=9B=84=20Authentic?= =?UTF-8?q?ation=20=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/service/JwtService.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 5bc422dc..275c138d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; import jakarta.servlet.http.Cookie; @@ -10,6 +11,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; /** @@ -24,6 +27,7 @@ public class JwtService { private final RedisStorageService redisStorageService; private final JwtTokenProvider jwtTokenProvider; private final JwtUtils jwtUtils; + private final UserDetailsService userDetailsService; /** * 최초 로그인 시 Access Token, Refresh Token 발급 @@ -47,6 +51,16 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 없습니다."); } + String username = jwtTokenProvider.getUsername(refreshToken); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + reissueToken(refreshToken, response); } From 8268709ba33297bf428dc967724abdc2740c828e Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 14 Nov 2024 18:06:59 +0900 Subject: [PATCH 0073/1002] =?UTF-8?q?[SC-57]=20Test=20:=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/TestController.java | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index e9cb80b2..0e038132 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -1,6 +1,7 @@ package inu.codin.codin; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -8,23 +9,50 @@ @RestController @RequestMapping(value = "/v3/api", produces = "plain/text; charset=utf-8") +@Tag(name = "TestController", description = "테스트용 API") public class TestController { - @Operation(summary = "기본 접근 테스트") - @GetMapping("/test") + @Operation(summary = "1 기본 접근 테스트 - 로그인 없이 접근 가능") + @GetMapping("/test1") public String test1() { - return "test"; + return "Test Success"; } - @Operation(summary = "기본 접근 테스트 - 로그인 데이터 확인") + @Operation(summary = "2 기본 접근 테스트 - 로그인 데이터 확인") @GetMapping("/test2") public String test2() { + return getUserData(); + } + + @Operation(summary = "3 유저 권한 테스트1 - USER 권한") + @GetMapping("/test3") + public String test3() { + return "USER 권한 접근 : " + getUserData(); + } + + @Operation(summary = "4 유저 권한 테스트2 - ADMIN 권한") + @GetMapping("/test4") + public String test4() { + return "ADMIN 권한 접근 : " + getUserData(); + } + + @Operation(summary = "5 유저 권한 테스트3 - MANAGER 권한") + @GetMapping("/test5") + public String test5() { + return "MANAGER 권한 접근 : " + getUserData(); + } + + private static String getUserData() { // SecurityContextHolder를 이용하여 현재 사용자의 정보를 가져올 수 있습니다. String username = SecurityContextHolder // SecurityContextHolder를 이용하여 현재 사용자의 정보를 가져올 수 있습니다. .getContext() .getAuthentication() .getName(); - return "유저 이름: " + username; + String userRole = SecurityContextHolder + .getContext() + .getAuthentication() + .getAuthorities() + .toString(); + return "유저 이름 : " + username + ", Role : " + userRole; } - } From 497b707f85651ddf3d52cc9b2738ac2762e44120 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 17 Nov 2024 00:37:35 +0900 Subject: [PATCH 0074/1002] =?UTF-8?q?Fix=20:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=20URL=20=EC=98=A4=EB=A5=98=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /api/ 뒤로 인식이 안되는 문제가 있어서 바꿈. Ref : https://binux.tistory.com/161 --- .../codin/common/config/SecurityConfig.java | 17 ----------------- .../codin/common/config/SwaggerConfig.java | 9 +++++++-- .../email/controller/EmailController.java | 2 +- .../domain/user/controller/UserController.java | 2 +- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index b40a925c..8c63ef61 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -88,16 +88,6 @@ public PasswordEncoder passwordEncoder() { } private static final String[] PERMIT_ALL = { - // Production Level - "/api/auth/login", - "/api/auth/reissue", - "/api/auth/logout", - "/api/users/sign-up", - "/api/email/auth/check", - "/api/email/auth/send", - "/api/v3/api/test1", - - // Local Test Level "/auth/login", "/auth/reissue", "/auth/logout", @@ -115,22 +105,15 @@ public PasswordEncoder passwordEncoder() { }; private static final String[] USER_AUTH_PATHS = { - "/api/v3/api/test2", - "/api/v3/api/test3", - "/v3/api/test2", "/v3/api/test3", }; private static final String[] ADMIN_AUTH_PATHS = { - "/api/v3/api/test4", - "/v3/api/test4", }; private static final String[] MANAGER_AUTH_PATHS = { - "/api/v3/api/test5", - "/v3/api/test5", }; diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index e3fbea6e..0ffd9a4e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; @@ -10,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ForwardedHeaderFilter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import java.util.List; @@ -43,8 +43,13 @@ public OpenAPI customOpenAPI() { .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), - new Server().url("https://www.codin.co.kr").description("Production Server") + new Server().url("https://www.codin.co.kr/api").description("Production Server") )); } + @Bean + public ForwardedHeaderFilter forwardedHeaderFilter() { + return new ForwardedHeaderFilter(); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index dad89d03..5fcbaaad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = "/api/email", produces = "plain/text; charset=utf-8") +@RequestMapping(value = "/email", produces = "plain/text; charset=utf-8") @Tag(name = "Email API") @RequiredArgsConstructor public class EmailController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 57f11e7d..c6121980 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping(value = "/api/users", produces = "plain/text; charset=utf-8") +@RequestMapping(value = "/users", produces = "plain/text; charset=utf-8") @Tag(name = "User API") @RequiredArgsConstructor public class UserController { From 2b0297846646d59a7e827808dca91d478f98fe7a Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 17 Nov 2024 02:52:58 +0900 Subject: [PATCH 0075/1002] =?UTF-8?q?Build=20:=20resources=EB=A5=BC=20Subm?= =?UTF-8?q?odule=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build : Gradle submodule copy task 수정 --- .../main/java/inu/codin/codin/common/config/SwaggerConfig.java | 2 +- codin-core/src/main/resources | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 codin-core/src/main/resources diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index 0ffd9a4e..c97c014a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -35,7 +35,7 @@ public OpenAPI customOpenAPI() { .name("Authorization"); SecurityRequirement securityRequirement = new SecurityRequirement() - .addList("bearerAuth"); + .addList("Bearer Auth"); return new OpenAPI() .info(info) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources new file mode 160000 index 00000000..a64e3e5c --- /dev/null +++ b/codin-core/src/main/resources @@ -0,0 +1 @@ +Subproject commit a64e3e5c72f990ef1f1539176216913960afe02f From 11e8feac9f18e7e357c5af8c66cf9d755d65a002 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 17 Nov 2024 02:52:58 +0900 Subject: [PATCH 0076/1002] =?UTF-8?q?[SC-71]=20Refactor=20:=20Department?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/Department.java | 14 ++- .../domain/user/dto/UserCreateRequestDto.java | 43 ++++++++ .../codin/domain/user/entity/UserEntity.java | 48 ++++++++ .../user/security/CustomUserDetails.java | 104 ++++++++++++++++++ 4 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/Department.java b/codin-core/src/main/java/inu/codin/codin/common/Department.java index 7463422b..8c171229 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/Department.java @@ -1,8 +1,18 @@ package inu.codin.codin.common; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter +@RequiredArgsConstructor public enum Department { - CSE, ITE, ESE, COLLEGE -} + + IT_COLLEGE("정보기술대학"), + COMPUTER_SCI("컴퓨터공학과"), + INFO_COMM("정보통신공학과"), + EMBEDDED("임베디드시스템공학과"), + STAFF("교직원"); + + private final String description; + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java new file mode 100644 index 00000000..c549e70a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java @@ -0,0 +1,43 @@ +package inu.codin.codin.domain.user.dto; + +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.Getter; + +@Data +@Getter +public class UserCreateRequestDto { + + // todo : 길이 관련 validation 추가 + + @Schema(description = "이메일 주소", example = "codin@inu.ac.kr") + @NotBlank + private String email; + + @Schema(description = "비밀번호", example = "password") + @NotBlank + private String password; + + @Schema(description = "학번", example = "20210000") + @NotBlank + private String studentId; + + @Schema(description = "이름", example = "홍길동") + @NotBlank + private String name; + + @Schema(description = "닉네임", example = "코딩") + @NotBlank + private String nickname; + + //todo 프로필 이미지 업로드 처리 ex) 이미지 크기 제한.. + @Schema(description = "프로필 이미지 URL", example = "https://avatars.githubusercontent.com/u/77490521?v=4") + @NotBlank + private String profileImageUrl; + + @Schema(description = "소속", example = "IT_COLLEGE") + private Department department; + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java new file mode 100644 index 00000000..08310eb7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -0,0 +1,48 @@ +package inu.codin.codin.domain.user.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.Department; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "users") +@Getter +public class UserEntity extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + private String email; + + private String password; + + private String studentId; + + private String name; + + private String nickname; + + private String profileImageUrl; + + private Department department; + + private UserRole role; + + private UserStatus status; + + @Builder + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status) { + this.email = email; + this.password = password; + this.studentId = studentId; + this.name = name; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.department = department; + this.role = role; + this.status = status; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java new file mode 100644 index 00000000..c78f297b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -0,0 +1,104 @@ +package inu.codin.codin.domain.user.security; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; +import lombok.Builder; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@Getter +public class CustomUserDetails implements UserDetails { + + private final String id; + private final String email; + private final String password; + private final String studentId; + private final String name; + private final String nickname; + private final String profileImageUrl; + private final Department department; + private final UserRole role; + private final UserStatus status; + + private final Collection authorities; + + @Builder + public CustomUserDetails(String id, String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status, Collection authorities) { + this.id = id; + this.email = email; + this.password = password; + this.studentId = studentId; + this.name = name; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.department = department; + this.role = role; + this.status = status; + this.authorities = authorities; + } + + public static CustomUserDetails from(UserEntity userEntity) { + return CustomUserDetails.builder() + .id(userEntity.getId()) + .email(userEntity.getEmail()) + .password(userEntity.getPassword()) + .studentId(userEntity.getStudentId()) + .name(userEntity.getName()) + .nickname(userEntity.getNickname()) + .profileImageUrl(userEntity.getProfileImageUrl()) + .department(userEntity.getDepartment()) + .role(userEntity.getRole()) + .status(userEntity.getStatus()) + .authorities(Collections.singletonList( + new SimpleGrantedAuthority("ROLE_" + userEntity.getRole().name()))) + .build(); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + /** + * Spring Security에서 사용하는 유저네임은 email로 사용 + */ + @Override + public String getUsername() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + // 계정 만료 여부 + return !status.equals(UserStatus.DISABLED); + } + + @Override + public boolean isAccountNonLocked() { + // 계정 잠금 여부 + return !status.equals(UserStatus.SUSPENDED); + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + // 계정 활성화 여부 + return status.equals(UserStatus.ACTIVE); + } +} From b56d9eda5b662ac48a08872356388a97be5f45e4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 18 Nov 2024 11:57:57 +0900 Subject: [PATCH 0077/1002] =?UTF-8?q?[SC-71]=20docs=20:=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EB=B0=8F=20@Schema=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/lab/dto/LabListResDTO.java | 17 +++++++++++++++++ .../domain/info/lab/dto/LabThumbnailResDTO.java | 4 ++++ .../info/office/dto/OfficeListResDTO.java | 17 +++++++++++++++++ .../domain/info/office/dto/OfficeMember.java | 3 +++ .../info/office/dto/OfficeMemberResDTO.java | 12 ++++++++---- .../info/professor/dto/ProfessorListResDTO.java | 4 ++++ .../professor/dto/ProfessorThumbnailResDTO.java | 4 ++++ 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java index 7a3633c1..bbd0b8d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java @@ -1,16 +1,33 @@ package inu.codin.codin.domain.info.lab.dto; import inu.codin.codin.domain.info.lab.entity.Lab; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; +/* + 연구실 리스트 반환 DTO + 존재하는 모든 연구실들을 반환한다. + */ + @Getter @Setter public class LabListResDTO { + @Schema(description = "Lab DB의 pk값", example = "b2jfbe432..") + @NotBlank private final String id; + + @Schema(description = "연구실 이름", example = "떙땡연구실") + @NotBlank private final String title; + + @Schema(description = "연구 내용", example = "00을 연구합니다.") private final String content; + + @Schema(description = "담당 교수", example = "홍길동") + @NotBlank private final String professor; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java index f3b297ba..db94f33c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java @@ -7,6 +7,10 @@ import lombok.Builder; import lombok.Data; +/* + 연구실 상세정보 반환 DTO + 해당하는 연구실에 대한 내용들을 모두 반환한다. + */ @Data public class LabThumbnailResDTO { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java index 4738e330..7e00bb0d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java @@ -2,23 +2,40 @@ import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.office.entity.Office; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; +/* + 학과 사무실 리스트 DTO + 모든 학과 사무실의 리스트를 반환한다. + */ @Getter @Setter public class OfficeListResDTO{ + + @Schema(description = "학과", example = "IT_COLLEGE") + @NotBlank private final Department department; + @Schema(description = "위치", example = "7호관 329호") + @NotBlank private final String location; + @Schema(description = "오픈 시간", example = "09-21시") private final String open; + @Schema(description = "방학 오픈 시간", example = "09-17시") private final String vacation; + @Schema(description = "연락처", example = "032-123-2434") + @NotBlank private final String office_number; + @Schema(description = "팩스", example = "032-432-1234") + @NotBlank private final String fax; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java index 8f486d46..a31b51a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java @@ -4,6 +4,9 @@ import jakarta.validation.constraints.NotBlank; import lombok.Getter; +/* + 학과 사무실 직원 정보 + */ @Getter public class OfficeMember { @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java index a174e891..b9b3fb16 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java @@ -8,6 +8,10 @@ import java.util.List; +/* + 학과 사무실 정보 반환 DTO + 학과 사무실 평면도 및 지원 정보들을 모두 반환한다. + */ @Getter @Setter public class OfficeMemberResDTO { @@ -15,18 +19,18 @@ public class OfficeMemberResDTO { private String img; @Schema(description = "학과사무실 직원") - private List member; + private List officeMembers; @Builder - public OfficeMemberResDTO(String img, List member) { + public OfficeMemberResDTO(String img, List officeMembers) { this.img = img; - this.member = member; + this.officeMembers = officeMembers; } public static OfficeMemberResDTO of(Office office) { return OfficeMemberResDTO.builder() .img(office.getImg()) - .member(office.getMember()) + .officeMembers(office.getMember()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java index 62b125b8..74de19fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java @@ -6,6 +6,10 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; +/* + 교수님 리스트 반환 DTO + 모든 교수님들의 리스트를 반환한다. + */ public record ProfessorListResDTO( @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java index 50d619ba..8ebab9b9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java @@ -6,6 +6,10 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; +/* + 교수님 상세 정보 반환 DTO + 해당되는 교수님의 상세 정보들을 반환한다. + */ public record ProfessorThumbnailResDTO( @NotBlank @Schema(description = "학과", example = "CSE") Department department, From 46bc03a1adf2772ead15ebf0ec4a151f3289aa93 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 18 Nov 2024 12:11:56 +0900 Subject: [PATCH 0078/1002] =?UTF-8?q?[SC-71]=20refactor=20:=20Response=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/office/controller/OfficeController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java index e124bcca..01450a2b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.info.office.controller; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; +import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; import inu.codin.codin.domain.info.office.service.OfficeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -11,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/api/office") @@ -21,14 +25,14 @@ public class OfficeController { @Operation(summary = "학과별 사무실 직원 정보 반환") @GetMapping("/{department}") - public ResponseEntity getOfficeByDepartment(@PathVariable("department") Department department){ + public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ return ResponseEntity.ok() .body(officeService.getOfficeByDepartment(department)); } @Operation(summary = "학과사무실 리스트 반환") @GetMapping - public ResponseEntity getAllOffice(){ + public ResponseEntity> getAllOffice(){ return ResponseEntity.ok() .body(officeService.getAllOffice()); } From 2a52fba1f026e31372d966eb2f6a9a57e64e4423 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 18 Nov 2024 12:12:15 +0900 Subject: [PATCH 0079/1002] =?UTF-8?q?[SC-71]=20docs=20:=20summary=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/professor/controller/ProfessorController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java index 3f6796d5..d43fd1e3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java @@ -23,7 +23,7 @@ public class ProfessorController { private final ProfessorService professorService; - @Operation(summary = "교수 목록 반환") + @Operation(summary = "교수 리스트 반환") @GetMapping("/{department}") public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ return ResponseEntity.ok() From 83493101630ae67d8e84bbabc05f706b6b53c524 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 18 Nov 2024 16:15:05 +0900 Subject: [PATCH 0080/1002] =?UTF-8?q?[SC-71]=20refactor=20:=20common=20dir?= =?UTF-8?q?ectory=EB=A1=9C=20Department=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/{domain/user/entity => common}/Department.java | 2 +- .../inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java | 2 +- .../main/java/inu/codin/codin/domain/info/lab/entity/Lab.java | 2 +- .../codin/domain/info/office/controller/OfficeController.java | 2 +- .../codin/codin/domain/info/office/dto/OfficeListResDTO.java | 2 +- .../java/inu/codin/codin/domain/info/office/entity/Office.java | 2 +- .../codin/domain/info/office/repository/OfficeRepository.java | 2 +- .../codin/codin/domain/info/office/service/OfficeService.java | 2 +- .../domain/info/professor/controller/ProfessorController.java | 2 +- .../codin/domain/info/professor/dto/ProfessorListResDTO.java | 2 +- .../domain/info/professor/dto/ProfessorThumbnailResDTO.java | 2 +- .../inu/codin/codin/domain/info/professor/entity/Professor.java | 2 +- .../domain/info/professor/repository/ProfessorRepository.java | 2 +- .../codin/domain/info/professor/service/ProfessorService.java | 2 +- .../inu/codin/codin/domain/user/dto/UserCreateRequestDto.java | 2 +- .../java/inu/codin/codin/domain/user/entity/UserEntity.java | 1 + .../inu/codin/codin/domain/user/security/CustomUserDetails.java | 2 +- 17 files changed, 17 insertions(+), 16 deletions(-) rename codin-core/src/main/java/inu/codin/codin/{domain/user/entity => common}/Department.java (88%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java b/codin-core/src/main/java/inu/codin/codin/common/Department.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java rename to codin-core/src/main/java/inu/codin/codin/common/Department.java index 1a654d04..eaee241d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/Department.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.entity; +package inu.codin.codin.common; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java index 77f5e2f8..0c112d14 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.lab.dto; import inu.codin.codin.domain.info.lab.entity.Lab; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java index bec307dc..53ad75ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.lab.entity; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java index 5f428475..4e74d1ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -3,7 +3,7 @@ import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; import inu.codin.codin.domain.info.office.service.OfficeService; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java index 8ab1bc63..a3e5db66 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.office.dto; import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java index 46a1319e..207ea80e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.office.entity; import inu.codin.codin.domain.info.office.dto.OfficeMember; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java index cab7ef9a..b12c1346 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.office.repository; import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import jakarta.validation.constraints.NotBlank; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java index 0a3cbf50..0f392a21 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java @@ -4,7 +4,7 @@ import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; import inu.codin.codin.domain.info.office.entity.Office; import inu.codin.codin.domain.info.office.repository.OfficeRepository; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java index 534f6382..e29c1c72 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java @@ -3,7 +3,7 @@ import inu.codin.codin.domain.info.professor.service.ProfessorService; import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java index 598ac671..616b39f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.dto; import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java index 53bc02aa..2105b3dd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.dto; import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java index f2be8123..98c09b4c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.entity; import inu.codin.codin.domain.info.lab.entity.Lab; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java index e11d21b5..3459b649 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.professor.repository; import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import org.springframework.data.mongodb.repository.MongoRepository; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java index d201c129..564f718d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java @@ -5,7 +5,7 @@ import inu.codin.codin.domain.info.professor.entity.Professor; import inu.codin.codin.domain.info.professor.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java index 3b784e14..c549e70a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.dto; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 7c10bfc9..08310eb7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.user.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.Department; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index b3243e6f..c78f297b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.security; -import inu.codin.codin.domain.user.entity.Department; +import inu.codin.codin.common.Department; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; From 4bea41d581f7cc30fd4309d90a022272926bfe59 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 18 Nov 2024 17:23:50 +0900 Subject: [PATCH 0081/1002] =?UTF-8?q?Feat=20:=20ResponseUtils=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공통된 응답 포맷 작성 - GlobalExceptionHandler - ExceptionHandlerFilter 위의 클래스에서 예외 상황에서도 공통 포맷 사용하도록함. --- .../codin/common/GlobalExceptionHandler.java | 15 ++++++ .../inu/codin/codin/common/ResponseUtils.java | 50 +++++++++++++++++++ .../security/controller/AuthController.java | 7 +-- .../filter/ExceptionHandlerFilter.java | 49 ++++-------------- .../email/controller/EmailController.java | 5 +- .../user/controller/UserController.java | 3 +- 6 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java new file mode 100644 index 00000000..6f81c408 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java @@ -0,0 +1,15 @@ +package inu.codin.codin.common; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + protected ResponseEntity handleException(Exception e) { + return ResponseUtils.error(e.getMessage()); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java b/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java new file mode 100644 index 00000000..bbe20826 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java @@ -0,0 +1,50 @@ +package inu.codin.codin.common; + +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.HashMap; + +public class ResponseUtils { + + /** + * 성공 응답 + * @param data class 형태의 데이터 타입 (e.g. DTO) + * @return ResponseEntity + */ + public static ResponseEntity success(Object data) { + return new ResponseEntity<>(data, HttpStatus.OK); + } + + /** + * 성공 응답 + * @param message 성공 응답 메세지 + * @return ResponseEntity + */ + public static ResponseEntity successMsg(String message) { + return new ResponseEntity<>(message, HttpStatus.OK); + } + + /** + * 실패 응답 + * @param message 실패 응답 메세지 (404에러) + * @return ResponseEntity + */ + public static ResponseEntity error(String message) { + return new ResponseEntity<>(message, HttpStatus.NOT_FOUND); + } + + /** + * 실패 응답 + * @param message 실패 응답 메세지 + * @param code SecurityErrorCode 참조 + * @return ResponseEntity + */ + public static ResponseEntity error(String message, SecurityErrorCode code) { + HashMap error = new HashMap<>(); + error.put("message", message); + error.put("errorCode", code.getErrorCode()); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index b52885f3..ec10901c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.security.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.common.security.dto.LoginRequestDto; import inu.codin.codin.common.security.service.JwtService; import io.swagger.v3.oas.annotations.Operation; @@ -38,21 +39,21 @@ public ResponseEntity login(@RequestBody LoginRequestDto loginRequestDto, Htt jwtService.createToken(response); - return ResponseEntity.ok("로그인 성공 / 토큰 발급 완료"); + return ResponseUtils.successMsg("로그인 성공 / 토큰 발급 완료"); } @Operation(summary = "로그아웃") @PostMapping("/logout") public ResponseEntity logout() { jwtService.deleteToken(); - return ResponseEntity.ok("로그아웃 성공 / 토큰 삭제 완료"); + return ResponseUtils.successMsg("로그아웃 성공 / 토큰 삭제 완료"); } @Operation(summary = "토큰 재발급") @PostMapping("/reissue") public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { jwtService.reissueToken(request, response); - return ResponseEntity.ok("토큰 재발급 완료"); + return ResponseUtils.successMsg("토큰 재발급 완료"); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java index b89430db..24761ce6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java @@ -1,21 +1,19 @@ package inu.codin.codin.common.security.filter; +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.common.security.exception.SecurityErrorCode; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.Builder; -import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.time.LocalDateTime; /** * 예외 처리 필터 @@ -38,41 +36,16 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } - private void sendErrorResponse(HttpServletResponse response, SecurityErrorCode errorCode) { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); + private void sendErrorResponse(HttpServletResponse response, SecurityErrorCode code) throws IOException { + ResponseEntity responseEntity = ResponseUtils.error("Security Fail", code); - ErrorResponse errorResponse = ErrorResponse.builder() - .status(HttpStatus.UNAUTHORIZED.value()) - .error(errorCode.name()) - .code(errorCode.getErrorCode()) - .message(errorCode.getMessage()) - .build(); + // Set the HttpServletResponse properties + response.setStatus(responseEntity.getStatusCode().value()); + response.setContentType("application/json"); - try { - response.getWriter().write(errorResponse.toString()); - } catch (IOException e) { - log.error("Error writing error response", e); - } - } - - @Getter - public static class ErrorResponse { - private int status; - private String error; - private String code; - private String message; - private LocalDateTime timestamp; - - @Builder - public ErrorResponse(int status, String error, String code, String message) { - this.status = status; - this.error = error; - this.code = code; - this.message = message; - this.timestamp = LocalDateTime.now(); - } + // Use an ObjectMapper to write the ResponseEntity body as JSON to the response output stream + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.writeValue(response.getWriter(), responseEntity.getBody()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 5fcbaaad..bffcb50a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.email.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.service.EmailAuthService; @@ -27,7 +28,7 @@ public ResponseEntity sendJoinAuthEmail( @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { emailAuthService.sendAuthEmail(emailAuthRequestDto); - return ResponseEntity.ok("이메일 인증 코드 전송 성공"); + return ResponseUtils.successMsg("이메일 인증 코드 전송 성공"); } @Operation(summary = "이메일 인증 코드 확인 - 학교인증 X") @@ -36,7 +37,7 @@ public ResponseEntity checkAuthNum( @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto ) { emailAuthService.checkAuthNum(joinEmailCheckRequestDto); - return ResponseEntity.ok("이메일 인증성공 - 회원가입 가능"); + return ResponseUtils.successMsg("이메일 인증 성공 - 회원가입 가능"); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index c6121980..6529b73c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.user.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; @@ -22,6 +23,6 @@ public class UserController { public ResponseEntity signUpUser( @RequestBody @Valid UserCreateRequestDto userCreateRequestDto) { userService.createUser(userCreateRequestDto); - return ResponseEntity.ok("회원가입 성공"); + return ResponseUtils.successMsg("회원가입 성공"); } } \ No newline at end of file From 63926a95de54dc6a42b49279db720da8822af301 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 18 Nov 2024 18:01:05 +0900 Subject: [PATCH 0082/1002] =?UTF-8?q?Perf=20:=20Swagger=20Tag=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/java/inu/codin/codin/TestController.java | 2 +- .../codin/codin/common/security/controller/AuthController.java | 2 +- .../codin/codin/domain/email/controller/EmailController.java | 2 +- .../inu/codin/codin/domain/user/controller/UserController.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index 0e038132..2c672c94 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -9,7 +9,7 @@ @RestController @RequestMapping(value = "/v3/api", produces = "plain/text; charset=utf-8") -@Tag(name = "TestController", description = "테스트용 API") +@Tag(name = "Test API", description = "[관리자] 유저 테스트용 API") public class TestController { @Operation(summary = "1 기본 접근 테스트 - 로그인 없이 접근 가능") diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index ec10901c..64d2762a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -20,7 +20,7 @@ @RestController @RequestMapping(value = "/auth" , produces = "plain/text; charset=utf-8") -@Tag(name = "Auth API") +@Tag(name = "User Auth API") @RequiredArgsConstructor public class AuthController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index bffcb50a..9d4d204a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -16,7 +16,7 @@ @RestController @RequestMapping(value = "/email", produces = "plain/text; charset=utf-8") -@Tag(name = "Email API") +@Tag(name = "User Auth API") @RequiredArgsConstructor public class EmailController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 6529b73c..7793f37c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -12,7 +12,7 @@ @RestController @RequestMapping(value = "/users", produces = "plain/text; charset=utf-8") -@Tag(name = "User API") +@Tag(name = "User Auth API") @RequiredArgsConstructor public class UserController { From 39452eff96de63292aa6fad78322b00b982591bd Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 18 Nov 2024 18:16:13 +0900 Subject: [PATCH 0083/1002] =?UTF-8?q?Perf=20:=20Info=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20Tag=20=ED=86=B5=ED=95=A9=20=EB=B0=8F=20RequestMapping=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/info/lab/controller/LabController.java | 4 ++-- .../codin/domain/info/office/controller/OfficeController.java | 4 ++-- .../domain/info/professor/controller/ProfessorController.java | 4 ++-- codin-core/src/main/resources | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java index 102477ad..522b72ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java @@ -16,8 +16,8 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/lab") -@Tag(name = "Lab Info API") +@RequestMapping("/info/lab") +@Tag(name = "Info API") public class LabController { private final LabService labService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java index 4e74d1ed..586e5a91 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -17,8 +17,8 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/office") -@Tag(name = "Department Office Info API") +@RequestMapping("/info/office") +@Tag(name = "Info API") public class OfficeController { private final OfficeService officeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java index e29c1c72..a21bb4da 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java @@ -17,8 +17,8 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/professor") -@Tag(name = "Professor Info API") +@RequestMapping("/info/professor") +@Tag(name = "Info API") public class ProfessorController { private final ProfessorService professorService; diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a64e3e5c..a45e23e5 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a64e3e5c72f990ef1f1539176216913960afe02f +Subproject commit a45e23e56b9d4bb2025330c4f59382a4fc5a4e7a From eece7baed21ad231647c2fae793f2e8b7641a5d9 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 18 Nov 2024 18:22:34 +0900 Subject: [PATCH 0084/1002] =?UTF-8?q?Perf=20:=20Info=20API=20ResponseUtils?= =?UTF-8?q?=20=ED=8F=AC=EB=A7=B7=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/ResponseUtils.java | 2 +- .../codin/domain/info/lab/controller/LabController.java | 7 +++---- .../domain/info/office/controller/OfficeController.java | 7 +++---- .../info/professor/controller/ProfessorController.java | 7 +++---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java b/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java index bbe20826..c8b0e68e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java @@ -13,7 +13,7 @@ public class ResponseUtils { * @param data class 형태의 데이터 타입 (e.g. DTO) * @return ResponseEntity */ - public static ResponseEntity success(Object data) { + public static ResponseEntity success(T data) { return new ResponseEntity<>(data, HttpStatus.OK); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java index 522b72ed..7bbf3e40 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.lab.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.info.lab.dto.LabListResDTO; import inu.codin.codin.domain.info.lab.service.LabService; import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; @@ -25,15 +26,13 @@ public class LabController { @Operation(summary = "연구실 썸네일 반환") @GetMapping("/thumbnail/{id}") public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ - return ResponseEntity.ok() - .body(labService.getLabThumbnail(id)); + return ResponseUtils.success(labService.getLabThumbnail(id)); } @Operation(summary = "연구실 리스트 반환") @GetMapping public ResponseEntity> getAllLab(){ - return ResponseEntity.ok() - .body(labService.getAllLab()); + return ResponseUtils.success(labService.getAllLab()); } //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java index 586e5a91..65c1aaab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.office.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; import inu.codin.codin.domain.info.office.service.OfficeService; @@ -26,14 +27,12 @@ public class OfficeController { @Operation(summary = "학과별 사무실 직원 정보 반환") @GetMapping("/{department}") public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ - return ResponseEntity.ok() - .body(officeService.getOfficeByDepartment(department)); + return ResponseUtils.success(officeService.getOfficeByDepartment(department)); } @Operation(summary = "학과사무실 리스트 반환") @GetMapping public ResponseEntity> getAllOffice(){ - return ResponseEntity.ok() - .body(officeService.getAllOffice()); + return ResponseUtils.success(officeService.getAllOffice()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java index a21bb4da..b9cdfc4a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.professor.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.info.professor.service.ProfessorService; import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; @@ -26,14 +27,12 @@ public class ProfessorController { @Operation(summary = "교수 리스트 반환") @GetMapping("/{department}") public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ - return ResponseEntity.ok() - .body(professorService.getProfessorByDepartment(department)); + return ResponseUtils.success(professorService.getProfessorByDepartment(department)); } @Operation(summary = "id값에 따른 교수 썸네일 반환") @GetMapping("/detail/{id}") public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ - return ResponseEntity.ok() - .body(professorService.getProfessorThumbnail(id)); + return ResponseUtils.success(professorService.getProfessorThumbnail(id)); } } From 596d407b501c51db524efa2957232a5719271ea3 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 18 Nov 2024 19:34:29 +0900 Subject: [PATCH 0085/1002] =?UTF-8?q?[SC-67]=20Feat=20:=20S3=20=EB=8B=A4?= =?UTF-8?q?=EC=A4=91=20Image=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/config/WebConfig.java | 23 ++++++ ...MultipartJackson2HttpMessageConverter.java | 35 +++++++++ .../codin/codin/common/util/S3Service.java | 76 +++++++++++++++++++ .../post/controller/PostController.java | 30 ++++++-- .../dto/request/PostContentUpdateReqDTO.java | 7 +- .../post/dto/request/PostCreateReqDTO.java | 6 +- .../post/dto/response/PostDetailResDTO.java | 4 +- .../codin/domain/post/entity/PostEntity.java | 12 ++- .../domain/post/service/PostService.java | 29 +++++-- 9 files changed, 201 insertions(+), 21 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java new file mode 100644 index 00000000..27c8112c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java @@ -0,0 +1,23 @@ +package inu.codin.codin.common.config; + +import inu.codin.codin.common.util.MultipartJackson2HttpMessageConverter; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter; + + public WebConfig(MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter) { + this.multipartJackson2HttpMessageConverter = multipartJackson2HttpMessageConverter; + } + + @Override + public void extendMessageConverters(List> converters) { + // 컨버터 리스트에 사용자 정의 컨버터 추가 + converters.add(multipartJackson2HttpMessageConverter); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java new file mode 100644 index 00000000..5fd59d8c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java @@ -0,0 +1,35 @@ +package inu.codin.codin.common.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; + +@Component +public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { + + /** + * "Content-Type: multipart/form-data" 헤더를 지원하는 HTTP 요청 변환기 + */ + public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java b/codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java new file mode 100644 index 00000000..71a03b1d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java @@ -0,0 +1,76 @@ +package inu.codin.codin.common.util; + +import com.amazonaws.services.s3.AmazonS3Client; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Service +public class S3Service { + + private final AmazonS3Client amazonS3Client; + + public S3Service(AmazonS3Client amazonS3Client) { + this.amazonS3Client = amazonS3Client; + } + @Value("codin-s3-bucket") + public String bucket; + + //모든 이미지 업로드 + public List uploadFiles(List multipartFiles) { + List uploadUrls = new ArrayList<>(); + for (MultipartFile multipartFile : multipartFiles) { + uploadUrls.add(uploadFile(multipartFile)); + } + return uploadUrls; + + } + + //각 이미지 S3에 업로드 + public String uploadFile(MultipartFile multipartFile) { + validateImageFile(multipartFile); + + String fileName = createFileName(multipartFile.getOriginalFilename()); + try{ + amazonS3Client.putObject(bucket, fileName, multipartFile.getInputStream(), null); + } catch (IOException e) { + throw new RuntimeException("Failed to upload file", e); + } + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + //중복 방지를 위해 이미지파일명 생성 + public String createFileName(String originalFilename) { + String extension = getExtension(originalFilename); + return UUID.randomUUID().toString() + "." + extension; // 고유한 파일명 생성 + + } + + //파일 유효성 검사( 이미지 관련 확장자만 업로드 가능 설정) + public void validateImageFile(MultipartFile multipartFile) { + List validExtensions = List.of("jpg", "jpeg", "png", "gif"); + String extension = getExtension(multipartFile.getOriginalFilename()); + if (extension == null || !validExtensions.contains(extension.toLowerCase())) { + throw new IllegalArgumentException("유효한 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다."); + } + } + + //확장자 추출 + private String getExtension(String fileName) { + if (fileName == null || fileName.lastIndexOf(".") == -1) { + return ""; // 확장자가 없는 경우 + } + return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + } + + + //삭제 + public void deleteFile(String fileName) { + amazonS3Client.deleteObject(bucket, fileName); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index daf00f41..ed1ef05c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -3,26 +3,44 @@ import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @RequestMapping("/api/posts") public class PostController { private final PostService postService; + public PostController(PostService postService) { this.postService = postService; } - @Operation(summary = "게시물 작성") - @PostMapping - public ResponseEntity createPost(@RequestBody @Valid PostCreateReqDTO postCreateReqDTO) { - postService.createPost(postCreateReqDTO); - //게시물 작성 성공시 상태코드 201 Created , 게시물 작성성공 메세지 반환. + @Operation( + summary = "게시물 작성", + description = "JSON 형식의 게시물 데이터(postContent)와 이미지 파일(postImages) 업로드" + ) + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createPost( + @RequestPart("postContent") @Valid PostCreateReqDTO postCreateReqDTO, + @RequestPart(value = "postImages", required = false) List postImages) { + + // postImages가 null이면 빈 리스트로 처리 + if (postImages == null) { + postImages = List.of(); + } + + postService.createPost(postCreateReqDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED).body("게시물 작성 성공"); } - } + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java index 842fa8e9..f5e16e5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java @@ -3,6 +3,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @Data public class PostContentUpdateReqDTO { @@ -15,6 +18,6 @@ public class PostContentUpdateReqDTO { @NotBlank private String content; - @Schema(description = "게시물 내 이미지 url", example = "example/updated_image.jpg") - private String postImageUrl; + //이미지 별도 Multipart (RequestPart 사용) + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java index 277df337..e06730ee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java @@ -5,6 +5,9 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @Data public class PostCreateReqDTO { @@ -25,8 +28,7 @@ public class PostCreateReqDTO { @NotBlank private String content; - @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") - private String postImageUrl; + //이미지 별도 Multipart (RequestPart 사용) @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") @NotNull diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java index f5f08c2c..0662756b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotBlank; import lombok.Data; +import java.util.List; + @Data public class PostDetailResDTO { @@ -29,7 +31,7 @@ public class PostDetailResDTO { private String content; @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") - private String postImageUrl; + private List postImageUrl; @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 7cb5d4d3..02c1b61d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -1,16 +1,20 @@ package inu.codin.codin.domain.post.entity; import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.List; + @Document(collection = "post") @Getter public class PostEntity extends BaseTimeEntity { - @Id + @Id @NotBlank + private String id; private String postId; private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 @@ -21,7 +25,7 @@ public class PostEntity extends BaseTimeEntity { private String content; - private String postImageUrl; + private List postImageUrls; private boolean isAnonymous; @@ -29,14 +33,14 @@ public class PostEntity extends BaseTimeEntity { @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, String postImageUrl, PostStatus postStatus) { + public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus) { this.postId = postId; this.userId = userId; this.postCategory = postCategory; this.title = title; this.content = content; this.isAnonymous = isAnonymous; - this.postImageUrl = postImageUrl; + this.postImageUrls = postImageUrls; this.postStatus = postStatus; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 47ddc7c9..a5c3bd9a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,29 +1,46 @@ package inu.codin.codin.domain.post.service; +import inu.codin.codin.common.util.S3Service; import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.exception.PostCreateFailException; import inu.codin.codin.domain.post.repository.PostRepository; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @Service public class PostService { private final PostRepository postRepository; - public PostService(PostRepository postRepository) { + private final S3Service s3Service; + + public PostService(PostRepository postRepository, S3Service s3Service) { this.postRepository = postRepository; + this.s3Service = s3Service; } - public void createPost(PostCreateReqDTO postCreateReqDTO) { + public void createPost(PostCreateReqDTO postCreateReqDTO, List postImages) { validateCreatePostRequest(postCreateReqDTO); + List imageUrls; + if (postImages != null && !postImages.isEmpty()) { + imageUrls = s3Service.uploadFiles(postImages); + } else { + imageUrls = List.of(); // 이미지가 없으면 빈 리스트로 초기화 + } + PostEntity postEntity = PostEntity.builder() .userId(postCreateReqDTO.getUserId()) .content(postCreateReqDTO.getContent()) .title(postCreateReqDTO.getTitle()) .postCategory(postCreateReqDTO.getPostCategory()) - .postImageUrl(postCreateReqDTO.getPostImageUrl()) + + //이미지 Url List 저장 + .postImageUrls(imageUrls) + .isAnonymous(postCreateReqDTO.isAnonymous()) //Default Status = Active @@ -34,9 +51,9 @@ public void createPost(PostCreateReqDTO postCreateReqDTO) { } private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { - if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { - throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); - } +// if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { +// throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); +// } } } From 412791173ab4b4b3e328d26f05753ef033147b08 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 18 Nov 2024 23:39:16 +0900 Subject: [PATCH 0086/1002] =?UTF-8?q?[SC-77]=20Pref:=20DB=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20info=20=ED=95=98=EB=82=98=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=ED=95=98=EC=97=AC=20=EC=83=81=EC=86=8D=20ent?= =?UTF-8?q?ity=20=EA=B5=AC=EC=84=B1,=20=EA=B7=B8=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/controller/LabController.java | 13 ++--- .../{ => domain}/lab/dto/LabListResDTO.java | 4 +- .../lab/dto/LabThumbnailResDTO.java | 4 +- .../info/{ => domain}/lab/entity/Lab.java | 16 ++---- .../lab/exception/LabNotFoundException.java | 2 +- .../info/domain/lab/service/LabService.java | 32 ++++++++++++ .../office/controller/OfficeController.java | 8 +-- .../office/dto/OfficeListResDTO.java | 6 +-- .../{ => domain}/office/dto/OfficeMember.java | 2 +- .../office/dto/OfficeMemberResDTO.java | 4 +- .../{ => domain}/office/entity/Office.java | 18 +++---- .../domain/office/service/OfficeService.java | 27 ++++++++++ .../controller/ProfessorController.java | 8 +-- .../professor/dto/ProfessorListResDTO.java | 4 +- .../dto/ProfessorThumbnailResDTO.java | 6 +-- .../professor/entity/Professor.java | 24 +++------ .../exception/ProfessorNotFoundException.java | 2 +- .../professor/service/ProfessorService.java | 30 +++++++++++ .../codin/codin/domain/info/entity/Info.java | 31 +++++++++++ .../codin/domain/info/entity/InfoType.java | 15 ++++++ .../info/lab/repository/LabRepository.java | 7 --- .../domain/info/lab/service/LabService.java | 51 ------------------- .../office/repository/OfficeRepository.java | 13 ----- .../info/office/service/OfficeService.java | 27 ---------- .../repository/ProfessorRepository.java | 12 ----- .../professor/service/ProfessorService.java | 30 ----------- .../info/repository/InfoRepository.java | 41 +++++++++++++++ 27 files changed, 225 insertions(+), 212 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/lab/controller/LabController.java (73%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/lab/dto/LabListResDTO.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/lab/dto/LabThumbnailResDTO.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/lab/entity/Lab.java (70%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/lab/exception/LabNotFoundException.java (71%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/office/controller/OfficeController.java (81%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/office/dto/OfficeListResDTO.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/office/dto/OfficeMember.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/office/dto/OfficeMemberResDTO.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/office/entity/Office.java (65%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/professor/controller/ProfessorController.java (81%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/professor/dto/ProfessorListResDTO.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/professor/dto/ProfessorThumbnailResDTO.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/professor/entity/Professor.java (51%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/professor/exception/ProfessorNotFoundException.java (70%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java similarity index 73% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 7bbf3e40..46683573 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.info.lab.controller; +package inu.codin.codin.domain.info.domain.lab.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.lab.dto.LabListResDTO; -import inu.codin.codin.domain.info.lab.service.LabService; -import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; +import inu.codin.codin.domain.info.domain.lab.dto.LabListResDTO; +import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResDTO; +import inu.codin.codin.domain.info.domain.lab.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -35,9 +35,4 @@ public ResponseEntity> getAllLab(){ return ResponseUtils.success(labService.getAllLab()); } - //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 - @GetMapping("/") - public void joinLab(){ - labService.joinlab(); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResDTO.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResDTO.java index bbd0b8d4..34d39309 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResDTO.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto; -import inu.codin.codin.domain.info.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResDTO.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResDTO.java index 0c112d14..f384dd84 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResDTO.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto; -import inu.codin.codin.domain.info.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java index 53ad75ed..5a388fb3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -1,23 +1,18 @@ -package inu.codin.codin.domain.info.lab.entity; +package inu.codin.codin.domain.info.domain.lab.entity; +import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -@Document(collection = "lab") +@Document(collection = "info") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class Lab { - - @Id @NotBlank - private String id; - - @NotBlank - private Department department; +public class Lab extends Info { @NotBlank private String professor; @@ -36,5 +31,4 @@ public class Lab { private String labNumber; private String site; - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java index 95982fd8..a3003257 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.lab.exception; +package inu.codin.codin.domain.info.domain.lab.exception; public class LabNotFoundException extends RuntimeException{ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java new file mode 100644 index 00000000..ab96df21 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.info.domain.lab.service; + +import inu.codin.codin.domain.info.domain.lab.dto.LabListResDTO; +import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResDTO; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.domain.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class LabService { + + private final InfoRepository infoRepository; + + public LabThumbnailResDTO getLabThumbnail(String id) { + Lab lab = infoRepository.findLabById(id) + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + return LabThumbnailResDTO.of(lab); + } + + public List getAllLab() { + List labs = infoRepository.findAllLabs(); + return labs.stream().map(LabListResDTO::of).toList(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 65c1aaab..49eac666 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.info.office.controller; +package inu.codin.codin.domain.info.domain.office.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; -import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; -import inu.codin.codin.domain.info.office.service.OfficeService; +import inu.codin.codin.domain.info.domain.office.service.OfficeService; +import inu.codin.codin.domain.info.domain.office.dto.OfficeListResDTO; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResDTO; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResDTO.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResDTO.java index a3e5db66..ad179c1c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResDTO.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.office.dto; +package inu.codin.codin.domain.info.domain.office.dto; -import inu.codin.codin.domain.info.office.entity.Office; +import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -55,7 +55,7 @@ public static OfficeListResDTO of(Office office){ .open(office.getOpen()) .vacation(office.getVacation()) .office_number(office.getOffice_number()) - .fax(office.getOffice_number()) + .fax(office.getFax()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMember.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMember.java index a31b51a8..138a624a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMember.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.office.dto; +package inu.codin.codin.domain.info.domain.office.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResDTO.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResDTO.java index b9b3fb16..e38b01b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResDTO.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.office.dto; +package inu.codin.codin.domain.info.domain.office.dto; -import inu.codin.codin.domain.info.office.entity.Office; +import inu.codin.codin.domain.info.domain.office.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java similarity index 65% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java index 207ea80e..728988b9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java @@ -1,25 +1,21 @@ -package inu.codin.codin.domain.info.office.entity; +package inu.codin.codin.domain.info.domain.office.entity; -import inu.codin.codin.domain.info.office.dto.OfficeMember; +import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMember; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; -@Document(collection = "department_office") -@Getter +@Document(collection = "info") @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Office { - @Id @NotBlank - private String id; - - @NotBlank - private Department department; +@Getter +public class Office extends Info { @NotBlank private String location; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java new file mode 100644 index 00000000..37f889ce --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.info.domain.office.service; + +import inu.codin.codin.domain.info.domain.office.dto.OfficeListResDTO; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResDTO; +import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class OfficeService { + + private final InfoRepository infoRepository; + public List getAllOffice() { + List offices = infoRepository.findAllOffices(); + return offices.stream().map(OfficeListResDTO::of).toList(); + } + + public List getOfficeByDepartment(Department department) { + List offices = infoRepository.findOfficeByDepartment(department); + return offices.stream().map(OfficeMemberResDTO::of).toList(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index b9cdfc4a..c42f2376 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.info.professor.controller; +package inu.codin.codin.domain.info.domain.professor.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.professor.service.ProfessorService; -import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; -import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResDTO; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResDTO.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResDTO.java index 616b39f9..503bcb1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResDTO.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.professor.dto; +package inu.codin.codin.domain.info.domain.professor.dto; -import inu.codin.codin.domain.info.professor.entity.Professor; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResDTO.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResDTO.java index 2105b3dd..e9ecc7c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResDTO.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.professor.dto; +package inu.codin.codin.domain.info.domain.professor.dto; -import inu.codin.codin.domain.info.professor.entity.Professor; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -44,7 +44,7 @@ public static ProfessorThumbnailResDTO of(Professor professor) { .site(professor.getSite()) .field(professor.getField()) .subject(professor.getSubject()) - .labId(professor.getLab().getId()) + .labId(professor.getLabId()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java similarity index 51% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java index 98c09b4c..8b248e9e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java @@ -1,24 +1,17 @@ -package inu.codin.codin.domain.info.professor.entity; +package inu.codin.codin.domain.info.domain.professor.entity; -import inu.codin.codin.domain.info.lab.entity.Lab; -import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.entity.Info; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; -@Document(collection = "professor") +@Document(collection = "info") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class Professor { - @Id @NotBlank - private String id; - - @NotBlank - private Department department; +public class Professor extends Info { @NotBlank private String name; @@ -39,10 +32,9 @@ public class Professor { private String subject; - @DBRef - private Lab lab; + private String labId; - public void updateLab(Lab lab){ - this.lab = lab; + public void updateLab(Lab lab) { + this.labId = lab.getId(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java index 766dd62b..39f3cc7d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.professor.exception; +package inu.codin.codin.domain.info.domain.professor.exception; public class ProfessorNotFoundException extends RuntimeException{ public ProfessorNotFoundException(String message){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java new file mode 100644 index 00000000..862fdb60 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -0,0 +1,30 @@ +package inu.codin.codin.domain.info.domain.professor.service; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResDTO; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; +import inu.codin.codin.domain.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ProfessorService { + + private final InfoRepository infoRepository; + + public List getProfessorByDepartment(Department department){ + List professors = infoRepository.findAllProfessorsByDepartment(department); + return professors.stream().map(ProfessorListResDTO::of).toList(); + } + + public ProfessorThumbnailResDTO getProfessorThumbnail(String id) { + Professor professor = infoRepository.findProfessorById(id) + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + return ProfessorThumbnailResDTO.of(professor); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java new file mode 100644 index 00000000..ef9f29b7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.info.entity; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "info") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public abstract class Info { + + @Id @NotBlank + private String id; + + @NotBlank + private Department department; + + @NotBlank + private InfoType infoType; + + public Info(String id, Department department, InfoType infoType) { + this.id = id; + this.department = department; + this.infoType = infoType; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java new file mode 100644 index 00000000..c6de57c7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.info.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum InfoType { + + PROFESSOR("교수님"), + LAB("연구실"), + OFFICE("학과 사무실"); + + private final String description; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java deleted file mode 100644 index 9612ddeb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.info.lab.repository; - -import inu.codin.codin.domain.info.lab.entity.Lab; -import org.springframework.data.mongodb.repository.MongoRepository; - -public interface LabRepository extends MongoRepository { -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java deleted file mode 100644 index f53f5627..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java +++ /dev/null @@ -1,51 +0,0 @@ -package inu.codin.codin.domain.info.lab.service; - -import inu.codin.codin.domain.info.lab.dto.LabListResDTO; -import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; -import inu.codin.codin.domain.info.lab.entity.Lab; -import inu.codin.codin.domain.info.lab.exception.LabNotFoundException; -import inu.codin.codin.domain.info.lab.repository.LabRepository; -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class LabService { - - private final LabRepository labRepository; - private final ProfessorRepository professorRepository; - - public LabThumbnailResDTO getLabThumbnail(String id) { - Lab lab = labRepository.findById(id) - .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); - return LabThumbnailResDTO.of(lab); - } - - //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 - @Transactional - public List joinlab() { - List professors = professorRepository.findAll(); - List labs = labRepository.findAll(); - - for (Professor professor : professors) { - for (Lab lab : labs) { - if (professor.getName().equals(lab.getProfessor())) { - professor.updateLab(lab); - professorRepository.save(professor); - break; - } - } - } - return professors; - } - - public List getAllLab() { - List labs = labRepository.findAll(); - return labs.stream().map(LabListResDTO::of).toList(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java deleted file mode 100644 index b12c1346..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package inu.codin.codin.domain.info.office.repository; - -import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.common.Department; -import jakarta.validation.constraints.NotBlank; -import org.springframework.data.mongodb.repository.MongoRepository; - -import java.util.List; - -public interface OfficeRepository extends MongoRepository { - - List findAllByDepartment(@NotBlank Department department); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java deleted file mode 100644 index 0f392a21..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codin.domain.info.office.service; - -import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; -import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; -import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.domain.info.office.repository.OfficeRepository; -import inu.codin.codin.common.Department; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class OfficeService { - - private final OfficeRepository officeRepository; - public List getAllOffice() { - List offices = officeRepository.findAll(); - return offices.stream().map(OfficeListResDTO::of).toList(); - } - - public List getOfficeByDepartment(Department department) { - List offices = officeRepository.findAllByDepartment(department); - return offices.stream().map(OfficeMemberResDTO::of).toList(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java deleted file mode 100644 index 3459b649..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package inu.codin.codin.domain.info.professor.repository; - -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.common.Department; -import org.springframework.data.mongodb.repository.MongoRepository; - -import java.util.List; - -public interface ProfessorRepository extends MongoRepository { - - List findAllByDepartment(Department department); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java deleted file mode 100644 index 564f718d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java +++ /dev/null @@ -1,30 +0,0 @@ -package inu.codin.codin.domain.info.professor.service; - -import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; -import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.info.professor.exception.ProfessorNotFoundException; -import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; -import inu.codin.codin.common.Department; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class ProfessorService { - - private final ProfessorRepository professorRepository; - - public List getProfessorByDepartment(Department department){ - List professors = professorRepository.findAllByDepartment(department); - return professors.stream().map(ProfessorListResDTO::of).toList(); - } - - public ProfessorThumbnailResDTO getProfessorThumbnail(String id) { - Professor professor = professorRepository.findById(id) - .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); - return ProfessorThumbnailResDTO.of(professor); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java new file mode 100644 index 00000000..f9fcfa58 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.info.repository; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.entity.Info; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import java.util.List; +import java.util.Optional; + +public interface InfoRepository extends MongoRepository { + + @Query("{ 'info_type': 'LAB' }") + List findAllLabs(); + + @Query("{ 'info_type': 'LAB', 'id': ?0}") + Optional findLabById(String id); + + @Query("{ 'info_type': 'OFFICE' }") + List findAllOffices(); + + @Query("{ 'info_type': 'OFFICE', 'department': ?0}") + List findOfficeByDepartment(Department department); + + @Query("{ 'info_type': 'PROFESSOR' , 'department': ?0}") + List findAllProfessorsByDepartment(Department department); + + @Query("{ 'info_type': 'PROFESSOR' , 'id': ?0}") + Optional findProfessorById(String id); + + @Query("{ 'info_type': 'PROFESSOR'}") + List findAllProfessor(); + + +} + + + From 99331ee1e86b2b56e28374b3cec96fbd6f2a2842 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 00:22:33 +0900 Subject: [PATCH 0087/1002] =?UTF-8?q?[SC-77]=20refactor=20:=20Dto=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/domain/lab/controller/LabController.java | 8 ++++---- ...LabListResDTO.java => LabListResponseDto.java} | 8 ++++---- ...ilResDTO.java => LabThumbnailResponseDto.java} | 12 ++++++------ .../info/domain/lab/service/LabService.java | 15 ++++++--------- .../office/controller/OfficeController.java | 8 ++++---- ...ListResDTO.java => OfficeListResponseDto.java} | 8 ++++---- ...erResDTO.java => OfficeMemberResponseDto.java} | 8 ++++---- .../info/domain/office/service/OfficeService.java | 12 ++++++------ .../professor/controller/ProfessorController.java | 8 ++++---- ...tResDTO.java => ProfessorListResponseDto.java} | 8 ++++---- ...TO.java => ProfessorThumbnailResponseDto.java} | 8 ++++---- .../professor/service/ProfessorService.java | 12 ++++++------ 12 files changed, 56 insertions(+), 59 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/{LabListResDTO.java => LabListResponseDto.java} (84%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/{LabThumbnailResDTO.java => LabThumbnailResponseDto.java} (84%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{OfficeListResDTO.java => OfficeListResponseDto.java} (85%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{OfficeMemberResDTO.java => OfficeMemberResponseDto.java} (77%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/{ProfessorListResDTO.java => ProfessorListResponseDto.java} (81%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/{ProfessorThumbnailResDTO.java => ProfessorThumbnailResponseDto.java} (89%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 46683573..ad733f71 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.info.domain.lab.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.domain.lab.dto.LabListResDTO; -import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResDTO; +import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; import inu.codin.codin.domain.info.domain.lab.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -25,13 +25,13 @@ public class LabController { @Operation(summary = "연구실 썸네일 반환") @GetMapping("/thumbnail/{id}") - public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ + public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ return ResponseUtils.success(labService.getLabThumbnail(id)); } @Operation(summary = "연구실 리스트 반환") @GetMapping - public ResponseEntity> getAllLab(){ + public ResponseEntity> getAllLab(){ return ResponseUtils.success(labService.getAllLab()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java index 34d39309..ce2df4f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java @@ -14,7 +14,7 @@ @Getter @Setter -public class LabListResDTO { +public class LabListResponseDto { @Schema(description = "Lab DB의 pk값", example = "b2jfbe432..") @NotBlank private final String id; @@ -31,15 +31,15 @@ public class LabListResDTO { private final String professor; @Builder - public LabListResDTO(String id, String title, String content, String professor) { + public LabListResponseDto(String id, String title, String content, String professor) { this.id = id; this.title = title; this.content = content; this.professor = professor; } - public static LabListResDTO of(Lab lab){ - return LabListResDTO.builder() + public static LabListResponseDto of(Lab lab){ + return LabListResponseDto.builder() .id(lab.getId()) .title(lab.getTitle()) .content(lab.getContent()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java index f384dd84..a17d3965 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java @@ -12,7 +12,7 @@ 해당하는 연구실에 대한 내용들을 모두 반환한다. */ @Data -public class LabThumbnailResDTO { +public class LabThumbnailResponseDto { @NotBlank @Schema(description = "학과", example = "CSE") @@ -45,9 +45,9 @@ public class LabThumbnailResDTO { private String site; @Builder - public LabThumbnailResDTO(Department department, String title, String content, String professor, - String professorLoc, String professorNumber, String labLoc, - String labNumber, String site) { + public LabThumbnailResponseDto(Department department, String title, String content, String professor, + String professorLoc, String professorNumber, String labLoc, + String labNumber, String site) { this.department = department; this.title = title; this.content = content; @@ -59,8 +59,8 @@ public LabThumbnailResDTO(Department department, String title, String content, S this.site = site; } - public static LabThumbnailResDTO of(Lab lab) { - return LabThumbnailResDTO.builder() + public static LabThumbnailResponseDto of(Lab lab) { + return LabThumbnailResponseDto.builder() .department(lab.getDepartment()) .title(lab.getTitle()) .content(lab.getContent()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index ab96df21..8cefc60f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -1,15 +1,12 @@ package inu.codin.codin.domain.info.domain.lab.service; -import inu.codin.codin.domain.info.domain.lab.dto.LabListResDTO; -import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResDTO; +import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; import inu.codin.codin.domain.info.domain.lab.entity.Lab; import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; -import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -19,14 +16,14 @@ public class LabService { private final InfoRepository infoRepository; - public LabThumbnailResDTO getLabThumbnail(String id) { + public LabThumbnailResponseDto getLabThumbnail(String id) { Lab lab = infoRepository.findLabById(id) .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); - return LabThumbnailResDTO.of(lab); + return LabThumbnailResponseDto.of(lab); } - public List getAllLab() { + public List getAllLab() { List labs = infoRepository.findAllLabs(); - return labs.stream().map(LabListResDTO::of).toList(); + return labs.stream().map(LabListResponseDto::of).toList(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 49eac666..2d4f714d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.info.domain.office.service.OfficeService; -import inu.codin.codin.domain.info.domain.office.dto.OfficeListResDTO; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResDTO; +import inu.codin.codin.domain.info.domain.office.dto.OfficeListResponseDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResponseDto; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -26,13 +26,13 @@ public class OfficeController { @Operation(summary = "학과별 사무실 직원 정보 반환") @GetMapping("/{department}") - public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ + public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ return ResponseUtils.success(officeService.getOfficeByDepartment(department)); } @Operation(summary = "학과사무실 리스트 반환") @GetMapping - public ResponseEntity> getAllOffice(){ + public ResponseEntity> getAllOffice(){ return ResponseUtils.success(officeService.getAllOffice()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java index ad179c1c..903439d0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java @@ -14,7 +14,7 @@ */ @Getter @Setter -public class OfficeListResDTO{ +public class OfficeListResponseDto { @Schema(description = "학과", example = "IT_COLLEGE") @NotBlank @@ -39,7 +39,7 @@ public class OfficeListResDTO{ private final String fax; @Builder - public OfficeListResDTO(Department department, String location, String open, String vacation, String office_number, String fax) { + public OfficeListResponseDto(Department department, String location, String open, String vacation, String office_number, String fax) { this.department = department; this.location = location; this.open = open; @@ -48,8 +48,8 @@ public OfficeListResDTO(Department department, String location, String open, Str this.fax = fax; } - public static OfficeListResDTO of(Office office){ - return OfficeListResDTO.builder() + public static OfficeListResponseDto of(Office office){ + return OfficeListResponseDto.builder() .department(office.getDepartment()) .location(office.getLocation()) .open(office.getOpen()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java similarity index 77% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java index e38b01b5..96c22c12 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java @@ -14,7 +14,7 @@ */ @Getter @Setter -public class OfficeMemberResDTO { +public class OfficeMemberResponseDto { @Schema(description = "사무실 평면도", example = "https://") private String img; @@ -22,13 +22,13 @@ public class OfficeMemberResDTO { private List officeMembers; @Builder - public OfficeMemberResDTO(String img, List officeMembers) { + public OfficeMemberResponseDto(String img, List officeMembers) { this.img = img; this.officeMembers = officeMembers; } - public static OfficeMemberResDTO of(Office office) { - return OfficeMemberResDTO.builder() + public static OfficeMemberResponseDto of(Office office) { + return OfficeMemberResponseDto.builder() .img(office.getImg()) .officeMembers(office.getMember()) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index 37f889ce..af0d3787 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.domain.office.service; -import inu.codin.codin.domain.info.domain.office.dto.OfficeListResDTO; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResDTO; +import inu.codin.codin.domain.info.domain.office.dto.OfficeListResponseDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResponseDto; import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.repository.InfoRepository; @@ -15,13 +15,13 @@ public class OfficeService { private final InfoRepository infoRepository; - public List getAllOffice() { + public List getAllOffice() { List offices = infoRepository.findAllOffices(); - return offices.stream().map(OfficeListResDTO::of).toList(); + return offices.stream().map(OfficeListResponseDto::of).toList(); } - public List getOfficeByDepartment(Department department) { + public List getOfficeByDepartment(Department department) { List offices = infoRepository.findOfficeByDepartment(department); - return offices.stream().map(OfficeMemberResDTO::of).toList(); + return offices.stream().map(OfficeMemberResponseDto::of).toList(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index c42f2376..64dbc421 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.info.domain.professor.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResDTO; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; @@ -26,13 +26,13 @@ public class ProfessorController { @Operation(summary = "교수 리스트 반환") @GetMapping("/{department}") - public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ + public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ return ResponseUtils.success(professorService.getProfessorByDepartment(department)); } @Operation(summary = "id값에 따른 교수 썸네일 반환") @GetMapping("/detail/{id}") - public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ + public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ return ResponseUtils.success(professorService.getProfessorThumbnail(id)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java index 503bcb1d..d95b1f62 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java @@ -11,7 +11,7 @@ 모든 교수님들의 리스트를 반환한다. */ -public record ProfessorListResDTO( +public record ProfessorListResponseDto( @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") String id, @@ -21,11 +21,11 @@ public record ProfessorListResDTO( @NotBlank @Schema(description = "학과", example = "CSE") Department department) { @Builder - public ProfessorListResDTO { + public ProfessorListResponseDto { } - public static ProfessorListResDTO of(Professor professor) { - return ProfessorListResDTO.builder() + public static ProfessorListResponseDto of(Professor professor) { + return ProfessorListResponseDto.builder() .id(professor.getId()) .name(professor.getName()) .department(professor.getDepartment()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java similarity index 89% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java index e9ecc7c5..ff2abff3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java @@ -10,7 +10,7 @@ 교수님 상세 정보 반환 DTO 해당되는 교수님의 상세 정보들을 반환한다. */ -public record ProfessorThumbnailResDTO( +public record ProfessorThumbnailResponseDto( @NotBlank @Schema(description = "학과", example = "CSE") Department department, @NotBlank @Schema(description = "성함", example = "홍길동") @@ -31,11 +31,11 @@ public record ProfessorThumbnailResDTO( String labId) { @Builder - public ProfessorThumbnailResDTO { + public ProfessorThumbnailResponseDto { } - public static ProfessorThumbnailResDTO of(Professor professor) { - return ProfessorThumbnailResDTO.builder() + public static ProfessorThumbnailResponseDto of(Professor professor) { + return ProfessorThumbnailResponseDto.builder() .department(professor.getDepartment()) .name(professor.getName()) .image(professor.getImage()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index 862fdb60..3e87574d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.info.domain.professor.service; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResDTO; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResDTO; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; @@ -17,14 +17,14 @@ public class ProfessorService { private final InfoRepository infoRepository; - public List getProfessorByDepartment(Department department){ + public List getProfessorByDepartment(Department department){ List professors = infoRepository.findAllProfessorsByDepartment(department); - return professors.stream().map(ProfessorListResDTO::of).toList(); + return professors.stream().map(ProfessorListResponseDto::of).toList(); } - public ProfessorThumbnailResDTO getProfessorThumbnail(String id) { + public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { Professor professor = infoRepository.findProfessorById(id) .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); - return ProfessorThumbnailResDTO.of(professor); + return ProfessorThumbnailResponseDto.of(professor); } } From 1b6d78b8af53fc48de618b6c2026125bb1b7e2a7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 19 Nov 2024 01:47:33 +0900 Subject: [PATCH 0088/1002] =?UTF-8?q?[SC-67]=20Refactor=20:=20DTO=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sUpdateReqDTO.java => PostAnonymousUpdateRequestDTO.java} | 2 +- ...entUpdateReqDTO.java => PostContentUpdateRequestDTO.java} | 5 +---- ...atusUpdateReqDTO.java => PostStatusUpdateRequestDTO.java} | 3 +-- .../{PostDetailResDTO.java => PostDetailResponseDTO.java} | 2 +- .../inu/codin/codin/domain/post/service/PostService.java | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/{PostAnonymousUpdateReqDTO.java => PostAnonymousUpdateRequestDTO.java} (86%) rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/{PostContentUpdateReqDTO.java => PostContentUpdateRequestDTO.java} (79%) rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/{PostStatusUpdateReqDTO.java => PostStatusUpdateRequestDTO.java} (80%) rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/{PostDetailResDTO.java => PostDetailResponseDTO.java} (96%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java similarity index 86% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java index 08e84d51..87d76aef 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java @@ -5,7 +5,7 @@ import lombok.Data; @Data -public class PostAnonymousUpdateReqDTO { +public class PostAnonymousUpdateRequestDTO { @Schema(description = "익명 여부", example = "true") @NotBlank private boolean isAnonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java similarity index 79% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java index f5e16e5d..f8f73bbc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java @@ -3,12 +3,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; @Data -public class PostContentUpdateReqDTO { +public class PostContentUpdateRequestDTO { @Schema(description = "게시물 제목", example = "Updated Title") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java similarity index 80% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java index 96ac66c1..b5b20bff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java @@ -3,11 +3,10 @@ import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; @Data -public class PostStatusUpdateReqDTO { +public class PostStatusUpdateRequestDTO { @Schema(description = "게시물 상태", example = "ACTIVE") @NotBlank private PostStatus postStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 0662756b..79d3628d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -8,7 +8,7 @@ import java.util.List; @Data -public class PostDetailResDTO { +public class PostDetailResponseDTO { @Schema(description = "유저 ID", example = "111111") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index a5c3bd9a..2e94095d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,10 +1,9 @@ package inu.codin.codin.domain.post.service; -import inu.codin.codin.common.util.S3Service; +import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; -import inu.codin.codin.domain.post.exception.PostCreateFailException; import inu.codin.codin.domain.post.repository.PostRepository; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; From 968eb197f777e0caced40403d3666fbee23db2d5 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 19 Nov 2024 01:47:53 +0900 Subject: [PATCH 0089/1002] =?UTF-8?q?[SC-67]=20Refactor=20:=20S3=20Service?= =?UTF-8?q?=20infra=20=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/{common/util => infra/s3}/S3Service.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename codin-core/src/main/java/inu/codin/codin/{common/util => infra/s3}/S3Service.java (98%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java rename to codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java index 71a03b1d..6e8af79a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/util/S3Service.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.util; +package inu.codin.codin.infra.s3; import com.amazonaws.services.s3.AmazonS3Client; import org.springframework.beans.factory.annotation.Value; From 0e6cf861b0f2428666ab3484ed9290526e755232 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 19 Nov 2024 10:29:43 +0900 Subject: [PATCH 0090/1002] [SC-67] Refactor : Image --- .../codin/domain/post/dto/request/PostCreateReqDTO.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java index e06730ee..51bbf164 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java @@ -3,7 +3,6 @@ import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.web.multipart.MultipartFile; @@ -17,7 +16,7 @@ public class PostCreateReqDTO { private String userId; @Schema(description = "게시물 종류", example = "구해요") - @NotNull + @NotBlank private PostCategory postCategory; @Schema(description = "게시물 제목", example = "Example") @@ -30,8 +29,8 @@ public class PostCreateReqDTO { //이미지 별도 Multipart (RequestPart 사용) - @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") - @NotNull + @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @NotBlank private boolean isAnonymous; //STATUS 필드 - DEFAULT :: ACTIVE From 14f1cbee27d4d4489e82fe9bea794ad8adc0a661 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 19 Nov 2024 14:27:47 +0900 Subject: [PATCH 0091/1002] =?UTF-8?q?[SC-67]=20=20:=20Post=20CRUD=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84=20Image=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 78 ++++++++++++++- .../PostAnonymousUpdateRequestDTO.java | 3 +- .../request/PostContentUpdateRequestDTO.java | 6 +- .../post/dto/request/PostCreateReqDTO.java | 5 +- .../request/PostStatusUpdateRequestDTO.java | 3 +- .../dto/response/PostDetailResponseDTO.java | 13 ++- .../codin/domain/post/entity/PostEntity.java | 20 +++- .../post/repository/PostRepository.java | 3 + .../domain/post/service/PostService.java | 96 +++++++++++++++++-- .../inu/codin/codin/infra/s3/S3Service.java | 22 ++++- 10 files changed, 230 insertions(+), 19 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index ed1ef05c..98417f11 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,11 +1,11 @@ package inu.codin.codin.domain.post.controller; +import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; +import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -42,5 +42,77 @@ public ResponseEntity createPost( postService.createPost(postCreateReqDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED).body("게시물 작성 성공"); } + + @Operation( + summary = "게시물 내용 이미지 수정 추가" + ) + @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity updatePostContent( + @PathVariable String postId, + @RequestPart("postContent") @Valid PostContentUpdateRequestDTO requestDTO, + @RequestPart(value = "postImages", required = false) List postImages) { + + postService.updatePostContent(postId, requestDTO, postImages); + return ResponseEntity.status(HttpStatus.OK).body("게시물 수정 성공"); + } + + @Operation( + summary = "상태 수정" + ) + @PatchMapping("/{postId}/status") + public ResponseEntity updatePostStatus( + @PathVariable String postId, + @RequestBody PostStatusUpdateRequestDTO requestDTO) { + postService.updatePostStatus(postId, requestDTO); + return ResponseEntity.status(HttpStatus.OK).body("게시물 상태 수정 성공"); + } + + @Operation( + summary = "해당 사용자 게시물 전체 조회" + ) + @GetMapping("/user/{userId}") + public ResponseEntity> getAllPosts(@PathVariable String userId) { + List posts = postService.getAllPosts(userId); + return ResponseEntity.status(HttpStatus.OK).body(posts); + } + + @Operation( + summary = "해당 게시물 상세 조회" + ) + @GetMapping("/{postId}") + public ResponseEntity getPost(@PathVariable String postId) { + PostDetailResponseDTO post = postService.getPost(postId); + return ResponseEntity.status(HttpStatus.OK).body(post); + } + + @Operation( + summary = "해당 게시물 삭제" + ) + @DeleteMapping("/postId") + public ResponseEntity deletePost(@PathVariable String postId) { + postService.deletePost(postId); + return ResponseEntity.status(HttpStatus.OK).body("게시물 삭제"); + } + + @Operation( + summary = "해당 이미지 삭제" + ) + @DeleteMapping("/{postId}/images") + public ResponseEntity deletePostImage( + @PathVariable String postId, + @RequestParam String imageUrls) { + postService.deletePostImage(postId, imageUrls); + return ResponseEntity.status(HttpStatus.OK).body("이미지 삭제"); + } + + + + + //익명 수정 불가? + + + + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java index 87d76aef..ca99744e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java @@ -2,11 +2,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class PostAnonymousUpdateRequestDTO { @Schema(description = "익명 여부", example = "true") - @NotBlank + @NotNull private boolean isAnonymous; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java index f8f73bbc..e1c480a7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java @@ -7,9 +7,9 @@ @Data public class PostContentUpdateRequestDTO { - @Schema(description = "게시물 제목", example = "Updated Title") - @NotBlank - private String title; +// @Schema(description = "게시물 제목", example = "Updated Title") +// @NotBlank +// private String title; @Schema(description = "게시물 내용", example = "Updated content") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java index 51bbf164..eecc2961 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.web.multipart.MultipartFile; @@ -16,7 +17,7 @@ public class PostCreateReqDTO { private String userId; @Schema(description = "게시물 종류", example = "구해요") - @NotBlank + @NotNull private PostCategory postCategory; @Schema(description = "게시물 제목", example = "Example") @@ -30,7 +31,7 @@ public class PostCreateReqDTO { //이미지 별도 Multipart (RequestPart 사용) @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") - @NotBlank + @NotNull private boolean isAnonymous; //STATUS 필드 - DEFAULT :: ACTIVE diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java index b5b20bff..32a0a7bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java @@ -3,12 +3,13 @@ import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class PostStatusUpdateRequestDTO { @Schema(description = "게시물 상태", example = "ACTIVE") - @NotBlank + @NotNull private PostStatus postStatus; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 79d3628d..889e7dbb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -1,8 +1,10 @@ package inu.codin.codin.domain.post.dto.response; import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.List; @@ -34,7 +36,16 @@ public class PostDetailResponseDTO { private List postImageUrl; @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") - @NotBlank + @NotNull private boolean isAnonymous; + public PostDetailResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous) { + this.userId = userId; + this.postId = postId; + this.content = content; + this.title = title; + this.postCategory = postCategory; + this.postImageUrl = postImageUrls; + this.isAnonymous = isAnonymous; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 02c1b61d..b8a8db63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -14,7 +14,6 @@ @Getter public class PostEntity extends BaseTimeEntity { @Id @NotBlank - private String id; private String postId; private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 @@ -43,4 +42,23 @@ public PostEntity(String postId, String userId, PostCategory postCategory, Strin this.postImageUrls = postImageUrls; this.postStatus = postStatus; } + + public void updatePostContent(String content, List postImageUrls) { + this.content = content; + this.postImageUrls = postImageUrls; + } + + public void updatePostStatus(PostStatus postStatus) { + if (this.postStatus == postStatus) { + throw new IllegalStateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); + } + this.postStatus = postStatus; + } + public void removePostImage(String imageUrl) { + this.postImageUrls.remove(imageUrl); + } + + public void removeAllPostImages() { + this.postImageUrls.clear(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 12495481..4da65b5a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -4,9 +4,12 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository public interface PostRepository extends MongoRepository { Optional findByTitle(String title); + + List findByUserId(String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 2e94095d..d47252e0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,5 +1,8 @@ package inu.codin.codin.domain.post.service; +import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.entity.PostEntity; @@ -9,6 +12,7 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.stream.Collectors; @Service public class PostService { @@ -20,16 +24,20 @@ public PostService(PostRepository postRepository, S3Service s3Service) { this.s3Service = s3Service; } + //이미지 업로드 메소드 + private List handleImageUpload(List postImages) { + if (postImages != null && !postImages.isEmpty()) { + return s3Service.uploadFiles(postImages); // 실제 업로드 처리 + } + return List.of(); // 이미지가 없을 경우 빈 리스트 반환 + } + public void createPost(PostCreateReqDTO postCreateReqDTO, List postImages) { validateCreatePostRequest(postCreateReqDTO); - List imageUrls; - if (postImages != null && !postImages.isEmpty()) { - imageUrls = s3Service.uploadFiles(postImages); - } else { - imageUrls = List.of(); // 이미지가 없으면 빈 리스트로 초기화 - } + List imageUrls = handleImageUpload(postImages); + PostEntity postEntity = PostEntity.builder() .userId(postCreateReqDTO.getUserId()) @@ -49,6 +57,82 @@ public void createPost(PostCreateReqDTO postCreateReqDTO, List po postRepository.save(postEntity); } + + public void updatePostContent(String postId, PostContentUpdateRequestDTO requestDTO, List postImages) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); + + List imageUrls = handleImageUpload(postImages); + + //이미지 삭제 로직 + + post.updatePostContent(requestDTO.getContent(), imageUrls); + } + + + public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); + post.updatePostStatus(requestDTO.getPostStatus()); + } + + public List getAllPosts(String userId) { + List posts = postRepository.findByUserId(userId); + return posts.stream() + .map(post -> new PostDetailResponseDTO( + post.getUserId(), + post.getPostId(), + post.getContent(), + post.getTitle(), + post.getPostCategory(), + post.getPostStatus(), + post.getPostImageUrls(), + post.isAnonymous() + )) + .collect(Collectors.toList()); + } + + public PostDetailResponseDTO getPost(String postId) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + return new PostDetailResponseDTO( + post.getUserId(), + post.getPostId(), + post.getContent(), + post.getTitle(), + post.getPostCategory(), + post.getPostStatus(), + post.getPostImageUrls(), + post.isAnonymous() + ); + + } + + public void deletePost(String postId) { + postRepository.deleteById(postId); + } + + public void deletePostImage(String postId, String imageUrl) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + if (!post.getPostImageUrls().contains(imageUrl)) { + throw new IllegalArgumentException("이미지가 게시물에 존재하지 않습니다."); + } + + try { + // S3에서 이미지 삭제 + s3Service.deleteFile(imageUrl); + + // 게시물의 이미지 리스트에서 제거 + post.removePostImage(imageUrl); + } catch (Exception e) { + throw new IllegalStateException("이미지 삭제 중 오류 발생: " + imageUrl, e); + } + } + + //유효성체크 private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { // if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { // throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java index 6e8af79a..9066e431 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java @@ -70,7 +70,27 @@ private String getExtension(String fileName) { //삭제 + //삭제 안됐을경우 에러처리 필요 public void deleteFile(String fileName) { - amazonS3Client.deleteObject(bucket, fileName); + if (bucket == null || bucket.isEmpty()) { + throw new IllegalStateException("S3 버킷 이름이 설정되지 않았습니다."); + } + + try { + amazonS3Client.deleteObject(bucket, fileName); + + // 삭제가 제대로 되었는지 추가 검증 + if (amazonS3Client.doesObjectExist(bucket, fileName)) { + throw new IllegalStateException("파일 삭제가 실패했습니다. 파일명: " + fileName); + } + } catch (Exception e) { + throw new IllegalArgumentException("파일 삭제 중 오류가 발생했습니다. 파일명: " + fileName, e); + } + } + + public void deleteFiles(List fileUrls) { + for (String fileUrl : fileUrls) { + deleteFile(fileUrl); + } } } From bd15ae5e1403176ff4e2e9122b695a2b177efb22 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 19 Nov 2024 14:51:49 +0900 Subject: [PATCH 0092/1002] Revert "Merge pull request #17 from CodIN-INU/SC-67-post-create" This reverts commit fda1c806b2998fbbb4535ad0ae6b926ab387f8ce, reversing changes made to ab6b2c00f0305e254332f2c7c209ddd540d7d6c4. --- .../codin/codin/common/config/WebConfig.java | 23 --- ...MultipartJackson2HttpMessageConverter.java | 35 ----- .../post/controller/PostController.java | 118 --------------- .../PostAnonymousUpdateRequestDTO.java | 13 -- .../request/PostContentUpdateRequestDTO.java | 20 --- .../post/dto/request/PostCreateReqDTO.java | 39 ----- .../request/PostStatusUpdateRequestDTO.java | 15 -- .../dto/response/PostDetailResponseDTO.java | 51 ------- .../domain/post/entity/PostCategory.java | 36 ----- .../codin/domain/post/entity/PostEntity.java | 64 -------- .../codin/domain/post/entity/PostStatus.java | 17 --- .../exception/PostCreateFailException.java | 7 - .../post/repository/PostRepository.java | 15 -- .../domain/post/service/PostService.java | 143 ------------------ .../inu/codin/codin/infra/s3/S3Service.java | 96 ------------ 15 files changed, 692 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java deleted file mode 100644 index 27c8112c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java +++ /dev/null @@ -1,23 +0,0 @@ -package inu.codin.codin.common.config; - -import inu.codin.codin.common.util.MultipartJackson2HttpMessageConverter; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import java.util.List; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - private final MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter; - - public WebConfig(MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter) { - this.multipartJackson2HttpMessageConverter = multipartJackson2HttpMessageConverter; - } - - @Override - public void extendMessageConverters(List> converters) { - // 컨버터 리스트에 사용자 정의 컨버터 추가 - converters.add(multipartJackson2HttpMessageConverter); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java deleted file mode 100644 index 5fd59d8c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -package inu.codin.codin.common.util; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.http.MediaType; -import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; -import org.springframework.stereotype.Component; - -import java.lang.reflect.Type; - -@Component -public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { - - /** - * "Content-Type: multipart/form-data" 헤더를 지원하는 HTTP 요청 변환기 - */ - public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { - super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); - } - - @Override - public boolean canWrite(Class clazz, MediaType mediaType) { - return false; - } - - @Override - public boolean canWrite(Type type, Class clazz, MediaType mediaType) { - return false; - } - - @Override - protected boolean canWrite(MediaType mediaType) { - return false; - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java deleted file mode 100644 index 98417f11..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ /dev/null @@ -1,118 +0,0 @@ -package inu.codin.codin.domain.post.controller; - -import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; -import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.service.PostService; -import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -@RestController -@RequestMapping("/api/posts") -public class PostController { - - private final PostService postService; - - public PostController(PostService postService) { - this.postService = postService; - } - - @Operation( - summary = "게시물 작성", - description = "JSON 형식의 게시물 데이터(postContent)와 이미지 파일(postImages) 업로드" - ) - @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createPost( - @RequestPart("postContent") @Valid PostCreateReqDTO postCreateReqDTO, - @RequestPart(value = "postImages", required = false) List postImages) { - - // postImages가 null이면 빈 리스트로 처리 - if (postImages == null) { - postImages = List.of(); - } - - postService.createPost(postCreateReqDTO, postImages); - return ResponseEntity.status(HttpStatus.CREATED).body("게시물 작성 성공"); - } - - @Operation( - summary = "게시물 내용 이미지 수정 추가" - ) - @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updatePostContent( - @PathVariable String postId, - @RequestPart("postContent") @Valid PostContentUpdateRequestDTO requestDTO, - @RequestPart(value = "postImages", required = false) List postImages) { - - postService.updatePostContent(postId, requestDTO, postImages); - return ResponseEntity.status(HttpStatus.OK).body("게시물 수정 성공"); - } - - @Operation( - summary = "상태 수정" - ) - @PatchMapping("/{postId}/status") - public ResponseEntity updatePostStatus( - @PathVariable String postId, - @RequestBody PostStatusUpdateRequestDTO requestDTO) { - postService.updatePostStatus(postId, requestDTO); - return ResponseEntity.status(HttpStatus.OK).body("게시물 상태 수정 성공"); - } - - @Operation( - summary = "해당 사용자 게시물 전체 조회" - ) - @GetMapping("/user/{userId}") - public ResponseEntity> getAllPosts(@PathVariable String userId) { - List posts = postService.getAllPosts(userId); - return ResponseEntity.status(HttpStatus.OK).body(posts); - } - - @Operation( - summary = "해당 게시물 상세 조회" - ) - @GetMapping("/{postId}") - public ResponseEntity getPost(@PathVariable String postId) { - PostDetailResponseDTO post = postService.getPost(postId); - return ResponseEntity.status(HttpStatus.OK).body(post); - } - - @Operation( - summary = "해당 게시물 삭제" - ) - @DeleteMapping("/postId") - public ResponseEntity deletePost(@PathVariable String postId) { - postService.deletePost(postId); - return ResponseEntity.status(HttpStatus.OK).body("게시물 삭제"); - } - - @Operation( - summary = "해당 이미지 삭제" - ) - @DeleteMapping("/{postId}/images") - public ResponseEntity deletePostImage( - @PathVariable String postId, - @RequestParam String imageUrls) { - postService.deletePostImage(postId, imageUrls); - return ResponseEntity.status(HttpStatus.OK).body("이미지 삭제"); - } - - - - - //익명 수정 불가? - - - - - -} - diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java deleted file mode 100644 index ca99744e..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package inu.codin.codin.domain.post.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Data -public class PostAnonymousUpdateRequestDTO { - @Schema(description = "익명 여부", example = "true") - @NotNull - private boolean isAnonymous; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java deleted file mode 100644 index e1c480a7..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java +++ /dev/null @@ -1,20 +0,0 @@ -package inu.codin.codin.domain.post.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -@Data -public class PostContentUpdateRequestDTO { - -// @Schema(description = "게시물 제목", example = "Updated Title") -// @NotBlank -// private String title; - - @Schema(description = "게시물 내용", example = "Updated content") - @NotBlank - private String content; - - //이미지 별도 Multipart (RequestPart 사용) - -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java deleted file mode 100644 index eecc2961..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateReqDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package inu.codin.codin.domain.post.dto.request; - -import inu.codin.codin.domain.post.entity.PostCategory; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -@Data -public class PostCreateReqDTO { - - @Schema(description = "유저 ID", example = "111111") - @NotBlank - private String userId; - - @Schema(description = "게시물 종류", example = "구해요") - @NotNull - private PostCategory postCategory; - - @Schema(description = "게시물 제목", example = "Example") - @NotBlank - private String title; - - @Schema(description = "게시물 내용", example = "example content") - @NotBlank - private String content; - - //이미지 별도 Multipart (RequestPart 사용) - - @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") - @NotNull - private boolean isAnonymous; - - //STATUS 필드 - DEFAULT :: ACTIVE - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java deleted file mode 100644 index 32a0a7bf..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package inu.codin.codin.domain.post.dto.request; - -import inu.codin.codin.domain.post.entity.PostStatus; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Data -public class PostStatusUpdateRequestDTO { - @Schema(description = "게시물 상태", example = "ACTIVE") - @NotNull - private PostStatus postStatus; - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java deleted file mode 100644 index 889e7dbb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostStatus; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.util.List; - -@Data -public class PostDetailResponseDTO { - - @Schema(description = "유저 ID", example = "111111") - @NotBlank - private String userId; - - @Schema(description = "게시물 ID", example = "111111") - @NotBlank - private String postId; - - @Schema(description = "게시물 종류", example = "구해요") - @NotBlank - private PostCategory postCategory; - - @Schema(description = "게시물 제목", example = "Example") - @NotBlank - private String title; - - @Schema(description = "게시물 내용", example = "example content") - @NotBlank - private String content; - - @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") - private List postImageUrl; - - @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") - @NotNull - private boolean isAnonymous; - - public PostDetailResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous) { - this.userId = userId; - this.postId = postId; - this.content = content; - this.title = title; - this.postCategory = postCategory; - this.postImageUrl = postImageUrls; - this.isAnonymous = isAnonymous; - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java deleted file mode 100644 index 39dc270a..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ /dev/null @@ -1,36 +0,0 @@ -package inu.codin.codin.domain.post.entity; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import lombok.Getter; - -@Getter -public enum PostCategory { - REQUEST("구해요"), - COMMUNICATION("소통해요"), - EXTRACURRICULAR("비교과"), - INFO("정보대소개"), - USED_BOOK("중고책"); - - private final String description; - - PostCategory(String description) { - this.description = description; - } - - @JsonValue - public String getDescription() { - return description; - } - - @JsonCreator - public static PostCategory fromDescription(String description) { - for (PostCategory category : PostCategory.values()) { - if (category.description.equals(description)) { - return category; - } - } - throw new IllegalArgumentException("Unknown description: " + description); - } - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java deleted file mode 100644 index b8a8db63..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ /dev/null @@ -1,64 +0,0 @@ -package inu.codin.codin.domain.post.entity; - -import inu.codin.codin.common.BaseTimeEntity; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; - -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -import java.util.List; - -@Document(collection = "post") -@Getter -public class PostEntity extends BaseTimeEntity { - @Id @NotBlank - private String postId; - - private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 - - private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) - - private String title; - - private String content; - - private List postImageUrls; - - private boolean isAnonymous; - - private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) - - - @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus) { - this.postId = postId; - this.userId = userId; - this.postCategory = postCategory; - this.title = title; - this.content = content; - this.isAnonymous = isAnonymous; - this.postImageUrls = postImageUrls; - this.postStatus = postStatus; - } - - public void updatePostContent(String content, List postImageUrls) { - this.content = content; - this.postImageUrls = postImageUrls; - } - - public void updatePostStatus(PostStatus postStatus) { - if (this.postStatus == postStatus) { - throw new IllegalStateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); - } - this.postStatus = postStatus; - } - public void removePostImage(String imageUrl) { - this.postImageUrls.remove(imageUrl); - } - - public void removeAllPostImages() { - this.postImageUrls.clear(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java deleted file mode 100644 index 2de6a255..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java +++ /dev/null @@ -1,17 +0,0 @@ -package inu.codin.codin.domain.post.entity; - -import lombok.Getter; - -@Getter -public enum PostStatus { - ACTIVE("활성"), - DISABLED("비활성"), - SUSPENDED("정지"); - - private final String description; - - PostStatus(String description) { - this.description = description; - } - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java deleted file mode 100644 index 105155d0..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostCreateFailException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.post.exception; - -public class PostCreateFailException extends RuntimeException { - public PostCreateFailException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java deleted file mode 100644 index 4da65b5a..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package inu.codin.codin.domain.post.repository; - -import inu.codin.codin.domain.post.entity.PostEntity; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface PostRepository extends MongoRepository { - Optional findByTitle(String title); - - List findByUserId(String userId); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java deleted file mode 100644 index d47252e0..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ /dev/null @@ -1,143 +0,0 @@ -package inu.codin.codin.domain.post.service; - -import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.infra.s3.S3Service; -import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.entity.PostStatus; -import inu.codin.codin.domain.post.repository.PostRepository; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -public class PostService { - private final PostRepository postRepository; - private final S3Service s3Service; - - public PostService(PostRepository postRepository, S3Service s3Service) { - this.postRepository = postRepository; - this.s3Service = s3Service; - } - - //이미지 업로드 메소드 - private List handleImageUpload(List postImages) { - if (postImages != null && !postImages.isEmpty()) { - return s3Service.uploadFiles(postImages); // 실제 업로드 처리 - } - return List.of(); // 이미지가 없을 경우 빈 리스트 반환 - } - - - public void createPost(PostCreateReqDTO postCreateReqDTO, List postImages) { - validateCreatePostRequest(postCreateReqDTO); - - List imageUrls = handleImageUpload(postImages); - - - PostEntity postEntity = PostEntity.builder() - .userId(postCreateReqDTO.getUserId()) - .content(postCreateReqDTO.getContent()) - .title(postCreateReqDTO.getTitle()) - .postCategory(postCreateReqDTO.getPostCategory()) - - //이미지 Url List 저장 - .postImageUrls(imageUrls) - - .isAnonymous(postCreateReqDTO.isAnonymous()) - - //Default Status = Active - .postStatus(PostStatus.ACTIVE) - - .build(); - postRepository.save(postEntity); - } - - - public void updatePostContent(String postId, PostContentUpdateRequestDTO requestDTO, List postImages) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); - - List imageUrls = handleImageUpload(postImages); - - //이미지 삭제 로직 - - post.updatePostContent(requestDTO.getContent(), imageUrls); - } - - - public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); - post.updatePostStatus(requestDTO.getPostStatus()); - } - - public List getAllPosts(String userId) { - List posts = postRepository.findByUserId(userId); - return posts.stream() - .map(post -> new PostDetailResponseDTO( - post.getUserId(), - post.getPostId(), - post.getContent(), - post.getTitle(), - post.getPostCategory(), - post.getPostStatus(), - post.getPostImageUrls(), - post.isAnonymous() - )) - .collect(Collectors.toList()); - } - - public PostDetailResponseDTO getPost(String postId) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - - return new PostDetailResponseDTO( - post.getUserId(), - post.getPostId(), - post.getContent(), - post.getTitle(), - post.getPostCategory(), - post.getPostStatus(), - post.getPostImageUrls(), - post.isAnonymous() - ); - - } - - public void deletePost(String postId) { - postRepository.deleteById(postId); - } - - public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - - if (!post.getPostImageUrls().contains(imageUrl)) { - throw new IllegalArgumentException("이미지가 게시물에 존재하지 않습니다."); - } - - try { - // S3에서 이미지 삭제 - s3Service.deleteFile(imageUrl); - - // 게시물의 이미지 리스트에서 제거 - post.removePostImage(imageUrl); - } catch (Exception e) { - throw new IllegalStateException("이미지 삭제 중 오류 발생: " + imageUrl, e); - } - } - - //유효성체크 - private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { -// if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { -// throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); -// } - - } -} - diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java deleted file mode 100644 index 9066e431..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java +++ /dev/null @@ -1,96 +0,0 @@ -package inu.codin.codin.infra.s3; - -import com.amazonaws.services.s3.AmazonS3Client; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Service -public class S3Service { - - private final AmazonS3Client amazonS3Client; - - public S3Service(AmazonS3Client amazonS3Client) { - this.amazonS3Client = amazonS3Client; - } - @Value("codin-s3-bucket") - public String bucket; - - //모든 이미지 업로드 - public List uploadFiles(List multipartFiles) { - List uploadUrls = new ArrayList<>(); - for (MultipartFile multipartFile : multipartFiles) { - uploadUrls.add(uploadFile(multipartFile)); - } - return uploadUrls; - - } - - //각 이미지 S3에 업로드 - public String uploadFile(MultipartFile multipartFile) { - validateImageFile(multipartFile); - - String fileName = createFileName(multipartFile.getOriginalFilename()); - try{ - amazonS3Client.putObject(bucket, fileName, multipartFile.getInputStream(), null); - } catch (IOException e) { - throw new RuntimeException("Failed to upload file", e); - } - return amazonS3Client.getUrl(bucket, fileName).toString(); - } - - //중복 방지를 위해 이미지파일명 생성 - public String createFileName(String originalFilename) { - String extension = getExtension(originalFilename); - return UUID.randomUUID().toString() + "." + extension; // 고유한 파일명 생성 - - } - - //파일 유효성 검사( 이미지 관련 확장자만 업로드 가능 설정) - public void validateImageFile(MultipartFile multipartFile) { - List validExtensions = List.of("jpg", "jpeg", "png", "gif"); - String extension = getExtension(multipartFile.getOriginalFilename()); - if (extension == null || !validExtensions.contains(extension.toLowerCase())) { - throw new IllegalArgumentException("유효한 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다."); - } - } - - //확장자 추출 - private String getExtension(String fileName) { - if (fileName == null || fileName.lastIndexOf(".") == -1) { - return ""; // 확장자가 없는 경우 - } - return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); - } - - - //삭제 - //삭제 안됐을경우 에러처리 필요 - public void deleteFile(String fileName) { - if (bucket == null || bucket.isEmpty()) { - throw new IllegalStateException("S3 버킷 이름이 설정되지 않았습니다."); - } - - try { - amazonS3Client.deleteObject(bucket, fileName); - - // 삭제가 제대로 되었는지 추가 검증 - if (amazonS3Client.doesObjectExist(bucket, fileName)) { - throw new IllegalStateException("파일 삭제가 실패했습니다. 파일명: " + fileName); - } - } catch (Exception e) { - throw new IllegalArgumentException("파일 삭제 중 오류가 발생했습니다. 파일명: " + fileName, e); - } - } - - public void deleteFiles(List fileUrls) { - for (String fileUrl : fileUrls) { - deleteFile(fileUrl); - } - } -} From 0161fe816a8cb27ea152ce1ad763a10cef4fdb0f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 19:54:54 +0900 Subject: [PATCH 0093/1002] =?UTF-8?q?[SC-77]=20feat=20:=20ADMIN,=20MANAGER?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=EC=9D=84=20=ED=86=B5=ED=95=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/lab/controller/LabController.java | 34 ++++++++++-- .../lab/dto/LabCreateUpdateRequestDto.java | 42 ++++++++++++++ .../lab/dto/LabThumbnailResponseDto.java | 10 ++-- .../domain/info/domain/lab/entity/Lab.java | 43 +++++++++++++++ .../info/domain/lab/service/LabService.java | 21 ++++++- .../office/controller/OfficeController.java | 41 +++++++++++--- .../office/dto/OfficeDetailsResponseDto.java | 36 ++++++++++++ ...ember.java => OfficeMemberRequestDto.java} | 7 +-- .../office/dto/OfficeMemberResponseDto.java | 49 +++++++++++------ .../office/dto/OfficeUpdateRequestDto.java | 53 ++++++++++++++++++ .../info/domain/office/entity/Office.java | 14 ++++- .../domain/office/entity/OfficeMember.java | 38 +++++++++++++ .../domain/office/service/OfficeService.java | 29 ++++++++-- .../controller/ProfessorController.java | 36 ++++++++++-- .../dto/ProfessorCreateUpdateRequestDto.java | 36 ++++++++++++ .../dto/ProfessorListResponseDto.java | 24 +++++--- .../dto/ProfessorThumbnailResponseDto.java | 55 ++++++++++++------- .../domain/professor/entity/Professor.java | 46 +++++++++++++++- .../ProfessorDuplicatedException.java | 7 +++ .../professor/service/ProfessorService.java | 23 ++++++++ .../codin/codin/domain/info/entity/Info.java | 6 +- .../info/repository/InfoRepository.java | 19 ++++--- 22 files changed, 575 insertions(+), 94 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{OfficeMember.java => OfficeMemberRequestDto.java} (90%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index ad733f71..30881068 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -1,23 +1,23 @@ package inu.codin.codin.domain.info.domain.lab.controller; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; import inu.codin.codin.domain.info.domain.lab.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +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.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequiredArgsConstructor -@RequestMapping("/info/lab") +@RequestMapping(value = "/info/lab") @Tag(name = "Info API") public class LabController { @@ -35,4 +35,28 @@ public ResponseEntity> getAllLab(){ return ResponseUtils.success(labService.getAllLab()); } + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 새로운 연구실 등록") + @PostMapping(produces = "plain/text; charset=utf-8") + public ResponseEntity createLab(@RequestBody LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + labService.createLab(labCreateUpdateRequestDto); + return ResponseUtils.successMsg("새로운 LAB 등록 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 연구실 정보 수정") + @PutMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity updateLab(@RequestBody LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ + labService.updateLab(labCreateUpdateRequestDto, id); + return ResponseUtils.successMsg("LAB 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 연구실 삭제") + @DeleteMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity deleteLab(@PathVariable("id") String id){ + labService.deleteLab(id); + return ResponseUtils.successMsg("LAB 삭제 완료"); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java new file mode 100644 index 00000000..cd913362 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.info.domain.lab.dto; + +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LabCreateUpdateRequestDto { + @NotBlank + @Schema(description = "학과", example = "EMBEDDED") + private Department department; + + @NotBlank + @Schema(description = "연구실 이름", example = "땡땡연구실") + private String title; + + @Schema(description = "연구 내용", example = "이것저것 연구합니다.") + private String content; + + @NotBlank + @Schema(description = "담당 교수", example = "홍길동") + private String professor; + + @Schema(description = "교수실 위치", example = "7호관 423호") + private String professorLoc; + + @Schema(description = "교수실 전화번호", example = "032-123-4567") + private String professorNumber; + + @Schema(description = "연구실 위치", example = "7호관 409호") + private String labLoc; + + @Schema(description = "연구실 전화번호", example = "032-987-0653") + private String labNumber; + + @Schema(description = "연구실 홈페이지", example = "http://~") + private String site; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java index a17d3965..345d650e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java @@ -1,21 +1,23 @@ package inu.codin.codin.domain.info.domain.lab.dto; -import inu.codin.codin.domain.info.domain.lab.entity.Lab; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; /* 연구실 상세정보 반환 DTO 해당하는 연구실에 대한 내용들을 모두 반환한다. */ -@Data +@Getter +@Setter public class LabThumbnailResponseDto { @NotBlank - @Schema(description = "학과", example = "CSE") + @Schema(description = "학과", example = "EMBEDDED") private Department department; @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java index 5a388fb3..dc2823a5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -1,10 +1,12 @@ package inu.codin.codin.domain.info.domain.lab.entity; +import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.data.mongodb.core.mapping.Document; @@ -31,4 +33,45 @@ public class Lab extends Info { private String labNumber; private String site; + + @Builder + public Lab(String id, Department department, InfoType infoType, String professor, String title, String content, String professorLoc, String professorNumber, String labLoc, String labNumber, String site) { + super(id, department, infoType); + this.professor = professor; + this.title = title; + this.content = content; + this.professorLoc = professorLoc; + this.professorNumber = professorNumber; + this.labLoc = labLoc; + this.labNumber = labNumber; + this.site = site; + } + + public static Lab of(LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + return Lab.builder() + .department(labCreateUpdateRequestDto.getDepartment()) + .infoType(InfoType.LAB) + .professor(labCreateUpdateRequestDto.getProfessor()) + .title(labCreateUpdateRequestDto.getTitle()) + .content(labCreateUpdateRequestDto.getContent()) + .professorLoc(labCreateUpdateRequestDto.getProfessorLoc()) + .professorNumber(labCreateUpdateRequestDto.getProfessorNumber()) + .labLoc(labCreateUpdateRequestDto.getLabLoc()) + .labNumber(labCreateUpdateRequestDto.getLabNumber()) + .site(labCreateUpdateRequestDto.getSite()) + .build(); + + } + + public void update(LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + this.department = labCreateUpdateRequestDto.getDepartment(); + this.title = labCreateUpdateRequestDto.getTitle(); + this.content = labCreateUpdateRequestDto.getContent(); + this.professor = labCreateUpdateRequestDto.getProfessor(); + this.professorLoc = labCreateUpdateRequestDto.getProfessorLoc(); + this.professorNumber = labCreateUpdateRequestDto.getProfessorNumber(); + this.labLoc = labCreateUpdateRequestDto.getLabLoc(); + this.labNumber = labCreateUpdateRequestDto.getLabNumber(); + this.site = labCreateUpdateRequestDto.getSite(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index 8cefc60f..0190bbf7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -1,11 +1,13 @@ package inu.codin.codin.domain.info.domain.lab.service; +import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; import inu.codin.codin.domain.info.domain.lab.entity.Lab; import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @@ -24,6 +26,23 @@ public LabThumbnailResponseDto getLabThumbnail(String id) { public List getAllLab() { List labs = infoRepository.findAllLabs(); - return labs.stream().map(LabListResponseDto::of).toList(); + List list = labs.stream().map(LabListResponseDto::of).toList(); + return list; + } + + public void createLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto) { + Lab lab = Lab.of(labCreateUpdateRequestDto); + infoRepository.save(lab); + } + + public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, String id) { + Lab lab = infoRepository.findLabById(id) + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + lab.update(labCreateUpdateRequestDto); + infoRepository.save(lab); + } + + public void deleteLab(String id) { + infoRepository.deleteById(id); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 2d4f714d..fc169dd9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,24 +1,26 @@ package inu.codin.codin.domain.info.domain.office.controller; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.service.OfficeService; import inu.codin.codin.domain.info.domain.office.dto.OfficeListResponseDto; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResponseDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; +import org.apache.coyote.Response; import org.springframework.http.ResponseEntity; -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.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequiredArgsConstructor -@RequestMapping("/info/office") +@RequestMapping(value = "/info/office") @Tag(name = "Info API") public class OfficeController { @@ -26,7 +28,7 @@ public class OfficeController { @Operation(summary = "학과별 사무실 직원 정보 반환") @GetMapping("/{department}") - public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ + public ResponseEntity getOfficeByDepartment(@PathVariable("department") Department department){ return ResponseUtils.success(officeService.getOfficeByDepartment(department)); } @@ -35,4 +37,29 @@ public ResponseEntity> getOfficeByDepartment(@Path public ResponseEntity> getAllOffice(){ return ResponseUtils.success(officeService.getAllOffice()); } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 정보 수정") + @PatchMapping(value = "/{department}" , produces = "plain/text; charset=utf-8") + public ResponseEntity updateOffice(@PathVariable("department") Department department, @RequestBody OfficeUpdateRequestDto officeUpdateRequestDto){ + officeService.updateOffice(department, officeUpdateRequestDto); + return ResponseUtils.successMsg("Office 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 수정") + @PatchMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") + public ResponseEntity updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, + @RequestBody OfficeMemberRequestDto officeMemberRequestDto){ + officeService.updateOfficeMember(department, num, officeMemberRequestDto); + return ResponseUtils.successMsg("Office Member 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 삭제") + @DeleteMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") + public ResponseEntity deleteOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num){ + officeService.deleteOfficeMember(department,num); + return ResponseUtils.successMsg("Office Member 정보 삭제 완료"); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java new file mode 100644 index 00000000..d6309e60 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.info.domain.office.dto; + +import inu.codin.codin.domain.info.domain.office.entity.Office; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/* + 학과 사무실 정보 반환 DTO + 학과 사무실 평면도 및 지원 정보들을 모두 반환한다. + */ +@Getter +@Setter +public class OfficeDetailsResponseDto { + @Schema(description = "사무실 평면도", example = "https://") + private String img; + + @Schema(description = "학과사무실 직원") + private List officeMemberResponseDtos; + + @Builder + public OfficeDetailsResponseDto(String img, List officeMemberResponseDtos) { + this.img = img; + this.officeMemberResponseDtos = officeMemberResponseDtos; + } + + public static OfficeDetailsResponseDto of(Office office) { + return OfficeDetailsResponseDto.builder() + .img(office.getImg()) + .officeMemberResponseDtos(office.getMember().stream().map(OfficeMemberResponseDto::of).toList()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMember.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberRequestDto.java index 138a624a..aa36f6cc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberRequestDto.java @@ -3,12 +3,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import lombok.Setter; -/* - 학과 사무실 직원 정보 - */ @Getter -public class OfficeMember { +@Setter +public class OfficeMemberRequestDto { @NotBlank @Schema(description = "성명", example = "홍길동") private String name; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java index 96c22c12..b8efb6ee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java @@ -1,36 +1,51 @@ package inu.codin.codin.domain.info.domain.office.dto; -import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.Setter; - -import java.util.List; /* - 학과 사무실 정보 반환 DTO - 학과 사무실 평면도 및 지원 정보들을 모두 반환한다. + 학과 사무실 직원 정보 */ @Getter -@Setter public class OfficeMemberResponseDto { - @Schema(description = "사무실 평면도", example = "https://") - private String img; + @NotBlank + @Schema(description = "성명", example = "홍길동") + private String name; + @NotBlank + @Schema(description = "직위", example = "조교") + private String position; + + @Schema(description = "담당 업무", example = "학과사무실 업무") + private String role; + + @NotBlank + @Schema(description = "연락처", example = "032-123-2345") + private String number; - @Schema(description = "학과사무실 직원") - private List officeMembers; + @NotBlank + @Schema(description = "이메일", example = "test@inu.ac.kr") + private String email; @Builder - public OfficeMemberResponseDto(String img, List officeMembers) { - this.img = img; - this.officeMembers = officeMembers; + public OfficeMemberResponseDto(String name, String position, String role, String number, String email) { + this.name = name; + this.position = position; + this.role = role; + this.number = number; + this.email = email; } - public static OfficeMemberResponseDto of(Office office) { + public static OfficeMemberResponseDto of(OfficeMember officeMember){ return OfficeMemberResponseDto.builder() - .img(office.getImg()) - .officeMembers(office.getMember()) + .name(officeMember.getName()) + .email(officeMember.getEmail()) + .position(officeMember.getPosition()) + .role(officeMember.getRole()) + .number(officeMember.getNumber()) + .email(officeMember.getEmail()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java new file mode 100644 index 00000000..ce58718a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java @@ -0,0 +1,53 @@ +package inu.codin.codin.domain.info.domain.office.dto; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.office.entity.Office; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OfficeUpdateRequestDto { + + @Schema(description = "위치", example = "7호관 329호") + @NotBlank + private final String location; + + @Schema(description = "오픈 시간", example = "09-21시") + private final String open; + + @Schema(description = "방학 오픈 시간", example = "09-17시") + private final String vacation; + + @Schema(description = "연락처", example = "032-123-2434") + @NotBlank + private final String office_number; + + @Schema(description = "팩스", example = "032-432-1234") + @NotBlank + private final String fax; + + @Builder + public OfficeUpdateRequestDto(String location, String open, String vacation, String office_number, String fax) { + this.location = location; + this.open = open; + this.vacation = vacation; + this.office_number = office_number; + this.fax = fax; + } + + + + public static OfficeUpdateRequestDto of(Office office){ + return OfficeUpdateRequestDto.builder() + .location(office.getLocation()) + .open(office.getOpen()) + .vacation(office.getVacation()) + .office_number(office.getOffice_number()) + .fax(office.getFax()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java index 728988b9..e0d3d73d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java @@ -1,9 +1,8 @@ package inu.codin.codin.domain.info.domain.office.entity; +import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMember; -import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.entity.InfoType; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResponseDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; @@ -35,4 +34,13 @@ public class Office extends Info { @NotBlank private String fax; + + public void update(OfficeUpdateRequestDto officeUpdateRequestDto) { + this.location=officeUpdateRequestDto.getLocation(); + this.open=officeUpdateRequestDto.getOpen(); + this.vacation=officeUpdateRequestDto.getVacation(); +// this.img = officeUpdateRequestDto.get + this.office_number = officeUpdateRequestDto.getOffice_number(); + this.fax = officeUpdateRequestDto.getFax(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java new file mode 100644 index 00000000..f4394fbc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java @@ -0,0 +1,38 @@ +package inu.codin.codin.domain.info.domain.office.entity; + +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberRequestDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +/* + 학과 사무실 직원 정보 + */ +@Getter +public class OfficeMember { + @NotBlank + @Schema(description = "성명", example = "홍길동") + private String name; + @NotBlank + @Schema(description = "직위", example = "조교") + private String position; + + @Schema(description = "담당 업무", example = "학과사무실 업무") + private String role; + + @NotBlank + @Schema(description = "연락처", example = "032-123-2345") + private String number; + + @NotBlank + @Schema(description = "이메일", example = "test@inu.ac.kr") + private String email; + + public void update(OfficeMemberRequestDto officeMemberRequestDto) { + this.name = officeMemberRequestDto.getName(); + this.position = officeMemberRequestDto.getPosition(); + this.role = officeMemberRequestDto.getRole(); + this.number = officeMemberRequestDto.getNumber(); + this.email = officeMemberRequestDto.getEmail(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index af0d3787..adb24dde 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.domain.office.service; -import inu.codin.codin.domain.info.domain.office.dto.OfficeListResponseDto; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResponseDto; +import inu.codin.codin.domain.info.domain.office.dto.*; import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,8 +20,27 @@ public List getAllOffice() { return offices.stream().map(OfficeListResponseDto::of).toList(); } - public List getOfficeByDepartment(Department department) { - List offices = infoRepository.findOfficeByDepartment(department); - return offices.stream().map(OfficeMemberResponseDto::of).toList(); + public OfficeDetailsResponseDto getOfficeByDepartment(Department department) { + Office offices = infoRepository.findOfficeByDepartment(department); + return OfficeDetailsResponseDto.of(offices); + } + + public void updateOffice(Department department, OfficeUpdateRequestDto officeUpdateRequestDto) { + Office office = infoRepository.findOfficeByDepartment(department); + office.update(officeUpdateRequestDto); + infoRepository.save(office); + } + + public void updateOfficeMember(Department department, int num, OfficeMemberRequestDto officeMemberRequestDto) { + Office office = infoRepository.findOfficeByDepartment(department); + OfficeMember officeMember = office.getMember().get(num); + officeMember.update(officeMemberRequestDto); + infoRepository.save(office); + } + + public void deleteOfficeMember(Department department, int num) { + Office office = infoRepository.findOfficeByDepartment(department); + office.getMember().remove(num); + infoRepository.save(office); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index 64dbc421..be7e8e63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -3,22 +3,22 @@ import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -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.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequiredArgsConstructor -@RequestMapping("/info/professor") +@RequestMapping(value = "/info/professor") @Tag(name = "Info API") public class ProfessorController { @@ -35,4 +35,30 @@ public ResponseEntity> getProfessorList(@PathVari public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ return ResponseUtils.success(professorService.getProfessorThumbnail(id)); } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 새로운 교수 정보 생성") + @PostMapping(produces = "plain/text; charset=utf-8") + public ResponseEntity createProfessor(@RequestBody ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + professorService.createProfessor(professorCreateUpdateRequestDto); + return ResponseUtils.successMsg("새로운 교수 정보 생성 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 교수 정보 수정") + @PatchMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity updateProfessor(@PathVariable("id") String id, @RequestBody ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + professorService.updateProfessor(id, professorCreateUpdateRequestDto); + return ResponseUtils.successMsg("교수 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 교수 정보 삭제") + @DeleteMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity deleteProfessor(@PathVariable("id") String id){ + professorService.deleteProfessor(id); + return ResponseUtils.successMsg("교수 정보 삭제 완료"); + } + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java new file mode 100644 index 00000000..b153bae6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.info.domain.professor.dto; + +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ProfessorCreateUpdateRequestDto { + + @NotBlank + @Schema(description = "학과", example = "COMPUTER_SCI") + Department department; + @NotBlank @Schema(description = "성함", example = "홍길동") + String name; + @NotBlank @Schema(description = "프로필 사진", example = "https://~") + String image; + @NotBlank @Schema(description = "전화번호", example = "032-123-4567") + String number; + @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") + String email; + @Schema(description = "연구실 홈페이지", example = "https://~") + String site; + @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") + String field; + @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") + String subject; + @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") + String labId; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java index d95b1f62..7cab9fa0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java @@ -5,23 +5,31 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; +import lombok.Getter; +import lombok.Setter; /* 교수님 리스트 반환 DTO 모든 교수님들의 리스트를 반환한다. */ -public record ProfessorListResponseDto( - @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") - String id, +@Getter +@Setter +public class ProfessorListResponseDto{ + @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") + String id; - @NotBlank @Schema(description = "교수 성함", example = "홍길동") - String name, + @NotBlank @Schema(description = "교수 성함", example = "홍길동") + String name; + + @NotBlank @Schema(description = "학과", example = "CSE") + Department department; - @NotBlank @Schema(description = "학과", example = "CSE") - Department department) { @Builder - public ProfessorListResponseDto { + public ProfessorListResponseDto(String id, String name, Department department) { + this.id = id; + this.name = name; + this.department = department; } public static ProfessorListResponseDto of(Professor professor) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java index ff2abff3..82b02ce4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java @@ -5,33 +5,48 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; +import lombok.Getter; +import lombok.Setter; /* 교수님 상세 정보 반환 DTO 해당되는 교수님의 상세 정보들을 반환한다. */ -public record ProfessorThumbnailResponseDto( - @NotBlank @Schema(description = "학과", example = "CSE") - Department department, - @NotBlank @Schema(description = "성함", example = "홍길동") - String name, - @NotBlank @Schema(description = "프로필 사진", example = "https://~") - String image, - @NotBlank @Schema(description = "전화번호", example = "032-123-4567") - String number, - @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") - String email, - @Schema(description = "연구실 홈페이지", example = "https://~") - String site, - @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") - String field, - @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") - String subject, - @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") - String labId) { + +@Getter +@Setter +public class ProfessorThumbnailResponseDto{ + + @NotBlank @Schema(description = "학과", example = "CSE") + Department department; + @NotBlank @Schema(description = "성함", example = "홍길동") + String name; + @NotBlank @Schema(description = "프로필 사진", example = "https://~") + String image; + @NotBlank @Schema(description = "전화번호", example = "032-123-4567") + String number; + @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") + String email; + @Schema(description = "연구실 홈페이지", example = "https://~") + String site; + @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") + String field; + @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") + String subject; + @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") + String labId; @Builder - public ProfessorThumbnailResponseDto { + public ProfessorThumbnailResponseDto(Department department, String name, String image, String number, String email, String site, String field, String subject, String labId) { + this.department = department; + this.name = name; + this.image = image; + this.number = number; + this.email = email; + this.site = site; + this.field = field; + this.subject = subject; + this.labId = labId; } public static ProfessorThumbnailResponseDto of(Professor professor) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java index 8b248e9e..11c7fdce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java @@ -1,9 +1,12 @@ package inu.codin.codin.domain.info.domain.professor.entity; -import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.data.mongodb.core.mapping.Document; @@ -34,7 +37,44 @@ public class Professor extends Info { private String labId; - public void updateLab(Lab lab) { - this.labId = lab.getId(); + @Builder + public Professor(String id, Department department, InfoType infoType, String name, String image, String number, String email, String site, String field, String subject, String labId) { + super(id, department, infoType); + this.name = name; + this.image = image; + this.number = number; + this.email = email; + this.site = site; + this.field = field; + this.subject = subject; + this.labId = labId; + } + + public static Professor of(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + return Professor.builder() + .department(professorCreateUpdateRequestDto.getDepartment()) + .infoType(InfoType.PROFESSOR) + .name(professorCreateUpdateRequestDto.getName()) + .image(professorCreateUpdateRequestDto.getImage()) + .number(professorCreateUpdateRequestDto.getNumber()) + .email(professorCreateUpdateRequestDto.getEmail()) + .site(professorCreateUpdateRequestDto.getSite()) + .field(professorCreateUpdateRequestDto.getField()) + .subject(professorCreateUpdateRequestDto.getSubject()) + .labId(professorCreateUpdateRequestDto.getLabId()) + .build(); + } + + public void update(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + this.department = professorCreateUpdateRequestDto.getDepartment(); + this.infoType = InfoType.PROFESSOR; + this.name = professorCreateUpdateRequestDto.getName(); + this.image = professorCreateUpdateRequestDto.getImage(); + this.number = professorCreateUpdateRequestDto.getNumber(); + this.email = professorCreateUpdateRequestDto.getEmail(); + this.site = professorCreateUpdateRequestDto.getSite(); + this.field = professorCreateUpdateRequestDto.getField(); + this.subject = professorCreateUpdateRequestDto.getSubject(); + this.labId = professorCreateUpdateRequestDto.getLabId(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java new file mode 100644 index 00000000..d78c76a3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.info.domain.professor.exception; + +public class ProfessorDuplicatedException extends RuntimeException{ + public ProfessorDuplicatedException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index 3e87574d..637abcd3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -3,7 +3,9 @@ import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.domain.professor.exception.ProfessorDuplicatedException; import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; @@ -27,4 +29,25 @@ public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); return ProfessorThumbnailResponseDto.of(professor); } + + public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + if (infoRepository.findProfessorByEmail(professorCreateUpdateRequestDto.getEmail()).isPresent()){ + throw new ProfessorDuplicatedException("이미 존재하는 Professor 정보 입니다."); + } + Professor professor = Professor.of(professorCreateUpdateRequestDto); + infoRepository.save(professor); + } + + public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + Professor professor = infoRepository.findProfessorById(id) + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + professor.update(professorCreateUpdateRequestDto); + infoRepository.save(professor); + } + + + + public void deleteProfessor(String id) { + infoRepository.deleteById(id); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java index ef9f29b7..4817a337 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -15,13 +15,13 @@ public abstract class Info { @Id @NotBlank - private String id; + protected String id; @NotBlank - private Department department; + protected Department department; @NotBlank - private InfoType infoType; + protected InfoType infoType; public Info(String id, Department department, InfoType infoType) { this.id = id; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index f9fcfa58..01ed8149 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.domain.info.entity.Info; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -13,26 +14,26 @@ public interface InfoRepository extends MongoRepository { - @Query("{ 'info_type': 'LAB' }") + @Query("{ 'infoType': 'LAB' }") List findAllLabs(); - @Query("{ 'info_type': 'LAB', 'id': ?0}") + @Query("{ 'infoType': 'LAB', '_id': ?0}") Optional findLabById(String id); - @Query("{ 'info_type': 'OFFICE' }") + @Query("{ 'infoType': 'OFFICE' }") List findAllOffices(); - @Query("{ 'info_type': 'OFFICE', 'department': ?0}") - List findOfficeByDepartment(Department department); + @Query("{ 'infoType': 'OFFICE', 'department': ?0}") + Office findOfficeByDepartment(Department department); - @Query("{ 'info_type': 'PROFESSOR' , 'department': ?0}") + @Query("{ 'infoType': 'PROFESSOR' , 'department': ?0}") List findAllProfessorsByDepartment(Department department); - @Query("{ 'info_type': 'PROFESSOR' , 'id': ?0}") + @Query("{ 'infoType': 'PROFESSOR' , '_id': ?0}") Optional findProfessorById(String id); - @Query("{ 'info_type': 'PROFESSOR'}") - List findAllProfessor(); + @Query("{ 'infoType': 'PROFESSOR' , 'email': ?0}") + Optional findProfessorByEmail(String email); } From cca1e9f88e92aad4cdd265ee05de915175362ca7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 19:57:23 +0900 Subject: [PATCH 0094/1002] =?UTF-8?q?[SC-77]=20chore=20:=20BaseEntity=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/inu/codin/codin/domain/info/entity/Info.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java index 4817a337..1616ab7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.entity; +import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import jakarta.validation.constraints.NotBlank; @@ -12,7 +13,7 @@ @Document(collection = "info") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public abstract class Info { +public abstract class Info extends BaseTimeEntity { @Id @NotBlank protected String id; From 0aaf557fcacea4a25721d89b3d7b4c3a0d1bee49 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 20:34:59 +0900 Subject: [PATCH 0095/1002] =?UTF-8?q?[SC-77]=20pref=20:=20soft=20delete=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/domain/lab/service/LabService.java | 5 ++++- .../info/domain/office/entity/OfficeMember.java | 3 ++- .../info/domain/office/service/OfficeService.java | 2 +- .../professor/service/ProfessorService.java | 5 ++++- .../inu/codin/codin/domain/info/entity/Info.java | 1 - .../domain/info/repository/InfoRepository.java | 15 +++++++-------- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index 0190bbf7..3f409315 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -43,6 +43,9 @@ public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, Strin } public void deleteLab(String id) { - infoRepository.deleteById(id); + Lab lab = infoRepository.findLabById(id) + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + lab.delete(); + infoRepository.save(lab); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java index f4394fbc..04cc8b7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.info.domain.office.entity; +import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberRequestDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -9,7 +10,7 @@ 학과 사무실 직원 정보 */ @Getter -public class OfficeMember { +public class OfficeMember extends BaseTimeEntity { @NotBlank @Schema(description = "성명", example = "홍길동") private String name; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index adb24dde..eec2a5b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -40,7 +40,7 @@ public void updateOfficeMember(Department department, int num, OfficeMemberReque public void deleteOfficeMember(Department department, int num) { Office office = infoRepository.findOfficeByDepartment(department); - office.getMember().remove(num); + office.getMember().get(num).delete(); infoRepository.save(office); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index 637abcd3..fd873bcd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -48,6 +48,9 @@ public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professor public void deleteProfessor(String id) { - infoRepository.deleteById(id); + Professor professor = infoRepository.findProfessorById(id) + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + professor.delete(); + infoRepository.save(professor); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java index 1616ab7c..22dee228 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 01ed8149..71c9f245 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -5,7 +5,6 @@ import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.domain.info.entity.Info; -import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -14,25 +13,25 @@ public interface InfoRepository extends MongoRepository { - @Query("{ 'infoType': 'LAB' }") + @Query("{ 'infoType': 'LAB', 'deletedAt': null }") List findAllLabs(); - @Query("{ 'infoType': 'LAB', '_id': ?0}") + @Query("{ 'infoType': 'LAB', '_id': ?0, 'deletedAt': null }") Optional findLabById(String id); - @Query("{ 'infoType': 'OFFICE' }") + @Query("{ 'infoType': 'OFFICE', 'deletedAt': null }") List findAllOffices(); - @Query("{ 'infoType': 'OFFICE', 'department': ?0}") + @Query("{ 'infoType': 'OFFICE', 'department': ?0, 'deletedAt': null }") Office findOfficeByDepartment(Department department); - @Query("{ 'infoType': 'PROFESSOR' , 'department': ?0}") + @Query("{ 'infoType': 'PROFESSOR', 'department': ?0, 'deletedAt': null }") List findAllProfessorsByDepartment(Department department); - @Query("{ 'infoType': 'PROFESSOR' , '_id': ?0}") + @Query("{ 'infoType': 'PROFESSOR', '_id': ?0, 'deletedAt': null }") Optional findProfessorById(String id); - @Query("{ 'infoType': 'PROFESSOR' , 'email': ?0}") + @Query("{ 'infoType': 'PROFESSOR', 'email': ?0, 'deletedAt': null }") Optional findProfessorByEmail(String email); From d888c1850fcaf61f05a8bf91fa410b816ff089cc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 20:41:51 +0900 Subject: [PATCH 0096/1002] =?UTF-8?q?[SC-77]=20feat=20:=20=EC=A7=81?= =?UTF-8?q?=EC=9B=90=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../office/controller/OfficeController.java | 16 ++++++--- ...> OfficeMemberCreateUpdateRequestDto.java} | 2 +- .../info/domain/office/entity/Office.java | 4 +++ .../domain/office/entity/OfficeMember.java | 34 +++++++++++++++---- .../domain/office/service/OfficeService.java | 16 ++++++--- 5 files changed, 56 insertions(+), 16 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{OfficeMemberRequestDto.java => OfficeMemberCreateUpdateRequestDto.java} (93%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index fc169dd9..3c5b9219 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.domain.office.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.service.OfficeService; import inu.codin.codin.domain.info.domain.office.dto.OfficeListResponseDto; @@ -11,7 +11,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; -import org.apache.coyote.Response; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -46,12 +45,21 @@ public ResponseEntity updateOffice(@PathVariable("department") Department dep return ResponseUtils.successMsg("Office 정보 수정 완료"); } + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 추가") + @PatchMapping(value = "/{department}/member", produces = "plain/text; charset=utf-8") + public ResponseEntity createOfficeMember(@PathVariable("department") Department department, + @RequestBody OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + officeService.createOfficeMember(department, officeMemberCreateUpdateRequestDto); + return ResponseUtils.successMsg("Office Member 추가 완료"); + } + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 수정") @PatchMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") public ResponseEntity updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, - @RequestBody OfficeMemberRequestDto officeMemberRequestDto){ - officeService.updateOfficeMember(department, num, officeMemberRequestDto); + @RequestBody OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + officeService.updateOfficeMember(department, num, officeMemberCreateUpdateRequestDto); return ResponseUtils.successMsg("Office Member 정보 수정 완료"); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java index aa36f6cc..cf477108 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java @@ -7,7 +7,7 @@ @Getter @Setter -public class OfficeMemberRequestDto { +public class OfficeMemberCreateUpdateRequestDto { @NotBlank @Schema(description = "성명", example = "홍길동") private String name; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java index e0d3d73d..0f301bbf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java @@ -43,4 +43,8 @@ public void update(OfficeUpdateRequestDto officeUpdateRequestDto) { this.office_number = officeUpdateRequestDto.getOffice_number(); this.fax = officeUpdateRequestDto.getFax(); } + + public void addOfficeMember(OfficeMember officeMember){ + this.member.add(officeMember); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java index 04cc8b7c..e743ac31 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java @@ -1,9 +1,10 @@ package inu.codin.codin.domain.info.domain.office.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Getter; /* @@ -29,11 +30,30 @@ public class OfficeMember extends BaseTimeEntity { @Schema(description = "이메일", example = "test@inu.ac.kr") private String email; - public void update(OfficeMemberRequestDto officeMemberRequestDto) { - this.name = officeMemberRequestDto.getName(); - this.position = officeMemberRequestDto.getPosition(); - this.role = officeMemberRequestDto.getRole(); - this.number = officeMemberRequestDto.getNumber(); - this.email = officeMemberRequestDto.getEmail(); + @Builder + public OfficeMember(String name, String position, String role, String number, String email) { + this.name = name; + this.position = position; + this.role = role; + this.number = number; + this.email = email; + } + + public static OfficeMember of(OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + return OfficeMember.builder() + .name(officeMemberCreateUpdateRequestDto.getName()) + .position(officeMemberCreateUpdateRequestDto.getPosition()) + .role(officeMemberCreateUpdateRequestDto.getRole()) + .number(officeMemberCreateUpdateRequestDto.getNumber()) + .email(officeMemberCreateUpdateRequestDto.getEmail()) + .build(); + } + + public void update(OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { + this.name = officeMemberCreateUpdateRequestDto.getName(); + this.position = officeMemberCreateUpdateRequestDto.getPosition(); + this.role = officeMemberCreateUpdateRequestDto.getRole(); + this.number = officeMemberCreateUpdateRequestDto.getNumber(); + this.email = officeMemberCreateUpdateRequestDto.getEmail(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index eec2a5b1..6035a745 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -21,8 +21,16 @@ public List getAllOffice() { } public OfficeDetailsResponseDto getOfficeByDepartment(Department department) { - Office offices = infoRepository.findOfficeByDepartment(department); - return OfficeDetailsResponseDto.of(offices); + Office office = infoRepository.findOfficeByDepartment(department); + return OfficeDetailsResponseDto.of(office); + } + + public void createOfficeMember(Department department, OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { + Office office = infoRepository.findOfficeByDepartment(department); + OfficeMember officeMember = OfficeMember.of(officeMemberCreateUpdateRequestDto); + office.addOfficeMember(officeMember); + infoRepository.save(office); + } public void updateOffice(Department department, OfficeUpdateRequestDto officeUpdateRequestDto) { @@ -31,10 +39,10 @@ public void updateOffice(Department department, OfficeUpdateRequestDto officeUpd infoRepository.save(office); } - public void updateOfficeMember(Department department, int num, OfficeMemberRequestDto officeMemberRequestDto) { + public void updateOfficeMember(Department department, int num, OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { Office office = infoRepository.findOfficeByDepartment(department); OfficeMember officeMember = office.getMember().get(num); - officeMember.update(officeMemberRequestDto); + officeMember.update(officeMemberCreateUpdateRequestDto); infoRepository.save(office); } From 154c064f3f134dde283baf90f9de613e3840820d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 20:52:10 +0900 Subject: [PATCH 0097/1002] =?UTF-8?q?[SC-77]=20feat=20:=20@EnableMethodSec?= =?UTF-8?q?urity=20=ED=86=B5=ED=95=9C=20@PreAuthorize=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/java/inu/codin/codin/CodinApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index a041ed7e..3e3d45b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -3,11 +3,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import java.util.TimeZone; @SpringBootApplication @EnableMongoAuditing +@EnableMethodSecurity public class CodinApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); From b2e9a7daf5c0867ce30aebd2e62ae5720e5aed75 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Nov 2024 23:30:33 +0900 Subject: [PATCH 0098/1002] =?UTF-8?q?[SC-77]=20refactor=20:=20DetailsRespo?= =?UTF-8?q?nse=20=EC=97=90=20=ED=95=99=EA=B3=BC=20=EC=82=AC=EB=AC=B4?= =?UTF-8?q?=EC=8B=A4=20=EC=A0=95=EB=B3=B4=20=EB=AA=A8=EB=91=90=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=ED=95=98=EC=97=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../office/controller/OfficeController.java | 9 --- .../office/dto/OfficeDetailsResponseDto.java | 45 ++++++++++++-- .../office/dto/OfficeListResponseDto.java | 61 ------------------- .../office/dto/OfficeUpdateRequestDto.java | 9 ++- .../info/domain/office/entity/Office.java | 5 +- .../domain/office/service/OfficeService.java | 4 -- 6 files changed, 47 insertions(+), 86 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 3c5b9219..2962bf81 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -4,7 +4,6 @@ import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.service.OfficeService; -import inu.codin.codin.domain.info.domain.office.dto.OfficeListResponseDto; import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; @@ -15,8 +14,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/office") @@ -31,12 +28,6 @@ public ResponseEntity getOfficeByDepartment(@PathVaria return ResponseUtils.success(officeService.getOfficeByDepartment(department)); } - @Operation(summary = "학과사무실 리스트 반환") - @GetMapping - public ResponseEntity> getAllOffice(){ - return ResponseUtils.success(officeService.getAllOffice()); - } - @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 정보 수정") @PatchMapping(value = "/{department}" , produces = "plain/text; charset=utf-8") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java index d6309e60..c25ecb33 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java @@ -1,7 +1,9 @@ package inu.codin.codin.domain.info.domain.office.dto; +import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.office.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -15,22 +17,57 @@ @Getter @Setter public class OfficeDetailsResponseDto { + + @Schema(description = "학과", example = "IT_COLLEGE") + @NotBlank + private final Department department; + + @Schema(description = "위치", example = "7호관 329호") + @NotBlank + private final String location; + + @Schema(description = "오픈 시간", example = "09-21시") + private final String open; + + @Schema(description = "방학 오픈 시간", example = "09-17시") + private final String vacation; + + @Schema(description = "연락처", example = "032-123-2434") + @NotBlank + private final String officeNumber; + + @Schema(description = "팩스", example = "032-432-1234") + @NotBlank + private final String fax; + @Schema(description = "사무실 평면도", example = "https://") private String img; @Schema(description = "학과사무실 직원") - private List officeMemberResponseDtos; + private List officeMember; @Builder - public OfficeDetailsResponseDto(String img, List officeMemberResponseDtos) { + public OfficeDetailsResponseDto(Department department, String location, String open, String vacation, String officeNumber, String fax, String img, List officeMember) { + this.department = department; + this.location = location; + this.open = open; + this.vacation = vacation; + this.officeNumber = officeNumber; + this.fax = fax; this.img = img; - this.officeMemberResponseDtos = officeMemberResponseDtos; + this.officeMember = officeMember; } public static OfficeDetailsResponseDto of(Office office) { return OfficeDetailsResponseDto.builder() + .department(office.getDepartment()) + .location(office.getLocation()) + .open(office.getOpen()) + .vacation(office.getVacation()) + .officeNumber(office.getOfficeNumber()) + .fax(office.getFax()) .img(office.getImg()) - .officeMemberResponseDtos(office.getMember().stream().map(OfficeMemberResponseDto::of).toList()) + .officeMember(office.getMember().stream().map(OfficeMemberResponseDto::of).toList()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java deleted file mode 100644 index 903439d0..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeListResponseDto.java +++ /dev/null @@ -1,61 +0,0 @@ -package inu.codin.codin.domain.info.domain.office.dto; - -import inu.codin.codin.domain.info.domain.office.entity.Office; -import inu.codin.codin.common.Department; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -/* - 학과 사무실 리스트 DTO - 모든 학과 사무실의 리스트를 반환한다. - */ -@Getter -@Setter -public class OfficeListResponseDto { - - @Schema(description = "학과", example = "IT_COLLEGE") - @NotBlank - private final Department department; - - @Schema(description = "위치", example = "7호관 329호") - @NotBlank - private final String location; - - @Schema(description = "오픈 시간", example = "09-21시") - private final String open; - - @Schema(description = "방학 오픈 시간", example = "09-17시") - private final String vacation; - - @Schema(description = "연락처", example = "032-123-2434") - @NotBlank - private final String office_number; - - @Schema(description = "팩스", example = "032-432-1234") - @NotBlank - private final String fax; - - @Builder - public OfficeListResponseDto(Department department, String location, String open, String vacation, String office_number, String fax) { - this.department = department; - this.location = location; - this.open = open; - this.vacation = vacation; - this.office_number = office_number; - this.fax = fax; - } - - public static OfficeListResponseDto of(Office office){ - return OfficeListResponseDto.builder() - .department(office.getDepartment()) - .location(office.getLocation()) - .open(office.getOpen()) - .vacation(office.getVacation()) - .office_number(office.getOffice_number()) - .fax(office.getFax()) - .build(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java index ce58718a..01ab1772 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.info.domain.office.dto; -import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.office.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -24,18 +23,18 @@ public class OfficeUpdateRequestDto { @Schema(description = "연락처", example = "032-123-2434") @NotBlank - private final String office_number; + private final String officeNumber; @Schema(description = "팩스", example = "032-432-1234") @NotBlank private final String fax; @Builder - public OfficeUpdateRequestDto(String location, String open, String vacation, String office_number, String fax) { + public OfficeUpdateRequestDto(String location, String open, String vacation, String officeNumber, String fax) { this.location = location; this.open = open; this.vacation = vacation; - this.office_number = office_number; + this.officeNumber = officeNumber; this.fax = fax; } @@ -46,7 +45,7 @@ public static OfficeUpdateRequestDto of(Office office){ .location(office.getLocation()) .open(office.getOpen()) .vacation(office.getVacation()) - .office_number(office.getOffice_number()) + .officeNumber(office.getOfficeNumber()) .fax(office.getFax()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java index 0f301bbf..673c253d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberResponseDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; @@ -30,7 +29,7 @@ public class Office extends Info { private List member; @NotBlank - private String office_number; + private String officeNumber; @NotBlank private String fax; @@ -40,7 +39,7 @@ public void update(OfficeUpdateRequestDto officeUpdateRequestDto) { this.open=officeUpdateRequestDto.getOpen(); this.vacation=officeUpdateRequestDto.getVacation(); // this.img = officeUpdateRequestDto.get - this.office_number = officeUpdateRequestDto.getOffice_number(); + this.officeNumber = officeUpdateRequestDto.getOfficeNumber(); this.fax = officeUpdateRequestDto.getFax(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index 6035a745..6df18db1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -15,10 +15,6 @@ public class OfficeService { private final InfoRepository infoRepository; - public List getAllOffice() { - List offices = infoRepository.findAllOffices(); - return offices.stream().map(OfficeListResponseDto::of).toList(); - } public OfficeDetailsResponseDto getOfficeByDepartment(Department department) { Office office = infoRepository.findOfficeByDepartment(department); From 3540db7fd3049f3e5e0111af85c00cd39f191621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=EA=B5=AD?= <121088189+X1n9fU@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:21:48 +0900 Subject: [PATCH 0099/1002] =?UTF-8?q?[SC-77]=20Pref:=20DB=20Info(lab,=20pr?= =?UTF-8?q?of,=20office)=20=ED=86=B5=EC=9D=BC=20entity=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20#16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SC-77] Pref: DB 테이블 info 하나로 통일하여 상속 entity 구성, 그에 따른 패키지 구조 변경 * [SC-77] refactor : Dto 이름 변경 * [SC-77] feat : ADMIN, MANAGER 권한을 통해 추가, 수정, 삭제 기능 구축 * [SC-77] chore : BaseEntity 추가 * [SC-77] pref : soft delete 적용 * [SC-77] feat : 직원 생성 로직 추가 * [SC-77] feat : @EnableMethodSecurity 통한 @PreAuthorize 사용 * [SC-77] refactor : DetailsResponse 에 학과 사무실 정보 모두 포함하여 반환 --- .../inu/codin/codin/CodinApplication.java | 2 + .../domain/lab/controller/LabController.java | 62 ++++++++++++++ .../lab/dto/LabCreateUpdateRequestDto.java | 42 ++++++++++ .../lab/dto/LabListResponseDto.java} | 12 +-- .../lab/dto/LabThumbnailResponseDto.java} | 24 +++--- .../domain/info/domain/lab/entity/Lab.java | 77 ++++++++++++++++++ .../lab/exception/LabNotFoundException.java | 2 +- .../info/domain/lab/service/LabService.java | 51 ++++++++++++ .../office/controller/OfficeController.java | 64 +++++++++++++++ .../office/dto/OfficeDetailsResponseDto.java | 73 +++++++++++++++++ .../OfficeMemberCreateUpdateRequestDto.java} | 9 +-- .../office/dto/OfficeMemberResponseDto.java | 51 ++++++++++++ .../office/dto/OfficeUpdateRequestDto.java} | 33 +++----- .../info/domain/office/entity/Office.java | 49 ++++++++++++ .../domain/office/entity/OfficeMember.java | 59 ++++++++++++++ .../domain/office/service/OfficeService.java | 50 ++++++++++++ .../controller/ProfessorController.java | 64 +++++++++++++++ .../dto/ProfessorCreateUpdateRequestDto.java | 36 +++++++++ .../dto/ProfessorListResponseDto.java | 42 ++++++++++ .../dto/ProfessorThumbnailResponseDto.java | 65 +++++++++++++++ .../domain/professor/entity/Professor.java | 80 +++++++++++++++++++ .../ProfessorDuplicatedException.java | 7 ++ .../exception/ProfessorNotFoundException.java | 2 +- .../professor/service/ProfessorService.java | 56 +++++++++++++ .../codin/codin/domain/info/entity/Info.java | 31 +++++++ .../codin/domain/info/entity/InfoType.java | 15 ++++ .../info/lab/controller/LabController.java | 43 ---------- .../codin/domain/info/lab/entity/Lab.java | 40 ---------- .../info/lab/repository/LabRepository.java | 7 -- .../domain/info/lab/service/LabService.java | 51 ------------ .../office/controller/OfficeController.java | 38 --------- .../info/office/dto/OfficeMemberResDTO.java | 36 --------- .../domain/info/office/entity/Office.java | 42 ---------- .../office/repository/OfficeRepository.java | 13 --- .../info/office/service/OfficeService.java | 27 ------- .../controller/ProfessorController.java | 38 --------- .../professor/dto/ProfessorListResDTO.java | 34 -------- .../dto/ProfessorThumbnailResDTO.java | 50 ------------ .../info/professor/entity/Professor.java | 48 ----------- .../repository/ProfessorRepository.java | 12 --- .../professor/service/ProfessorService.java | 30 ------- .../info/repository/InfoRepository.java | 41 ++++++++++ 42 files changed, 1054 insertions(+), 554 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{lab/dto/LabListResDTO.java => domain/lab/dto/LabListResponseDto.java} (76%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{lab/dto/LabThumbnailResDTO.java => domain/lab/dto/LabThumbnailResponseDto.java} (75%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/lab/exception/LabNotFoundException.java (71%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{office/dto/OfficeMember.java => domain/office/dto/OfficeMemberCreateUpdateRequestDto.java} (82%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{office/dto/OfficeListResDTO.java => domain/office/dto/OfficeUpdateRequestDto.java} (52%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java rename codin-core/src/main/java/inu/codin/codin/domain/info/{ => domain}/professor/exception/ProfessorNotFoundException.java (70%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index a041ed7e..3e3d45b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -3,11 +3,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import java.util.TimeZone; @SpringBootApplication @EnableMongoAuditing +@EnableMethodSecurity public class CodinApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java new file mode 100644 index 00000000..30881068 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -0,0 +1,62 @@ +package inu.codin.codin.domain.info.domain.lab.controller; + +import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.lab.service.LabService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/info/lab") +@Tag(name = "Info API") +public class LabController { + + private final LabService labService; + + @Operation(summary = "연구실 썸네일 반환") + @GetMapping("/thumbnail/{id}") + public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ + return ResponseUtils.success(labService.getLabThumbnail(id)); + } + + @Operation(summary = "연구실 리스트 반환") + @GetMapping + public ResponseEntity> getAllLab(){ + return ResponseUtils.success(labService.getAllLab()); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 새로운 연구실 등록") + @PostMapping(produces = "plain/text; charset=utf-8") + public ResponseEntity createLab(@RequestBody LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + labService.createLab(labCreateUpdateRequestDto); + return ResponseUtils.successMsg("새로운 LAB 등록 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 연구실 정보 수정") + @PutMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity updateLab(@RequestBody LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ + labService.updateLab(labCreateUpdateRequestDto, id); + return ResponseUtils.successMsg("LAB 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 연구실 삭제") + @DeleteMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity deleteLab(@PathVariable("id") String id){ + labService.deleteLab(id); + return ResponseUtils.successMsg("LAB 삭제 완료"); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java new file mode 100644 index 00000000..cd913362 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.info.domain.lab.dto; + +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LabCreateUpdateRequestDto { + @NotBlank + @Schema(description = "학과", example = "EMBEDDED") + private Department department; + + @NotBlank + @Schema(description = "연구실 이름", example = "땡땡연구실") + private String title; + + @Schema(description = "연구 내용", example = "이것저것 연구합니다.") + private String content; + + @NotBlank + @Schema(description = "담당 교수", example = "홍길동") + private String professor; + + @Schema(description = "교수실 위치", example = "7호관 423호") + private String professorLoc; + + @Schema(description = "교수실 전화번호", example = "032-123-4567") + private String professorNumber; + + @Schema(description = "연구실 위치", example = "7호관 409호") + private String labLoc; + + @Schema(description = "연구실 전화번호", example = "032-987-0653") + private String labNumber; + + @Schema(description = "연구실 홈페이지", example = "http://~") + private String site; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java similarity index 76% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java index bbd0b8d4..ce2df4f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto; -import inu.codin.codin.domain.info.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; @@ -14,7 +14,7 @@ @Getter @Setter -public class LabListResDTO { +public class LabListResponseDto { @Schema(description = "Lab DB의 pk값", example = "b2jfbe432..") @NotBlank private final String id; @@ -31,15 +31,15 @@ public class LabListResDTO { private final String professor; @Builder - public LabListResDTO(String id, String title, String content, String professor) { + public LabListResponseDto(String id, String title, String content, String professor) { this.id = id; this.title = title; this.content = content; this.professor = professor; } - public static LabListResDTO of(Lab lab){ - return LabListResDTO.builder() + public static LabListResponseDto of(Lab lab){ + return LabListResponseDto.builder() .id(lab.getId()) .title(lab.getTitle()) .content(lab.getContent()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java similarity index 75% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java index 0c112d14..345d650e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/dto/LabThumbnailResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java @@ -1,21 +1,23 @@ -package inu.codin.codin.domain.info.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto; -import inu.codin.codin.domain.info.lab.entity.Lab; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; /* 연구실 상세정보 반환 DTO 해당하는 연구실에 대한 내용들을 모두 반환한다. */ -@Data -public class LabThumbnailResDTO { +@Getter +@Setter +public class LabThumbnailResponseDto { @NotBlank - @Schema(description = "학과", example = "CSE") + @Schema(description = "학과", example = "EMBEDDED") private Department department; @NotBlank @@ -45,9 +47,9 @@ public class LabThumbnailResDTO { private String site; @Builder - public LabThumbnailResDTO(Department department, String title, String content, String professor, - String professorLoc, String professorNumber, String labLoc, - String labNumber, String site) { + public LabThumbnailResponseDto(Department department, String title, String content, String professor, + String professorLoc, String professorNumber, String labLoc, + String labNumber, String site) { this.department = department; this.title = title; this.content = content; @@ -59,8 +61,8 @@ public LabThumbnailResDTO(Department department, String title, String content, S this.site = site; } - public static LabThumbnailResDTO of(Lab lab) { - return LabThumbnailResDTO.builder() + public static LabThumbnailResponseDto of(Lab lab) { + return LabThumbnailResponseDto.builder() .department(lab.getDepartment()) .title(lab.getTitle()) .content(lab.getContent()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java new file mode 100644 index 00000000..dc2823a5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -0,0 +1,77 @@ +package inu.codin.codin.domain.info.domain.lab.entity; + +import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.entity.InfoType; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "info") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Lab extends Info { + + @NotBlank + private String professor; + + @NotBlank + private String title; + + private String content; + + private String professorLoc; + + private String professorNumber; + + private String labLoc; + + private String labNumber; + + private String site; + + @Builder + public Lab(String id, Department department, InfoType infoType, String professor, String title, String content, String professorLoc, String professorNumber, String labLoc, String labNumber, String site) { + super(id, department, infoType); + this.professor = professor; + this.title = title; + this.content = content; + this.professorLoc = professorLoc; + this.professorNumber = professorNumber; + this.labLoc = labLoc; + this.labNumber = labNumber; + this.site = site; + } + + public static Lab of(LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + return Lab.builder() + .department(labCreateUpdateRequestDto.getDepartment()) + .infoType(InfoType.LAB) + .professor(labCreateUpdateRequestDto.getProfessor()) + .title(labCreateUpdateRequestDto.getTitle()) + .content(labCreateUpdateRequestDto.getContent()) + .professorLoc(labCreateUpdateRequestDto.getProfessorLoc()) + .professorNumber(labCreateUpdateRequestDto.getProfessorNumber()) + .labLoc(labCreateUpdateRequestDto.getLabLoc()) + .labNumber(labCreateUpdateRequestDto.getLabNumber()) + .site(labCreateUpdateRequestDto.getSite()) + .build(); + + } + + public void update(LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + this.department = labCreateUpdateRequestDto.getDepartment(); + this.title = labCreateUpdateRequestDto.getTitle(); + this.content = labCreateUpdateRequestDto.getContent(); + this.professor = labCreateUpdateRequestDto.getProfessor(); + this.professorLoc = labCreateUpdateRequestDto.getProfessorLoc(); + this.professorNumber = labCreateUpdateRequestDto.getProfessorNumber(); + this.labLoc = labCreateUpdateRequestDto.getLabLoc(); + this.labNumber = labCreateUpdateRequestDto.getLabNumber(); + this.site = labCreateUpdateRequestDto.getSite(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java index 95982fd8..a3003257 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/exception/LabNotFoundException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.lab.exception; +package inu.codin.codin.domain.info.domain.lab.exception; public class LabNotFoundException extends RuntimeException{ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java new file mode 100644 index 00000000..3f409315 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -0,0 +1,51 @@ +package inu.codin.codin.domain.info.domain.lab.service; + +import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; +import inu.codin.codin.domain.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class LabService { + + private final InfoRepository infoRepository; + + public LabThumbnailResponseDto getLabThumbnail(String id) { + Lab lab = infoRepository.findLabById(id) + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + return LabThumbnailResponseDto.of(lab); + } + + public List getAllLab() { + List labs = infoRepository.findAllLabs(); + List list = labs.stream().map(LabListResponseDto::of).toList(); + return list; + } + + public void createLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto) { + Lab lab = Lab.of(labCreateUpdateRequestDto); + infoRepository.save(lab); + } + + public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, String id) { + Lab lab = infoRepository.findLabById(id) + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + lab.update(labCreateUpdateRequestDto); + infoRepository.save(lab); + } + + public void deleteLab(String id) { + Lab lab = infoRepository.findLabById(id) + .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + lab.delete(); + infoRepository.save(lab); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java new file mode 100644 index 00000000..2962bf81 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -0,0 +1,64 @@ +package inu.codin.codin.domain.info.domain.office.controller; + +import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.service.OfficeService; +import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/info/office") +@Tag(name = "Info API") +public class OfficeController { + + private final OfficeService officeService; + + @Operation(summary = "학과별 사무실 직원 정보 반환") + @GetMapping("/{department}") + public ResponseEntity getOfficeByDepartment(@PathVariable("department") Department department){ + return ResponseUtils.success(officeService.getOfficeByDepartment(department)); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 정보 수정") + @PatchMapping(value = "/{department}" , produces = "plain/text; charset=utf-8") + public ResponseEntity updateOffice(@PathVariable("department") Department department, @RequestBody OfficeUpdateRequestDto officeUpdateRequestDto){ + officeService.updateOffice(department, officeUpdateRequestDto); + return ResponseUtils.successMsg("Office 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 추가") + @PatchMapping(value = "/{department}/member", produces = "plain/text; charset=utf-8") + public ResponseEntity createOfficeMember(@PathVariable("department") Department department, + @RequestBody OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + officeService.createOfficeMember(department, officeMemberCreateUpdateRequestDto); + return ResponseUtils.successMsg("Office Member 추가 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 수정") + @PatchMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") + public ResponseEntity updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, + @RequestBody OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + officeService.updateOfficeMember(department, num, officeMemberCreateUpdateRequestDto); + return ResponseUtils.successMsg("Office Member 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 삭제") + @DeleteMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") + public ResponseEntity deleteOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num){ + officeService.deleteOfficeMember(department,num); + return ResponseUtils.successMsg("Office Member 정보 삭제 완료"); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java new file mode 100644 index 00000000..c25ecb33 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java @@ -0,0 +1,73 @@ +package inu.codin.codin.domain.info.domain.office.dto; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.office.entity.Office; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/* + 학과 사무실 정보 반환 DTO + 학과 사무실 평면도 및 지원 정보들을 모두 반환한다. + */ +@Getter +@Setter +public class OfficeDetailsResponseDto { + + @Schema(description = "학과", example = "IT_COLLEGE") + @NotBlank + private final Department department; + + @Schema(description = "위치", example = "7호관 329호") + @NotBlank + private final String location; + + @Schema(description = "오픈 시간", example = "09-21시") + private final String open; + + @Schema(description = "방학 오픈 시간", example = "09-17시") + private final String vacation; + + @Schema(description = "연락처", example = "032-123-2434") + @NotBlank + private final String officeNumber; + + @Schema(description = "팩스", example = "032-432-1234") + @NotBlank + private final String fax; + + @Schema(description = "사무실 평면도", example = "https://") + private String img; + + @Schema(description = "학과사무실 직원") + private List officeMember; + + @Builder + public OfficeDetailsResponseDto(Department department, String location, String open, String vacation, String officeNumber, String fax, String img, List officeMember) { + this.department = department; + this.location = location; + this.open = open; + this.vacation = vacation; + this.officeNumber = officeNumber; + this.fax = fax; + this.img = img; + this.officeMember = officeMember; + } + + public static OfficeDetailsResponseDto of(Office office) { + return OfficeDetailsResponseDto.builder() + .department(office.getDepartment()) + .location(office.getLocation()) + .open(office.getOpen()) + .vacation(office.getVacation()) + .officeNumber(office.getOfficeNumber()) + .fax(office.getFax()) + .img(office.getImg()) + .officeMember(office.getMember().stream().map(OfficeMemberResponseDto::of).toList()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java similarity index 82% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java index a31b51a8..cf477108 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java @@ -1,14 +1,13 @@ -package inu.codin.codin.domain.info.office.dto; +package inu.codin.codin.domain.info.domain.office.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import lombok.Setter; -/* - 학과 사무실 직원 정보 - */ @Getter -public class OfficeMember { +@Setter +public class OfficeMemberCreateUpdateRequestDto { @NotBlank @Schema(description = "성명", example = "홍길동") private String name; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java new file mode 100644 index 00000000..b8efb6ee --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java @@ -0,0 +1,51 @@ +package inu.codin.codin.domain.info.domain.office.dto; + +import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; + +/* + 학과 사무실 직원 정보 + */ +@Getter +public class OfficeMemberResponseDto { + @NotBlank + @Schema(description = "성명", example = "홍길동") + private String name; + @NotBlank + @Schema(description = "직위", example = "조교") + private String position; + + @Schema(description = "담당 업무", example = "학과사무실 업무") + private String role; + + @NotBlank + @Schema(description = "연락처", example = "032-123-2345") + private String number; + + @NotBlank + @Schema(description = "이메일", example = "test@inu.ac.kr") + private String email; + + @Builder + public OfficeMemberResponseDto(String name, String position, String role, String number, String email) { + this.name = name; + this.position = position; + this.role = role; + this.number = number; + this.email = email; + } + + public static OfficeMemberResponseDto of(OfficeMember officeMember){ + return OfficeMemberResponseDto.builder() + .name(officeMember.getName()) + .email(officeMember.getEmail()) + .position(officeMember.getPosition()) + .role(officeMember.getRole()) + .number(officeMember.getNumber()) + .email(officeMember.getEmail()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java similarity index 52% rename from codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java index a3e5db66..01ab1772 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeListResDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java @@ -1,24 +1,15 @@ -package inu.codin.codin.domain.info.office.dto; +package inu.codin.codin.domain.info.domain.office.dto; -import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.office.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; -/* - 학과 사무실 리스트 DTO - 모든 학과 사무실의 리스트를 반환한다. - */ @Getter @Setter -public class OfficeListResDTO{ - - @Schema(description = "학과", example = "IT_COLLEGE") - @NotBlank - private final Department department; +public class OfficeUpdateRequestDto { @Schema(description = "위치", example = "7호관 329호") @NotBlank @@ -32,30 +23,30 @@ public class OfficeListResDTO{ @Schema(description = "연락처", example = "032-123-2434") @NotBlank - private final String office_number; + private final String officeNumber; @Schema(description = "팩스", example = "032-432-1234") @NotBlank private final String fax; @Builder - public OfficeListResDTO(Department department, String location, String open, String vacation, String office_number, String fax) { - this.department = department; + public OfficeUpdateRequestDto(String location, String open, String vacation, String officeNumber, String fax) { this.location = location; this.open = open; this.vacation = vacation; - this.office_number = office_number; + this.officeNumber = officeNumber; this.fax = fax; } - public static OfficeListResDTO of(Office office){ - return OfficeListResDTO.builder() - .department(office.getDepartment()) + + + public static OfficeUpdateRequestDto of(Office office){ + return OfficeUpdateRequestDto.builder() .location(office.getLocation()) .open(office.getOpen()) .vacation(office.getVacation()) - .office_number(office.getOffice_number()) - .fax(office.getOffice_number()) + .officeNumber(office.getOfficeNumber()) + .fax(office.getFax()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java new file mode 100644 index 00000000..673c253d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java @@ -0,0 +1,49 @@ +package inu.codin.codin.domain.info.domain.office.entity; + +import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.entity.Info; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.List; + +@Document(collection = "info") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Office extends Info { + + @NotBlank + private String location; + + @NotBlank + private String open; + + @NotBlank + private String vacation; + + private String img; + + private List member; + + @NotBlank + private String officeNumber; + + @NotBlank + private String fax; + + public void update(OfficeUpdateRequestDto officeUpdateRequestDto) { + this.location=officeUpdateRequestDto.getLocation(); + this.open=officeUpdateRequestDto.getOpen(); + this.vacation=officeUpdateRequestDto.getVacation(); +// this.img = officeUpdateRequestDto.get + this.officeNumber = officeUpdateRequestDto.getOfficeNumber(); + this.fax = officeUpdateRequestDto.getFax(); + } + + public void addOfficeMember(OfficeMember officeMember){ + this.member.add(officeMember); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java new file mode 100644 index 00000000..e743ac31 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java @@ -0,0 +1,59 @@ +package inu.codin.codin.domain.info.domain.office.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; + +/* + 학과 사무실 직원 정보 + */ +@Getter +public class OfficeMember extends BaseTimeEntity { + @NotBlank + @Schema(description = "성명", example = "홍길동") + private String name; + @NotBlank + @Schema(description = "직위", example = "조교") + private String position; + + @Schema(description = "담당 업무", example = "학과사무실 업무") + private String role; + + @NotBlank + @Schema(description = "연락처", example = "032-123-2345") + private String number; + + @NotBlank + @Schema(description = "이메일", example = "test@inu.ac.kr") + private String email; + + @Builder + public OfficeMember(String name, String position, String role, String number, String email) { + this.name = name; + this.position = position; + this.role = role; + this.number = number; + this.email = email; + } + + public static OfficeMember of(OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + return OfficeMember.builder() + .name(officeMemberCreateUpdateRequestDto.getName()) + .position(officeMemberCreateUpdateRequestDto.getPosition()) + .role(officeMemberCreateUpdateRequestDto.getRole()) + .number(officeMemberCreateUpdateRequestDto.getNumber()) + .email(officeMemberCreateUpdateRequestDto.getEmail()) + .build(); + } + + public void update(OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { + this.name = officeMemberCreateUpdateRequestDto.getName(); + this.position = officeMemberCreateUpdateRequestDto.getPosition(); + this.role = officeMemberCreateUpdateRequestDto.getRole(); + this.number = officeMemberCreateUpdateRequestDto.getNumber(); + this.email = officeMemberCreateUpdateRequestDto.getEmail(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java new file mode 100644 index 00000000..6df18db1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -0,0 +1,50 @@ +package inu.codin.codin.domain.info.domain.office.service; + +import inu.codin.codin.domain.info.domain.office.dto.*; +import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; +import inu.codin.codin.domain.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class OfficeService { + + private final InfoRepository infoRepository; + + public OfficeDetailsResponseDto getOfficeByDepartment(Department department) { + Office office = infoRepository.findOfficeByDepartment(department); + return OfficeDetailsResponseDto.of(office); + } + + public void createOfficeMember(Department department, OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { + Office office = infoRepository.findOfficeByDepartment(department); + OfficeMember officeMember = OfficeMember.of(officeMemberCreateUpdateRequestDto); + office.addOfficeMember(officeMember); + infoRepository.save(office); + + } + + public void updateOffice(Department department, OfficeUpdateRequestDto officeUpdateRequestDto) { + Office office = infoRepository.findOfficeByDepartment(department); + office.update(officeUpdateRequestDto); + infoRepository.save(office); + } + + public void updateOfficeMember(Department department, int num, OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { + Office office = infoRepository.findOfficeByDepartment(department); + OfficeMember officeMember = office.getMember().get(num); + officeMember.update(officeMemberCreateUpdateRequestDto); + infoRepository.save(office); + } + + public void deleteOfficeMember(Department department, int num) { + Office office = infoRepository.findOfficeByDepartment(department); + office.getMember().get(num).delete(); + infoRepository.save(office); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java new file mode 100644 index 00000000..be7e8e63 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -0,0 +1,64 @@ +package inu.codin.codin.domain.info.domain.professor.controller; + +import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/info/professor") +@Tag(name = "Info API") +public class ProfessorController { + + private final ProfessorService professorService; + + @Operation(summary = "교수 리스트 반환") + @GetMapping("/{department}") + public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ + return ResponseUtils.success(professorService.getProfessorByDepartment(department)); + } + + @Operation(summary = "id값에 따른 교수 썸네일 반환") + @GetMapping("/detail/{id}") + public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ + return ResponseUtils.success(professorService.getProfessorThumbnail(id)); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 새로운 교수 정보 생성") + @PostMapping(produces = "plain/text; charset=utf-8") + public ResponseEntity createProfessor(@RequestBody ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + professorService.createProfessor(professorCreateUpdateRequestDto); + return ResponseUtils.successMsg("새로운 교수 정보 생성 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 교수 정보 수정") + @PatchMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity updateProfessor(@PathVariable("id") String id, @RequestBody ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + professorService.updateProfessor(id, professorCreateUpdateRequestDto); + return ResponseUtils.successMsg("교수 정보 수정 완료"); + } + + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation(summary = "[ADMIN, MANAGER] 교수 정보 삭제") + @DeleteMapping(value = "/{id}", produces = "plain/text; charset=utf-8") + public ResponseEntity deleteProfessor(@PathVariable("id") String id){ + professorService.deleteProfessor(id); + return ResponseUtils.successMsg("교수 정보 삭제 완료"); + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java new file mode 100644 index 00000000..b153bae6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.info.domain.professor.dto; + +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ProfessorCreateUpdateRequestDto { + + @NotBlank + @Schema(description = "학과", example = "COMPUTER_SCI") + Department department; + @NotBlank @Schema(description = "성함", example = "홍길동") + String name; + @NotBlank @Schema(description = "프로필 사진", example = "https://~") + String image; + @NotBlank @Schema(description = "전화번호", example = "032-123-4567") + String number; + @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") + String email; + @Schema(description = "연구실 홈페이지", example = "https://~") + String site; + @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") + String field; + @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") + String subject; + @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") + String labId; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java new file mode 100644 index 00000000..7cab9fa0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.info.domain.professor.dto; + +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +/* + 교수님 리스트 반환 DTO + 모든 교수님들의 리스트를 반환한다. + */ + +@Getter +@Setter +public class ProfessorListResponseDto{ + @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") + String id; + + @NotBlank @Schema(description = "교수 성함", example = "홍길동") + String name; + + @NotBlank @Schema(description = "학과", example = "CSE") + Department department; + + @Builder + public ProfessorListResponseDto(String id, String name, Department department) { + this.id = id; + this.name = name; + this.department = department; + } + + public static ProfessorListResponseDto of(Professor professor) { + return ProfessorListResponseDto.builder() + .id(professor.getId()) + .name(professor.getName()) + .department(professor.getDepartment()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java new file mode 100644 index 00000000..82b02ce4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java @@ -0,0 +1,65 @@ +package inu.codin.codin.domain.info.domain.professor.dto; + +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +/* + 교수님 상세 정보 반환 DTO + 해당되는 교수님의 상세 정보들을 반환한다. + */ + +@Getter +@Setter +public class ProfessorThumbnailResponseDto{ + + @NotBlank @Schema(description = "학과", example = "CSE") + Department department; + @NotBlank @Schema(description = "성함", example = "홍길동") + String name; + @NotBlank @Schema(description = "프로필 사진", example = "https://~") + String image; + @NotBlank @Schema(description = "전화번호", example = "032-123-4567") + String number; + @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") + String email; + @Schema(description = "연구실 홈페이지", example = "https://~") + String site; + @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") + String field; + @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") + String subject; + @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") + String labId; + + @Builder + public ProfessorThumbnailResponseDto(Department department, String name, String image, String number, String email, String site, String field, String subject, String labId) { + this.department = department; + this.name = name; + this.image = image; + this.number = number; + this.email = email; + this.site = site; + this.field = field; + this.subject = subject; + this.labId = labId; + } + + public static ProfessorThumbnailResponseDto of(Professor professor) { + return ProfessorThumbnailResponseDto.builder() + .department(professor.getDepartment()) + .name(professor.getName()) + .image(professor.getImage()) + .number(professor.getNumber()) + .email(professor.getEmail()) + .site(professor.getSite()) + .field(professor.getField()) + .subject(professor.getSubject()) + .labId(professor.getLabId()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java new file mode 100644 index 00000000..11c7fdce --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java @@ -0,0 +1,80 @@ +package inu.codin.codin.domain.info.domain.professor.entity; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.domain.info.entity.InfoType; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "info") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Professor extends Info { + + @NotBlank + private String name; + + @NotBlank + private String image; + + @NotBlank + private String number; + + @NotBlank + private String email; + + private String site; + + @NotBlank + private String field; + + private String subject; + + private String labId; + + @Builder + public Professor(String id, Department department, InfoType infoType, String name, String image, String number, String email, String site, String field, String subject, String labId) { + super(id, department, infoType); + this.name = name; + this.image = image; + this.number = number; + this.email = email; + this.site = site; + this.field = field; + this.subject = subject; + this.labId = labId; + } + + public static Professor of(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + return Professor.builder() + .department(professorCreateUpdateRequestDto.getDepartment()) + .infoType(InfoType.PROFESSOR) + .name(professorCreateUpdateRequestDto.getName()) + .image(professorCreateUpdateRequestDto.getImage()) + .number(professorCreateUpdateRequestDto.getNumber()) + .email(professorCreateUpdateRequestDto.getEmail()) + .site(professorCreateUpdateRequestDto.getSite()) + .field(professorCreateUpdateRequestDto.getField()) + .subject(professorCreateUpdateRequestDto.getSubject()) + .labId(professorCreateUpdateRequestDto.getLabId()) + .build(); + } + + public void update(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + this.department = professorCreateUpdateRequestDto.getDepartment(); + this.infoType = InfoType.PROFESSOR; + this.name = professorCreateUpdateRequestDto.getName(); + this.image = professorCreateUpdateRequestDto.getImage(); + this.number = professorCreateUpdateRequestDto.getNumber(); + this.email = professorCreateUpdateRequestDto.getEmail(); + this.site = professorCreateUpdateRequestDto.getSite(); + this.field = professorCreateUpdateRequestDto.getField(); + this.subject = professorCreateUpdateRequestDto.getSubject(); + this.labId = professorCreateUpdateRequestDto.getLabId(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java new file mode 100644 index 00000000..d78c76a3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.info.domain.professor.exception; + +public class ProfessorDuplicatedException extends RuntimeException{ + public ProfessorDuplicatedException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java index 766dd62b..39f3cc7d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/exception/ProfessorNotFoundException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.professor.exception; +package inu.codin.codin.domain.info.domain.professor.exception; public class ProfessorNotFoundException extends RuntimeException{ public ProfessorNotFoundException(String message){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java new file mode 100644 index 00000000..fd873bcd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -0,0 +1,56 @@ +package inu.codin.codin.domain.info.domain.professor.service; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.domain.professor.exception.ProfessorDuplicatedException; +import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; +import inu.codin.codin.domain.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ProfessorService { + + private final InfoRepository infoRepository; + + public List getProfessorByDepartment(Department department){ + List professors = infoRepository.findAllProfessorsByDepartment(department); + return professors.stream().map(ProfessorListResponseDto::of).toList(); + } + + public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { + Professor professor = infoRepository.findProfessorById(id) + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + return ProfessorThumbnailResponseDto.of(professor); + } + + public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + if (infoRepository.findProfessorByEmail(professorCreateUpdateRequestDto.getEmail()).isPresent()){ + throw new ProfessorDuplicatedException("이미 존재하는 Professor 정보 입니다."); + } + Professor professor = Professor.of(professorCreateUpdateRequestDto); + infoRepository.save(professor); + } + + public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + Professor professor = infoRepository.findProfessorById(id) + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + professor.update(professorCreateUpdateRequestDto); + infoRepository.save(professor); + } + + + + public void deleteProfessor(String id) { + Professor professor = infoRepository.findProfessorById(id) + .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + professor.delete(); + infoRepository.save(professor); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java new file mode 100644 index 00000000..22dee228 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.info.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.Department; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "info") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public abstract class Info extends BaseTimeEntity { + + @Id @NotBlank + protected String id; + + @NotBlank + protected Department department; + + @NotBlank + protected InfoType infoType; + + public Info(String id, Department department, InfoType infoType) { + this.id = id; + this.department = department; + this.infoType = infoType; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java new file mode 100644 index 00000000..c6de57c7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/InfoType.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.info.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum InfoType { + + PROFESSOR("교수님"), + LAB("연구실"), + OFFICE("학과 사무실"); + + private final String description; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java deleted file mode 100644 index 7bbf3e40..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/controller/LabController.java +++ /dev/null @@ -1,43 +0,0 @@ -package inu.codin.codin.domain.info.lab.controller; - -import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.lab.dto.LabListResDTO; -import inu.codin.codin.domain.info.lab.service.LabService; -import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -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.RestController; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/info/lab") -@Tag(name = "Info API") -public class LabController { - - private final LabService labService; - - @Operation(summary = "연구실 썸네일 반환") - @GetMapping("/thumbnail/{id}") - public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ - return ResponseUtils.success(labService.getLabThumbnail(id)); - } - - @Operation(summary = "연구실 리스트 반환") - @GetMapping - public ResponseEntity> getAllLab(){ - return ResponseUtils.success(labService.getAllLab()); - } - - //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 - @GetMapping("/") - public void joinLab(){ - labService.joinlab(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java deleted file mode 100644 index 53ad75ed..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/entity/Lab.java +++ /dev/null @@ -1,40 +0,0 @@ -package inu.codin.codin.domain.info.lab.entity; - -import inu.codin.codin.common.Department; -import jakarta.validation.constraints.NotBlank; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -@Document(collection = "lab") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class Lab { - - @Id @NotBlank - private String id; - - @NotBlank - private Department department; - - @NotBlank - private String professor; - - @NotBlank - private String title; - - private String content; - - private String professorLoc; - - private String professorNumber; - - private String labLoc; - - private String labNumber; - - private String site; - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java deleted file mode 100644 index 9612ddeb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/repository/LabRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.info.lab.repository; - -import inu.codin.codin.domain.info.lab.entity.Lab; -import org.springframework.data.mongodb.repository.MongoRepository; - -public interface LabRepository extends MongoRepository { -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java deleted file mode 100644 index f53f5627..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/lab/service/LabService.java +++ /dev/null @@ -1,51 +0,0 @@ -package inu.codin.codin.domain.info.lab.service; - -import inu.codin.codin.domain.info.lab.dto.LabListResDTO; -import inu.codin.codin.domain.info.lab.dto.LabThumbnailResDTO; -import inu.codin.codin.domain.info.lab.entity.Lab; -import inu.codin.codin.domain.info.lab.exception.LabNotFoundException; -import inu.codin.codin.domain.info.lab.repository.LabRepository; -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class LabService { - - private final LabRepository labRepository; - private final ProfessorRepository professorRepository; - - public LabThumbnailResDTO getLabThumbnail(String id) { - Lab lab = labRepository.findById(id) - .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); - return LabThumbnailResDTO.of(lab); - } - - //잠시 교수님과 연구실을 참조하기 위해 만들어놓음. 추후 삭제 예정 - @Transactional - public List joinlab() { - List professors = professorRepository.findAll(); - List labs = labRepository.findAll(); - - for (Professor professor : professors) { - for (Lab lab : labs) { - if (professor.getName().equals(lab.getProfessor())) { - professor.updateLab(lab); - professorRepository.save(professor); - break; - } - } - } - return professors; - } - - public List getAllLab() { - List labs = labRepository.findAll(); - return labs.stream().map(LabListResDTO::of).toList(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java deleted file mode 100644 index 65c1aaab..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/controller/OfficeController.java +++ /dev/null @@ -1,38 +0,0 @@ -package inu.codin.codin.domain.info.office.controller; - -import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; -import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; -import inu.codin.codin.domain.info.office.service.OfficeService; -import inu.codin.codin.common.Department; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -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.RestController; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/info/office") -@Tag(name = "Info API") -public class OfficeController { - - private final OfficeService officeService; - - @Operation(summary = "학과별 사무실 직원 정보 반환") - @GetMapping("/{department}") - public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ - return ResponseUtils.success(officeService.getOfficeByDepartment(department)); - } - - @Operation(summary = "학과사무실 리스트 반환") - @GetMapping - public ResponseEntity> getAllOffice(){ - return ResponseUtils.success(officeService.getAllOffice()); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java deleted file mode 100644 index b9b3fb16..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/dto/OfficeMemberResDTO.java +++ /dev/null @@ -1,36 +0,0 @@ -package inu.codin.codin.domain.info.office.dto; - -import inu.codin.codin.domain.info.office.entity.Office; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -/* - 학과 사무실 정보 반환 DTO - 학과 사무실 평면도 및 지원 정보들을 모두 반환한다. - */ -@Getter -@Setter -public class OfficeMemberResDTO { - @Schema(description = "사무실 평면도", example = "https://") - private String img; - - @Schema(description = "학과사무실 직원") - private List officeMembers; - - @Builder - public OfficeMemberResDTO(String img, List officeMembers) { - this.img = img; - this.officeMembers = officeMembers; - } - - public static OfficeMemberResDTO of(Office office) { - return OfficeMemberResDTO.builder() - .img(office.getImg()) - .officeMembers(office.getMember()) - .build(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java deleted file mode 100644 index 207ea80e..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/entity/Office.java +++ /dev/null @@ -1,42 +0,0 @@ -package inu.codin.codin.domain.info.office.entity; - -import inu.codin.codin.domain.info.office.dto.OfficeMember; -import inu.codin.codin.common.Department; -import jakarta.validation.constraints.NotBlank; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -import java.util.List; - -@Document(collection = "department_office") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Office { - @Id @NotBlank - private String id; - - @NotBlank - private Department department; - - @NotBlank - private String location; - - @NotBlank - private String open; - - @NotBlank - private String vacation; - - private String img; - - private List member; - - @NotBlank - private String office_number; - - @NotBlank - private String fax; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java deleted file mode 100644 index b12c1346..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/repository/OfficeRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package inu.codin.codin.domain.info.office.repository; - -import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.common.Department; -import jakarta.validation.constraints.NotBlank; -import org.springframework.data.mongodb.repository.MongoRepository; - -import java.util.List; - -public interface OfficeRepository extends MongoRepository { - - List findAllByDepartment(@NotBlank Department department); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java deleted file mode 100644 index 0f392a21..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/office/service/OfficeService.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codin.domain.info.office.service; - -import inu.codin.codin.domain.info.office.dto.OfficeListResDTO; -import inu.codin.codin.domain.info.office.dto.OfficeMemberResDTO; -import inu.codin.codin.domain.info.office.entity.Office; -import inu.codin.codin.domain.info.office.repository.OfficeRepository; -import inu.codin.codin.common.Department; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class OfficeService { - - private final OfficeRepository officeRepository; - public List getAllOffice() { - List offices = officeRepository.findAll(); - return offices.stream().map(OfficeListResDTO::of).toList(); - } - - public List getOfficeByDepartment(Department department) { - List offices = officeRepository.findAllByDepartment(department); - return offices.stream().map(OfficeMemberResDTO::of).toList(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java deleted file mode 100644 index b9cdfc4a..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/controller/ProfessorController.java +++ /dev/null @@ -1,38 +0,0 @@ -package inu.codin.codin.domain.info.professor.controller; - -import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.professor.service.ProfessorService; -import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; -import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; -import inu.codin.codin.common.Department; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -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.RestController; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/info/professor") -@Tag(name = "Info API") -public class ProfessorController { - - private final ProfessorService professorService; - - @Operation(summary = "교수 리스트 반환") - @GetMapping("/{department}") - public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ - return ResponseUtils.success(professorService.getProfessorByDepartment(department)); - } - - @Operation(summary = "id값에 따른 교수 썸네일 반환") - @GetMapping("/detail/{id}") - public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ - return ResponseUtils.success(professorService.getProfessorThumbnail(id)); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java deleted file mode 100644 index 616b39f9..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorListResDTO.java +++ /dev/null @@ -1,34 +0,0 @@ -package inu.codin.codin.domain.info.professor.dto; - -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.common.Department; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; - -/* - 교수님 리스트 반환 DTO - 모든 교수님들의 리스트를 반환한다. - */ - -public record ProfessorListResDTO( - @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") - String id, - - @NotBlank @Schema(description = "교수 성함", example = "홍길동") - String name, - - @NotBlank @Schema(description = "학과", example = "CSE") - Department department) { - @Builder - public ProfessorListResDTO { - } - - public static ProfessorListResDTO of(Professor professor) { - return ProfessorListResDTO.builder() - .id(professor.getId()) - .name(professor.getName()) - .department(professor.getDepartment()) - .build(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java deleted file mode 100644 index 2105b3dd..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/dto/ProfessorThumbnailResDTO.java +++ /dev/null @@ -1,50 +0,0 @@ -package inu.codin.codin.domain.info.professor.dto; - -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.common.Department; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; - -/* - 교수님 상세 정보 반환 DTO - 해당되는 교수님의 상세 정보들을 반환한다. - */ -public record ProfessorThumbnailResDTO( - @NotBlank @Schema(description = "학과", example = "CSE") - Department department, - @NotBlank @Schema(description = "성함", example = "홍길동") - String name, - @NotBlank @Schema(description = "프로필 사진", example = "https://~") - String image, - @NotBlank @Schema(description = "전화번호", example = "032-123-4567") - String number, - @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") - String email, - @Schema(description = "연구실 홈페이지", example = "https://~") - String site, - @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") - String field, - @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") - String subject, - @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") - String labId) { - - @Builder - public ProfessorThumbnailResDTO { - } - - public static ProfessorThumbnailResDTO of(Professor professor) { - return ProfessorThumbnailResDTO.builder() - .department(professor.getDepartment()) - .name(professor.getName()) - .image(professor.getImage()) - .number(professor.getNumber()) - .email(professor.getEmail()) - .site(professor.getSite()) - .field(professor.getField()) - .subject(professor.getSubject()) - .labId(professor.getLab().getId()) - .build(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java deleted file mode 100644 index 98c09b4c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/entity/Professor.java +++ /dev/null @@ -1,48 +0,0 @@ -package inu.codin.codin.domain.info.professor.entity; - -import inu.codin.codin.domain.info.lab.entity.Lab; -import inu.codin.codin.common.Department; -import jakarta.validation.constraints.NotBlank; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; -import org.springframework.data.mongodb.core.mapping.Document; - -@Document(collection = "professor") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class Professor { - @Id @NotBlank - private String id; - - @NotBlank - private Department department; - - @NotBlank - private String name; - - @NotBlank - private String image; - - @NotBlank - private String number; - - @NotBlank - private String email; - - private String site; - - @NotBlank - private String field; - - private String subject; - - @DBRef - private Lab lab; - - public void updateLab(Lab lab){ - this.lab = lab; - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java deleted file mode 100644 index 3459b649..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/repository/ProfessorRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package inu.codin.codin.domain.info.professor.repository; - -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.common.Department; -import org.springframework.data.mongodb.repository.MongoRepository; - -import java.util.List; - -public interface ProfessorRepository extends MongoRepository { - - List findAllByDepartment(Department department); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java deleted file mode 100644 index 564f718d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/professor/service/ProfessorService.java +++ /dev/null @@ -1,30 +0,0 @@ -package inu.codin.codin.domain.info.professor.service; - -import inu.codin.codin.domain.info.professor.dto.ProfessorListResDTO; -import inu.codin.codin.domain.info.professor.dto.ProfessorThumbnailResDTO; -import inu.codin.codin.domain.info.professor.entity.Professor; -import inu.codin.codin.domain.info.professor.exception.ProfessorNotFoundException; -import inu.codin.codin.domain.info.professor.repository.ProfessorRepository; -import inu.codin.codin.common.Department; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class ProfessorService { - - private final ProfessorRepository professorRepository; - - public List getProfessorByDepartment(Department department){ - List professors = professorRepository.findAllByDepartment(department); - return professors.stream().map(ProfessorListResDTO::of).toList(); - } - - public ProfessorThumbnailResDTO getProfessorThumbnail(String id) { - Professor professor = professorRepository.findById(id) - .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); - return ProfessorThumbnailResDTO.of(professor); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java new file mode 100644 index 00000000..71c9f245 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.info.repository; + +import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.entity.Info; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import java.util.List; +import java.util.Optional; + +public interface InfoRepository extends MongoRepository { + + @Query("{ 'infoType': 'LAB', 'deletedAt': null }") + List findAllLabs(); + + @Query("{ 'infoType': 'LAB', '_id': ?0, 'deletedAt': null }") + Optional findLabById(String id); + + @Query("{ 'infoType': 'OFFICE', 'deletedAt': null }") + List findAllOffices(); + + @Query("{ 'infoType': 'OFFICE', 'department': ?0, 'deletedAt': null }") + Office findOfficeByDepartment(Department department); + + @Query("{ 'infoType': 'PROFESSOR', 'department': ?0, 'deletedAt': null }") + List findAllProfessorsByDepartment(Department department); + + @Query("{ 'infoType': 'PROFESSOR', '_id': ?0, 'deletedAt': null }") + Optional findProfessorById(String id); + + @Query("{ 'infoType': 'PROFESSOR', 'email': ?0, 'deletedAt': null }") + Optional findProfessorByEmail(String email); + + +} + + + From a2e26d0b6eea1bc1d78013df62d13e1f0d33eb15 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 20 Nov 2024 14:11:49 +0900 Subject: [PATCH 0100/1002] =?UTF-8?q?Refactor=20:=20Swagger=20Tag=20descri?= =?UTF-8?q?ption=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/controller/AuthController.java | 2 +- .../codin/domain/email/controller/EmailController.java | 2 +- .../domain/info/domain/lab/controller/LabController.java | 3 +-- .../info/domain/office/controller/OfficeController.java | 2 +- .../domain/professor/controller/ProfessorController.java | 7 +++---- .../codin/codin/domain/user/controller/UserController.java | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 64d2762a..44ce1de3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -20,7 +20,7 @@ @RestController @RequestMapping(value = "/auth" , produces = "plain/text; charset=utf-8") -@Tag(name = "User Auth API") +@Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class AuthController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 9d4d204a..3f5cfbc1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -16,7 +16,7 @@ @RestController @RequestMapping(value = "/email", produces = "plain/text; charset=utf-8") -@Tag(name = "User Auth API") +@Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class EmailController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 30881068..387dbe10 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -7,7 +7,6 @@ import inu.codin.codin.domain.info.domain.lab.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -18,7 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/lab") -@Tag(name = "Info API") +@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") public class LabController { private final LabService labService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 2962bf81..c0cdd401 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -17,7 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/office") -@Tag(name = "Info API") +@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") public class OfficeController { private final OfficeService officeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index be7e8e63..46086ae6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -1,15 +1,14 @@ package inu.codin.codin.domain.info.domain.professor.controller; +import inu.codin.codin.common.Department; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; -import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -19,7 +18,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/professor") -@Tag(name = "Info API") +@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") public class ProfessorController { private final ProfessorService professorService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 7793f37c..0dd9eef6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -12,7 +12,7 @@ @RestController @RequestMapping(value = "/users", produces = "plain/text; charset=utf-8") -@Tag(name = "User Auth API") +@Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class UserController { From 5d8923054c08d62839bf2be3475e91b97bf804a8 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 20 Nov 2024 14:11:49 +0900 Subject: [PATCH 0101/1002] =?UTF-8?q?[SC-64]=20Refactor=20:=20Swagger=20Ta?= =?UTF-8?q?g=20description=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/controller/AuthController.java | 2 +- .../codin/domain/email/controller/EmailController.java | 2 +- .../domain/info/domain/lab/controller/LabController.java | 3 +-- .../info/domain/office/controller/OfficeController.java | 2 +- .../domain/professor/controller/ProfessorController.java | 7 +++---- .../codin/codin/domain/user/controller/UserController.java | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 64d2762a..44ce1de3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -20,7 +20,7 @@ @RestController @RequestMapping(value = "/auth" , produces = "plain/text; charset=utf-8") -@Tag(name = "User Auth API") +@Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class AuthController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 9d4d204a..3f5cfbc1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -16,7 +16,7 @@ @RestController @RequestMapping(value = "/email", produces = "plain/text; charset=utf-8") -@Tag(name = "User Auth API") +@Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class EmailController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 30881068..387dbe10 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -7,7 +7,6 @@ import inu.codin.codin.domain.info.domain.lab.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -18,7 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/lab") -@Tag(name = "Info API") +@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") public class LabController { private final LabService labService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 2962bf81..c0cdd401 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -17,7 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/office") -@Tag(name = "Info API") +@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") public class OfficeController { private final OfficeService officeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index be7e8e63..46086ae6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -1,15 +1,14 @@ package inu.codin.codin.domain.info.domain.professor.controller; +import inu.codin.codin.common.Department; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; -import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -19,7 +18,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/professor") -@Tag(name = "Info API") +@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") public class ProfessorController { private final ProfessorService professorService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 7793f37c..0dd9eef6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -12,7 +12,7 @@ @RestController @RequestMapping(value = "/users", produces = "plain/text; charset=utf-8") -@Tag(name = "User Auth API") +@Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class UserController { From 180055ebc82c935e668a23078904ad38771a73eb Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 20 Nov 2024 17:02:12 +0900 Subject: [PATCH 0102/1002] =?UTF-8?q?[SC-64]=20Refactor=20:=20RedisStorage?= =?UTF-8?q?Service=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service => infra/redis}/RedisStorageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename codin-core/src/main/java/inu/codin/codin/{common/security/service => infra/redis}/RedisStorageService.java (97%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisStorageService.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java rename to codin-core/src/main/java/inu/codin/codin/infra/redis/RedisStorageService.java index 05c2bc7c..3f1677a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/RedisStorageService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisStorageService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.infra.redis; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; From 42e3574381651e979324fd2403a353f7c9c5ee64 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 20 Nov 2024 17:03:03 +0900 Subject: [PATCH 0103/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20NotificationEnti?= =?UTF-8?q?ty=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationEntity.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java new file mode 100644 index 00000000..b16971fe --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -0,0 +1,37 @@ +package inu.codin.codin.domain.notification.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + + +@Document(collection = "notification") +public class NotificationEntity extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + private String userId; + + private String type; + + private String message; + + private boolean isRead = false; + + private String priority; + + @Builder + public NotificationEntity(String userId, String type, String message, String priority) { + this.userId = userId; + this.type = type; + this.message = message; + this.priority = priority; + } + + public void read() { + this.isRead = true; + } +} From ba0b8cd69340b0bc6773915cec294cb50a9191b1 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 20 Nov 2024 17:03:17 +0900 Subject: [PATCH 0104/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20NotificationPref?= =?UTF-8?q?erence=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationPreference.java | 41 +++++++++++++++++++ .../codin/domain/user/entity/UserEntity.java | 4 ++ 2 files changed, 45 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPreference.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPreference.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPreference.java new file mode 100644 index 00000000..22ad43c7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPreference.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.notification.entity; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +// 알림 설정 +@NoArgsConstructor +@Getter +public class NotificationPreference { + + // 푸시 알림 허용 여부 + private boolean allowPush = true; + + // 이메일 알림 허용 여부 + private boolean allowEmail = true; + + // 알림 설정 + private Preference preference = new Preference(); + + @Builder + public NotificationPreference(boolean allowPush, boolean allowEmail, Preference preference) { + this.allowPush = allowPush; + this.allowEmail = allowEmail; + this.preference = preference; + } + + @Getter + @Setter + class Preference { + // 댓글 알림 + private boolean notifyOnComment = true; + // 좋아요 알림 + private boolean notifyOnLike = true; + // 답글 알림 + private boolean notifyOnReply = true; + // 베스트 게시글 선정 알림 + private boolean notifyOnBestPost = true; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 08310eb7..2c35924d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.notification.entity.NotificationPreference; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -33,6 +34,9 @@ public class UserEntity extends BaseTimeEntity { private UserStatus status; + // 알림 설정 + private NotificationPreference notificationPreference = new NotificationPreference(); + @Builder public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status) { this.email = email; From 11c6175987b0830d7c6ed013ca2f93e365899bb6 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 20 Nov 2024 17:04:56 +0900 Subject: [PATCH 0105/1002] =?UTF-8?q?[SC-64]=20Refactor=20:=20RedisStorage?= =?UTF-8?q?Service=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/jwt/JwtTokenProvider.java | 2 +- .../inu/codin/codin/common/security/service/JwtService.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index 5ee22f30..94d4269d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.security.jwt; -import inu.codin.codin.common.security.service.RedisStorageService; +import inu.codin.codin.infra.redis.RedisStorageService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 275c138d..fbd93336 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -5,6 +5,7 @@ import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.infra.redis.RedisStorageService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; From 147867ee3f234f2514fa16bcc8333a374d7c496e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 21 Nov 2024 00:12:19 +0900 Subject: [PATCH 0106/1002] =?UTF-8?q?[SC-77]=20refactor=20:=20@Valid=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/domain/lab/controller/LabController.java | 4 ++-- .../info/domain/office/controller/OfficeController.java | 7 ++++--- .../domain/professor/controller/ProfessorController.java | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 30881068..19e6c14e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -38,7 +38,7 @@ public ResponseEntity> getAllLab(){ @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 새로운 연구실 등록") @PostMapping(produces = "plain/text; charset=utf-8") - public ResponseEntity createLab(@RequestBody LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + public ResponseEntity createLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto){ labService.createLab(labCreateUpdateRequestDto); return ResponseUtils.successMsg("새로운 LAB 등록 완료"); } @@ -46,7 +46,7 @@ public ResponseEntity createLab(@RequestBody LabCreateUpdateRequestDto labCre @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 연구실 정보 수정") @PutMapping(value = "/{id}", produces = "plain/text; charset=utf-8") - public ResponseEntity updateLab(@RequestBody LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ + public ResponseEntity updateLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ labService.updateLab(labCreateUpdateRequestDto, id); return ResponseUtils.successMsg("LAB 정보 수정 완료"); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 2962bf81..2250ce25 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -8,6 +8,7 @@ import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -31,7 +32,7 @@ public ResponseEntity getOfficeByDepartment(@PathVaria @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 정보 수정") @PatchMapping(value = "/{department}" , produces = "plain/text; charset=utf-8") - public ResponseEntity updateOffice(@PathVariable("department") Department department, @RequestBody OfficeUpdateRequestDto officeUpdateRequestDto){ + public ResponseEntity updateOffice(@PathVariable("department") Department department, @RequestBody @Valid OfficeUpdateRequestDto officeUpdateRequestDto){ officeService.updateOffice(department, officeUpdateRequestDto); return ResponseUtils.successMsg("Office 정보 수정 완료"); } @@ -40,7 +41,7 @@ public ResponseEntity updateOffice(@PathVariable("department") Department dep @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 추가") @PatchMapping(value = "/{department}/member", produces = "plain/text; charset=utf-8") public ResponseEntity createOfficeMember(@PathVariable("department") Department department, - @RequestBody OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + @RequestBody @Valid OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ officeService.createOfficeMember(department, officeMemberCreateUpdateRequestDto); return ResponseUtils.successMsg("Office Member 추가 완료"); } @@ -49,7 +50,7 @@ public ResponseEntity createOfficeMember(@PathVariable("department") Departme @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 수정") @PatchMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") public ResponseEntity updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, - @RequestBody OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + @RequestBody @Valid OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ officeService.updateOfficeMember(department, num, officeMemberCreateUpdateRequestDto); return ResponseUtils.successMsg("Office Member 정보 수정 완료"); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index be7e8e63..89fe562d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -8,6 +8,7 @@ import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -39,7 +40,7 @@ public ResponseEntity getProfessorThumbnail(@Path @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 새로운 교수 정보 생성") @PostMapping(produces = "plain/text; charset=utf-8") - public ResponseEntity createProfessor(@RequestBody ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + public ResponseEntity createProfessor(@RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ professorService.createProfessor(professorCreateUpdateRequestDto); return ResponseUtils.successMsg("새로운 교수 정보 생성 완료"); } @@ -47,7 +48,7 @@ public ResponseEntity createProfessor(@RequestBody ProfessorCreateUpdateReque @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 교수 정보 수정") @PatchMapping(value = "/{id}", produces = "plain/text; charset=utf-8") - public ResponseEntity updateProfessor(@PathVariable("id") String id, @RequestBody ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + public ResponseEntity updateProfessor(@PathVariable("id") String id, @RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ professorService.updateProfessor(id, professorCreateUpdateRequestDto); return ResponseUtils.successMsg("교수 정보 수정 완료"); } From cd7eb5fcca3a87168fb3c82193829c21a0611ae6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 22 Nov 2024 02:09:04 +0900 Subject: [PATCH 0107/1002] =?UTF-8?q?[SC-65]=20=20Feat:=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EB=AC=BC=20soft=20Delete=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 28 +++++++---- .../codin/domain/post/entity/PostEntity.java | 7 +++ .../post/repository/PostRepository.java | 12 +++++ .../domain/post/service/PostService.java | 46 ++++++++++++++----- .../user/repository/UserRepository.java | 1 - .../inu/codin/codin/infra/s3/S3Service.java | 23 +++++++++- 6 files changed, 93 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 98417f11..5b1730a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -66,13 +66,21 @@ public ResponseEntity updatePostStatus( postService.updatePostStatus(postId, requestDTO); return ResponseEntity.status(HttpStatus.OK).body("게시물 상태 수정 성공"); } + @Operation( + summary = "삭제되지 않은 모든 게시물 조회" + ) + @GetMapping("") + public ResponseEntity> getAllPosts() { + List posts = postService.getAllPosts(); + return ResponseEntity.ok(posts); + } @Operation( summary = "해당 사용자 게시물 전체 조회" ) @GetMapping("/user/{userId}") public ResponseEntity> getAllPosts(@PathVariable String userId) { - List posts = postService.getAllPosts(userId); + List posts = postService.getAllUserPosts(userId); return ResponseEntity.status(HttpStatus.OK).body(posts); } @@ -85,14 +93,6 @@ public ResponseEntity getPost(@PathVariable String postId return ResponseEntity.status(HttpStatus.OK).body(post); } - @Operation( - summary = "해당 게시물 삭제" - ) - @DeleteMapping("/postId") - public ResponseEntity deletePost(@PathVariable String postId) { - postService.deletePost(postId); - return ResponseEntity.status(HttpStatus.OK).body("게시물 삭제"); - } @Operation( summary = "해당 이미지 삭제" @@ -106,6 +106,16 @@ public ResponseEntity deletePostImage( } + @Operation( + summary = "해당 게시물 Soft 삭제" + ) + @DeleteMapping("/{postId}") + public ResponseEntity softDeletePost(@PathVariable String postId) { + postService.softDeletePost(postId); + return ResponseEntity.status(HttpStatus.OK).body("게시물 안전삭제"); + } + + //익명 수정 불가? diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index b8a8db63..1df27b25 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -28,6 +28,8 @@ public class PostEntity extends BaseTimeEntity { private boolean isAnonymous; + private boolean isDeleted = false; + private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) @@ -61,4 +63,9 @@ public void removePostImage(String imageUrl) { public void removeAllPostImages() { this.postImageUrls.clear(); } + + public void softDeletePost() { + this.isDeleted = true; + this.delete(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 4da65b5a..a312dfc2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; @@ -12,4 +13,15 @@ public interface PostRepository extends MongoRepository { Optional findByTitle(String title); List findByUserId(String userId); + + @Query("{'userId': ?0, 'isDeleted': false}") + List findByUserIdNotDeleted(String userId); + + @Query("{'isDeleted': false}") + List findALlNotDeleted(); + + @Query("{'postId': ?0, 'isDeleted': false}") + PostEntity findByPostIdNotDeleted(String postId); + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index d47252e0..0aaefbc6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -15,15 +16,11 @@ import java.util.stream.Collectors; @Service +@RequiredArgsConstructor public class PostService { private final PostRepository postRepository; private final S3Service s3Service; - public PostService(PostRepository postRepository, S3Service s3Service) { - this.postRepository = postRepository; - this.s3Service = s3Service; - } - //이미지 업로드 메소드 private List handleImageUpload(List postImages) { if (postImages != null && !postImages.isEmpty()) { @@ -76,8 +73,24 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT post.updatePostStatus(requestDTO.getPostStatus()); } - public List getAllPosts(String userId) { - List posts = postRepository.findByUserId(userId); + public List getAllPosts() { + List posts = postRepository.findALlNotDeleted(); + return posts.stream() + .map(post -> new PostDetailResponseDTO( + post.getUserId(), + post.getPostId(), + post.getContent(), + post.getTitle(), + post.getPostCategory(), + post.getPostStatus(), + post.getPostImageUrls(), + post.isAnonymous() + )) + .collect(Collectors.toList()); + } + + public List getAllUserPosts(String userId) { + List posts = postRepository.findByUserIdNotDeleted(userId); return posts.stream() .map(post -> new PostDetailResponseDTO( post.getUserId(), @@ -93,8 +106,7 @@ public List getAllPosts(String userId) { } public PostDetailResponseDTO getPost(String postId) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + PostEntity post = postRepository.findByPostIdNotDeleted(postId); return new PostDetailResponseDTO( post.getUserId(), @@ -109,11 +121,21 @@ public PostDetailResponseDTO getPost(String postId) { } - public void deletePost(String postId) { - postRepository.deleteById(postId); + public void softDeletePost(String postId) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(()-> new IllegalArgumentException("게시물을 찾을 수 없음.")); + + if (post.isDeleted()) { + throw new IllegalArgumentException("이미 삭제된 게시물"); + } + + post.softDeletePost(); + postRepository.save(post); + } public void deletePostImage(String postId, String imageUrl) { + PostEntity post = postRepository.findById(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); @@ -124,9 +146,9 @@ public void deletePostImage(String postId, String imageUrl) { try { // S3에서 이미지 삭제 s3Service.deleteFile(imageUrl); - // 게시물의 이미지 리스트에서 제거 post.removePostImage(imageUrl); + postRepository.save(post); } catch (Exception e) { throw new IllegalStateException("이미지 삭제 중 오류 발생: " + imageUrl, e); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 6c477daf..f10a8e24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -10,7 +10,6 @@ public interface UserRepository extends MongoRepository { Optional findByEmail(String email); - Optional findByStudentId(String studentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java index 9066e431..9821a578 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java @@ -21,19 +21,38 @@ public S3Service(AmazonS3Client amazonS3Client) { @Value("codin-s3-bucket") public String bucket; + private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB + private static final int MAX_FILE_COUNT = 10; // 최대 파일 개수 + + //모든 이미지 업로드 public List uploadFiles(List multipartFiles) { + validateFileCount(multipartFiles); List uploadUrls = new ArrayList<>(); for (MultipartFile multipartFile : multipartFiles) { + validateImageFileSize(multipartFile); + validateImageFileExtension(multipartFile); uploadUrls.add(uploadFile(multipartFile)); } return uploadUrls; } + private void validateImageFileSize(MultipartFile multipartFile) { + if (multipartFile.getSize() > MAX_FILE_SIZE) { + throw new IllegalArgumentException("파일 크기가 5MB를 초과할 수 없습니다."); + } + } + + private void validateFileCount(List multipartFiles) { + if (multipartFiles.size() > MAX_FILE_COUNT) { + throw new IllegalArgumentException("이미지 파일 개수는 최대 10개까지 업로드 가능합니다."); + } + } + //각 이미지 S3에 업로드 public String uploadFile(MultipartFile multipartFile) { - validateImageFile(multipartFile); + validateImageFileExtension(multipartFile); String fileName = createFileName(multipartFile.getOriginalFilename()); try{ @@ -52,7 +71,7 @@ public String createFileName(String originalFilename) { } //파일 유효성 검사( 이미지 관련 확장자만 업로드 가능 설정) - public void validateImageFile(MultipartFile multipartFile) { + public void validateImageFileExtension(MultipartFile multipartFile) { List validExtensions = List.of("jpg", "jpeg", "png", "gif"); String extension = getExtension(multipartFile.getOriginalFilename()); if (extension == null || !validExtensions.contains(extension.toLowerCase())) { From e252218a556a96e8ef42316599c5da8c989eb707 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 24 Nov 2024 18:22:07 +0900 Subject: [PATCH 0108/1002] =?UTF-8?q?[SC-68]=20=20Feat:=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EB=B0=8F=20=EB=8C=80=EB=8C=93=EA=B8=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(=20=EB=8C=93=EA=B8=80,?= =?UTF-8?q?=EB=8C=80=EB=8C=93=EA=B8=80=20=EC=B6=94=EA=B0=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=82=AD=EC=A0=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/CommentController.java | 81 ++++++++++++++++ .../dto/request/CommentCreateRequsetDTO.java | 16 ++++ .../dto/request/ReplyCreateRequestDTO.java | 16 ++++ .../dto/response/CommentsResponseDTO.java | 32 +++++++ .../response/PostWithCommentsResponseDTO.java | 52 ++++++++++ .../domain/post/entity/CommentEntity.java | 50 ++++++++++ .../codin/domain/post/entity/PostEntity.java | 40 +++++++- .../post/repository/PostRepository.java | 4 + .../domain/post/service/CommentService.java | 96 +++++++++++++++++++ .../domain/post/service/PostService.java | 44 ++++++++- 10 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java new file mode 100644 index 00000000..51eed72f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java @@ -0,0 +1,81 @@ +package inu.codin.codin.domain.post.controller; + +import inu.codin.codin.domain.post.dto.request.CommentCreateRequsetDTO; +import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithCommentsResponseDTO; +import inu.codin.codin.domain.post.service.CommentService; +import inu.codin.codin.domain.post.service.PostService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/comments") +public class CommentController { + private final CommentService commentService; + private final PostService postService; + + public CommentController(CommentService commentService, PostService postService) { + this.commentService = commentService; + this.postService = postService; + } + + + @Operation( + summary = "댓글 추가" + ) + @PostMapping("/{postId}") + public ResponseEntity addComment(@PathVariable String postId, @RequestBody @Valid CommentCreateRequsetDTO requestDTO) { + commentService.addComment(postId, requestDTO); + return ResponseEntity.status(HttpStatus.CREATED).body("댓글이 추가되었습니다."); + } + + @Operation( + summary = "대댓글 추가" + ) + @PostMapping("/{postId}/{parentCommentId}") + public ResponseEntity addReply(@PathVariable String postId, @PathVariable String parentCommentId, @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { + commentService.addReply(postId, parentCommentId, requestDTO); + return ResponseEntity.status(HttpStatus.CREATED).body("대댓글이 추가되었습니다."); + } + + @Operation( + summary = "해당 유저가 남긴 댓글 및 대댓글 조회" + ) + @GetMapping("/{userId}") + public ResponseEntity> getPostWithComments(@PathVariable String userId) { + List response = commentService.getCommentsByUser(userId); + return ResponseEntity.ok(response); + } + @Operation( + summary = "해당 사용자 게시물 전체 조회" + ) + @GetMapping("/user/{userId}") + public ResponseEntity> getAllPosts(@PathVariable String userId) { + List posts = postService.getAllUserPostsAndComments(userId); + return ResponseEntity.status(HttpStatus.OK).body(posts); + } + + @Operation(summary = "댓글 삭제") + @DeleteMapping("/{postId}/{commentId}") + public ResponseEntity deleteComment(@PathVariable String postId, @PathVariable String commentId) { + commentService.deleteComment(postId, commentId); + return ResponseEntity.status(HttpStatus.OK).body("댓글이 삭제되었습니다."); + } + + @Operation(summary = "대댓글 삭제") + @DeleteMapping("/{postId}/{parentCommentId}/{replyId}") + public ResponseEntity deleteReply(@PathVariable String postId, @PathVariable String parentCommentId, @PathVariable String replyId) { + commentService.deleteReply(postId, parentCommentId, replyId); + return ResponseEntity.status(HttpStatus.OK).body("대댓글이 삭제되었습니다."); + } + + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java new file mode 100644 index 00000000..721ecd13 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.post.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class CommentCreateRequsetDTO { + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "댓글 내용", example = "content") + @NotBlank + private String content; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java new file mode 100644 index 00000000..23f406d8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.post.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class ReplyCreateRequestDTO { + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "댓글 내용", example = "content") + @NotBlank + private String content; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java new file mode 100644 index 00000000..e09b2706 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.post.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.List; + +@Data +public class CommentsResponseDTO { + @Schema(description = "댓글 ID", example = "111111") + @NotBlank + private String commentId; + + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "댓글 내용", example = "111111") + @NotBlank + private String content; + + @Schema(description = "대댓글", example = "111111") + private List replies; + + public CommentsResponseDTO(String commentId, String userId, String content, List replies) { + this.commentId = commentId; + this.userId = userId; + this.content = content; + this.replies = replies; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java new file mode 100644 index 00000000..211adff8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java @@ -0,0 +1,52 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +public class PostWithCommentsResponseDTO { + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "게시물 ID", example = "111111") + @NotBlank + private String postId; + + @Schema(description = "게시물 종류", example = "구해요") + @NotBlank + private PostCategory postCategory; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private String content; + + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") + private List postImageUrl; + + @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @NotNull + private boolean isAnonymous; + + @Schema(description = "댓글 및 대댓글", example = "0") + private List comments; + + public PostWithCommentsResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, List comments) { + this.userId = userId; + this.postId = postId; + this.content = content; + this.title = title; + this.postCategory = postCategory; + this.postImageUrl = postImageUrls; + this.isAnonymous = isAnonymous; + this.comments = comments; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java new file mode 100644 index 00000000..fc0fc95d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java @@ -0,0 +1,50 @@ +package inu.codin.codin.domain.post.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class CommentEntity extends BaseTimeEntity { + @Id //몽고디비에선 독립된 컬렉션이 아닐 경우 자동으로 ID 값을 생성하지 않음 + private String commentId; + private String userId; + private String content; + private boolean isDeleted = false; // Soft delete 상태 + private List replies = new ArrayList<>(); + @Builder + public CommentEntity(String commentId, String userId, String content, List replies) { + this.commentId = commentId; + this.userId = userId; + this.content = content; + this.replies = replies != null ? replies : new ArrayList<>(); // null 체크 후 초기화 + + } + + // Soft Delete + public void softDelete() { + this.isDeleted = true; + this.delete(); + } + + public void softDeleteReply(String replyId) { + this.replies.stream() + .filter(reply -> reply.getCommentId().equals(replyId)) + .findFirst() + .ifPresent(CommentEntity::softDelete); + } + + // 대댓글 추가 + public void addReply(CommentEntity reply) { + if (this.replies == null) { + this.replies = new ArrayList<>(); // null 방지 + } + this.replies.add(reply); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 1df27b25..1c194f14 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -8,6 +8,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.ArrayList; import java.util.List; @Document(collection = "post") @@ -32,9 +33,12 @@ public class PostEntity extends BaseTimeEntity { private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) + private List comments = new ArrayList<>(); + + @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus) { + public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus, List comments) { this.postId = postId; this.userId = userId; this.postCategory = postCategory; @@ -43,6 +47,7 @@ public PostEntity(String postId, String userId, PostCategory postCategory, Strin this.isAnonymous = isAnonymous; this.postImageUrls = postImageUrls; this.postStatus = postStatus; + this.comments = comments; } public void updatePostContent(String content, List postImageUrls) { @@ -68,4 +73,37 @@ public void softDeletePost() { this.isDeleted = true; this.delete(); } + + + // 댓글 추가 + public void addComment(CommentEntity comment) { + if (this.comments == null) { + this.comments = new ArrayList<>(); // null 방지 + } + this.comments.add(comment); + } + // 댓글 삭제 (Soft Delete) + public void softDeleteComment(String commentId) { + this.comments.stream() + .filter(comment -> comment.getCommentId().equals(commentId)) + .findFirst() + .ifPresent(CommentEntity::softDelete); + } + + public void softDeleteReply(String parentCommentId, String replyId) { + this.comments.stream() + .filter(comment -> comment.getCommentId().equals(parentCommentId)) + .findFirst() + .ifPresent(parentComment -> parentComment.softDeleteReply(replyId)); + } + + // 대댓글 추가 + public void addReply(String parentCommentId, CommentEntity reply) { + this.comments.stream() + .filter(comment -> comment.getCommentId().equals(parentCommentId)) + .findFirst() + .ifPresent(parentComment -> parentComment.addReply(reply)); + } + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index a312dfc2..511952f4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -23,5 +23,9 @@ public interface PostRepository extends MongoRepository { @Query("{'postId': ?0, 'isDeleted': false}") PostEntity findByPostIdNotDeleted(String postId); + @Query("{'_id': ?0, 'comments.isDeleted': false}") + Optional findPostWithActiveComments(String postId); + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java new file mode 100644 index 00000000..56ee7ad1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java @@ -0,0 +1,96 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.domain.post.dto.request.CommentCreateRequsetDTO; +import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.domain.post.entity.CommentEntity; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentService { + private final PostRepository postRepository; + + public void addComment(String postId, CommentCreateRequsetDTO requestDTO) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + CommentEntity comment = CommentEntity.builder() + .commentId(UUID.randomUUID().toString()) // UUID로 고유 ID 생성 + .userId(requestDTO.getUserId()) + .content(requestDTO.getContent()) + .build(); + post.addComment(comment); + postRepository.save(post); + log.info("Comment added successfully to postId: {}. Request data: {}", postId, requestDTO); + } + + public void addReply(String postId, String parentCommentId, ReplyCreateRequestDTO requestDTO) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + CommentEntity reply = CommentEntity.builder() + .commentId(UUID.randomUUID().toString()) // UUID로 고유 ID 생성 + .userId(requestDTO.getUserId()) + .content(requestDTO.getContent()) + .build(); + post.addReply(parentCommentId, reply); + postRepository.save(post); + } + + public List getCommentsByUser(String userId) { + List posts = postRepository.findAll(); // 모든 게시물 조회 + List userComments = new ArrayList<>(); + + for (PostEntity post : posts) { + // 게시물의 댓글 중 해당 사용자가 작성한 댓글 필터링 + post.getComments().stream() + .filter(comment -> comment.getUserId().equals(userId) && !comment.isDeleted()) + .map(this::convertToDTO) // CommentEntity -> CommentsResponseDTO + .forEach(userComments::add); + + // 게시물의 대댓글 중 해당 사용자가 작성한 대댓글 필터링 + post.getComments().forEach(comment -> comment.getReplies().stream() + .filter(reply -> reply.getUserId().equals(userId) && !reply.isDeleted()) + .map(this::convertToDTO) // CommentEntity -> CommentsResponseDTO + .forEach(userComments::add)); + } + + return userComments; + } + + private CommentsResponseDTO convertToDTO(CommentEntity comment) { + return new CommentsResponseDTO( + comment.getCommentId(), + comment.getUserId(), + comment.getContent(), + comment.getReplies().stream() + .filter(reply -> !reply.isDeleted()) // 삭제되지 않은 대댓글만 변환 + .map(this::convertToDTO) + .collect(Collectors.toList()) // 대댓글도 재귀적으로 DTO로 변환 + ); + } + + + public void deleteComment(String postId, String commentId) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + post.softDeleteComment(commentId); + postRepository.save(post); + } + + public void deleteReply(String postId, String parentCommentId, String replyId) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + post.softDeleteReply(parentCommentId, replyId); + postRepository.save(post); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 0aaefbc6..c39afec0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -2,7 +2,10 @@ import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithCommentsResponseDTO; +import inu.codin.codin.domain.post.entity.CommentEntity; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.entity.PostEntity; @@ -12,6 +15,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -49,7 +53,7 @@ public void createPost(PostCreateReqDTO postCreateReqDTO, List po //Default Status = Active .postStatus(PostStatus.ACTIVE) - + .comments(new ArrayList<>()) .build(); postRepository.save(postEntity); } @@ -154,6 +158,9 @@ public void deletePostImage(String postId, String imageUrl) { } } + + + //유효성체크 private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { // if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { @@ -161,5 +168,40 @@ private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { // } } + public List getAllUserPostsAndComments(String userId) { + // 삭제되지 않은 사용자 게시물 조회 + List posts = postRepository.findByUserIdNotDeleted(userId); + + // PostEntity를 PostWithCommentsResponseDTO로 변환 + return posts.stream() + .map(post -> new PostWithCommentsResponseDTO( + post.getUserId(), + post.getPostId(), + post.getContent(), + post.getTitle(), + post.getPostCategory(), + post.getPostStatus(), + post.getPostImageUrls(), + post.isAnonymous(), + convertCommentsToDTO(post.getComments()) // 댓글과 대댓글을 DTO로 변환 + )) + .collect(Collectors.toList()); + } + + private List convertCommentsToDTO(List comments) { + if (comments == null || comments.isEmpty()) { + return List.of(); // 댓글이 없는 경우 빈 리스트 반환 + } + + return comments.stream() + .filter(comment -> !comment.isDeleted()) // 삭제되지 않은 댓글만 포함 + .map(comment -> new CommentsResponseDTO( + comment.getCommentId(), + comment.getUserId(), + comment.getContent(), + convertCommentsToDTO(comment.getReplies()) // 재귀적으로 대댓글 변환 + )) + .collect(Collectors.toList()); + } } From e93592f855cc6dad22594d4a8b2390de0f0743f0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 25 Nov 2024 02:48:01 +0900 Subject: [PATCH 0109/1002] =?UTF-8?q?[SC-69]=20=20Feat:=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EB=B0=8F=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20Redis->=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=9E=84=EC=8B=9C=EC=A0=80=EC=9E=A5=20DB?= =?UTF-8?q?=20->=20Sync=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EC=A3=BC=EA=B8=B0?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=A0=80=EC=9E=A5=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20:=20Redis=20Data=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/LikeController.java | 41 +++++++++ .../codin/codin/domain/like/LikeEntity.java | 22 +++++ .../codin/domain/like/LikeRepository.java | 9 ++ .../codin/codin/domain/like/LikeService.java | 49 ++++++++++ .../post/controller/PostController.java | 10 ++ .../PostWithLikeAndScrapResponseDTO.java | 59 ++++++++++++ .../codin/domain/post/entity/PostEntity.java | 17 +++- .../domain/post/service/PostService.java | 46 ++++++++++ .../codin/domain/scrap/ScrapController.java | 31 +++++++ .../codin/codin/domain/scrap/ScrapEntity.java | 22 +++++ .../codin/domain/scrap/ScrapRepository.java | 9 ++ .../codin/domain/scrap/ScrapService.java | 49 ++++++++++ .../codin/infra/redis/SyncScheduler.java | 91 +++++++++++++++++++ 13 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java new file mode 100644 index 00000000..cbb4e6fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.like; + +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/posts/{postId}/likes") +@RequiredArgsConstructor +public class LikeController { + + private final LikeService likeService; + + @Operation( + summary = "좋아요 추가" + ) + @PostMapping("/{userId}") + public ResponseEntity likePost(@PathVariable String postId, @PathVariable String userId) { + likeService.addLike(postId, userId); + return ResponseEntity.ok("Liked successfully"); + } + + @Operation( + summary = "좋아요 삭제" + ) + @DeleteMapping("/{userId}") + public ResponseEntity unlikePost(@PathVariable String postId, @PathVariable String userId) { + likeService.removeLike(postId, userId); + return ResponseEntity.ok("Unliked successfully"); + } + + @Operation( + summary = "해당 게시물 좋아요 조회" + ) + @GetMapping + public ResponseEntity getLikeCount(@PathVariable String postId) { + long likeCount = likeService.getLikeCount(postId); + return ResponseEntity.ok(likeCount); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java new file mode 100644 index 00000000..dc1a834c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.like; + +import inu.codin.codin.common.BaseTimeEntity; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "likes") +@Getter +public class LikeEntity extends BaseTimeEntity { + @Id + private String id; + private String postId; + private String userId; + + @Builder + public LikeEntity(String postId, String userId) { + this.postId = postId; + this.userId = userId; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java new file mode 100644 index 00000000..62bb77fd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.like; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LikeRepository extends MongoRepository { + boolean findByPostIdAndUserId(String postId, String userId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java new file mode 100644 index 00000000..11f43560 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java @@ -0,0 +1,49 @@ +package inu.codin.codin.domain.like; + +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LikeService { + + private final RedisTemplate redisTemplate; + private final PostRepository postRepository; // MongoDB의 게시글 저장소 + + public void addLike(String postId, String userId) { + String redisKey = "post:likes:" + postId; + + if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId))) { + throw new IllegalStateException("이미 해당 게시물에 좋아요를 눌렀습니다."); + } + + // Redis에 좋아요 추가 + redisTemplate.opsForSet().add(redisKey, userId); + + } + + public void removeLike(String postId, String userId) { + String redisKey = "post:likes:" + postId; + + if (!Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId))) { + throw new IllegalStateException("해당 게시물에 좋아요를 누르지 않았습니다."); + } + + // Redis에서 좋아요 제거 + redisTemplate.opsForSet().remove(redisKey, userId); + + } + + public long getLikeCount(String postId) { + String redisKey = "post:likes:" + postId; + return redisTemplate.opsForSet().size(redisKey); + } + + public boolean isPostLikedByUser(String postId, String userId) { + String redisKey = "post:likes:" + postId; + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId)); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 5b1730a3..d9f293d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithLikeAndScrapResponseDTO; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -93,6 +94,15 @@ public ResponseEntity getPost(@PathVariable String postId return ResponseEntity.status(HttpStatus.OK).body(post); } + @Operation( + summary = "해당 게시물 좋아요,스크랩 포함 조회" + ) + @GetMapping("/{postId}/details") + public ResponseEntity getPostDetail(@PathVariable String postId) { + PostWithLikeAndScrapResponseDTO post = postService.getPostWithLikeAndScrap(postId); + return ResponseEntity.status(HttpStatus.OK).body(post); + } + @Operation( summary = "해당 이미지 삭제" diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java new file mode 100644 index 00000000..a1c23e44 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java @@ -0,0 +1,59 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class PostWithLikeAndScrapResponseDTO { + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "게시물 ID", example = "111111") + @NotBlank + private String postId; + + @Schema(description = "게시물 종류", example = "구해요") + @NotBlank + private PostCategory postCategory; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private String content; + + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") + private List postImageUrl; + + @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @NotNull + private boolean isAnonymous; + + @Schema(description = "댓글 및 대댓글", example = "0") + private List comments; + + private int likeCount; + private int scrapCount; + + public PostWithLikeAndScrapResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, List comments, int likeCount, int scrapCount) { + this.userId = userId; + this.postId = postId; + this.content = content; + this.title = title; + this.postCategory = postCategory; + this.postImageUrl = postImageUrls; + this.isAnonymous = isAnonymous; + this.comments = comments; + this.likeCount = likeCount; + this.scrapCount = scrapCount; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 1c194f14..d69a7403 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -35,10 +35,12 @@ public class PostEntity extends BaseTimeEntity { private List comments = new ArrayList<>(); + private int likeCount = 0; // 좋아요 카운트 + private int scrapCount = 0; // 스크랩 카운트 @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus, List comments) { + public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus, List comments, Integer likeCount, Integer scrapCount) { this.postId = postId; this.userId = userId; this.postCategory = postCategory; @@ -47,7 +49,9 @@ public PostEntity(String postId, String userId, PostCategory postCategory, Strin this.isAnonymous = isAnonymous; this.postImageUrls = postImageUrls; this.postStatus = postStatus; - this.comments = comments; + this.comments = comments != null ? comments : new ArrayList<>(); + this.likeCount = likeCount != null ? likeCount : 0; // 기본값 설정 + this.scrapCount = scrapCount != null ? scrapCount : 0; // 기본값 설정 } public void updatePostContent(String content, List postImageUrls) { @@ -105,5 +109,14 @@ public void addReply(String parentCommentId, CommentEntity reply) { .ifPresent(parentComment -> parentComment.addReply(reply)); } + //좋아요 업데이트 + public void updateLikeCount(int likeCount) { + this.likeCount=likeCount; + } + //스크랩 업데이트 + public void updateScrapCount(int scrapCount) { + this.scrapCount=likeCount; + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index c39afec0..9cac898b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostWithCommentsResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithLikeAndScrapResponseDTO; import inu.codin.codin.domain.post.entity.CommentEntity; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; @@ -12,6 +13,7 @@ import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -24,6 +26,7 @@ public class PostService { private final PostRepository postRepository; private final S3Service s3Service; + private final RedisTemplate redisTemplate; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -188,6 +191,49 @@ public List getAllUserPostsAndComments(String userI .collect(Collectors.toList()); } + public PostWithLikeAndScrapResponseDTO getPostWithLikeAndScrap(String postId) { + String likeKey = "post:likes:" + postId; + String scrapKey = "post:scraps:" + postId; + + // Redis에서 좋아요 및 스크랩 카운트 조회 + Long likeCount = redisTemplate.opsForSet().size(likeKey); + Long scrapCount = redisTemplate.opsForSet().size(scrapKey); + + // Redis에 데이터가 없을 경우 MongoDB에서 조회 후 Redis 갱신 + if (likeCount == null || scrapCount == null) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + if (likeCount == null) { + likeCount = (long) post.getLikeCount(); + redisTemplate.opsForSet().add(likeKey, String.valueOf(likeCount)); + } + + if (scrapCount == null) { + scrapCount = (long) post.getScrapCount(); + redisTemplate.opsForSet().add(scrapKey, String.valueOf(scrapCount)); + } + } + + // MongoDB에서 게시물 조회 + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + // PostWithLikeAndScrapResponseDTO 반환 + return new PostWithLikeAndScrapResponseDTO( + post.getUserId(), + post.getPostId(), + post.getContent(), + post.getTitle(), + post.getPostCategory(), + post.getPostStatus(), + post.getPostImageUrls(), + post.isAnonymous(), + convertCommentsToDTO(post.getComments()), // 댓글 변환 메소드 호출 + likeCount.intValue(), + scrapCount.intValue() + ); + } private List convertCommentsToDTO(List comments) { if (comments == null || comments.isEmpty()) { return List.of(); // 댓글이 없는 경우 빈 리스트 반환 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java new file mode 100644 index 00000000..c9c63dea --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.scrap; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/posts/{postId}/scraps") +@RequiredArgsConstructor +public class ScrapController { + + private final ScrapService scrapService; + + @PostMapping("/{userId}") + public ResponseEntity scrapPost(@PathVariable String postId, @PathVariable String userId) { + scrapService.addScrap(postId, userId); + return ResponseEntity.ok("Scrapped successfully"); + } + + @DeleteMapping("/{userId}") + public ResponseEntity unscrapPost(@PathVariable String postId, @PathVariable String userId) { + scrapService.removeScrap(postId, userId); + return ResponseEntity.ok("Unscrapped successfully"); + } + + @GetMapping + public ResponseEntity getScrapCount(@PathVariable String postId) { + long scrapCount = scrapService.getScrapCount(postId); + return ResponseEntity.ok(scrapCount); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java new file mode 100644 index 00000000..d4b692a9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.scrap; + +import inu.codin.codin.common.BaseTimeEntity; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "scraps") +@Getter +public class ScrapEntity extends BaseTimeEntity { + @Id + private String id; + private String postId; + private String userId; + + @Builder + public ScrapEntity(String postId, String userId) { + this.postId = postId; + this.userId = userId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java new file mode 100644 index 00000000..444fd3da --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.scrap; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ScrapRepository extends MongoRepository { + boolean findByPostIdAndUserId(String postId, String userId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java new file mode 100644 index 00000000..5e523d53 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java @@ -0,0 +1,49 @@ +package inu.codin.codin.domain.scrap; + +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; + +@Service +@RequiredArgsConstructor +public class ScrapService { + + private final RedisTemplate redisTemplate; + + public void addScrap(String userId, String postId) { + String redisKey = "user:scraps:" + userId; + + if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, postId))) { + throw new IllegalStateException("이미 해당 게시물을 스크랩 했습니다."); + } + + // Redis에 스크랩 추가 + redisTemplate.opsForSet().add(redisKey, postId); + + } + + public void removeScrap(String userId, String postId) { + String redisKey = "user:scraps:" + userId; + + if (!Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, postId))) { + throw new IllegalStateException("해당 게시물을 스크랩 하지 않았습니다."); + } + + // Redis에서 스크랩 제거 + redisTemplate.opsForSet().remove(redisKey, postId); + + } + + public long getScrapCount(String postId) { + return redisTemplate.opsForSet().size("scraps:" + postId); + } + + public Set getScrappedPosts(String userId) { + String redisKey = "user:scraps:" + userId; + return redisTemplate.opsForSet().members(redisKey); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java new file mode 100644 index 00000000..759ae0bc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -0,0 +1,91 @@ +package inu.codin.codin.infra.redis; + +import inu.codin.codin.domain.like.LikeEntity; +import inu.codin.codin.domain.like.LikeRepository; +import inu.codin.codin.domain.like.LikeService; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostService; +import inu.codin.codin.domain.scrap.ScrapEntity; +import inu.codin.codin.domain.scrap.ScrapRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Set; + +@Component +@RequiredArgsConstructor +public class SyncScheduler { + + private final RedisTemplate redisTemplate; + private final PostRepository postRepository; + private final LikeRepository likeRepository; + private final ScrapRepository scrapRepository; + + @Scheduled(fixedRate = 60000) // 매 1분마다 실행 + public void syncLikesAndScraps() { + syncLikes(); + syncScraps(); + } + + private void syncLikes() { + Set keys = redisTemplate.keys("post:likes:*"); + if (keys == null || keys.isEmpty()) return; + + for (String key : keys) { + String postId = key.replace("post:likes:", ""); + Set users = redisTemplate.opsForSet().members(key); + + if (users != null && !users.isEmpty()) { + // LikeEntity 동기화 + users.forEach(userId -> { + if (!likeRepository.findByPostIdAndUserId(postId, userId)) { + likeRepository.save(new LikeEntity(postId, userId)); + } + }); + + // PostEntity의 likeCount 업데이트 + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + int newLikeCount = users.size(); + if (post.getLikeCount() != newLikeCount) { // 기존 카운트와 다를 경우만 업데이트 + post.updateLikeCount(newLikeCount); + postRepository.save(post); + } + } + } + } + + private void syncScraps() { + Set keys = redisTemplate.keys("user:scraps:*"); + if (keys == null || keys.isEmpty()) return; + + for (String key : keys) { + String userId = key.replace("user:scraps:", ""); + Set posts = redisTemplate.opsForSet().members(key); + + if (posts != null && !posts.isEmpty()) { + // ScrapEntity 동기화 + posts.forEach(postId -> { + if (!scrapRepository.findByPostIdAndUserId(postId, userId)) { + scrapRepository.save(new ScrapEntity(postId, userId)); + } + }); + + // PostEntity의 scrapCount 업데이트 + posts.forEach(postId -> { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + int newScrapCount = redisTemplate.opsForSet().size("post:scraps:" + postId).intValue(); + if (post.getScrapCount() != newScrapCount) { // 기존 카운트와 다를 경우만 업데이트 + post.updateScrapCount(newScrapCount); + postRepository.save(post); + } + }); + } + } + } + +} From aa2b31fa6ae024618ab6b185a573e685d31d0a79 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 25 Nov 2024 13:27:28 +0900 Subject: [PATCH 0110/1002] =?UTF-8?q?[SC-69]=20=20Refactor=20:=20Api=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/domain/like/LikeController.java | 2 +- .../main/java/inu/codin/codin/domain/scrap/ScrapController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java index cbb4e6fa..ce3eec4a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/posts/{postId}/likes") +@RequestMapping("/api/likes/{postId}") @RequiredArgsConstructor public class LikeController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java index c9c63dea..0f98e4bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/posts/{postId}/scraps") +@RequestMapping("/api/scraps/{postId}") @RequiredArgsConstructor public class ScrapController { From 60f26f77d87bd4442cefdfdce05dc248408260c9 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 13:44:42 +0900 Subject: [PATCH 0111/1002] =?UTF-8?q?Fix=20:=20CORS=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 8c63ef61..dc46a1f4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -15,6 +15,7 @@ import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; @@ -40,7 +41,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(CsrfConfigurer::disable) // csrf 비활성화 - .cors(Customizer.withDefaults()) // cors 설정 + .cors(AbstractHttpConfigurer::disable) // cors 비활성화 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 From f1eb304373978efddbf5c3e1b2629bbbea9bcbef Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 13:47:38 +0900 Subject: [PATCH 0112/1002] =?UTF-8?q?[SC-64]=20=EC=95=8C=EB=A6=BC=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B8=B0=EB=B3=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationPriority.java | 6 ++++ .../repository/NotificationRepository.java | 9 +++++ .../service/NotificationService.java | 16 +++++++++ .../inu/codin/codin/infra/fcm/FcmConfig.java | 34 +++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java new file mode 100644 index 00000000..d262308f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java @@ -0,0 +1,6 @@ +package inu.codin.codin.domain.notification.entity; + +public enum NotificationPriority { + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java new file mode 100644 index 00000000..3bd8d2cb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.notification.repository; + +import inu.codin.codin.domain.notification.entity.NotificationEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface NotificationRepository extends MongoRepository { +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java new file mode 100644 index 00000000..1e7bb10d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.notification.service; + +import inu.codin.codin.infra.fcm.FcmConfig; +import inu.codin.codin.infra.redis.RedisStorageService; +import inu.codin.codin.domain.notification.repository.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NotificationService { + + private NotificationRepository notificationRepository; + private RedisStorageService redisStorageService; + private FcmConfig fcmConfig; +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java new file mode 100644 index 00000000..cd897270 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java @@ -0,0 +1,34 @@ +package inu.codin.codin.infra.fcm; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.FileInputStream; +import java.io.IOException; + +@Component +@Slf4j +public class FcmConfig { + + @Value("${google.firebase.key-path}") + private String fcmKeyPath; + + @PostConstruct + public void init(){ + try { + FileInputStream serviceAccount = new FileInputStream(fcmKeyPath); + FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(GoogleCredentials.fromStream(serviceAccount)) + .build(); + FirebaseApp.initializeApp(options); + log.info("[init] FirebaseApp initialized"); + } catch (IOException e) { + throw new RuntimeException(e.getMessage()); + } + } +} From c577ec114f503e2da56a190c8a516e0aec5f3d16 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 25 Nov 2024 14:06:07 +0900 Subject: [PATCH 0113/1002] =?UTF-8?q?[SC-67]=20=20Refactor=20:=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20ENUM=20=EC=84=B8=EB=B6=80?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/entity/PostCategory.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 39dc270a..9d2c272b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -6,10 +6,21 @@ @Getter public enum PostCategory { - REQUEST("구해요"), - COMMUNICATION("소통해요"), - EXTRACURRICULAR("비교과"), - INFO("정보대소개"), + REQUEST_STUDY("구해요_스터디"), + REQUEST_PROJECT("구해요_프로젝트"), + REQUEST_COMPETITION("구해요_공모전_대회"), + REQUEST_GROUP("구해요_소모임"), + + COMMUNICATION_QUESTION("소통해요_질문"), + COMMUNICATION_JOB("소통해요_취업수기"), + COMMUNICATION_TIP("소통해요_꿀팁공유"), + + EXTRACURRICULAR_OUTER("비교과_교외"), + EXTRACURRICULAR_INNER("비교과_교내"), + + INFO_PROFESSOR("정보대소개_교수_연구실"), + INFO_PHONE_NUMBER("정보대소개_정보대전화번호"), + USED_BOOK("중고책"); private final String description; From b125943429d6573d8e2fde9b685ad2239b91566b Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 16:39:00 +0900 Subject: [PATCH 0114/1002] =?UTF-8?q?Fix=20:=20CorsConfig=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/config/CorsConfig.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java new file mode 100644 index 00000000..3d40e6c3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java @@ -0,0 +1,14 @@ +package inu.codin.codin.common.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("http://localhost:3000"); + } +} From a48087453eda88acc8785cbeefac903c42a592c1 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 16:55:42 +0900 Subject: [PATCH 0115/1002] =?UTF-8?q?Fix=20:=20CorsConfiguration=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A1=9C=EC=BB=AC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/config/CorsConfig.java | 20 +++++++++++++++---- .../codin/common/config/SecurityConfig.java | 5 +++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java index 3d40e6c3..48fa7c04 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java @@ -1,14 +1,26 @@ package inu.codin.codin.common.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.List; + @Configuration public class CorsConfig implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**").allowedOrigins("http://localhost:3000"); + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.setAllowedOrigins(List.of("http://localhost:[*]", "https://www.codin.co.kr/[*]")); + config.setAllowedHeaders(List.of("*")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index dc46a1f4..ebd9a5b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -15,7 +15,6 @@ import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; @@ -25,6 +24,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.cors.CorsConfigurationSource; @Configuration @EnableWebSecurity @@ -35,13 +35,14 @@ public class SecurityConfig { private final UserDetailsService userDetailsService; private final JwtService jwtService; private final JwtUtils jwtUtils; + private final CorsConfigurationSource corsConfigurationSource; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(CsrfConfigurer::disable) // csrf 비활성화 - .cors(AbstractHttpConfigurer::disable) // cors 비활성화 + .cors(security -> security.configurationSource(corsConfigurationSource)) // cors 비활성화 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 From 9c6f9826829e87ad88cf664022c23b16e64a60b1 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 17:35:25 +0900 Subject: [PATCH 0116/1002] =?UTF-8?q?Fix=20:=20CorsConfiguration=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A1=9C=EC=BB=AC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/inu/codin/codin/common/config/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java index 48fa7c04..7456e509 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java @@ -16,7 +16,7 @@ public class CorsConfig implements WebMvcConfigurer { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:[*]", "https://www.codin.co.kr/[*]")); + config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr/[*]")); config.setAllowedHeaders(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); From 335a0aaaa7b193ffeaebf023778fe96bc2335038 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 22:46:02 +0900 Subject: [PATCH 0117/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20BaseTimeEntity?= =?UTF-8?q?=EC=97=90=20createdBy,=20lastModifiedBy=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/BaseTimeEntity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java index 62ebd48e..7346cf65 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -1,7 +1,9 @@ package inu.codin.codin.common; import lombok.Getter; +import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.mongodb.core.mapping.Field; @@ -13,11 +15,16 @@ public abstract class BaseTimeEntity { @CreatedDate @Field("created_at") private LocalDateTime createdAt; + @CreatedBy + private String createdUser; @LastModifiedDate @Field("updated_at") private LocalDateTime updatedAt; + @LastModifiedBy + private String updatedUser; + @Field("deleted_at") private LocalDateTime deletedAt; public void delete() { From 78849dac77dedb2a3e0a2cc7ffff6d69b0ce8d99 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 22:50:29 +0900 Subject: [PATCH 0118/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20FCM=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/fcm/controller/FcmController.java | 28 +++++ .../codin/infra/fcm/dto/FcmMessageDto.java | 26 ++++ .../codin/infra/fcm/dto/FcmTokenRequest.java | 20 +++ .../infra/fcm/entity/FcmTokenEntity.java | 31 +++++ .../fcm/repository/FcmTokenRepository.java | 19 +++ .../codin/infra/fcm/service/FcmService.java | 115 ++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java new file mode 100644 index 00000000..d1c61bad --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java @@ -0,0 +1,28 @@ +package inu.codin.codin.infra.fcm.controller; + +import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; +import inu.codin.codin.infra.fcm.service.FcmService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +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; + +@RestController +@RequestMapping("/fcm") +@RequiredArgsConstructor +public class FcmController { + + private final FcmService fcmService; + + @PostMapping("/send") + public void sendFcmMessage( + @RequestBody @Valid FcmTokenRequest fcmTokenRequest, + @AuthenticationPrincipal UserDetails userDetails + ) { + fcmService.saveFcmToken(fcmTokenRequest, userDetails); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java new file mode 100644 index 00000000..4823d6bf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java @@ -0,0 +1,26 @@ +package inu.codin.codin.infra.fcm.dto; + +import inu.codin.codin.domain.user.entity.UserEntity; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +/** + * Fcm 메시지 DTO + * 서버 내부 로직에서 사용 + */ +@Getter +@Data +public class FcmMessageDto { + + private UserEntity user; + private String title; + private String body; + + @Builder + public FcmMessageDto(UserEntity user, String title, String body) { + this.user = user; + this.title = title; + this.body = body; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java new file mode 100644 index 00000000..d873c705 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java @@ -0,0 +1,20 @@ +package inu.codin.codin.infra.fcm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.Getter; + +@Data +@Getter +public class FcmTokenRequest { + + @Schema(description = "Fcm Token") + @NotBlank + private String fcmToken; + + @Schema(description = "Android, IOS") + @NotBlank + private String deviceType; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java new file mode 100644 index 00000000..52077a64 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -0,0 +1,31 @@ +package inu.codin.codin.infra.fcm.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.user.entity.UserEntity; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "fcmToken") +@Getter +public class FcmTokenEntity extends BaseTimeEntity { + + @Id + private String id; + + @DBRef(lazy = true) + private UserEntity user; + + private String fcmToken; + + private String deviceType; + + @Builder + public FcmTokenEntity(UserEntity user, String fcmToken, String deviceType) { + this.user = user; + this.fcmToken = fcmToken; + this.deviceType = deviceType; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java new file mode 100644 index 00000000..91b1f502 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java @@ -0,0 +1,19 @@ +package inu.codin.codin.infra.fcm.repository; + +import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface FcmTokenRepository extends MongoRepository { + + @Query("{ 'user.id': ?0, deletedAt: null }") + List findAllByUserId(String userId); + + @Query("{ 'fcmToken': ?0, deletedAt: null }") + Optional findByFcmToken(String fcmToken); +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java new file mode 100644 index 00000000..91cabf77 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -0,0 +1,115 @@ +package inu.codin.codin.infra.fcm.service; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; +import inu.codin.codin.domain.notification.entity.NotificationPreference; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.service.UserService; +import inu.codin.codin.infra.fcm.dto.FcmMessageDto; +import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; +import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; +import inu.codin.codin.infra.fcm.repository.FcmTokenRepository; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +@Slf4j +@RequiredArgsConstructor +public class FcmService { + + private final UserService userService; + private final FcmTokenRepository fcmTokenRepository; + + /** + * 클라이언트로부터 받은 FCM 토큰을 저장하는 로직 + * @param fcmTokenRequest FCM 토큰 요청 DTO + * @param userDetails 로그인한 유저 정보 + */ + public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails userDetails) { + // 유저 조회 + UserEntity user = null; + String email = userDetails.getUsername(); + try { + user = userService.findUserByEmail(email); + } catch (IllegalArgumentException e) { + log.error("[saveFcmToken] 유저를 찾을 수 없습니다. email : {}", email); + return; + } + + // 이미 존재하는 FCM 토큰이라면 삭제 + fcmTokenRepository.findByFcmToken(fcmTokenRequest.getFcmToken()) + .ifPresent(fcmTokenEntity -> { + log.info("[saveFcmToken] 이미 존재하는 FCM 토큰입니다. email : {}, fcmToken : {}", email, fcmTokenRequest.getFcmToken()); + fcmTokenRepository.delete(fcmTokenEntity); + }); + + FcmTokenEntity fcmTokenEntity = FcmTokenEntity.builder() + .user(user) + .fcmToken(fcmTokenRequest.getFcmToken()) + .deviceType(fcmTokenRequest.getDeviceType()) + .build(); + + fcmTokenRepository.save(fcmTokenEntity); + } + + /** + * FCM 메시지를 전송하는 로직 - 서버 내부 사용 + * @param msgDto FCM 메시지 DTO + */ + public void sendFcmMessage(FcmMessageDto msgDto) { + // 유저의 알림 설정 조회 + String userId = msgDto.getUser().getId(); + NotificationPreference userPreference = getUserNotificationPreference(userId); + // 알림 설정이 Off 라면 전송하지 않음 + if (!userPreference.isAllowPush()) { // todo : NotificationService로 분리 + log.info("[sendFcmMessage] 알림 설정이 Off User : {}", userId); + return; + } + // 유저의 FCM 토큰 조회 + var fcmTokens = getUserFcmToken(userId); + if (fcmTokens.isEmpty()) { + log.info("[sendFcmMessage] FCM 토큰이 없습니다. User : {}", userId); + return; + } + + // 유저가 가진 모든 토큰으로 FCM 메시지 전송 + for (String token : fcmTokens) { + try { + Message message = Message.builder() + .setNotification(Notification.builder() + .setTitle(msgDto.getTitle()) + .setBody(msgDto.getBody()) + .build()) + .setToken(token) + .build(); + + String response = FirebaseMessaging.getInstance().send(message); + log.info("[sendFcmMessage] 알림 전송 성공 : {}", response); + } catch (Exception e) { + log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); + } + } + } + + // 유저의 알림 설정을 조회하는 로직 + private NotificationPreference getUserNotificationPreference(String userId) { + return userService.getUserNotificationPreference(userId); + } + + // 유저의 FCM 토큰을 조회하는 로직 + private List getUserFcmToken(String userId) { + return fcmTokenRepository.findAllByUserId(userId) + .stream() + .map(FcmTokenEntity::getFcmToken) + .toList(); + } + + +} From 518ce751e7593d5d08eb93c8fb2f0373018fee4d Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 22:51:38 +0900 Subject: [PATCH 0119/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20Fcm=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20=EC=9C=84=ED=95=9C=20User=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/repository/UserRepository.java | 3 +++ .../domain/user/service/UserService.java | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 6c477daf..49925cc0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -13,4 +14,6 @@ public interface UserRepository extends MongoRepository { Optional findByStudentId(String studentId); + Optional findById(String id); + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 1a739d47..3b7fda6a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.notification.entity.NotificationPreference; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; @@ -57,4 +58,25 @@ private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) throw new UserCreateFailException("이미 존재하는 학번입니다."); } + + /** + * 유저의 알림 설정을 조회 + * @param userId 유저 id + * @return NotificationPreference + */ + public NotificationPreference getUserNotificationPreference(String userId) { + UserEntity user = userRepository.findById(userId). + orElseThrow(() -> new IllegalArgumentException("존재하지 않는 유저입니다.")); + return user.getNotificationPreference(); + } + + /** + * 이메일(username == email)로 유저 조회 + * @param email + * @return UserEntity + */ + public UserEntity findUserByEmail(String email) { + return userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 유저입니다.")); + } } From bc087ecee0c54306b33faf69ccb0faae961a7f9f Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 22:52:03 +0900 Subject: [PATCH 0120/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20Notification=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationEntity.java | 9 ++-- .../entity/NotificationPriority.java | 5 ++- .../repository/NotificationRepository.java | 2 +- .../service/NotificationService.java | 45 ++++++++++++++++++- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index b16971fe..3d085fa1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -1,9 +1,11 @@ package inu.codin.codin.domain.notification.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.user.entity.UserEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; @@ -13,7 +15,8 @@ public class NotificationEntity extends BaseTimeEntity { @Id @NotBlank private String id; - private String userId; + @DBRef(lazy = true) + private UserEntity user; private String type; @@ -24,8 +27,8 @@ public class NotificationEntity extends BaseTimeEntity { private String priority; @Builder - public NotificationEntity(String userId, String type, String message, String priority) { - this.userId = userId; + public NotificationEntity(UserEntity user, String type, String message, String priority) { + this.user = user; this.type = type; this.message = message; this.priority = priority; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java index d262308f..72e9255c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationPriority.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.notification.entity; public enum NotificationPriority { - - + HIGH, + MEDIUM, + LOW } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java index 3bd8d2cb..133f97b9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java @@ -6,4 +6,4 @@ @Repository public interface NotificationRepository extends MongoRepository { -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 1e7bb10d..dd041ac1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,16 +1,57 @@ package inu.codin.codin.domain.notification.service; +import inu.codin.codin.domain.notification.entity.NotificationEntity; +import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.FcmConfig; +import inu.codin.codin.infra.fcm.dto.FcmMessageDto; +import inu.codin.codin.infra.fcm.service.FcmService; import inu.codin.codin.infra.redis.RedisStorageService; import inu.codin.codin.domain.notification.repository.NotificationRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Slf4j public class NotificationService { private NotificationRepository notificationRepository; - private RedisStorageService redisStorageService; - private FcmConfig fcmConfig; + private FcmService fcmService; + + /** + * FCM 메시지를 특정 사용자에게 전송하는 로직 + * @param title 메시지 제목 + * @param body 메시지 내용 + * @param user 메시지를 받을 사용자 + */ + public void sendFcmMessageToUser(String title, String body, UserEntity user) { + FcmMessageDto msgDto = FcmMessageDto.builder() + .user(user) + .title(title) + .body(body) + .build(); + + // FCM 메시지 전송 + try { + fcmService.sendFcmMessage(msgDto); + log.info("[sendFcmMessage] 알림 전송 성공"); + } catch (Exception e) { + log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); + } + + // 알림 로그 저장 + saveNotificationLog(msgDto); + } + + // 알림 로그를 저장하는 로직 + private void saveNotificationLog(FcmMessageDto msgDto) { + NotificationEntity notificationEntity = NotificationEntity.builder() + .user(msgDto.getUser()) + .type("push") + .message(msgDto.getBody()) + .priority("high") + .build(); + notificationRepository.save(notificationEntity); + } } From c59f7ec9b1d5932caa93a10777c7a910d05d4f96 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 22:56:10 +0900 Subject: [PATCH 0121/1002] =?UTF-8?q?[SC-64]=20Refactor=20:=20import=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notification/service/NotificationService.java | 4 +--- .../codin/codin/domain/user/repository/UserRepository.java | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index dd041ac1..c2768fc1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,12 +1,10 @@ package inu.codin.codin.domain.notification.service; import inu.codin.codin.domain.notification.entity.NotificationEntity; +import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.infra.fcm.FcmConfig; import inu.codin.codin.infra.fcm.dto.FcmMessageDto; import inu.codin.codin.infra.fcm.service.FcmService; -import inu.codin.codin.infra.redis.RedisStorageService; -import inu.codin.codin.domain.notification.repository.NotificationRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 49925cc0..55fe7cc0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.user.entity.UserEntity; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; From 570abbdc3d75b023886a65ebbf15a2daf73f000f Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 25 Nov 2024 22:56:34 +0900 Subject: [PATCH 0122/1002] =?UTF-8?q?[SC-64]=20Comment=20:=20findUserByEma?= =?UTF-8?q?il=20=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/user/service/UserService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 3b7fda6a..e6a4264a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -71,8 +71,8 @@ public NotificationPreference getUserNotificationPreference(String userId) { } /** - * 이메일(username == email)로 유저 조회 - * @param email + * 이메일로 유저 조회 + * @param email 이메일 == UserDetails.username * @return UserEntity */ public UserEntity findUserByEmail(String email) { From 767bb4ea11750c4eb56ee1308bf5e2fa099f6c6e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 26 Nov 2024 16:26:01 +0900 Subject: [PATCH 0123/1002] =?UTF-8?q?[SC-81]=20feat=20:=20WebSocket=20Conf?= =?UTF-8?q?ig=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/WebSocketConfig.java | 28 +++++++++++++++++++ .../codin/domain/chat/ChatController.java | 24 ++++++++++++++++ .../codin/domain/chat/ChatRequestDto.java | 11 ++++++++ codin-core/src/main/resources | 2 +- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java new file mode 100644 index 00000000..1b992979 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -0,0 +1,28 @@ +package inu.codin.codin.common.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws-stomp") //handshake endpoint + .withSockJS(); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/topic", "/queue"); + //해당 주소를 구독하고 있는 클라이언트들에게 메세지 전달 + //메세지를 브로커로 라우팅 + registry.setApplicationDestinationPrefixes("/pub"); + //클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐 + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java new file mode 100644 index 00000000..6893efa9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.chat; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; + +@Controller +@RequiredArgsConstructor +@Slf4j +public class ChatController { + + private final SimpMessagingTemplate simpMessagingTemplate; + + @MessageMapping("/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 + @SendTo("/topic/{chatRoomId}") + public void chat(@DestinationVariable("chatRoomId") String id, @RequestBody ChatRequestDto chatRequestDto){ + log.info("Message [{}] send by member: {} to chatting room: {}", chatRequestDto.getContent(), chatRequestDto.getSenderId(), id); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java new file mode 100644 index 00000000..9463cac3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.chat; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChatRequestDto { + private Long senderId; + private String content; +} diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a45e23e5..b101cf25 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a45e23e56b9d4bb2025330c4f59382a4fc5a4e7a +Subproject commit b101cf2500e54f902ee06ec6108d045e1d0135c2 From 878488de46ecba9da1cad21951f9b4897e569365 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 26 Nov 2024 20:29:50 +0900 Subject: [PATCH 0124/1002] =?UTF-8?q?[SC-76]=20refactor=20:=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/entity/PostCategory.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 9d2c272b..a272dca6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -17,9 +17,7 @@ public enum PostCategory { EXTRACURRICULAR_OUTER("비교과_교외"), EXTRACURRICULAR_INNER("비교과_교내"), - - INFO_PROFESSOR("정보대소개_교수_연구실"), - INFO_PHONE_NUMBER("정보대소개_정보대전화번호"), + EXTRACURRICULAR_STARINU("비교과_STARINU"), USED_BOOK("중고책"); From 00b24e8045f0cc2cf94eb7f8a7f20dd0fe223cdf Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 26 Nov 2024 20:43:59 +0900 Subject: [PATCH 0125/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20FcmToken?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20Exception=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/fcm/exception/FcmDuplicatedTokenException.java | 7 +++++++ .../inu/codin/codin/infra/fcm/exception/FcmException.java | 7 +++++++ .../infra/fcm/exception/FcmTokenNotFoundException.java | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmDuplicatedTokenException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmTokenNotFoundException.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmDuplicatedTokenException.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmDuplicatedTokenException.java new file mode 100644 index 00000000..42707c81 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmDuplicatedTokenException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.fcm.exception; + +public class FcmDuplicatedTokenException extends FcmException { + public FcmDuplicatedTokenException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmException.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmException.java new file mode 100644 index 00000000..fb2613a5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.fcm.exception; + +public class FcmException extends RuntimeException { + public FcmException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmTokenNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmTokenNotFoundException.java new file mode 100644 index 00000000..9571fe5d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/exception/FcmTokenNotFoundException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.fcm.exception; + +public class FcmTokenNotFoundException extends FcmException { + public FcmTokenNotFoundException(String message) { + super(message); + } +} From 206c340de1f26e0bf475ee9de7fcd995f56efea4 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 26 Nov 2024 20:45:34 +0900 Subject: [PATCH 0126/1002] =?UTF-8?q?[SC-64]=20Perf=20:=20Fcm=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EA=B8=B0=EB=B0=98=20=EC=A0=84=EC=86=A1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationEntity.java | 1 + .../domain/user/service/UserService.java | 48 +++++----- .../infra/fcm/controller/FcmController.java | 9 +- .../infra/fcm/entity/FcmTokenEntity.java | 35 +++++-- .../fcm/repository/FcmTokenRepository.java | 9 +- .../codin/infra/fcm/service/FcmService.java | 91 +++++++------------ 6 files changed, 97 insertions(+), 96 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index 3d085fa1..c73c8ed4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -24,6 +24,7 @@ public class NotificationEntity extends BaseTimeEntity { private boolean isRead = false; + // 알림 중요도 - 미사용중 private String priority; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index e6a4264a..7295c7c7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.repository.EmailAuthRepository; -import inu.codin.codin.domain.notification.entity.NotificationPreference; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; @@ -59,24 +58,31 @@ private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto throw new UserCreateFailException("이미 존재하는 학번입니다."); } - /** - * 유저의 알림 설정을 조회 - * @param userId 유저 id - * @return NotificationPreference - */ - public NotificationPreference getUserNotificationPreference(String userId) { - UserEntity user = userRepository.findById(userId). - orElseThrow(() -> new IllegalArgumentException("존재하지 않는 유저입니다.")); - return user.getNotificationPreference(); - } - - /** - * 이메일로 유저 조회 - * @param email 이메일 == UserDetails.username - * @return UserEntity - */ - public UserEntity findUserByEmail(String email) { - return userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 유저입니다.")); - } + // todo : 중복 코드 제거 +// /** +// * 유저의 알림 설정을 조회 +// * @param userId 유저 id +// * @return NotificationPreference +// */ +// public NotificationPreference getUserNotificationPreference(String userId) { +// UserEntity user = userRepository.findById(userId). +// orElseThrow(() -> new IllegalArgumentException("존재하지 않는 유저입니다.")); // Exception 교체 필요 +// +// // 알림 설정이 Off 라면 전송하지 않음 +// if (!user.getNotificationPreference().isAllowPush()) { +// log.info("[sendFcmMessage] 알림 설정이 Off User : {}", userId); +// return null; +// } +// return user.getNotificationPreference(); +// } +// +// /** +// * 이메일로 유저 조회 +// * @param email 이메일 == UserDetails.username +// * @return UserEntity +// */ +// public UserEntity findUserByEmail(String email) { +// return userRepository.findByEmail(email) +// .orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 유저입니다.")); +// } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java index d1c61bad..7c1b3db2 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java @@ -1,9 +1,12 @@ package inu.codin.codin.infra.fcm.controller; +import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; import inu.codin.codin.infra.fcm.service.FcmService; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.PostMapping; @@ -13,16 +16,18 @@ @RestController @RequestMapping("/fcm") +@Tag(name = "FCM API", description = "FCM 토큰 저장 API") @RequiredArgsConstructor public class FcmController { private final FcmService fcmService; - @PostMapping("/send") - public void sendFcmMessage( + @PostMapping("/save") + public ResponseEntity sendFcmMessage( @RequestBody @Valid FcmTokenRequest fcmTokenRequest, @AuthenticationPrincipal UserDetails userDetails ) { fcmService.saveFcmToken(fcmTokenRequest, userDetails); + return ResponseUtils.successMsg("FCM 토큰 저장 성공"); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index 52077a64..c9089e51 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,13 +1,15 @@ package inu.codin.codin.infra.fcm.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.List; + @Document(collection = "fcmToken") @Getter public class FcmTokenEntity extends BaseTimeEntity { @@ -15,17 +17,34 @@ public class FcmTokenEntity extends BaseTimeEntity { @Id private String id; - @DBRef(lazy = true) - private UserEntity user; + // username와 같음 + private String email; - private String fcmToken; + private List fcmTokenList; private String deviceType; @Builder - public FcmTokenEntity(UserEntity user, String fcmToken, String deviceType) { - this.user = user; - this.fcmToken = fcmToken; + public FcmTokenEntity(String email, List fcmTokenList, String deviceType) { + this.email = email; + this.fcmTokenList = fcmTokenList; this.deviceType = deviceType; } + + /** + * 유저의 FcmToken을 추가하는 메서드 + */ + public void addFcmToken(@NotBlank String fcmToken) { + checkDuplicatedFcmToken(fcmToken); + fcmTokenList.add(fcmToken); + } + + /** + * fcmTokenList안에 중복되는지 확인하는 메서드 + */ + private void checkDuplicatedFcmToken(String fcmToken) { + if (fcmTokenList.contains(fcmToken)) { + throw new FcmDuplicatedTokenException("이미 등록된 FCM 토큰입니다."); + } + } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java index 91b1f502..5b843ae0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java @@ -5,15 +5,10 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository public interface FcmTokenRepository extends MongoRepository { - - @Query("{ 'user.id': ?0, deletedAt: null }") - List findAllByUserId(String userId); - - @Query("{ 'fcmToken': ?0, deletedAt: null }") - Optional findByFcmToken(String fcmToken); + @Query("{ 'email': ?0, deletedAt: null }") + Optional findByEmail(String email); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 91cabf77..be03516c 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -1,14 +1,14 @@ package inu.codin.codin.infra.fcm.service; import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; import inu.codin.codin.domain.notification.entity.NotificationPreference; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.fcm.dto.FcmMessageDto; import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; +import inu.codin.codin.infra.fcm.exception.FcmTokenNotFoundException; import inu.codin.codin.infra.fcm.repository.FcmTokenRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -17,14 +17,13 @@ import org.springframework.stereotype.Service; import java.util.List; - +import java.util.Optional; @Service @Slf4j @RequiredArgsConstructor public class FcmService { - private final UserService userService; private final FcmTokenRepository fcmTokenRepository; /** @@ -33,30 +32,23 @@ public class FcmService { * @param userDetails 로그인한 유저 정보 */ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails userDetails) { - // 유저 조회 - UserEntity user = null; + // 유저의 FCM 토큰이 존재하는지 확인 String email = userDetails.getUsername(); - try { - user = userService.findUserByEmail(email); - } catch (IllegalArgumentException e) { - log.error("[saveFcmToken] 유저를 찾을 수 없습니다. email : {}", email); - return; - } - - // 이미 존재하는 FCM 토큰이라면 삭제 - fcmTokenRepository.findByFcmToken(fcmTokenRequest.getFcmToken()) - .ifPresent(fcmTokenEntity -> { - log.info("[saveFcmToken] 이미 존재하는 FCM 토큰입니다. email : {}, fcmToken : {}", email, fcmTokenRequest.getFcmToken()); - fcmTokenRepository.delete(fcmTokenEntity); - }); - - FcmTokenEntity fcmTokenEntity = FcmTokenEntity.builder() - .user(user) - .fcmToken(fcmTokenRequest.getFcmToken()) - .deviceType(fcmTokenRequest.getDeviceType()) - .build(); + Optional fcmToken = fcmTokenRepository.findByEmail(email); - fcmTokenRepository.save(fcmTokenEntity); + if (fcmToken.isPresent()) { // 이미 존재하는 유저라면 토큰 추가 + FcmTokenEntity fcmTokenEntity = fcmToken.get(); + fcmTokenEntity.addFcmToken(fcmTokenRequest.getFcmToken()); + fcmTokenRepository.save(fcmTokenEntity); + } + else { // 존재하지 않는 FCM 토큰이라면 저장 + FcmTokenEntity newFcmTokenEntity = FcmTokenEntity.builder() + .email(email) + .fcmTokenList(List.of(fcmTokenRequest.getFcmToken())) + .deviceType(fcmTokenRequest.getDeviceType()) + .build(); + fcmTokenRepository.save(newFcmTokenEntity); + } } /** @@ -65,51 +57,34 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails use */ public void sendFcmMessage(FcmMessageDto msgDto) { // 유저의 알림 설정 조회 - String userId = msgDto.getUser().getId(); - NotificationPreference userPreference = getUserNotificationPreference(userId); - // 알림 설정이 Off 라면 전송하지 않음 - if (!userPreference.isAllowPush()) { // todo : NotificationService로 분리 - log.info("[sendFcmMessage] 알림 설정이 Off User : {}", userId); - return; - } + String email = msgDto.getUser().getEmail(); + NotificationPreference userPreference = msgDto.getUser().getNotificationPreference(); + // 유저의 FCM 토큰 조회 - var fcmTokens = getUserFcmToken(userId); - if (fcmTokens.isEmpty()) { - log.info("[sendFcmMessage] FCM 토큰이 없습니다. User : {}", userId); + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByEmail(email).orElseThrow(() + -> new FcmTokenNotFoundException("유저에게 FCM 토큰이 존재하지 않습니다.")); + + // 알림 설정에 따라 알림 전송 + if (!userPreference.isAllowPush()) { + log.info("[sendFcmMessage] 알림 설정에서 푸시 알림을 허용하지 않았습니다. : {}", email); return; } - - // 유저가 가진 모든 토큰으로 FCM 메시지 전송 - for (String token : fcmTokens) { + for (String fcmToken : fcmTokenEntity.getFcmTokenList()) { try { Message message = Message.builder() .setNotification(Notification.builder() .setTitle(msgDto.getTitle()) .setBody(msgDto.getBody()) .build()) - .setToken(token) + .setToken(fcmToken) .build(); String response = FirebaseMessaging.getInstance().send(message); log.info("[sendFcmMessage] 알림 전송 성공 : {}", response); - } catch (Exception e) { - log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); + } catch (FirebaseMessagingException e) { + log.error("[sendFcmMessage] 알림 전송 실패, errorCode : {}, msg : {}", e.getErrorCode(), e.getMessage()); + // todo : 알림 전송 실패 시 로직 추가 FCM 토큰 만료시 삭제 로직 추가 } } } - - // 유저의 알림 설정을 조회하는 로직 - private NotificationPreference getUserNotificationPreference(String userId) { - return userService.getUserNotificationPreference(userId); - } - - // 유저의 FCM 토큰을 조회하는 로직 - private List getUserFcmToken(String userId) { - return fcmTokenRepository.findAllByUserId(userId) - .stream() - .map(FcmTokenEntity::getFcmToken) - .toList(); - } - - -} +} \ No newline at end of file From cb7dabe541480738aa960d627a6c0cd3c01976e6 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 26 Nov 2024 22:30:26 +0900 Subject: [PATCH 0127/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20FcmMessageDto?= =?UTF-8?q?=EC=97=90=20ImageUrl=20=EC=B2=A8=EB=B6=80=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java index 4823d6bf..5a7ab748 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java @@ -5,6 +5,8 @@ import lombok.Data; import lombok.Getter; +import java.util.Map; + /** * Fcm 메시지 DTO * 서버 내부 로직에서 사용 @@ -16,11 +18,15 @@ public class FcmMessageDto { private UserEntity user; private String title; private String body; + private String imageUrl; + private Map data; @Builder - public FcmMessageDto(UserEntity user, String title, String body) { + public FcmMessageDto(UserEntity user, String title, String body, String imageUrl, Map data) { this.user = user; this.title = title; this.body = body; + this.imageUrl = imageUrl; + this.data = data; } } From f3e71fc68402d1e03243f0a18022091ae2b7075d Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 26 Nov 2024 22:30:43 +0900 Subject: [PATCH 0128/1002] =?UTF-8?q?[SC-64]=20Comment=20:=20todo=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notification/entity/NotificationEntity.java | 6 +++++- .../domain/notification/service/NotificationService.java | 1 - .../inu/codin/codin/infra/fcm/service/FcmService.java | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index c73c8ed4..154a861d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -18,10 +18,14 @@ public class NotificationEntity extends BaseTimeEntity { @DBRef(lazy = true) private UserEntity user; - private String type; + private String topic; private String message; + // 미사용중 + private String type; + + // 미사용중 private boolean isRead = false; // 알림 중요도 - 미사용중 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index c2768fc1..176b2155 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -37,7 +37,6 @@ public void sendFcmMessageToUser(String title, String body, UserEntity user) { } catch (Exception e) { log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); } - // 알림 로그 저장 saveNotificationLog(msgDto); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index be03516c..3e3e4d9a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -75,16 +75,24 @@ public void sendFcmMessage(FcmMessageDto msgDto) { .setNotification(Notification.builder() .setTitle(msgDto.getTitle()) .setBody(msgDto.getBody()) + .setImage(msgDto.getImageUrl()) .build()) .setToken(fcmToken) + .putAllData(msgDto.getData()) .build(); String response = FirebaseMessaging.getInstance().send(message); log.info("[sendFcmMessage] 알림 전송 성공 : {}", response); } catch (FirebaseMessagingException e) { log.error("[sendFcmMessage] 알림 전송 실패, errorCode : {}, msg : {}", e.getErrorCode(), e.getMessage()); + // todo : 에러 관리 및 리포팅 기능 추가 // todo : 알림 전송 실패 시 로직 추가 FCM 토큰 만료시 삭제 로직 추가 + // todo : 토큰 만료 관리 추가 } } } + + // todo : FCM Bulk 메시지 전송 로직 추가 + // todo : FCM 토칙 기반 메세지 전송 로직 추가 - 공지사항, 학과별 알림, 게시글 내 모든 댓글 인원에게 알림 + } \ No newline at end of file From 483dcf3120c580b7898f6eca5d124562e0d134fc Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 26 Nov 2024 23:05:15 +0900 Subject: [PATCH 0129/1002] =?UTF-8?q?Fix=20:=20Cors=20Config=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20Import=20Refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/CorsConfig.java | 3 ++- .../inu/codin/codin/common/config/SecurityConfig.java | 6 ++---- .../codin/domain/email/service/EmailAuthService.java | 2 +- .../codin/codin/domain/info/domain/lab/entity/Lab.java | 2 +- .../codin/domain/info/domain/lab/service/LabService.java | 1 - .../info/domain/office/controller/OfficeController.java | 4 ++-- .../domain/info/domain/office/service/OfficeService.java | 8 ++++---- .../domain/professor/dto/ProfessorListResponseDto.java | 2 +- .../professor/dto/ProfessorThumbnailResponseDto.java | 2 +- .../info/domain/professor/service/ProfessorService.java | 2 +- .../codin/domain/user/controller/UserController.java | 5 ++++- 11 files changed, 19 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java index 7456e509..bbde65df 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java @@ -16,9 +16,10 @@ public class CorsConfig implements WebMvcConfigurer { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr/[*]")); + config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr")); config.setAllowedHeaders(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + config.addExposedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index ebd9a5b5..7f1250d3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -11,7 +11,6 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -41,13 +40,12 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource)) // cors 설정 .csrf(CsrfConfigurer::disable) // csrf 비활성화 - .cors(security -> security.configurationSource(corsConfigurationSource)) // cors 비활성화 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 ) - .httpBasic(Customizer.withDefaults()) // httpBasic 활성화 // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests @@ -81,7 +79,7 @@ public AuthenticationManager authenticationManager(HttpSecurity http) throws Exc @Bean public RoleHierarchy roleHierarchy() { - return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_MANGER > ROLE_USER"); + return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_MANAGER > ROLE_USER"); } @Bean diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 5dc598a3..72c75a1c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java index dc2823a5..99143ee1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.info.domain.lab.entity; +import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; -import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index 3f409315..3a4d35fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -7,7 +7,6 @@ import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index fc814c02..dbfda3cc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,11 +1,11 @@ package inu.codin.codin.domain.info.domain.office.controller; +import inu.codin.codin.common.Department; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.service.OfficeService; -import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; -import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index 6df18db1..ff38ee64 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -1,15 +1,15 @@ package inu.codin.codin.domain.info.domain.office.service; -import inu.codin.codin.domain.info.domain.office.dto.*; -import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class OfficeService { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java index 7cab9fa0..b5375f5b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.domain.professor.dto; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java index 82b02ce4..0de5aadc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.domain.professor.dto; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index fd873bcd..77aa47dc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.domain.professor.service; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.domain.info.domain.professor.exception.ProfessorDuplicatedException; import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 0dd9eef6..94813f4b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -8,7 +8,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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; @RestController @RequestMapping(value = "/users", produces = "plain/text; charset=utf-8") From 7d4ae9b6a452ca375236ce1aa17555e45eedde61 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 27 Nov 2024 00:00:25 +0900 Subject: [PATCH 0130/1002] =?UTF-8?q?Fix=20:=20Cors=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 7f1250d3..87914708 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -14,6 +14,7 @@ import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; @@ -40,7 +41,7 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .cors(cors -> cors.configurationSource(corsConfigurationSource)) // cors 설정 + .cors(AbstractHttpConfigurer::disable) // cors 설정 .csrf(CsrfConfigurer::disable) // csrf 비활성화 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 .sessionManagement(sessionManagement -> sessionManagement From dba844cf63407ae77b1d0c0393d548eaeb219895 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 00:05:17 +0900 Subject: [PATCH 0131/1002] =?UTF-8?q?[SC-81]=20feat=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20CRUD=20api=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatRoomController.java | 59 +++++++++++++++++++ .../dto/ChatRoomCreateRequestDto.java | 12 ++++ .../chatroom/dto/ChatRoomListResponseDto.java | 37 ++++++++++++ .../domain/chat/chatroom/entity/ChatRoom.java | 53 +++++++++++++++++ .../chat/chatroom/entity/Participants.java | 23 ++++++++ .../exception/ChatRoomNotFoundException.java | 7 +++ .../repository/ChatRoomRepository.java | 13 ++++ .../chatroom/service/ChatRoomService.java | 57 ++++++++++++++++++ 8 files changed, 261 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomNotFoundException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java new file mode 100644 index 00000000..a7b0fb71 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -0,0 +1,59 @@ +package inu.codin.codin.domain.chat.chatroom.controller; + +import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/api/chats") +public class ChatRoomController { + + private final ChatRoomService chatRoomService; + + @Operation( + summary = "채팅방 생성" + ) + @PostMapping + public ResponseEntity createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ + chatRoomService.createChatRoom(chatRoomCreateRequestDto, userDetails); + return ResponseUtils.successMsg("채팅방 생성 완료"); + } + + @Operation( + summary = "사용자가 포함된 모든 채팅방 리스트 반환" + ) + @GetMapping + public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ + return ResponseUtils.success(chatRoomService.getAllChatRoomByUser(userDetails)); + } + + @Operation( + summary = "채팅방 나가기" + ) + @DeleteMapping("/{chatRoomId}") + public ResponseEntity leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ + chatRoomService.leaveChatRoom(chatRoomId, userDetails); + return ResponseUtils.successMsg("채팅방 나가기 완료"); + } + + @Operation( + summary = "채팅방 알림 여부 수정" + ) + @GetMapping("/notification/{chatRoomId}") + public ResponseEntity setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ + chatRoomService.setNotificationChatRoom(chatRoomId, userDetails); + return ResponseUtils.successMsg("채팅방 알림 여부 수정 완료"); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java new file mode 100644 index 00000000..5f4c4c82 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.chat.chatroom.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChatRoomCreateRequestDto { + + private String roomName; + private String receiverId; //채팅 수신자 +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java new file mode 100644 index 00000000..a25245d6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java @@ -0,0 +1,37 @@ +package inu.codin.codin.domain.chat.chatroom.dto; + +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatting.Message; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +@Setter +public class ChatRoomListResponseDto { + + private final String roomName; + private final String message; + private final LocalDateTime currentMessageDate; + private final boolean notificationEnabled; + + @Builder + public ChatRoomListResponseDto(String roomName, String message, LocalDateTime currentMessageDate, boolean notificationEnabled) { + this.roomName = roomName; + this.message = message; + this.currentMessageDate = currentMessageDate; + this.notificationEnabled = notificationEnabled; + } + + public static ChatRoomListResponseDto of(ChatRoom chatRoom) { + Message message = chatRoom.getMessages().get(chatRoom.getMessages().size()-1); + return ChatRoomListResponseDto.builder() + .roomName(chatRoom.getRoomName()) + .message(message.getContent()) + .currentMessageDate(message.getCreatedAt()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java new file mode 100644 index 00000000..958f8f0e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -0,0 +1,53 @@ +package inu.codin.codin.domain.chat.chatroom.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatting.Message; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.security.core.parameters.P; + +import java.util.ArrayList; +import java.util.List; + +@Document(collection = "chat") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class ChatRoom extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + @NotBlank + private String roomName; + + @NotBlank + private List participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) + + @NotBlank + private List messages; + + + @Builder + public ChatRoom(String roomName, List participants, List messages) { + this.roomName = roomName; + this.participants = participants; + this.messages = messages; + } + + public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, String senderId){ + ArrayList participants = new ArrayList<>(2); + participants.add(new Participants(chatRoomCreateRequestDto.getReceiverId(), true)); + participants.add(new Participants(senderId, true)); + return ChatRoom.builder() + .roomName(chatRoomCreateRequestDto.getRoomName()) + .participants(participants) + .messages(new ArrayList<>()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java new file mode 100644 index 00000000..da7419d7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.chat.chatroom.entity; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Participants { + + private final String userId; + private boolean notificationsEnabled; + + @Builder + public Participants(String userId, boolean notificationsEnabled) { + this.userId = userId; + this.notificationsEnabled = notificationsEnabled; + } + + public void updateNotification() { + this.notificationsEnabled = !notificationsEnabled; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomNotFoundException.java new file mode 100644 index 00000000..7ac7c3cd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomNotFoundException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.chat.chatroom.exception; + +public class ChatRoomNotFoundException extends RuntimeException { + public ChatRoomNotFoundException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java new file mode 100644 index 00000000..5d660b3f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.chat.chatroom.repository; + +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import java.util.List; + +public interface ChatRoomRepository extends MongoRepository { + + @Query("{ 'participants': ?0 }") + List findByParticipant(String userId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java new file mode 100644 index 00000000..f90360a5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -0,0 +1,57 @@ +package inu.codin.codin.domain.chat.chatroom.service; + +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.user.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ChatRoomService { + + private final ChatRoomRepository chatRoomRepository; + + public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { + String senderId = ((CustomUserDetails) userDetails).getId(); + ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); + chatRoomRepository.save(chatRoom); + } + + public List getAllChatRoomByUser(UserDetails userDetails) { + String userId = ((CustomUserDetails) userDetails).getId(); + List chatRooms = chatRoomRepository.findByParticipant(userId); + return chatRooms.stream().map(ChatRoomListResponseDto::of).toList(); + } + + public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { + String userId = ((CustomUserDetails) userDetails).getId(); + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + chatRoom.getParticipants().remove(userId); + if (chatRoom.getParticipants().isEmpty()) { + chatRoom.delete(); + log.info("[LeaveChatRoom] 채팅방에 참여자가 없어 채팅방 삭제"); + } + chatRoomRepository.save(chatRoom); + } + + public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) { + String userId = ((CustomUserDetails) userDetails).getId(); + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + chatRoom.getParticipants().stream() + .filter(participants -> participants.getUserId().equals(userId)) + .forEach(Participants::updateNotification); + chatRoomRepository.save(chatRoom); + } +} From cef2c16f2e6211227e997f82e70b657c3c74160d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 00:05:50 +0900 Subject: [PATCH 0132/1002] =?UTF-8?q?[SC-81]=20feat=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EA=B8=B0=EB=B3=B8=20=EA=B5=AC=EC=A1=B0=EB=A7=8C=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/chatting/ChattingController.java | 21 ++++++++++++++ .../chat/chatting/ChattingRequestDto.java | 11 ++++++++ .../codin/domain/chat/chatting/Message.java | 28 +++++++++++++++++++ .../domain/chat/chatting/MessageType.java | 11 ++++++++ 4 files changed, 71 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java new file mode 100644 index 00000000..e5a95187 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java @@ -0,0 +1,21 @@ +package inu.codin.codin.domain.chat.chatting; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; + +@Controller +@RequiredArgsConstructor +@Slf4j +public class ChattingController { + + @MessageMapping("/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 + @SendTo("/topic/{chatRoomId}") + public void chat(@DestinationVariable("chatRoomId") String id, @RequestBody ChattingRequestDto chattingRequestDto){ + log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java new file mode 100644 index 00000000..d73a7bca --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.chat.chatting; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChattingRequestDto { + private Long senderId; + private String content; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java new file mode 100644 index 00000000..9d50122d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.chat.chatting; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class Message { + + @Id @NotBlank + private String messageId; + + private String senderId; + + private String content; + + private MessageType messageType; + + @CreatedDate + @Field("created_at") + private LocalDateTime createdAt; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java new file mode 100644 index 00000000..9b497d88 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.chat.chatting; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum MessageType { + TEXT("텍스트"), + IMAGE("이미지"); + + private final String description; +} From 9b87307082b395e5b2ba8dc6f28e036b6f2e2365 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 00:29:33 +0900 Subject: [PATCH 0133/1002] =?UTF-8?q?[SC-71]=20fix=20:=20department=20requ?= =?UTF-8?q?est=EB=A5=BC=20@NotBlank=20->=20@NotNull=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/dto/{ => request}/LabCreateUpdateRequestDto.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/{ => request}/LabCreateUpdateRequestDto.java (91%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java index cd913362..1cfe9ea8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java @@ -1,15 +1,16 @@ -package inu.codin.codin.domain.info.domain.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto.request; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @Getter @Setter public class LabCreateUpdateRequestDto { - @NotBlank + @NotNull @Schema(description = "학과", example = "EMBEDDED") private Department department; From d75b1cb732a6bd88d2a072cfeee2bd49e5b47d29 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 00:31:01 +0900 Subject: [PATCH 0134/1002] =?UTF-8?q?[SC-71]=20chore=20:=20dto=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EC=97=90=20request,=20response=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/domain/lab/controller/LabController.java | 6 +++--- .../lab/dto/{ => response}/LabListResponseDto.java | 2 +- .../dto/{ => response}/LabThumbnailResponseDto.java | 2 +- .../codin/domain/info/domain/lab/entity/Lab.java | 2 +- .../domain/info/domain/lab/service/LabService.java | 7 +++---- .../domain/office/controller/OfficeController.java | 6 +++--- .../OfficeMemberCreateUpdateRequestDto.java | 2 +- .../dto/{ => request}/OfficeUpdateRequestDto.java | 2 +- .../{ => response}/OfficeDetailsResponseDto.java | 2 +- .../dto/{ => response}/OfficeMemberResponseDto.java | 2 +- .../domain/info/domain/office/entity/Office.java | 2 +- .../info/domain/office/entity/OfficeMember.java | 2 +- .../info/domain/office/service/OfficeService.java | 6 +++--- .../professor/controller/ProfessorController.java | 6 +++--- .../ProfessorCreateUpdateRequestDto.java | 13 +++++++++++-- .../{ => response}/ProfessorListResponseDto.java | 2 +- .../ProfessorThumbnailResponseDto.java | 2 +- .../info/domain/professor/entity/Professor.java | 2 +- .../domain/professor/service/ProfessorService.java | 6 +++--- codin-core/src/main/resources | 2 +- 20 files changed, 42 insertions(+), 34 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/{ => response}/LabListResponseDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/{ => response}/LabThumbnailResponseDto.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{ => request}/OfficeMemberCreateUpdateRequestDto.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{ => request}/OfficeUpdateRequestDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{ => response}/OfficeDetailsResponseDto.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/{ => response}/OfficeMemberResponseDto.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/{ => request}/ProfessorCreateUpdateRequestDto.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/{ => response}/ProfessorListResponseDto.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/{ => response}/ProfessorThumbnailResponseDto.java (97%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 5f94569c..6f40c13b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.domain.lab.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; -import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.lab.dto.response.LabListResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.response.LabThumbnailResponseDto; import inu.codin.codin.domain.info.domain.lab.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java index ce2df4f5..44b4dda7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto.response; import inu.codin.codin.domain.info.domain.lab.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java index 345d650e..e2cac527 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/LabThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.lab.dto; +package inu.codin.codin.domain.info.domain.lab.dto.response; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.lab.entity.Lab; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java index dc2823a5..27f2f111 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.lab.entity; -import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.entity.InfoType; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index 3f409315..d3474c81 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -1,13 +1,12 @@ package inu.codin.codin.domain.info.domain.lab.service; -import inu.codin.codin.domain.info.domain.lab.dto.LabCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.lab.dto.LabListResponseDto; -import inu.codin.codin.domain.info.domain.lab.dto.LabThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.lab.dto.response.LabListResponseDto; +import inu.codin.codin.domain.info.domain.lab.dto.response.LabThumbnailResponseDto; import inu.codin.codin.domain.info.domain.lab.entity.Lab; import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index fc814c02..32354ea4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.info.domain.office.controller; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.service.OfficeService; -import inu.codin.codin.domain.info.domain.office.dto.OfficeDetailsResponseDto; +import inu.codin.codin.domain.info.domain.office.dto.response.OfficeDetailsResponseDto; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java index cf477108..42ae288b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.office.dto; +package inu.codin.codin.domain.info.domain.office.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java index 01ab1772..956ebf04 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.office.dto; +package inu.codin.codin.domain.info.domain.office.dto.request; import inu.codin.codin.domain.info.domain.office.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java index c25ecb33..252933ca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.office.dto; +package inu.codin.codin.domain.info.domain.office.dto.response; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.office.entity.Office; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java index b8efb6ee..be5e1862 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/OfficeMemberResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.office.dto; +package inu.codin.codin.domain.info.domain.office.dto.response; import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java index 673c253d..bb998af1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.office.entity; -import inu.codin.codin.domain.info.domain.office.dto.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java index e743ac31..e77c5359 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.domain.office.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.info.domain.office.dto.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index 6df18db1..06663476 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.info.domain.office.service; -import inu.codin.codin.domain.info.domain.office.dto.*; +import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.domain.office.dto.response.OfficeDetailsResponseDto; import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.common.Department; import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; @@ -8,8 +10,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class OfficeService { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index c4b54d9e..a09fd8b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -2,9 +2,9 @@ import inu.codin.codin.common.Department; import inu.codin.codin.common.ResponseUtils; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorListResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorThumbnailResponseDto; import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java index b153bae6..234b1a9a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java @@ -1,8 +1,9 @@ -package inu.codin.codin.domain.info.domain.professor.dto; +package inu.codin.codin.domain.info.domain.professor.dto.request; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,23 +15,31 @@ @NoArgsConstructor public class ProfessorCreateUpdateRequestDto { - @NotBlank + @NotNull @Schema(description = "학과", example = "COMPUTER_SCI") Department department; + @NotBlank @Schema(description = "성함", example = "홍길동") String name; + @NotBlank @Schema(description = "프로필 사진", example = "https://~") String image; + @NotBlank @Schema(description = "전화번호", example = "032-123-4567") String number; + @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") String email; + @Schema(description = "연구실 홈페이지", example = "https://~") String site; + @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") String field; + @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") String subject; + @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") String labId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java index 7cab9fa0..5279f98d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.professor.dto; +package inu.codin.codin.domain.info.domain.professor.dto.response; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.common.Department; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java index 82b02ce4..1a917d9c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.professor.dto; +package inu.codin.codin.domain.info.domain.professor.dto.response; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.common.Department; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java index 11c7fdce..13fa01fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.domain.professor.entity; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index fd873bcd..4c8aa807 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.domain.professor.service; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorListResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorListResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.domain.info.domain.professor.exception.ProfessorDuplicatedException; import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a45e23e5..b101cf25 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a45e23e56b9d4bb2025330c4f59382a4fc5a4e7a +Subproject commit b101cf2500e54f902ee06ec6108d045e1d0135c2 From 05a4456e9b4bc6a4b8a4c6aec020084526c866b4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 00:43:54 +0900 Subject: [PATCH 0135/1002] =?UTF-8?q?fix=20:=20SecurityConfig=20=EC=97=90?= =?UTF-8?q?=20cors=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/config/CorsConfig.java | 27 ------------------- .../codin/common/config/SecurityConfig.java | 20 +++++++++++++- 2 files changed, 19 insertions(+), 28 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java deleted file mode 100644 index bbde65df..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/config/CorsConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codin.common.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import java.util.List; - -@Configuration -public class CorsConfig implements WebMvcConfigurer { - - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr")); - config.setAllowedHeaders(List.of("*")); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); - config.addExposedHeader("Authorization"); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 87914708..f98c3aba 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -24,7 +24,11 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @Configuration @EnableWebSecurity @@ -41,7 +45,7 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .cors(AbstractHttpConfigurer::disable) // cors 설정 + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // cors 설정 .csrf(CsrfConfigurer::disable) // csrf 비활성화 .formLogin(FormLoginConfigurer::disable) // form login 비활성화 .sessionManagement(sessionManagement -> sessionManagement @@ -118,4 +122,18 @@ public PasswordEncoder passwordEncoder() { "/v3/api/test5", }; + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr")); + config.setAllowedHeaders(List.of("*")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + config.addExposedHeader("Authorization"); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } + } From ab83bcf19abfe864af8016b254d8e91df53b9566 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 27 Nov 2024 11:06:47 +0900 Subject: [PATCH 0136/1002] =?UTF-8?q?Fix=20:=20Cors=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index f98c3aba..f50e6dca 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -14,7 +14,6 @@ import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; @@ -39,7 +38,6 @@ public class SecurityConfig { private final UserDetailsService userDetailsService; private final JwtService jwtService; private final JwtUtils jwtUtils; - private final CorsConfigurationSource corsConfigurationSource; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { From 8e8fb1e44f714051d80a13c1c9b7545421203f54 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 17:26:37 +0900 Subject: [PATCH 0137/1002] =?UTF-8?q?refactor=20:=20SingleResponse,=20List?= =?UTF-8?q?Response=20=EB=B0=98=ED=99=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/response/CommonResponse.java | 16 +++++++ .../email/controller/EmailController.java | 15 ++++--- .../domain/lab/controller/LabController.java | 39 +++++++++------- .../office/controller/OfficeController.java | 45 ++++++++++--------- .../controller/ProfessorController.java | 38 +++++++++------- .../user/controller/UserController.java | 14 +++--- 6 files changed, 101 insertions(+), 66 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java new file mode 100644 index 00000000..e35ef50b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java @@ -0,0 +1,16 @@ +package inu.codin.codin.common.response; + +import lombok.Getter; + +@Getter +public class CommonResponse { + boolean success; + int code; + String message; + + public CommonResponse(boolean success, int code, String message) { + this.success = success; + this.code = code; + this.message = message; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 3f5cfbc1..556f2efd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.controller; -import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.service.EmailAuthService; @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = "/email", produces = "plain/text; charset=utf-8") +@RequestMapping(value = "/email") @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class EmailController { @@ -24,20 +24,21 @@ public class EmailController { @Operation(summary = "이메일 인증 코드 전송 - 학교인증 X") @PostMapping("/auth/send") - public ResponseEntity sendJoinAuthEmail( + public ResponseEntity> sendJoinAuthEmail( @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { emailAuthService.sendAuthEmail(emailAuthRequestDto); - return ResponseUtils.successMsg("이메일 인증 코드 전송 성공"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "이메일 인증 코드 전송 성공", null)); } @Operation(summary = "이메일 인증 코드 확인 - 학교인증 X") @PostMapping("/auth/check") - public ResponseEntity checkAuthNum( + public ResponseEntity> checkAuthNum( @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto ) { emailAuthService.checkAuthNum(joinEmailCheckRequestDto); - return ResponseUtils.successMsg("이메일 인증 성공 - 회원가입 가능"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "이메일 인증 성공 - 회원가입 가능", null)); } - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index 6f40c13b..ec7e103f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.info.domain.lab.controller; -import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.lab.dto.response.LabListResponseDto; import inu.codin.codin.domain.info.domain.lab.dto.response.LabThumbnailResponseDto; @@ -9,12 +10,11 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/lab") @@ -25,38 +25,43 @@ public class LabController { @Operation(summary = "연구실 썸네일 반환") @GetMapping("/thumbnail/{id}") - public ResponseEntity getLabThumbnail(@PathVariable("id") String id){ - return ResponseUtils.success(labService.getLabThumbnail(id)); + public ResponseEntity> getLabThumbnail(@PathVariable("id") String id){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "연구실 썸네일 반환 성공", labService.getLabThumbnail(id))); } @Operation(summary = "연구실 리스트 반환") @GetMapping - public ResponseEntity> getAllLab(){ - return ResponseUtils.success(labService.getAllLab()); + public ResponseEntity> getAllLab(){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "연구실 리스트 반환 성공", labService.getAllLab())); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 새로운 연구실 등록") - @PostMapping(produces = "plain/text; charset=utf-8") - public ResponseEntity createLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto){ + @PostMapping + public ResponseEntity> createLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto){ labService.createLab(labCreateUpdateRequestDto); - return ResponseUtils.successMsg("새로운 LAB 등록 완료"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "새로운 LAB 등록 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 연구실 정보 수정") - @PutMapping(value = "/{id}", produces = "plain/text; charset=utf-8") - public ResponseEntity updateLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ + @PutMapping(value = "/{id}") + public ResponseEntity> updateLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ labService.updateLab(labCreateUpdateRequestDto, id); - return ResponseUtils.successMsg("LAB 정보 수정 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "LAB 정보 수정 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 연구실 삭제") - @DeleteMapping(value = "/{id}", produces = "plain/text; charset=utf-8") - public ResponseEntity deleteLab(@PathVariable("id") String id){ + @DeleteMapping(value = "/{id}") + public ResponseEntity> deleteLab(@PathVariable("id") String id){ labService.deleteLab(id); - return ResponseUtils.successMsg("LAB 삭제 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "LAB 삭제 완료", null)); } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index 32354ea4..bb018024 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,16 +1,16 @@ package inu.codin.codin.domain.info.domain.office.controller; -import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.Department; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.service.OfficeService; -import inu.codin.codin.domain.info.domain.office.dto.response.OfficeDetailsResponseDto; -import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -23,43 +23,48 @@ public class OfficeController { private final OfficeService officeService; - @Operation(summary = "학과별 사무실 직원 정보 반환") + @Operation(summary = "학과별 사무실 정보 반환") @GetMapping("/{department}") - public ResponseEntity getOfficeByDepartment(@PathVariable("department") Department department){ - return ResponseUtils.success(officeService.getOfficeByDepartment(department)); + public ResponseEntity> getOfficeByDepartment(@PathVariable("department") Department department){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "학과별 사무실 정보 반환 성공", officeService.getOfficeByDepartment(department))); } @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 정보 수정") - @PatchMapping(value = "/{department}" , produces = "plain/text; charset=utf-8") - public ResponseEntity updateOffice(@PathVariable("department") Department department, @RequestBody @Valid OfficeUpdateRequestDto officeUpdateRequestDto){ + @PatchMapping(value = "/{department}") + public ResponseEntity> updateOffice(@PathVariable("department") Department department, @RequestBody @Valid OfficeUpdateRequestDto officeUpdateRequestDto){ officeService.updateOffice(department, officeUpdateRequestDto); - return ResponseUtils.successMsg("Office 정보 수정 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "Office 정보 수정 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 추가") - @PatchMapping(value = "/{department}/member", produces = "plain/text; charset=utf-8") - public ResponseEntity createOfficeMember(@PathVariable("department") Department department, - @RequestBody @Valid OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + @PatchMapping(value = "/{department}/member") + public ResponseEntity> createOfficeMember(@PathVariable("department") Department department, + @RequestBody @Valid OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ officeService.createOfficeMember(department, officeMemberCreateUpdateRequestDto); - return ResponseUtils.successMsg("Office Member 추가 완료"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "Office Member 추가 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 수정") - @PatchMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") - public ResponseEntity updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, - @RequestBody @Valid OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ + @PatchMapping(value = "/{department}/member/{num}") + public ResponseEntity> updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, + @RequestBody @Valid OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto){ officeService.updateOfficeMember(department, num, officeMemberCreateUpdateRequestDto); - return ResponseUtils.successMsg("Office Member 정보 수정 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "Office Member 정보 수정 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 삭제") - @DeleteMapping(value = "/{department}/member/{num}", produces = "plain/text; charset=utf-8") + @DeleteMapping(value = "/{department}/member/{num}") public ResponseEntity deleteOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num){ officeService.deleteOfficeMember(department,num); - return ResponseUtils.successMsg("Office Member 정보 삭제 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "Office Member 정보 삭제 완료", null)); } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index a09fd8b7..468e9545 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -1,7 +1,8 @@ package inu.codin.codin.domain.info.domain.professor.controller; import inu.codin.codin.common.Department; -import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorThumbnailResponseDto; @@ -14,8 +15,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/professor") @@ -26,39 +25,44 @@ public class ProfessorController { @Operation(summary = "교수 리스트 반환") @GetMapping("/{department}") - public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ - return ResponseUtils.success(professorService.getProfessorByDepartment(department)); + public ResponseEntity> getProfessorList(@PathVariable("department") Department department){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "교수 리스트 반환 완료", professorService.getProfessorByDepartment(department))); } @Operation(summary = "id값에 따른 교수 썸네일 반환") @GetMapping("/detail/{id}") - public ResponseEntity getProfessorThumbnail(@PathVariable("id") String id){ - return ResponseUtils.success(professorService.getProfessorThumbnail(id)); + public ResponseEntity> getProfessorThumbnail(@PathVariable("id") String id){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "교수 썸네일 반환 성공", professorService.getProfessorThumbnail(id))); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 새로운 교수 정보 생성") - @PostMapping(produces = "plain/text; charset=utf-8") - public ResponseEntity createProfessor(@RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + @PostMapping + public ResponseEntity> createProfessor(@RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ professorService.createProfessor(professorCreateUpdateRequestDto); - return ResponseUtils.successMsg("새로운 교수 정보 생성 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "새로운 교수 정보 생성 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 교수 정보 수정") - @PatchMapping(value = "/{id}", produces = "plain/text; charset=utf-8") - public ResponseEntity updateProfessor(@PathVariable("id") String id, @RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ + @PatchMapping(value = "/{id}") + public ResponseEntity> updateProfessor(@PathVariable("id") String id, @RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ professorService.updateProfessor(id, professorCreateUpdateRequestDto); - return ResponseUtils.successMsg("교수 정보 수정 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "교수 정보 수정 완료", null)); } @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation(summary = "[ADMIN, MANAGER] 교수 정보 삭제") - @DeleteMapping(value = "/{id}", produces = "plain/text; charset=utf-8") - public ResponseEntity deleteProfessor(@PathVariable("id") String id){ + @DeleteMapping(value = "/{id}") + public ResponseEntity> deleteProfessor(@PathVariable("id") String id){ professorService.deleteProfessor(id); - return ResponseUtils.successMsg("교수 정보 삭제 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "교수 정보 삭제 완료", null)); } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 0dd9eef6..b6094101 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.controller; -import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; @@ -8,10 +8,13 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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; @RestController -@RequestMapping(value = "/users", produces = "plain/text; charset=utf-8") +@RequestMapping(value = "/users") @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class UserController { @@ -20,9 +23,10 @@ public class UserController { @Operation(summary = "회원가입") @PostMapping("/signup") - public ResponseEntity signUpUser( + public ResponseEntity> signUpUser( @RequestBody @Valid UserCreateRequestDto userCreateRequestDto) { userService.createUser(userCreateRequestDto); - return ResponseUtils.successMsg("회원가입 성공"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "회원가입 성공", null)); } } \ No newline at end of file From fcb47068720d8b5c01b64ef472f24811390fe0a2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 17:26:52 +0900 Subject: [PATCH 0138/1002] =?UTF-8?q?refactor=20:=20SingleResponse,=20List?= =?UTF-8?q?Response=20=EB=B0=98=ED=99=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/response/ExceptionResponse.java | 13 +++++++++++++ .../codin/common/response/ListResponse.java | 17 +++++++++++++++++ .../codin/common/response/SingleResponse.java | 15 +++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java new file mode 100644 index 00000000..afa36599 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java @@ -0,0 +1,13 @@ +package inu.codin.codin.common.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ExceptionResponse extends CommonResponse { + + @Builder + public ExceptionResponse(String message, int code) { + super(false, code, message); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java new file mode 100644 index 00000000..1f2b1c11 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java @@ -0,0 +1,17 @@ +package inu.codin.codin.common.response; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ListResponse extends CommonResponse{ + List dataList; + + @Builder + public ListResponse(int code, String message, List dataList) { + super(true, code, message); + this.dataList = dataList; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java new file mode 100644 index 00000000..32955bcc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java @@ -0,0 +1,15 @@ +package inu.codin.codin.common.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class SingleResponse extends CommonResponse { + T data; + + @Builder + public SingleResponse(int code, String message, T data) { + super(true, code, message); + this.data = data; + } +} \ No newline at end of file From 6b83a62fd54fd130faba22474ac9c8cb882f7720 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 27 Nov 2024 17:33:02 +0900 Subject: [PATCH 0139/1002] =?UTF-8?q?refactor=20:=20ExceptionResponse=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/GlobalExceptionHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java index 6f81c408..fc4b9575 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java @@ -1,5 +1,7 @@ package inu.codin.codin.common; +import inu.codin.codin.common.response.ExceptionResponse; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -8,8 +10,9 @@ public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) - protected ResponseEntity handleException(Exception e) { - return ResponseUtils.error(e.getMessage()); + protected ResponseEntity handleException(Exception e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); } } From c6abe2c13428ebb8c352e7c4f3c5fd4dd6b04fef Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 27 Nov 2024 19:52:36 +0900 Subject: [PATCH 0140/1002] =?UTF-8?q?Fix=20:=20Cookie=20SameSite=20:=20Non?= =?UTF-8?q?e=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/JwtService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 275c138d..58e309b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -93,6 +93,8 @@ private void createBothToken(HttpServletResponse response) { // 쿠키에 새로운 Refresh Token 추가 Cookie refreshTokenCookie = new Cookie("RT", newToken.getRefreshToken()); refreshTokenCookie.setHttpOnly(true); + refreshTokenCookie.setAttribute("SameSite", "None"); // CORS 설정을 통해 SameSite=None 설정 필요 + refreshTokenCookie.setSecure(true); refreshTokenCookie.setPath("/"); response.addCookie(refreshTokenCookie); From 72c2a4891fca3a9ed81186493d091f8f924f560e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 28 Nov 2024 14:01:37 +0900 Subject: [PATCH 0141/1002] =?UTF-8?q?refactor=20:=20api=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/ChatController.java | 24 ------------------- .../codin/domain/chat/ChatRequestDto.java | 11 --------- .../controller/ChatRoomController.java | 2 +- .../chat/chatting/ChattingController.java | 6 +++-- 4 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java deleted file mode 100644 index 6893efa9..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatController.java +++ /dev/null @@ -1,24 +0,0 @@ -package inu.codin.codin.domain.chat; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.DestinationVariable; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestBody; - -@Controller -@RequiredArgsConstructor -@Slf4j -public class ChatController { - - private final SimpMessagingTemplate simpMessagingTemplate; - - @MessageMapping("/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 - @SendTo("/topic/{chatRoomId}") - public void chat(@DestinationVariable("chatRoomId") String id, @RequestBody ChatRequestDto chatRequestDto){ - log.info("Message [{}] send by member: {} to chatting room: {}", chatRequestDto.getContent(), chatRequestDto.getSenderId(), id); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java deleted file mode 100644 index 9463cac3..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/ChatRequestDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package inu.codin.codin.domain.chat; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ChatRequestDto { - private Long senderId; - private String content; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index a7b0fb71..da2932c8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -17,7 +17,7 @@ @RestController @RequiredArgsConstructor @Slf4j -@RequestMapping("/api/chats") +@RequestMapping("/chatroom") public class ChatRoomController { private final ChatRoomService chatRoomService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java index e5a95187..03396fde 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java @@ -13,9 +13,11 @@ @Slf4j public class ChattingController { - @MessageMapping("/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 - @SendTo("/topic/{chatRoomId}") + @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 + @SendTo("/queue/{chatRoomId}") public void chat(@DestinationVariable("chatRoomId") String id, @RequestBody ChattingRequestDto chattingRequestDto){ log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); } + + } From f22f95baae8cbddc7063298787b70adadb925d0c Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 28 Nov 2024 21:32:30 +0900 Subject: [PATCH 0142/1002] [SC-64] Rename : FcmMessageDto to FcmMessageUserDto --- .../domain/notification/service/NotificationService.java | 6 +++--- .../dto/{FcmMessageDto.java => FcmMessageUserDto.java} | 4 ++-- .../inu/codin/codin/infra/fcm/service/FcmService.java | 8 ++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) rename codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/{FcmMessageDto.java => FcmMessageUserDto.java} (79%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 176b2155..e6b5fa10 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,7 +3,7 @@ import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.infra.fcm.dto.FcmMessageDto; +import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.service.FcmService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,7 +24,7 @@ public class NotificationService { * @param user 메시지를 받을 사용자 */ public void sendFcmMessageToUser(String title, String body, UserEntity user) { - FcmMessageDto msgDto = FcmMessageDto.builder() + FcmMessageUserDto msgDto = FcmMessageUserDto.builder() .user(user) .title(title) .body(body) @@ -42,7 +42,7 @@ public void sendFcmMessageToUser(String title, String body, UserEntity user) { } // 알림 로그를 저장하는 로직 - private void saveNotificationLog(FcmMessageDto msgDto) { + private void saveNotificationLog(FcmMessageUserDto msgDto) { NotificationEntity notificationEntity = NotificationEntity.builder() .user(msgDto.getUser()) .type("push") diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java similarity index 79% rename from codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java rename to codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java index 5a7ab748..89a7a095 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java @@ -13,7 +13,7 @@ */ @Getter @Data -public class FcmMessageDto { +public class FcmMessageUserDto { private UserEntity user; private String title; @@ -22,7 +22,7 @@ public class FcmMessageDto { private Map data; @Builder - public FcmMessageDto(UserEntity user, String title, String body, String imageUrl, Map data) { + public FcmMessageUserDto(UserEntity user, String title, String body, String imageUrl, Map data) { this.user = user; this.title = title; this.body = body; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 3e3e4d9a..a582f2f7 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -5,7 +5,7 @@ import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; import inu.codin.codin.domain.notification.entity.NotificationPreference; -import inu.codin.codin.infra.fcm.dto.FcmMessageDto; +import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; import inu.codin.codin.infra.fcm.exception.FcmTokenNotFoundException; @@ -55,7 +55,7 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails use * FCM 메시지를 전송하는 로직 - 서버 내부 사용 * @param msgDto FCM 메시지 DTO */ - public void sendFcmMessage(FcmMessageDto msgDto) { + public void sendFcmMessage(FcmMessageUserDto msgDto) { // 유저의 알림 설정 조회 String email = msgDto.getUser().getEmail(); NotificationPreference userPreference = msgDto.getUser().getNotificationPreference(); @@ -95,4 +95,8 @@ public void sendFcmMessage(FcmMessageDto msgDto) { // todo : FCM Bulk 메시지 전송 로직 추가 // todo : FCM 토칙 기반 메세지 전송 로직 추가 - 공지사항, 학과별 알림, 게시글 내 모든 댓글 인원에게 알림 + public void sendFcmMessageByTopic() { + + } + } \ No newline at end of file From 9a937a4c5ebafaa5cdc157746eea44b1f69d560a Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 28 Nov 2024 21:34:33 +0900 Subject: [PATCH 0143/1002] =?UTF-8?q?[SC-64]=20Refactor=20:=20Getter=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java index 89a7a095..0ff7355a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java @@ -11,7 +11,6 @@ * Fcm 메시지 DTO * 서버 내부 로직에서 사용 */ -@Getter @Data public class FcmMessageUserDto { From 51a60495c8a8e11544a2694d78a9cfd87917e5e6 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 28 Nov 2024 21:34:56 +0900 Subject: [PATCH 0144/1002] =?UTF-8?q?[SC-64]=20Comment=20:=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java index 0ff7355a..9dbbe65e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java @@ -8,7 +8,7 @@ import java.util.Map; /** - * Fcm 메시지 DTO + * Fcm 메시지 DTO to User * 서버 내부 로직에서 사용 */ @Data From ad566c2d7238371355fd4d51e2f861d556501313 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 28 Nov 2024 21:51:49 +0900 Subject: [PATCH 0145/1002] =?UTF-8?q?[SC-64]=20Feat=20:=20Fcm=20Topic?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/NotificationService.java | 42 +++++++++++++++---- .../infra/fcm/dto/FcmMessageTopicDto.java | 29 +++++++++++++ .../codin/infra/fcm/service/FcmService.java | 40 ++++++++++++++---- 3 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageTopicDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index e6b5fa10..061dc467 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,12 +3,15 @@ import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.service.FcmService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Map; + @Service @RequiredArgsConstructor @Slf4j @@ -38,17 +41,38 @@ public void sendFcmMessageToUser(String title, String body, UserEntity user) { log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); } // 알림 로그 저장 - saveNotificationLog(msgDto); +// saveNotificationLog(msgDto); } - // 알림 로그를 저장하는 로직 - private void saveNotificationLog(FcmMessageUserDto msgDto) { - NotificationEntity notificationEntity = NotificationEntity.builder() - .user(msgDto.getUser()) - .type("push") - .message(msgDto.getBody()) - .priority("high") + public void sendFcmMessageToTopic(String title, String body, Map data, String imageUrl, String topic) { + FcmMessageTopicDto msgDto = FcmMessageTopicDto.builder() + .topic(topic) + .title(title) + .body(body) + .data(data) + .imageUrl(imageUrl) .build(); - notificationRepository.save(notificationEntity); + + // FCM 메시지 전송 + try { + fcmService.sendFcmMessageByTopic(msgDto); + log.info("[sendFcmMessage] 알림 전송 성공"); + } catch (Exception e) { + log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); + } + // 알림 로그 저장 +// saveNotificationLog(msgDto); } + + // todo : 알림로그 저장하는 방식에 대한 고찰 필요 + // 알림 로그를 저장하는 로직 +// private void saveNotificationLog(FcmMessageUserDto msgDto) { +// NotificationEntity notificationEntity = NotificationEntity.builder() +// .user(msgDto.getUser()) +// .type("push") +// .message(msgDto.getBody()) +// .priority("high") +// .build(); +// notificationRepository.save(notificationEntity); +// } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageTopicDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageTopicDto.java new file mode 100644 index 00000000..f727af03 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageTopicDto.java @@ -0,0 +1,29 @@ +package inu.codin.codin.infra.fcm.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +/** + * Fcm 메시지 DTO to Topic + * 서버 내부 로직에서 사용 + */ +@Data +public class FcmMessageTopicDto { + + private String topic; + private String title; + private String body; + private String imageUrl; + private Map data; + + @Builder + public FcmMessageTopicDto(String topic, String title, String body, String imageUrl, Map data) { + this.topic = topic; + this.title = title; + this.body = body; + this.imageUrl = imageUrl; + this.data = data; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index a582f2f7..1072df3a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -5,6 +5,7 @@ import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; import inu.codin.codin.domain.notification.entity.NotificationPreference; +import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; @@ -53,12 +54,12 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails use /** * FCM 메시지를 전송하는 로직 - 서버 내부 사용 - * @param msgDto FCM 메시지 DTO + * @param fcmMessageUserDto FCM 메시지 유저 DTO */ - public void sendFcmMessage(FcmMessageUserDto msgDto) { + public void sendFcmMessage(FcmMessageUserDto fcmMessageUserDto) { // 유저의 알림 설정 조회 - String email = msgDto.getUser().getEmail(); - NotificationPreference userPreference = msgDto.getUser().getNotificationPreference(); + String email = fcmMessageUserDto.getUser().getEmail(); + NotificationPreference userPreference = fcmMessageUserDto.getUser().getNotificationPreference(); // 유저의 FCM 토큰 조회 FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByEmail(email).orElseThrow(() @@ -73,12 +74,12 @@ public void sendFcmMessage(FcmMessageUserDto msgDto) { try { Message message = Message.builder() .setNotification(Notification.builder() - .setTitle(msgDto.getTitle()) - .setBody(msgDto.getBody()) - .setImage(msgDto.getImageUrl()) + .setTitle(fcmMessageUserDto.getTitle()) + .setBody(fcmMessageUserDto.getBody()) + .setImage(fcmMessageUserDto.getImageUrl()) .build()) .setToken(fcmToken) - .putAllData(msgDto.getData()) + .putAllData(fcmMessageUserDto.getData()) .build(); String response = FirebaseMessaging.getInstance().send(message); @@ -95,8 +96,29 @@ public void sendFcmMessage(FcmMessageUserDto msgDto) { // todo : FCM Bulk 메시지 전송 로직 추가 // todo : FCM 토칙 기반 메세지 전송 로직 추가 - 공지사항, 학과별 알림, 게시글 내 모든 댓글 인원에게 알림 - public void sendFcmMessageByTopic() { + /** + * FCM 메시지를 특정 토픽으로 전송하는 로직 - 서버 내부 로직 + * 브로드 캐스팅 알림을 위해 사용 + * @param fcmMessageTopicDto FCM 메시지 토픽 DTO + */ + public void sendFcmMessageByTopic(FcmMessageTopicDto fcmMessageTopicDto) { + // + try { + Message message = Message.builder() + .setNotification(Notification.builder() + .setTitle(fcmMessageTopicDto.getTitle()) + .setBody(fcmMessageTopicDto.getBody()) + .setImage(fcmMessageTopicDto.getImageUrl()) + .build()) + .setTopic(fcmMessageTopicDto.getTopic()) + .putAllData(fcmMessageTopicDto.getData()) + .build(); + String response = FirebaseMessaging.getInstance().send(message); + log.info("[sendFcmMessageByTopic] 알림 전송 성공 : {}", response); + } catch (FirebaseMessagingException e) { + log.error("[sendFcmMessageByTopic] 알림 전송 실패, errorCode : {}, msg : {}", e.getErrorCode(), e.getMessage()); + } } } \ No newline at end of file From c7624abfabe72dbfcbfb4171fb2cde89396c8132 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 29 Nov 2024 21:31:25 +0900 Subject: [PATCH 0146/1002] =?UTF-8?q?[SC-65]=20=20Refactoring=20:=20Like,?= =?UTF-8?q?=20Scrap=20Post=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/{ => post}/like/LikeController.java | 2 +- .../codin/codin/domain/{ => post}/like/LikeEntity.java | 2 +- .../codin/domain/{ => post}/like/LikeRepository.java | 2 +- .../codin/domain/{ => post}/like/LikeService.java | 2 +- .../codin/domain/{ => post}/scrap/ScrapController.java | 2 +- .../codin/domain/{ => post}/scrap/ScrapEntity.java | 2 +- .../codin/domain/{ => post}/scrap/ScrapRepository.java | 2 +- .../codin/domain/{ => post}/scrap/ScrapService.java | 2 +- .../inu/codin/codin/infra/redis/SyncScheduler.java | 10 ++++------ 9 files changed, 12 insertions(+), 14 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/like/LikeController.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/like/LikeEntity.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/like/LikeRepository.java (86%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/like/LikeService.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/scrap/ScrapController.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/scrap/ScrapEntity.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/scrap/ScrapRepository.java (86%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => post}/scrap/ScrapService.java (97%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java index ce3eec4a..a8a40d20 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.like; +package inu.codin.codin.domain.post.like; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java index dc1a834c..cd118697 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.like; +package inu.codin.codin.domain.post.like; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java similarity index 86% rename from codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java index 62bb77fd..baf63118 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.like; +package inu.codin.codin.domain.post.like; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java index 11f43560..314797fd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.like; +package inu.codin.codin.domain.post.like; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java index 0f98e4bf..d1ea37dd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.scrap; +package inu.codin.codin.domain.post.scrap; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapEntity.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapEntity.java index d4b692a9..d58ad7e5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.scrap; +package inu.codin.codin.domain.post.scrap; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java similarity index 86% rename from codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java index 444fd3da..c52efa0b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.scrap; +package inu.codin.codin.domain.post.scrap; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java index 5e523d53..0a9838db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.scrap; +package inu.codin.codin.domain.post.scrap; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 759ae0bc..a87edeba 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -1,13 +1,11 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.domain.like.LikeEntity; -import inu.codin.codin.domain.like.LikeRepository; -import inu.codin.codin.domain.like.LikeService; +import inu.codin.codin.domain.post.like.LikeEntity; +import inu.codin.codin.domain.post.like.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.service.PostService; -import inu.codin.codin.domain.scrap.ScrapEntity; -import inu.codin.codin.domain.scrap.ScrapRepository; +import inu.codin.codin.domain.post.scrap.ScrapEntity; +import inu.codin.codin.domain.post.scrap.ScrapRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; From 38a6937f4ba6f37bd549a727bb9172130508719a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 29 Nov 2024 21:38:54 +0900 Subject: [PATCH 0147/1002] [SC-65] Refactor : Post Status,Category --- .../domain/post/entity/PostCategory.java | 43 +++++++++++++++++++ .../codin/domain/post/entity/PostStatus.java | 17 ++++++++ 2 files changed, 60 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java new file mode 100644 index 00000000..3a49d95a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -0,0 +1,43 @@ +package inu.codin.codin.domain.post.entity; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; + +@Getter +public enum PostCategory { + REQUEST_STUDY("구해요_스터디"), + REQUEST_PROJECT("구해요_프로젝트"), + REQUEST_COMPETITION("구해요_공모전_대회"), + REQUEST_GROUP("구해요_소모임"), + COMMUNICATION_QUESTION("소통해요_질문"), + COMMUNICATION_JOB("소통해요_취업수기"), + COMMUNICATION_TIP("소통해요_꿀팁공유"), + EXTRACURRICULAR_OUTER("비교과_교외"), + EXTRACURRICULAR_INNER("비교과_교내"), + INFO_PROFESSOR("정보대소개_교수_연구실"), + INFO_PHONE_NUMBER("정보대소개_정보대전화번호"), + USED_BOOK("중고책"); + + private final String description; + + PostCategory(String description) { + this.description = description; + } + + @JsonValue + public String getDescription() { + return description; + } + + @JsonCreator + public static PostCategory fromDescription(String description) { + for (PostCategory category : PostCategory.values()) { + if (category.description.equals(description)) { + return category; + } + } + throw new IllegalArgumentException("Unknown description: " + description); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java new file mode 100644 index 00000000..7c38c080 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostStatus.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.post.entity; + +import lombok.Getter; + +@Getter +public enum PostStatus { + ACTIVE("활성"), + DISABLED("비활성"), + SUSPENDED("정지"); + + private final String description; + + PostStatus(String description) { + this.description = description; + } + +} \ No newline at end of file From bf8b331267e11ef4e1cd6d5045ef6a4b2430cbac Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 30 Nov 2024 02:57:11 +0900 Subject: [PATCH 0148/1002] =?UTF-8?q?[SC-68]=20=20Refactor=20:=20Post=20<-?= =?UTF-8?q?>=20Comment=20<->=20Reply=20DB=20Reference=20=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20crud=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/config/WebConfig.java | 23 ++ ...MultipartJackson2HttpMessageConverter.java | 35 ++++ .../comment/controller/CommentController.java | 60 ++++++ .../post/comment/entity/CommentEntity.java | 47 +++++ .../post/comment/entity/ReplyEntity.java | 44 ++++ .../comment/repository/CommentRepository.java | 13 ++ .../comment/repository/ReplyRepository.java | 11 + .../post/comment/service/CommentService.java | 161 ++++++++++++++ .../post/controller/CommentController.java | 81 ------- .../post/controller/PostController.java | 71 +++---- ...tDTO.java => CommentCreateRequestDTO.java} | 2 +- .../PostAnonymousUpdateRequestDTO.java | 12 ++ .../request/PostContentUpdateRequestDTO.java | 14 ++ .../dto/request/PostCreateRequestDTO.java | 38 ++++ .../request/PostStatusUpdateRequestDTO.java | 13 ++ .../dto/response/CommentsResponseDTO.java | 16 +- ...TO.java => PostWithCountsResponseDTO.java} | 17 +- .../response/PostWithDetailResponseDTO.java | 84 ++++++++ .../domain/post/entity/CommentEntity.java | 50 ----- .../codin/domain/post/entity/PostEntity.java | 74 +++---- .../domain/post/like/LikeController.java | 41 ++-- .../codin/domain/post/like/LikeService.java | 38 +--- .../domain/post/service/CommentService.java | 96 --------- .../domain/post/service/PostService.java | 197 ++++++++---------- 24 files changed, 749 insertions(+), 489 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/{CommentCreateRequsetDTO.java => CommentCreateRequestDTO.java} (90%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/{PostWithLikeAndScrapResponseDTO.java => PostWithCountsResponseDTO.java} (71%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java new file mode 100644 index 00000000..27c8112c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java @@ -0,0 +1,23 @@ +package inu.codin.codin.common.config; + +import inu.codin.codin.common.util.MultipartJackson2HttpMessageConverter; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter; + + public WebConfig(MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter) { + this.multipartJackson2HttpMessageConverter = multipartJackson2HttpMessageConverter; + } + + @Override + public void extendMessageConverters(List> converters) { + // 컨버터 리스트에 사용자 정의 컨버터 추가 + converters.add(multipartJackson2HttpMessageConverter); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java new file mode 100644 index 00000000..5fd59d8c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java @@ -0,0 +1,35 @@ +package inu.codin.codin.common.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; + +@Component +public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { + + /** + * "Content-Type: multipart/form-data" 헤더를 지원하는 HTTP 요청 변환기 + */ + public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java new file mode 100644 index 00000000..d6a46d9f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java @@ -0,0 +1,60 @@ +package inu.codin.codin.domain.post.comment.controller; + +import inu.codin.codin.domain.post.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.domain.post.comment.service.CommentService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/comments") +public class CommentController { + private final CommentService commentService; + + public CommentController(CommentService commentService) { + this.commentService = commentService; + } + + @Operation(summary = "댓글 추가") + @PostMapping("/{postId}") + public ResponseEntity addComment(@PathVariable String postId, + @RequestBody @Valid CommentCreateRequestDTO requestDTO) { + commentService.addComment(postId, requestDTO); + return ResponseEntity.status(HttpStatus.CREATED).body("댓글이 추가되었습니다."); + } + + @Operation(summary = "대댓글 추가") + @PostMapping("/{commentId}/replies") + public ResponseEntity addReply(@PathVariable String commentId, + @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { + commentService.addReply(commentId, requestDTO); + return ResponseEntity.status(HttpStatus.CREATED).body("대댓글이 추가되었습니다."); + } + + @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회") + @GetMapping("/post/{postId}") + public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { + List response = commentService.getCommentsByPostId(postId); + return ResponseEntity.ok(response); + } + + @Operation(summary = "댓글 삭제") + @DeleteMapping("/{commentId}") + public ResponseEntity deleteComment(@PathVariable String commentId) { + commentService.deleteComment(commentId); + return ResponseEntity.status(HttpStatus.OK).body("댓글이 삭제되었습니다."); + } + + @Operation(summary = "대댓글 삭제") + @DeleteMapping("/replies/{replyId}") + public ResponseEntity deleteReply(@PathVariable String replyId) { + commentService.deleteReply(replyId); + return ResponseEntity.status(HttpStatus.OK).body("대댓글이 삭제되었습니다."); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java new file mode 100644 index 00000000..9d622a04 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java @@ -0,0 +1,47 @@ +package inu.codin.codin.domain.post.comment.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.ArrayList; +import java.util.List; + +@Document(collection = "comments") +@Getter +public class CommentEntity extends BaseTimeEntity { + @Id @NotBlank + private String commentId; + + private String postId; //게시글 ID 참조 + private String userId; + private String content; + private boolean isDeleted = false; // Soft delete 상태 + + private int likeCount = 0; // 좋아요 수 (Redis에서 관리) + + @Builder + public CommentEntity(String commentId, String postId, String userId, String content) { + this.commentId = commentId; + this.postId = postId; + this.userId = userId; + this.content = content; + } + + // Soft Delete + public void softDelete() { + this.isDeleted = true; + this.delete(); + } + + + //좋아요 수 업데이트 + public void updateLikeCount(int likeCount) { + this.likeCount=likeCount; + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java new file mode 100644 index 00000000..ec2558f7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java @@ -0,0 +1,44 @@ +package inu.codin.codin.domain.post.comment.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "replies") +@Getter +public class ReplyEntity extends BaseTimeEntity { + @Id + @NotBlank + private String replyId; + + private String commentId; // 댓글 ID 참조 + private String userId; // 작성자 ID + private String content; + private boolean isDeleted = false; + + private int likeCount = 0; // 좋아요 카운트 + + @Builder + public ReplyEntity(String replyId, String commentId, String userId, String content, int likeCount) { + this.replyId = replyId; + this.commentId = commentId; + this.userId = userId; + this.content = content; + this.likeCount = likeCount; + } + + // Soft Delete + public void softDelete() { + this.isDeleted = true; + this.delete(); + } + + //좋아요 수 업데이트 + public void updateLikeCount(int likeCount) { + this.likeCount=likeCount; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java new file mode 100644 index 00000000..5e3ca707 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.comment.repository; + +import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import jakarta.validation.constraints.NotBlank; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface CommentRepository extends MongoRepository { + List findByPostId(String postId); + + int countByPostId(@NotBlank String postId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java new file mode 100644 index 00000000..b9d42416 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.post.comment.repository; + + +import inu.codin.codin.domain.post.comment.entity.ReplyEntity; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface ReplyRepository extends MongoRepository { + List findByCommentId(String commentId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java new file mode 100644 index 00000000..dbf4ec53 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java @@ -0,0 +1,161 @@ +package inu.codin.codin.domain.post.comment.service; + +import inu.codin.codin.domain.post.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.comment.repository.ReplyRepository; +import inu.codin.codin.infra.redis.RedisService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentService { + + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ReplyRepository replyRepository; + private final RedisService redisService; + + // 댓글 추가 + public void addComment(String postId, CommentCreateRequestDTO requestDTO) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + CommentEntity comment = CommentEntity.builder() + .postId(postId) + .userId(requestDTO.getUserId()) + .content(requestDTO.getContent()) + .build(); + + commentRepository.save(comment); + + // 댓글 수 증가 + post.updateCommentCount(post.getCommentCount() + 1); + postRepository.save(post); + log.info("댓글 추가완료 postId: {}.", postId); + } + + // 대댓글 추가 + public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { + CommentEntity comment = commentRepository.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + PostEntity post = postRepository.findById(comment.getPostId()) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + ReplyEntity reply = ReplyEntity.builder() + .commentId(commentId) + .userId(requestDTO.getUserId()) + .content(requestDTO.getContent()) + .build(); + + replyRepository.save(reply); + + // 댓글 수 증가 (대댓글도 댓글 수에 포함) + log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); + post.updateCommentCount(post.getCommentCount() + 1); + postRepository.save(post); + log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); + } + + // 댓글 삭제 (Soft Delete) + public void deleteComment(String commentId) { + CommentEntity comment = commentRepository.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + + if (comment.isDeleted()) { + throw new IllegalArgumentException("이미 삭제된 댓글입니다."); + } + + // 댓글의 대댓글 조회 + List replies = replyRepository.findByCommentId(commentId); + + // 대댓글 Soft Delete 처리 + replies.forEach(reply -> { + if (!reply.isDeleted()) { + reply.softDelete(); + replyRepository.save(reply); + } + }); + + // 댓글 Soft Delete 처리 + comment.softDelete(); + commentRepository.save(comment); + + // 댓글 수 감소 (댓글 + 대댓글 수만큼 감소) + PostEntity post = postRepository.findById(comment.getPostId()) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + post.updateCommentCount(post.getCommentCount() - (1 + replies.size())); + postRepository.save(post); + + log.info("삭제된 commentId: {} , 대댓글 {} . 총 삭제 수: {} postId: {}", + commentId, replies.size(), (1 + replies.size()), post.getPostId()); + } + + // 대댓글 삭제 (Soft Delete) + public void deleteReply(String replyId) { + ReplyEntity reply = replyRepository.findById(replyId) + .orElseThrow(() -> new IllegalArgumentException("대댓글을 찾을 수 없습니다.")); + + if (reply.isDeleted()) { + throw new IllegalArgumentException("이미 삭제된 대댓글입니다."); + } + + // 대댓글 삭제 + reply.softDelete(); + replyRepository.save(reply); + + // 댓글 수 감소 (대댓글도 댓글 수에서 감소) + CommentEntity comment = commentRepository.findById(reply.getCommentId()) + .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + + PostEntity post = postRepository.findById(comment.getPostId()) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + post.updateCommentCount(post.getCommentCount() - 1); + postRepository.save(post); + + log.info("대댓글 성공적 삭제 replyId: {} , postId: {}.", replyId, post.getPostId()); + } + + // 특정 게시물의 댓글 및 대댓글 조회 + public List getCommentsByPostId(String postId) { + List comments = commentRepository.findByPostId(postId); + + return comments.stream() + .filter(comment -> !comment.isDeleted()) + .map(comment -> new CommentsResponseDTO( + comment.getCommentId(), + comment.getUserId(), + comment.getContent(), + getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 + redisService.getLikeCount("comment", comment.getCommentId()) // 댓글 좋아요 수 + )) + .collect(Collectors.toList()); + } + + // 특정 댓글의 대댓글 조회 + private List getRepliesByCommentId(String commentId) { + List replies = replyRepository.findByCommentId(commentId); + + return replies.stream() + .filter(reply -> !reply.isDeleted()) + .map(reply -> new CommentsResponseDTO( + reply.getReplyId(), + reply.getUserId(), + reply.getContent(), + List.of(), // 대댓글은 하위 대댓글이 없음 + redisService.getLikeCount("reply", reply.getReplyId()) + )) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java deleted file mode 100644 index 51eed72f..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/CommentController.java +++ /dev/null @@ -1,81 +0,0 @@ -package inu.codin.codin.domain.post.controller; - -import inu.codin.codin.domain.post.dto.request.CommentCreateRequsetDTO; -import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostWithCommentsResponseDTO; -import inu.codin.codin.domain.post.service.CommentService; -import inu.codin.codin.domain.post.service.PostService; -import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/comments") -public class CommentController { - private final CommentService commentService; - private final PostService postService; - - public CommentController(CommentService commentService, PostService postService) { - this.commentService = commentService; - this.postService = postService; - } - - - @Operation( - summary = "댓글 추가" - ) - @PostMapping("/{postId}") - public ResponseEntity addComment(@PathVariable String postId, @RequestBody @Valid CommentCreateRequsetDTO requestDTO) { - commentService.addComment(postId, requestDTO); - return ResponseEntity.status(HttpStatus.CREATED).body("댓글이 추가되었습니다."); - } - - @Operation( - summary = "대댓글 추가" - ) - @PostMapping("/{postId}/{parentCommentId}") - public ResponseEntity addReply(@PathVariable String postId, @PathVariable String parentCommentId, @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { - commentService.addReply(postId, parentCommentId, requestDTO); - return ResponseEntity.status(HttpStatus.CREATED).body("대댓글이 추가되었습니다."); - } - - @Operation( - summary = "해당 유저가 남긴 댓글 및 대댓글 조회" - ) - @GetMapping("/{userId}") - public ResponseEntity> getPostWithComments(@PathVariable String userId) { - List response = commentService.getCommentsByUser(userId); - return ResponseEntity.ok(response); - } - @Operation( - summary = "해당 사용자 게시물 전체 조회" - ) - @GetMapping("/user/{userId}") - public ResponseEntity> getAllPosts(@PathVariable String userId) { - List posts = postService.getAllUserPostsAndComments(userId); - return ResponseEntity.status(HttpStatus.OK).body(posts); - } - - @Operation(summary = "댓글 삭제") - @DeleteMapping("/{postId}/{commentId}") - public ResponseEntity deleteComment(@PathVariable String postId, @PathVariable String commentId) { - commentService.deleteComment(postId, commentId); - return ResponseEntity.status(HttpStatus.OK).body("댓글이 삭제되었습니다."); - } - - @Operation(summary = "대댓글 삭제") - @DeleteMapping("/{postId}/{parentCommentId}/{replyId}") - public ResponseEntity deleteReply(@PathVariable String postId, @PathVariable String parentCommentId, @PathVariable String replyId) { - commentService.deleteReply(postId, parentCommentId, replyId); - return ResponseEntity.status(HttpStatus.OK).body("대댓글이 삭제되었습니다."); - } - - - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index d9f293d2..6d36b7ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,10 +1,11 @@ package inu.codin.codin.domain.post.controller; +import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; +import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostWithLikeAndScrapResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -32,7 +33,7 @@ public PostController(PostService postService) { ) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createPost( - @RequestPart("postContent") @Valid PostCreateReqDTO postCreateReqDTO, + @RequestPart("postContent") @Valid PostCreateRequestDTO postCreateRequestDTO, @RequestPart(value = "postImages", required = false) List postImages) { // postImages가 null이면 빈 리스트로 처리 @@ -40,7 +41,7 @@ public ResponseEntity createPost( postImages = List.of(); } - postService.createPost(postCreateReqDTO, postImages); + postService.createPost(postCreateRequestDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED).body("게시물 작성 성공"); } @@ -67,12 +68,24 @@ public ResponseEntity updatePostStatus( postService.updatePostStatus(postId, requestDTO); return ResponseEntity.status(HttpStatus.OK).body("게시물 상태 수정 성공"); } + + + @Operation(summary = "게시물 익명 설정 수정") + @PatchMapping("/{postId}/anonymous") + public ResponseEntity updatePostAnonymous( + @PathVariable String postId, + @RequestBody @Valid PostAnonymousUpdateRequestDTO requestDTO) { + postService.updatePostAnonymous(postId, requestDTO); + return ResponseEntity.status(HttpStatus.OK).body("게시물 익명 설정이 수정되었습니다."); + } + + @Operation( summary = "삭제되지 않은 모든 게시물 조회" ) @GetMapping("") - public ResponseEntity> getAllPosts() { - List posts = postService.getAllPosts(); + public ResponseEntity> getAllPosts() { + List posts = postService.getAllPosts(); return ResponseEntity.ok(posts); } @@ -80,59 +93,39 @@ public ResponseEntity> getAllPosts() { summary = "해당 사용자 게시물 전체 조회" ) @GetMapping("/user/{userId}") - public ResponseEntity> getAllPosts(@PathVariable String userId) { - List posts = postService.getAllUserPosts(userId); - return ResponseEntity.status(HttpStatus.OK).body(posts); + public ResponseEntity> getAllUserPosts(@PathVariable String userId) { + List posts = postService.getAllUserPosts(userId); + return ResponseEntity.ok(posts); } @Operation( summary = "해당 게시물 상세 조회" ) @GetMapping("/{postId}") - public ResponseEntity getPost(@PathVariable String postId) { - PostDetailResponseDTO post = postService.getPost(postId); - return ResponseEntity.status(HttpStatus.OK).body(post); - } - - @Operation( - summary = "해당 게시물 좋아요,스크랩 포함 조회" - ) - @GetMapping("/{postId}/details") - public ResponseEntity getPostDetail(@PathVariable String postId) { - PostWithLikeAndScrapResponseDTO post = postService.getPostWithLikeAndScrap(postId); - return ResponseEntity.status(HttpStatus.OK).body(post); + public ResponseEntity getPostWithDetail(@PathVariable String postId) { + PostWithDetailResponseDTO post = postService.getPostWithDetail(postId); + return ResponseEntity.ok(post); } - @Operation( - summary = "해당 이미지 삭제" - ) + @Operation(summary = "게시물 이미지 삭제") @DeleteMapping("/{postId}/images") public ResponseEntity deletePostImage( @PathVariable String postId, - @RequestParam String imageUrls) { - postService.deletePostImage(postId, imageUrls); - return ResponseEntity.status(HttpStatus.OK).body("이미지 삭제"); + @RequestParam String imageUrl) { + postService.deletePostImage(postId, imageUrl); + return ResponseEntity.status(HttpStatus.OK).body("이미지가 삭제되었습니다."); } - - @Operation( - summary = "해당 게시물 Soft 삭제" - ) + @Operation(summary = "게시물 삭제 (Soft Delete)") @DeleteMapping("/{postId}") public ResponseEntity softDeletePost(@PathVariable String postId) { postService.softDeletePost(postId); - return ResponseEntity.status(HttpStatus.OK).body("게시물 안전삭제"); + return ResponseEntity.status(HttpStatus.OK).body("게시물이 삭제되었습니다."); } - //익명 수정 불가? - - - - - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequestDTO.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequestDTO.java index 721ecd13..9de41764 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequsetDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequestDTO.java @@ -5,7 +5,7 @@ import lombok.Data; @Data -public class CommentCreateRequsetDTO { +public class CommentCreateRequestDTO { @Schema(description = "유저 ID", example = "111111") @NotBlank private String userId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java new file mode 100644 index 00000000..01c1d5d9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.post.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PostAnonymousUpdateRequestDTO { + @Schema(description = "익명 여부", example = "true") + @NotNull + private boolean isAnonymous; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java new file mode 100644 index 00000000..a1cc6916 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.post.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PostContentUpdateRequestDTO { + @Schema(description = "게시물 내용", example = "Updated content") + @NotBlank + private String content; + + //이미지 별도 Multipart (RequestPart 사용) +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java new file mode 100644 index 00000000..b50ff5db --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -0,0 +1,38 @@ +package inu.codin.codin.domain.post.dto.request; + +import inu.codin.codin.domain.post.entity.PostCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Data +public class PostCreateRequestDTO { + + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private String userId; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private String content; + + //이미지 별도 Multipart (RequestPart 사용) + + @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "true") + @NotNull + private boolean isAnonymous; + + @Schema(description = "게시물 종류", example = "구해요_스터디") + @NotNull + private PostCategory postCategory; + //STATUS 필드 - DEFAULT :: ACTIVE + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java new file mode 100644 index 00000000..62920680 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.dto.request; + +import inu.codin.codin.domain.post.entity.PostStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PostStatusUpdateRequestDTO { + @Schema(description = "게시물 상태", example = "ACTIVE") + @NotNull + private PostStatus postStatus; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java index e09b2706..ecc3a89e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java @@ -8,25 +8,29 @@ @Data public class CommentsResponseDTO { - @Schema(description = "댓글 ID", example = "111111") + @Schema(description = "댓글 또는 대댓글 ID", example = "111111") @NotBlank private String commentId; - @Schema(description = "유저 ID", example = "111111") + @Schema(description = "유저 ID", example = "user123") @NotBlank private String userId; - @Schema(description = "댓글 내용", example = "111111") + @Schema(description = "댓글 또는 대댓글 내용", example = "This is a comment.") @NotBlank private String content; - @Schema(description = "대댓글", example = "111111") + @Schema(description = "대댓글 리스트", example = "[...]") private List replies; - public CommentsResponseDTO(String commentId, String userId, String content, List replies) { + @Schema(description = "좋아요 수", example = "5") + private int likeCount; + + public CommentsResponseDTO(String commentId, String userId, String content, List replies, int likeCount) { this.commentId = commentId; this.userId = userId; this.content = content; this.replies = replies; + this.likeCount = likeCount; } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCountsResponseDTO.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCountsResponseDTO.java index a1c23e44..60e8fe3c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithLikeAndScrapResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCountsResponseDTO.java @@ -5,12 +5,14 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.List; @Data -public class PostWithLikeAndScrapResponseDTO { +public class PostWithCountsResponseDTO { @Schema(description = "유저 ID", example = "111111") @NotBlank private String userId; @@ -38,13 +40,16 @@ public class PostWithLikeAndScrapResponseDTO { @NotNull private boolean isAnonymous; - @Schema(description = "댓글 및 대댓글", example = "0") - private List comments; + @Schema(description = "댓글 및 대댓글 count", example = "0") + private int commentCount; + @Schema(description = "좋아요 count", example = "0") private int likeCount; + + @Schema(description = "스크랩 count", example = "0") private int scrapCount; - public PostWithLikeAndScrapResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, List comments, int likeCount, int scrapCount) { + public PostWithCountsResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount) { this.userId = userId; this.postId = postId; this.content = content; @@ -52,8 +57,8 @@ public PostWithLikeAndScrapResponseDTO(String userId, String postId, String cont this.postCategory = postCategory; this.postImageUrl = postImageUrls; this.isAnonymous = isAnonymous; - this.comments = comments; + this.commentCount = commentCount; this.likeCount = likeCount; this.scrapCount = scrapCount; } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java new file mode 100644 index 00000000..b4abcb67 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java @@ -0,0 +1,84 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +public class PostWithDetailResponseDTO { + + @Schema(description = "게시물 작성자 ID", example = "user123") + @NotBlank + private String userId; + + @Schema(description = "게시물 ID", example = "post123") + @NotBlank + private String postId; + + @Schema(description = "게시물 내용", example = "This is a detailed content of the post.") + @NotBlank + private String content; + + @Schema(description = "게시물 제목", example = "Post Title") + @NotBlank + private String title; + + @Schema(description = "게시물 카테고리", example = "구해요") + @NotNull + private PostCategory postCategory; + + @Schema(description = "게시물 상태", example = "ACTIVE") + @NotNull + private PostStatus postStatus; + + @Schema(description = "게시물 이미지 URL 리스트", example = "[\"image1.jpg\", \"image2.jpg\"]") + private List postImageUrls; + + @Schema(description = "게시물 익명 여부", example = "true") + private boolean isAnonymous; + + @Schema(description = "댓글 및 대댓글 데이터") + private List comments; + + @Schema(description = "좋아요 수", example = "10") + private int likeCount; + + @Schema(description = "스크랩 수", example = "5") + private int scrapCount; + + // Constructor for partial initialization + public PostWithDetailResponseDTO( + String userId, + String postId, + String content, + String title, + PostCategory postCategory, + PostStatus postStatus, + List postImageUrls, + boolean isAnonymous, + List comments, + int likeCount, + int scrapCount + ) { + this.userId = userId; + this.postId = postId; + this.content = content; + this.title = title; + this.postCategory = postCategory; + this.postStatus = postStatus; + this.postImageUrls = postImageUrls; + this.isAnonymous = isAnonymous; + this.comments = comments; + this.likeCount = likeCount; + this.scrapCount = scrapCount; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java deleted file mode 100644 index fc0fc95d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/CommentEntity.java +++ /dev/null @@ -1,50 +0,0 @@ -package inu.codin.codin.domain.post.entity; - -import inu.codin.codin.common.BaseTimeEntity; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -import java.util.ArrayList; -import java.util.List; - -@Getter -public class CommentEntity extends BaseTimeEntity { - @Id //몽고디비에선 독립된 컬렉션이 아닐 경우 자동으로 ID 값을 생성하지 않음 - private String commentId; - private String userId; - private String content; - private boolean isDeleted = false; // Soft delete 상태 - private List replies = new ArrayList<>(); - @Builder - public CommentEntity(String commentId, String userId, String content, List replies) { - this.commentId = commentId; - this.userId = userId; - this.content = content; - this.replies = replies != null ? replies : new ArrayList<>(); // null 체크 후 초기화 - - } - - // Soft Delete - public void softDelete() { - this.isDeleted = true; - this.delete(); - } - - public void softDeleteReply(String replyId) { - this.replies.stream() - .filter(reply -> reply.getCommentId().equals(replyId)) - .findFirst() - .ifPresent(CommentEntity::softDelete); - } - - // 대댓글 추가 - public void addReply(CommentEntity reply) { - if (this.replies == null) { - this.replies = new ArrayList<>(); // null 방지 - } - this.replies.add(reply); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index d69a7403..86f43d51 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -19,39 +19,35 @@ public class PostEntity extends BaseTimeEntity { private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 - private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) - private String title; - private String content; - - private List postImageUrls; - + private List postImageUrls = new ArrayList<>(); private boolean isAnonymous; - private boolean isDeleted = false; + private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) - private List comments = new ArrayList<>(); - - private int likeCount = 0; // 좋아요 카운트 - - private int scrapCount = 0; // 스크랩 카운트 + private int commentCount = 0; // 댓글 + 대댓글 카운트 + private int likeCount = 0; // 좋아요 카운트 (redis) + private int scrapCount = 0; // 스크랩 카운트 (redis) @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, boolean isAnonymous, List postImageUrls, PostStatus postStatus, List comments, Integer likeCount, Integer scrapCount) { + public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, boolean isDeleted, PostStatus postStatus, int commentCount, int likeCount, int scrapCount) { this.postId = postId; + this.userId = userId; - this.postCategory = postCategory; this.title = title; this.content = content; - this.isAnonymous = isAnonymous; this.postImageUrls = postImageUrls; + this.isAnonymous = isAnonymous; + this.isDeleted = isDeleted; + this.postCategory = postCategory; this.postStatus = postStatus; - this.comments = comments != null ? comments : new ArrayList<>(); - this.likeCount = likeCount != null ? likeCount : 0; // 기본값 설정 - this.scrapCount = scrapCount != null ? scrapCount : 0; // 기본값 설정 + + this.commentCount = commentCount; + this.likeCount = likeCount; + this.scrapCount = scrapCount; } public void updatePostContent(String content, List postImageUrls) { @@ -59,6 +55,13 @@ public void updatePostContent(String content, List postImageUrls) { this.postImageUrls = postImageUrls; } + public void updatePostAnonymous(boolean isAnonymous) { + if (this.isAnonymous == isAnonymous) { + throw new IllegalStateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); + } + this.isAnonymous = isAnonymous; + } + public void updatePostStatus(PostStatus postStatus) { if (this.postStatus == postStatus) { throw new IllegalStateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); @@ -79,41 +82,16 @@ public void softDeletePost() { } - // 댓글 추가 - public void addComment(CommentEntity comment) { - if (this.comments == null) { - this.comments = new ArrayList<>(); // null 방지 - } - this.comments.add(comment); - } - // 댓글 삭제 (Soft Delete) - public void softDeleteComment(String commentId) { - this.comments.stream() - .filter(comment -> comment.getCommentId().equals(commentId)) - .findFirst() - .ifPresent(CommentEntity::softDelete); - } - public void softDeleteReply(String parentCommentId, String replyId) { - this.comments.stream() - .filter(comment -> comment.getCommentId().equals(parentCommentId)) - .findFirst() - .ifPresent(parentComment -> parentComment.softDeleteReply(replyId)); + //댓글+대댓글 수 업데이트 + public void updateCommentCount(int commentCount) { + this.commentCount=commentCount; } - - // 대댓글 추가 - public void addReply(String parentCommentId, CommentEntity reply) { - this.comments.stream() - .filter(comment -> comment.getCommentId().equals(parentCommentId)) - .findFirst() - .ifPresent(parentComment -> parentComment.addReply(reply)); - } - - //좋아요 업데이트 + //좋아요 수 업데이트 public void updateLikeCount(int likeCount) { this.likeCount=likeCount; } - //스크랩 업데이트 + //스크랩 수 업데이트 public void updateScrapCount(int scrapCount) { this.scrapCount=likeCount; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java index a8a40d20..d9443fa6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java @@ -6,36 +6,33 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/likes/{postId}") +@RequestMapping("/api/likes") @RequiredArgsConstructor public class LikeController { private final LikeService likeService; - @Operation( - summary = "좋아요 추가" - ) - @PostMapping("/{userId}") - public ResponseEntity likePost(@PathVariable String postId, @PathVariable String userId) { - likeService.addLike(postId, userId); + @Operation(summary = "게시물, 댓글, 대댓글 좋아요 추가"+ + "entityType = post,comment,reply" + +"entityId = postId, commentId, replyId") + @PostMapping("/{entityType}/{entityId}/{userId}") + public ResponseEntity likeEntity( + @PathVariable String entityType, + @PathVariable String entityId, + @PathVariable String userId) { + likeService.addLike(entityType, entityId, userId); return ResponseEntity.ok("Liked successfully"); } - @Operation( - summary = "좋아요 삭제" - ) - @DeleteMapping("/{userId}") - public ResponseEntity unlikePost(@PathVariable String postId, @PathVariable String userId) { - likeService.removeLike(postId, userId); + @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 " + + "entityType = post,comment,reply" + +"entityId = postId, commentId, replyId") + @DeleteMapping("/{entityType}/{entityId}/{userId}") + public ResponseEntity unlikeEntity( + @PathVariable String entityType, + @PathVariable String entityId, + @PathVariable String userId) { + likeService.removeLike(entityType, entityId, userId); return ResponseEntity.ok("Unliked successfully"); } - - @Operation( - summary = "해당 게시물 좋아요 조회" - ) - @GetMapping - public ResponseEntity getLikeCount(@PathVariable String postId) { - long likeCount = likeService.getLikeCount(postId); - return ResponseEntity.ok(likeCount); - } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java index 314797fd..5539f3a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -10,40 +11,13 @@ @RequiredArgsConstructor public class LikeService { - private final RedisTemplate redisTemplate; - private final PostRepository postRepository; // MongoDB의 게시글 저장소 + private final RedisService redisService; - public void addLike(String postId, String userId) { - String redisKey = "post:likes:" + postId; - - if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId))) { - throw new IllegalStateException("이미 해당 게시물에 좋아요를 눌렀습니다."); - } - - // Redis에 좋아요 추가 - redisTemplate.opsForSet().add(redisKey, userId); - - } - - public void removeLike(String postId, String userId) { - String redisKey = "post:likes:" + postId; - - if (!Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId))) { - throw new IllegalStateException("해당 게시물에 좋아요를 누르지 않았습니다."); - } - - // Redis에서 좋아요 제거 - redisTemplate.opsForSet().remove(redisKey, userId); - - } - - public long getLikeCount(String postId) { - String redisKey = "post:likes:" + postId; - return redisTemplate.opsForSet().size(redisKey); + public void addLike(String entityType, String entityId, String userId) { + redisService.addLike(entityType, entityId, userId); } - public boolean isPostLikedByUser(String postId, String userId) { - String redisKey = "post:likes:" + postId; - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId)); + public void removeLike(String entityType, String entityId, String userId) { + redisService.removeLike(entityType, entityId, userId); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java deleted file mode 100644 index 56ee7ad1..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/CommentService.java +++ /dev/null @@ -1,96 +0,0 @@ -package inu.codin.codin.domain.post.service; - -import inu.codin.codin.domain.post.dto.request.CommentCreateRequsetDTO; -import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; -import inu.codin.codin.domain.post.entity.CommentEntity; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CommentService { - private final PostRepository postRepository; - - public void addComment(String postId, CommentCreateRequsetDTO requestDTO) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - CommentEntity comment = CommentEntity.builder() - .commentId(UUID.randomUUID().toString()) // UUID로 고유 ID 생성 - .userId(requestDTO.getUserId()) - .content(requestDTO.getContent()) - .build(); - post.addComment(comment); - postRepository.save(post); - log.info("Comment added successfully to postId: {}. Request data: {}", postId, requestDTO); - } - - public void addReply(String postId, String parentCommentId, ReplyCreateRequestDTO requestDTO) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - CommentEntity reply = CommentEntity.builder() - .commentId(UUID.randomUUID().toString()) // UUID로 고유 ID 생성 - .userId(requestDTO.getUserId()) - .content(requestDTO.getContent()) - .build(); - post.addReply(parentCommentId, reply); - postRepository.save(post); - } - - public List getCommentsByUser(String userId) { - List posts = postRepository.findAll(); // 모든 게시물 조회 - List userComments = new ArrayList<>(); - - for (PostEntity post : posts) { - // 게시물의 댓글 중 해당 사용자가 작성한 댓글 필터링 - post.getComments().stream() - .filter(comment -> comment.getUserId().equals(userId) && !comment.isDeleted()) - .map(this::convertToDTO) // CommentEntity -> CommentsResponseDTO - .forEach(userComments::add); - - // 게시물의 대댓글 중 해당 사용자가 작성한 대댓글 필터링 - post.getComments().forEach(comment -> comment.getReplies().stream() - .filter(reply -> reply.getUserId().equals(userId) && !reply.isDeleted()) - .map(this::convertToDTO) // CommentEntity -> CommentsResponseDTO - .forEach(userComments::add)); - } - - return userComments; - } - - private CommentsResponseDTO convertToDTO(CommentEntity comment) { - return new CommentsResponseDTO( - comment.getCommentId(), - comment.getUserId(), - comment.getContent(), - comment.getReplies().stream() - .filter(reply -> !reply.isDeleted()) // 삭제되지 않은 대댓글만 변환 - .map(this::convertToDTO) - .collect(Collectors.toList()) // 대댓글도 재귀적으로 DTO로 변환 - ); - } - - - public void deleteComment(String postId, String commentId) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - post.softDeleteComment(commentId); - postRepository.save(post); - } - - public void deleteReply(String postId, String parentCommentId, String replyId) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - post.softDeleteReply(parentCommentId, replyId); - postRepository.save(post); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 9cac898b..b5647b99 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,23 +1,27 @@ package inu.codin.codin.domain.post.service; +import inu.codin.codin.domain.post.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostWithCommentsResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostWithLikeAndScrapResponseDTO; -import inu.codin.codin.domain.post.entity.CommentEntity; +import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; +import inu.codin.codin.domain.post.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.comment.repository.ReplyRepository; + +import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; -import inu.codin.codin.domain.post.dto.request.PostCreateReqDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; + import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -25,8 +29,10 @@ @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ReplyRepository replyRepository; private final S3Service s3Service; - private final RedisTemplate redisTemplate; + private final RedisService redisService; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -37,26 +43,24 @@ private List handleImageUpload(List postImages) { } - public void createPost(PostCreateReqDTO postCreateReqDTO, List postImages) { - validateCreatePostRequest(postCreateReqDTO); + public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + validateCreatePostRequest(postCreateRequestDTO); List imageUrls = handleImageUpload(postImages); PostEntity postEntity = PostEntity.builder() - .userId(postCreateReqDTO.getUserId()) - .content(postCreateReqDTO.getContent()) - .title(postCreateReqDTO.getTitle()) - .postCategory(postCreateReqDTO.getPostCategory()) + .userId(postCreateRequestDTO.getUserId()) + .title(postCreateRequestDTO.getTitle()) + .content(postCreateRequestDTO.getContent()) //이미지 Url List 저장 .postImageUrls(imageUrls) - .isAnonymous(postCreateReqDTO.isAnonymous()) - + .isAnonymous(postCreateRequestDTO.isAnonymous()) + .postCategory(postCreateRequestDTO.getPostCategory()) //Default Status = Active .postStatus(PostStatus.ACTIVE) - .comments(new ArrayList<>()) .build(); postRepository.save(postEntity); } @@ -68,11 +72,15 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request List imageUrls = handleImageUpload(postImages); - //이미지 삭제 로직 - post.updatePostContent(requestDTO.getContent(), imageUrls); + postRepository.save(post); } + public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); + post.updatePostAnonymous(requestDTO.isAnonymous()); + } public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findById(postId) @@ -80,10 +88,13 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT post.updatePostStatus(requestDTO.getPostStatus()); } - public List getAllPosts() { + + // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + public List getAllPosts() { List posts = postRepository.findALlNotDeleted(); + return posts.stream() - .map(post -> new PostDetailResponseDTO( + .map(post -> new PostWithCountsResponseDTO( post.getUserId(), post.getPostId(), post.getContent(), @@ -91,15 +102,21 @@ public List getAllPosts() { post.getPostCategory(), post.getPostStatus(), post.getPostImageUrls(), - post.isAnonymous() + post.isAnonymous(), + post.getCommentCount(), // 댓글 수 + redisService.getLikeCount("post",post.getPostId()), // 좋아요 수 + redisService.getScrapCount(post.getPostId()) // 스크랩 수 )) .collect(Collectors.toList()); } - public List getAllUserPosts(String userId) { + + //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + public List getAllUserPosts(String userId) { List posts = postRepository.findByUserIdNotDeleted(userId); + return posts.stream() - .map(post -> new PostDetailResponseDTO( + .map(post -> new PostWithCountsResponseDTO( post.getUserId(), post.getPostId(), post.getContent(), @@ -107,15 +124,20 @@ public List getAllUserPosts(String userId) { post.getPostCategory(), post.getPostStatus(), post.getPostImageUrls(), - post.isAnonymous() + post.isAnonymous(), + commentRepository.countByPostId(post.getPostId()), // 댓글 수 + redisService.getLikeCount("post",post.getPostId()), // 좋아요 수 + redisService.getScrapCount(post.getPostId()) // 스크랩 수 )) .collect(Collectors.toList()); } - public PostDetailResponseDTO getPost(String postId) { - PostEntity post = postRepository.findByPostIdNotDeleted(postId); + //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 + public PostWithDetailResponseDTO getPostWithDetail(String postId) { + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - return new PostDetailResponseDTO( + return new PostWithDetailResponseDTO( post.getUserId(), post.getPostId(), post.getContent(), @@ -123,11 +145,48 @@ public PostDetailResponseDTO getPost(String postId) { post.getPostCategory(), post.getPostStatus(), post.getPostImageUrls(), - post.isAnonymous() + post.isAnonymous(), + getCommentsByPostId(postId), // 댓글 및 대댓글 + redisService.getLikeCount("post",post.getPostId()), // 좋아요 수 + redisService.getScrapCount(post.getPostId()) // 스크랩 수 ); + } + + + // 댓글 및 대댓글 조회 로직 + private List getCommentsByPostId(String postId) { + List comments = commentRepository.findByPostId(postId); + return comments.stream() + .filter(comment -> !comment.isDeleted()) + .map(comment -> new CommentsResponseDTO( + comment.getCommentId(), + comment.getUserId(), + comment.getContent(), + getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 및 변환 + redisService.getLikeCount("comment",comment.getCommentId()) // 댓글 좋아요 수 + )) + .collect(Collectors.toList()); } + + // 대댓글 조회 로직 + private List getRepliesByCommentId(String commentId) { + List replies = replyRepository.findByCommentId(commentId); + + return replies.stream() + .filter(reply -> !reply.isDeleted()) + .map(reply -> new CommentsResponseDTO( + reply.getReplyId(), + reply.getUserId(), + reply.getContent(), + List.of(), // 대댓글은 하위 대댓글이 없음 + redisService.getLikeCount("reply",reply.getReplyId()) // 대댓글 좋아요 수 + )) + .collect(Collectors.toList()); + } + + public void softDeletePost(String postId) { PostEntity post = postRepository.findById(postId) .orElseThrow(()-> new IllegalArgumentException("게시물을 찾을 수 없음.")); @@ -165,89 +224,11 @@ public void deletePostImage(String postId, String imageUrl) { //유효성체크 - private void validateCreatePostRequest(PostCreateReqDTO postCreateReqDTO) { + private void validateCreatePostRequest(PostCreateRequestDTO postCreateReqDTO) { // if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { // throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); // } - - } - public List getAllUserPostsAndComments(String userId) { - // 삭제되지 않은 사용자 게시물 조회 - List posts = postRepository.findByUserIdNotDeleted(userId); - - // PostEntity를 PostWithCommentsResponseDTO로 변환 - return posts.stream() - .map(post -> new PostWithCommentsResponseDTO( - post.getUserId(), - post.getPostId(), - post.getContent(), - post.getTitle(), - post.getPostCategory(), - post.getPostStatus(), - post.getPostImageUrls(), - post.isAnonymous(), - convertCommentsToDTO(post.getComments()) // 댓글과 대댓글을 DTO로 변환 - )) - .collect(Collectors.toList()); } - public PostWithLikeAndScrapResponseDTO getPostWithLikeAndScrap(String postId) { - String likeKey = "post:likes:" + postId; - String scrapKey = "post:scraps:" + postId; - - // Redis에서 좋아요 및 스크랩 카운트 조회 - Long likeCount = redisTemplate.opsForSet().size(likeKey); - Long scrapCount = redisTemplate.opsForSet().size(scrapKey); - - // Redis에 데이터가 없을 경우 MongoDB에서 조회 후 Redis 갱신 - if (likeCount == null || scrapCount == null) { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - - if (likeCount == null) { - likeCount = (long) post.getLikeCount(); - redisTemplate.opsForSet().add(likeKey, String.valueOf(likeCount)); - } - - if (scrapCount == null) { - scrapCount = (long) post.getScrapCount(); - redisTemplate.opsForSet().add(scrapKey, String.valueOf(scrapCount)); - } - } - - // MongoDB에서 게시물 조회 - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - - // PostWithLikeAndScrapResponseDTO 반환 - return new PostWithLikeAndScrapResponseDTO( - post.getUserId(), - post.getPostId(), - post.getContent(), - post.getTitle(), - post.getPostCategory(), - post.getPostStatus(), - post.getPostImageUrls(), - post.isAnonymous(), - convertCommentsToDTO(post.getComments()), // 댓글 변환 메소드 호출 - likeCount.intValue(), - scrapCount.intValue() - ); - } - private List convertCommentsToDTO(List comments) { - if (comments == null || comments.isEmpty()) { - return List.of(); // 댓글이 없는 경우 빈 리스트 반환 - } - - return comments.stream() - .filter(comment -> !comment.isDeleted()) // 삭제되지 않은 댓글만 포함 - .map(comment -> new CommentsResponseDTO( - comment.getCommentId(), - comment.getUserId(), - comment.getContent(), - convertCommentsToDTO(comment.getReplies()) // 재귀적으로 대댓글 변환 - )) - .collect(Collectors.toList()); - } } From ead7ee187c83cacb2a015557d036a98710027b3a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 30 Nov 2024 03:10:22 +0900 Subject: [PATCH 0149/1002] =?UTF-8?q?[SC-68]=20=20Refactor=20:=20Response?= =?UTF-8?q?=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 29 +++++---- .../post/controller/PostController.java | 60 ++++++++++--------- 2 files changed, 51 insertions(+), 38 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java index d6a46d9f..5f2072a7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.post.comment.controller; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; @@ -23,38 +25,45 @@ public CommentController(CommentService commentService) { @Operation(summary = "댓글 추가") @PostMapping("/{postId}") - public ResponseEntity addComment(@PathVariable String postId, + public ResponseEntity> addComment(@PathVariable String postId, @RequestBody @Valid CommentCreateRequestDTO requestDTO) { commentService.addComment(postId, requestDTO); - return ResponseEntity.status(HttpStatus.CREATED).body("댓글이 추가되었습니다."); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "댓글이 추가되었습니다.", null)); } @Operation(summary = "대댓글 추가") @PostMapping("/{commentId}/replies") - public ResponseEntity addReply(@PathVariable String commentId, + public ResponseEntity> addReply(@PathVariable String commentId, @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { commentService.addReply(commentId, requestDTO); - return ResponseEntity.status(HttpStatus.CREATED).body("대댓글이 추가되었습니다."); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "대댓글이 추가되었습니다.", null)); + } @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회") @GetMapping("/post/{postId}") - public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { + public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { List response = commentService.getCommentsByPostId(postId); - return ResponseEntity.ok(response); + return ResponseEntity.ok() + .body(new ListResponse<>(200, "해당 게시물의 댓글 및 대댓글 조회 성공", response)); + } @Operation(summary = "댓글 삭제") @DeleteMapping("/{commentId}") - public ResponseEntity deleteComment(@PathVariable String commentId) { + public ResponseEntity> deleteComment(@PathVariable String commentId) { commentService.deleteComment(commentId); - return ResponseEntity.status(HttpStatus.OK).body("댓글이 삭제되었습니다."); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "댓글이 삭제되었습니다.", null)); } @Operation(summary = "대댓글 삭제") @DeleteMapping("/replies/{replyId}") - public ResponseEntity deleteReply(@PathVariable String replyId) { + public ResponseEntity> deleteReply(@PathVariable String replyId) { commentService.deleteReply(replyId); - return ResponseEntity.status(HttpStatus.OK).body("대댓글이 삭제되었습니다."); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 6d36b7ea..bcf4fd82 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.post.controller; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; @@ -32,7 +34,7 @@ public PostController(PostService postService) { description = "JSON 형식의 게시물 데이터(postContent)와 이미지 파일(postImages) 업로드" ) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createPost( + public ResponseEntity> createPost( @RequestPart("postContent") @Valid PostCreateRequestDTO postCreateRequestDTO, @RequestPart(value = "postImages", required = false) List postImages) { @@ -42,41 +44,45 @@ public ResponseEntity createPost( } postService.createPost(postCreateRequestDTO, postImages); - return ResponseEntity.status(HttpStatus.CREATED).body("게시물 작성 성공"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", null)); } @Operation( summary = "게시물 내용 이미지 수정 추가" ) @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updatePostContent( + public ResponseEntity> updatePostContent( @PathVariable String postId, @RequestPart("postContent") @Valid PostContentUpdateRequestDTO requestDTO, @RequestPart(value = "postImages", required = false) List postImages) { postService.updatePostContent(postId, requestDTO, postImages); - return ResponseEntity.status(HttpStatus.OK).body("게시물 수정 성공"); + return ResponseEntity.status(HttpStatus.OK) + .body(new SingleResponse<>(200, "게시물 내용이 수정되었습니다.", null)); } @Operation( summary = "상태 수정" ) @PatchMapping("/{postId}/status") - public ResponseEntity updatePostStatus( + public ResponseEntity> updatePostStatus( @PathVariable String postId, @RequestBody PostStatusUpdateRequestDTO requestDTO) { postService.updatePostStatus(postId, requestDTO); - return ResponseEntity.status(HttpStatus.OK).body("게시물 상태 수정 성공"); + return ResponseEntity.status(HttpStatus.OK) + .body(new SingleResponse<>(200, "게시물 상태가 수정되었습니다.", null)); } @Operation(summary = "게시물 익명 설정 수정") @PatchMapping("/{postId}/anonymous") - public ResponseEntity updatePostAnonymous( + public ResponseEntity> updatePostAnonymous( @PathVariable String postId, @RequestBody @Valid PostAnonymousUpdateRequestDTO requestDTO) { postService.updatePostAnonymous(postId, requestDTO); - return ResponseEntity.status(HttpStatus.OK).body("게시물 익명 설정이 수정되었습니다."); + return ResponseEntity.status(HttpStatus.OK) + .body(new SingleResponse<>(200, "게시물 익명 설정이 수정되었습니다.", null)); } @@ -84,48 +90,46 @@ public ResponseEntity updatePostAnonymous( summary = "삭제되지 않은 모든 게시물 조회" ) @GetMapping("") - public ResponseEntity> getAllPosts() { + public ResponseEntity> getAllPosts() { List posts = postService.getAllPosts(); - return ResponseEntity.ok(posts); + return ResponseEntity.ok() + .body(new ListResponse<>(200, "삭제되지 않은 모든 게시물 조회 성공", posts)); } @Operation( summary = "해당 사용자 게시물 전체 조회" ) @GetMapping("/user/{userId}") - public ResponseEntity> getAllUserPosts(@PathVariable String userId) { + public ResponseEntity> getAllUserPosts(@PathVariable String userId) { List posts = postService.getAllUserPosts(userId); - return ResponseEntity.ok(posts); + return ResponseEntity.ok() + .body(new ListResponse<>(200, "사용자 게시물 조회 성공", posts)); } - @Operation( - summary = "해당 게시물 상세 조회" - ) + @Operation(summary = "해당 게시물 상세 조회") @GetMapping("/{postId}") - public ResponseEntity getPostWithDetail(@PathVariable String postId) { + public ResponseEntity> getPostWithDetail(@PathVariable String postId) { PostWithDetailResponseDTO post = postService.getPostWithDetail(postId); - return ResponseEntity.ok(post); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } - @Operation(summary = "게시물 이미지 삭제") @DeleteMapping("/{postId}/images") - public ResponseEntity deletePostImage( + public ResponseEntity> deletePostImage( @PathVariable String postId, @RequestParam String imageUrl) { + postService.deletePostImage(postId, imageUrl); - return ResponseEntity.status(HttpStatus.OK).body("이미지가 삭제되었습니다."); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "게시물 이미지가 삭제되었습니다.", null)); } @Operation(summary = "게시물 삭제 (Soft Delete)") @DeleteMapping("/{postId}") - public ResponseEntity softDeletePost(@PathVariable String postId) { + public ResponseEntity> softDeletePost(@PathVariable String postId) { postService.softDeletePost(postId); - return ResponseEntity.status(HttpStatus.OK).body("게시물이 삭제되었습니다."); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); } - - - - -} - +} \ No newline at end of file From 6107539e274fde52f8c203b9531288b1565a58a8 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 30 Nov 2024 22:48:23 +0900 Subject: [PATCH 0150/1002] =?UTF-8?q?[SC-69]=20=20Refactor=20:=20Like=20An?= =?UTF-8?q?d=20Scrap=201.=20redis=20=EC=9E=A5=EC=95=A0=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC=20=EC=88=98=EC=A0=95=EC=A4=91=202.=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/CodinApplication.java | 2 + .../domain/post/like/LikeController.java | 12 +- .../codin/domain/post/like/LikeEntity.java | 10 +- .../domain/post/like/LikeRepository.java | 14 +- .../codin/domain/post/like/LikeService.java | 47 ++++- .../exception/LikeCreateFailException.java | 7 + .../exception/LikeRemoveFailException.java | 7 + .../domain/post/scrap/ScrapController.java | 32 ++-- .../domain/post/scrap/ScrapRepository.java | 10 + .../codin/domain/post/scrap/ScrapService.java | 58 +++--- .../exception/ScrapCreateFailException.java | 7 + .../exception/ScrapRemoveFailException.java | 7 + .../codin/infra/redis/RedisHealthChecker.java | 68 +++++++ .../redis/RedisRecoverSyncScheduler.java | 76 ++++++++ .../codin/codin/infra/redis/RedisService.java | 67 +++++++ .../codin/infra/redis/SyncScheduler.java | 179 ++++++++++++------ 16 files changed, 499 insertions(+), 104 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index 3e3d45b3..8e0a271f 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import java.util.TimeZone; @@ -10,6 +11,7 @@ @SpringBootApplication @EnableMongoAuditing @EnableMethodSecurity +@EnableScheduling public class CodinApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java index d9443fa6..a3a7b44d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java @@ -1,7 +1,9 @@ package inu.codin.codin.domain.post.like; +import inu.codin.codin.common.response.SingleResponse; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,23 +18,25 @@ public class LikeController { "entityType = post,comment,reply" +"entityId = postId, commentId, replyId") @PostMapping("/{entityType}/{entityId}/{userId}") - public ResponseEntity likeEntity( + public ResponseEntity> likeEntity( @PathVariable String entityType, @PathVariable String entityId, @PathVariable String userId) { likeService.addLike(entityType, entityId, userId); - return ResponseEntity.ok("Liked successfully"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "좋아요가 추가되었습니다.", null)); } @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 " + "entityType = post,comment,reply" +"entityId = postId, commentId, replyId") @DeleteMapping("/{entityType}/{entityId}/{userId}") - public ResponseEntity unlikeEntity( + public ResponseEntity> unlikeEntity( @PathVariable String entityType, @PathVariable String entityId, @PathVariable String userId) { likeService.removeLike(entityType, entityId, userId); - return ResponseEntity.ok("Unliked successfully"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "좋아요가 취소되었습니다.", null)); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java index cd118697..2d544ae4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java @@ -11,12 +11,14 @@ public class LikeEntity extends BaseTimeEntity { @Id private String id; - private String postId; - private String userId; + private String entityId; // 게시글, 댓글, 대댓글의 ID + private String entityType; // 엔티티 타입 (post, comment, reply) + private String userId; // 좋아요를 누른 사용자 ID @Builder - public LikeEntity(String postId, String userId) { - this.postId = postId; + public LikeEntity(String entityId, String entityType, String userId) { + this.entityId = entityId; + this.entityType = entityType; this.userId = userId; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java index baf63118..c2795666 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java @@ -3,7 +3,19 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository + public interface LikeRepository extends MongoRepository { - boolean findByPostIdAndUserId(String postId, String userId); + // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 + long countByEntityTypeAndEntityId(String entityType, String entityId); + + // 특정 엔티티의 좋아요 데이터 조회 + List findByEntityTypeAndEntityId(String entityType, String entityId); + + // 특정 사용자의 좋아요 삭제 + void deleteByEntityTypeAndEntityIdAndUserId(String entityType, String entityId, String userId); + + boolean existsByEntityTypeAndEntityIdAndUserId(String entityType, String entityId, String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java index 5539f3a0..8fde24cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java @@ -1,23 +1,64 @@ package inu.codin.codin.domain.post.like; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.like.exception.LikeCreateFailException; +import inu.codin.codin.domain.post.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Slf4j public class LikeService { private final RedisService redisService; + private final LikeRepository likeRepository; + private final RedisHealthChecker redisHealthChecker; public void addLike(String entityType, String entityId, String userId) { - redisService.addLike(entityType, entityId, userId); - } + + // 중복 좋아요 검증 + if (likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { + throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); + } + + if (redisHealthChecker.isRedisAvailable()) { + redisService.addLike(entityType, entityId, userId); + } + LikeEntity like = LikeEntity.builder() + .entityType(entityType) + .entityId(entityId) + .userId(userId) + .build(); + likeRepository.save(like); + } public void removeLike(String entityType, String entityId, String userId) { - redisService.removeLike(entityType, entityId, userId); + // 없는 좋아요 방지 + if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { + throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); + } + if (redisHealthChecker.isRedisAvailable()) { + redisService.removeLike(entityType, entityId, userId); + } + likeRepository.deleteByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId); + } + + public int getLikeCount(String entityType, String entityId) { + if (redisHealthChecker.isRedisAvailable()) { + return redisService.getLikeCount(entityType, entityId); + } + return (int) likeRepository.countByEntityTypeAndEntityId(entityType, entityId); + } + + public void recoverRedisFromDB() { + likeRepository.findAll().forEach(like -> { + redisService.addLike(like.getEntityType(), like.getEntityId(), like.getUserId()); + }); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java new file mode 100644 index 00000000..ae3ebb17 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.like.exception; + +public class LikeCreateFailException extends RuntimeException{ + public LikeCreateFailException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java new file mode 100644 index 00000000..47aa85ad --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.like.exception; + +public class LikeRemoveFailException extends RuntimeException { + public LikeRemoveFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java index d1ea37dd..8f23a57c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java @@ -1,31 +1,37 @@ package inu.codin.codin.domain.post.scrap; +import inu.codin.codin.common.response.SingleResponse; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/scraps/{postId}") +@RequestMapping("/api/scraps") @RequiredArgsConstructor public class ScrapController { private final ScrapService scrapService; - @PostMapping("/{userId}") - public ResponseEntity scrapPost(@PathVariable String postId, @PathVariable String userId) { + @Operation(summary = "게시물 스크랩 추가") + @PostMapping("/{postId}/{userId}") + public ResponseEntity> addScrap( + @PathVariable String postId, + @PathVariable String userId) { scrapService.addScrap(postId, userId); - return ResponseEntity.ok("Scrapped successfully"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "스크랩 성공.", null)); } - @DeleteMapping("/{userId}") - public ResponseEntity unscrapPost(@PathVariable String postId, @PathVariable String userId) { + @Operation(summary = "게시물 스크랩 삭제") + @DeleteMapping("/{postId}/{userId}") + public ResponseEntity> removeScrap( + @PathVariable String postId, + @PathVariable String userId) { scrapService.removeScrap(postId, userId); - return ResponseEntity.ok("Unscrapped successfully"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "스크랩 취소되었습니다.", null)); } +} - @GetMapping - public ResponseEntity getScrapCount(@PathVariable String postId) { - long scrapCount = scrapService.getScrapCount(postId); - return ResponseEntity.ok(scrapCount); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java index c52efa0b..1d999518 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java @@ -3,7 +3,17 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface ScrapRepository extends MongoRepository { boolean findByPostIdAndUserId(String postId, String userId); + + List findByPostId(String postId); + + Object countByPostId(String postId); + + void deleteByPostIdAndUserId(String postId, String userId); + + boolean existsByPostIdAndUserId(String postId, String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java index 0a9838db..90a5665e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java @@ -1,8 +1,13 @@ package inu.codin.codin.domain.post.scrap; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.scrap.exception.ScrapCreateFailException; +import inu.codin.codin.infra.redis.RedisHealthChecker; +import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -10,40 +15,49 @@ @Service @RequiredArgsConstructor +@Slf4j public class ScrapService { - private final RedisTemplate redisTemplate; + private final RedisService redisService; + private final ScrapRepository scrapRepository; + private final RedisHealthChecker redisHealthChecker; - public void addScrap(String userId, String postId) { - String redisKey = "user:scraps:" + userId; - - if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, postId))) { - throw new IllegalStateException("이미 해당 게시물을 스크랩 했습니다."); + public void addScrap(String postId, String userId) { + if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { + throw new ScrapCreateFailException("이미 스크랩 한 상태 입니다."); } - // Redis에 스크랩 추가 - redisTemplate.opsForSet().add(redisKey, postId); - + if (redisHealthChecker.isRedisAvailable()) { + redisService.addScrap(postId, userId); + } + ScrapEntity scrap = ScrapEntity.builder() + .postId(postId) + .userId(userId) + .build(); + scrapRepository.save(scrap); } - public void removeScrap(String userId, String postId) { - String redisKey = "user:scraps:" + userId; - - if (!Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, postId))) { - throw new IllegalStateException("해당 게시물을 스크랩 하지 않았습니다."); + public void removeScrap(String postId, String userId) { + if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { + throw new LikeRemoveFailException("스크랩한 적이 없는 게시물입니다."); } - // Redis에서 스크랩 제거 - redisTemplate.opsForSet().remove(redisKey, postId); - + if (redisHealthChecker.isRedisAvailable()) { + redisService.removeScrap(postId, userId); + } + scrapRepository.deleteByPostIdAndUserId(postId, userId); } - public long getScrapCount(String postId) { - return redisTemplate.opsForSet().size("scraps:" + postId); + public int getScrapCount(String postId) { + if (redisHealthChecker.isRedisAvailable()) { + return redisService.getScrapCount(postId); + } + return (int) scrapRepository.countByPostId(postId); } - public Set getScrappedPosts(String userId) { - String redisKey = "user:scraps:" + userId; - return redisTemplate.opsForSet().members(redisKey); + public void recoverRedisFromDB() { + scrapRepository.findAll().forEach(scrap -> { + redisService.addScrap(scrap.getPostId(), scrap.getUserId()); + }); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java new file mode 100644 index 00000000..807e6b93 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.scrap.exception; + +public class ScrapCreateFailException extends RuntimeException { + public ScrapCreateFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java new file mode 100644 index 00000000..459ab93e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.scrap.exception; + +public class ScrapRemoveFailException extends RuntimeException { + public ScrapRemoveFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java new file mode 100644 index 00000000..c7e94207 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java @@ -0,0 +1,68 @@ +package inu.codin.codin.infra.redis; + +import inu.codin.codin.infra.redis.exception.RedisUnavailableException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + + +@Component +@RequiredArgsConstructor +@Slf4j +public class RedisHealthChecker { + + private final RedisTemplate redisTemplate; + private volatile boolean redisAvailable = true; // Redis의 현재 상태 + + /** + * Redis 상태를 확인하고 사용 가능 여부를 갱신합니다. + * 장애 발생 시 예외를 던져 호출자가 처리하도록 합니다. + * + * @throws RedisUnavailableException Redis 장애 발생 시 예외 + */ + public void checkRedisStatus() { + try { + boolean status = "PONG".equals(redisTemplate.getConnectionFactory().getConnection().ping()); + if (status && !redisAvailable) { + handleRedisRecovery(); // 복구 처리 + } else if (!status && redisAvailable) { + handleRedisFailure(new RuntimeException("Redis가 응답하지 않습니다.")); + } + redisAvailable = status; + } catch (Exception e) { + handleRedisFailure(e); + throw new RedisUnavailableException("Redis 상태 확인 중 오류 발생: " + e.getMessage()); + } + } + + /** + * Redis의 현재 사용 가능 여부를 반환합니다. + * + * @return Redis가 활성화 상태라면 true + */ + public boolean isRedisAvailable() { + return redisAvailable; + } + + /** + * Redis 복구 처리 로직. + */ + private void handleRedisRecovery() { + redisAvailable = true; + log.info("[Redis 상태 변경] Redis가 활성화되었습니다. 정상 상태로 전환."); + } + + /** + * Redis 장애 처리 로직. + * 상태를 비활성화로 설정하고, 장애 내용을 로깅합니다. + * + * @param e Redis 장애 원인 + */ + private void handleRedisFailure(Exception e) { + if (redisAvailable) { + redisAvailable = false; + log.warn("[Redis 상태 변경] Redis가 비활성화되었습니다. 원인: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java new file mode 100644 index 00000000..31ccd5b5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -0,0 +1,76 @@ +package inu.codin.codin.infra.redis; + +import inu.codin.codin.domain.post.like.LikeService; +import inu.codin.codin.domain.post.scrap.ScrapService; +import inu.codin.codin.infra.redis.exception.RedisUnavailableException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.time.Duration; +@Component +@RequiredArgsConstructor +@Slf4j +public class RedisRecoverSyncScheduler { + + private final RedisHealthChecker redisHealthChecker; + private final LikeService likeService; + private final ScrapService scrapService; + + private Instant lastRecoveryTime = Instant.MIN; // 마지막 복구 시간 + + /** + * Redis 상태를 주기적으로 확인하고, 필요한 경우 복구 작업을 수행합니다. + */ + @Scheduled(fixedRate = 10000) // 10초마다 실행 (테스트용) + public void monitorRedisAndRecover() { + try { + redisHealthChecker.checkRedisStatus(); // Redis 상태 확인 + if (shouldRecoverRedis()) { + recoverRedisData(); // Redis 복구 + } + } catch (RedisUnavailableException e) { + log.warn("Redis 장애 감지: {}. MongoDB 우회 처리 중...", e.getMessage()); + } + } + + /** + * Redis 복구가 필요한지 확인합니다. + * + * @return Redis가 복구된 상태라면 true + */ + private boolean shouldRecoverRedis() { + return redisHealthChecker.isRedisAvailable() && canPerformRecovery(); + } + + /** + * Redis 복구 작업을 실행합니다. + */ + private void recoverRedisData() { + if (!canPerformRecovery()) { + log.info("최근 복구 이후 간격이 짧아 복구 작업을 건너뜁니다."); + return; + } + try { + log.info("[Redis 복구 작업] Redis 복구 작업 시작..."); + likeService.recoverRedisFromDB(); + scrapService.recoverRedisFromDB(); + lastRecoveryTime = Instant.now(); + log.info("[Redis 복구 작업] Redis 데이터 복구 완료."); + } catch (Exception e) { + log.error("[Redis 복구 작업] Redis 복구 작업 실패: {}", e.getMessage(), e); + } + } + + /** + * 마지막 복구 이후 충분한 간격이 지났는지 확인합니다. + * + * @return 복구 작업 실행 가능 여부 + */ + private boolean canPerformRecovery() { + Instant now = Instant.now(); + return Duration.between(lastRecoveryTime, now).toMinutes() >= 5; // 최소 5분 간격 + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java new file mode 100644 index 00000000..2a7efc8f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -0,0 +1,67 @@ +package inu.codin.codin.infra.redis; + + +import inu.codin.codin.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RedisService { + /** + * Redis 기반 Like/Scrap 관리 Service + * Redis 작업 실패시 DB 기반으로 직접 처리 + * 장애 복구를 대비한 보완 로직 추가 + */ + private final RedisTemplate redisTemplate; + private final PostRepository postRepository; + + //post, comment, reply 구분 + public Set getKeys(String pattern) { + Set keys = redisTemplate.keys(pattern); + return keys != null + ? keys.stream().filter(key -> key != null && !key.isEmpty()).collect(Collectors.toSet()) + : Set.of(); + } + + public void addLike(String entityType, String entityId, String userId) { + String redisKey = entityType + ":likes:" + entityId; + redisTemplate.opsForSet().add(redisKey, userId); + } + + public void removeLike(String entityType, String entityId, String userId) { + String redisKey = entityType + ":likes:" + entityId; + redisTemplate.opsForSet().remove(redisKey, userId); + } + + public int getLikeCount(String entityType, String entityId) { + String redisKey = entityType + ":likes:" + entityId; + Long count = redisTemplate.opsForSet().size(redisKey); + return count != null ? count.intValue() : 0; + } + + public Set getLikedUsers(String entityType, String entityId) { + String redisKey = entityType + ":likes:" + entityId; + return redisTemplate.opsForSet().members(redisKey); + } + + public void addScrap(String postId, String userId) { + String redisKey = "post:scraps:" + postId; + redisTemplate.opsForSet().add(redisKey, userId); + } + + public void removeScrap(String postId, String userId) { + String redisKey = "post:scraps:" + postId; + redisTemplate.opsForSet().remove(redisKey, userId); + } + + public int getScrapCount(String postId) { + String redisKey = "post:scraps:" + postId; + Long scrapCount = redisTemplate.opsForSet().size(redisKey); + return scrapCount != null ? scrapCount.intValue() : 0; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index a87edeba..21d05210 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -1,5 +1,9 @@ package inu.codin.codin.infra.redis; +import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.comment.repository.ReplyRepository; import inu.codin.codin.domain.post.like.LikeEntity; import inu.codin.codin.domain.post.like.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; @@ -7,83 +11,144 @@ import inu.codin.codin.domain.post.scrap.ScrapEntity; import inu.codin.codin.domain.post.scrap.ScrapRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; @Component @RequiredArgsConstructor +@Slf4j public class SyncScheduler { - private final RedisTemplate redisTemplate; + private final RedisService redisService; private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ReplyRepository replyRepository; private final LikeRepository likeRepository; private final ScrapRepository scrapRepository; - @Scheduled(fixedRate = 60000) // 매 1분마다 실행 - public void syncLikesAndScraps() { - syncLikes(); - syncScraps(); + @Scheduled(fixedRate = 10000) // 매 10초마다 실행(테스트목적) + public void syncLikes() { + log.info("좋아요 동기화 작업 시작"); + syncEntityLikes("post", postRepository); + syncEntityLikes("comment", commentRepository); + syncEntityLikes("reply", replyRepository); + syncPostScraps(); + log.info("좋아요 동기화 작업 완료"); } - private void syncLikes() { - Set keys = redisTemplate.keys("post:likes:*"); - if (keys == null || keys.isEmpty()) return; - - for (String key : keys) { - String postId = key.replace("post:likes:", ""); - Set users = redisTemplate.opsForSet().members(key); - - if (users != null && !users.isEmpty()) { - // LikeEntity 동기화 - users.forEach(userId -> { - if (!likeRepository.findByPostIdAndUserId(postId, userId)) { - likeRepository.save(new LikeEntity(postId, userId)); - } - }); - - // PostEntity의 likeCount 업데이트 - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - int newLikeCount = users.size(); - if (post.getLikeCount() != newLikeCount) { // 기존 카운트와 다를 경우만 업데이트 - post.updateLikeCount(newLikeCount); - postRepository.save(post); + private void syncEntityLikes(String entityType, MongoRepository repository) { + Set redisKeys = redisService.getKeys(entityType + ":likes:*"); + if (redisKeys == null || redisKeys.isEmpty()) { + log.info("{} 엔티티에 대한 Redis 키가 존재하지 않음", entityType); + return; + } + + for (String redisKey : redisKeys) { + String entityId = redisKey.replace(entityType + ":likes:", ""); + Set likedUsers = redisService.getLikedUsers(entityType, entityId); + + // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 + List dbLikes = likeRepository.findByEntityTypeAndEntityId(entityType, entityId); + for (LikeEntity dbLike : dbLikes) { + if (!likedUsers.contains(dbLike.getUserId())) { + log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), entityId); + likeRepository.delete(dbLike); + } + } + + // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 + for (String userId : likedUsers) { + if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { + log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, entityId); + LikeEntity dbLike = LikeEntity.builder() + .entityType(entityType) + .entityId(entityId) + .userId(userId) + .build(); + likeRepository.save(dbLike); + } + } + + // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 + int likeCount = likedUsers.size(); + if (repository instanceof PostRepository postRepo) { + PostEntity post = postRepo.findById(entityId).orElse(null); + if (post != null && post.getLikeCount() != likeCount) { + log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); + post.updateLikeCount(likeCount); + postRepo.save(post); + } + } else if (repository instanceof CommentRepository commentRepo) { + CommentEntity comment = commentRepo.findById(entityId).orElse(null); + if (comment != null && comment.getLikeCount() != likeCount) { + log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); + comment.updateLikeCount(likeCount); + commentRepo.save(comment); + } + } else if (repository instanceof ReplyRepository replyRepo) { + ReplyEntity reply = replyRepo.findById(entityId).orElse(null); + if (reply != null && reply.getLikeCount() != likeCount) { + log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); + reply.updateLikeCount(likeCount); + replyRepo.save(reply); } } } } - private void syncScraps() { - Set keys = redisTemplate.keys("user:scraps:*"); - if (keys == null || keys.isEmpty()) return; - - for (String key : keys) { - String userId = key.replace("user:scraps:", ""); - Set posts = redisTemplate.opsForSet().members(key); - - if (posts != null && !posts.isEmpty()) { - // ScrapEntity 동기화 - posts.forEach(postId -> { - if (!scrapRepository.findByPostIdAndUserId(postId, userId)) { - scrapRepository.save(new ScrapEntity(postId, userId)); - } - }); - - // PostEntity의 scrapCount 업데이트 - posts.forEach(postId -> { - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - int newScrapCount = redisTemplate.opsForSet().size("post:scraps:" + postId).intValue(); - if (post.getScrapCount() != newScrapCount) { // 기존 카운트와 다를 경우만 업데이트 - post.updateScrapCount(newScrapCount); - postRepository.save(post); - } - }); + public void syncPostScraps() { + + Set redisKeys = redisService.getKeys("post:scraps:*"); + if (redisKeys == null || redisKeys.isEmpty()) { + log.info("스크랩에 대한 Redis 키가 존재하지 않음"); + return; + } + + for (String redisKey : redisKeys) { + String postId = redisKey.replace("post:scraps:", ""); + Set redisScrappedUsers = redisService.getLikedUsers("post", postId); + + // MongoDB의 스크랩 데이터 가져오기 + List dbScraps = scrapRepository.findByPostId(postId); + Set dbScrappedUsers = dbScraps.stream() + .map(ScrapEntity::getUserId) + .collect(Collectors.toSet()); + + // (스크랩 삭제) MongoDB에 있지만 Redis에 없는 사용자 삭제 + for (ScrapEntity dbScrap : dbScraps) { + if (!redisScrappedUsers.contains(dbScrap.getUserId())) { + log.info("MongoDB에서 사용자 삭제: UserID={}, PostID={}", dbScrap.getUserId(), postId); + scrapRepository.delete(dbScrap); + } + } + + // (스크랩 추가) Redis에 있지만 MongoDB에 없는 사용자 추가 + for (String redisUser : redisScrappedUsers) { + if (!dbScrappedUsers.contains(redisUser)) { + log.info("MongoDB에 사용자 추가: UserID={}, PostID={}", redisUser, postId); + ScrapEntity dbScrap = ScrapEntity.builder() + .postId(postId) + .userId(redisUser) + .build(); + scrapRepository.save(dbScrap); + } + } + + // Redis 사용자 수로 PostEntity의 scrapCount 업데이트 + int redisScrapCount = redisScrappedUsers.size(); + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + if (post.getScrapCount() != redisScrapCount) { + log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, redisScrapCount); + post.updateScrapCount(redisScrapCount); + postRepository.save(post); } } } - -} +} \ No newline at end of file From 17822ba6bd131fedf32ea4cd14a5a9e2aae65cd7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 30 Nov 2024 22:48:29 +0900 Subject: [PATCH 0151/1002] =?UTF-8?q?[SC-69]=20=20Refactor=20:=20Like=20An?= =?UTF-8?q?d=20Scrap=201.=20redis=20=EC=9E=A5=EC=95=A0=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC=20=EC=88=98=EC=A0=95=EC=A4=91=202.=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/exception/RedisUnavailableException.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/exception/RedisUnavailableException.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/exception/RedisUnavailableException.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/exception/RedisUnavailableException.java new file mode 100644 index 00000000..0ac52f71 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/exception/RedisUnavailableException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.redis.exception; + +public class RedisUnavailableException extends RuntimeException { + public RedisUnavailableException(String message) { + super(message); + } +} From 5add299b4f8a91193fefecd106e6d4dc9e45f90f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 30 Nov 2024 23:34:29 +0900 Subject: [PATCH 0152/1002] =?UTF-8?q?[SC-65]=20=20Refactor=20:=20post,comm?= =?UTF-8?q?ent=20=EC=A1=B0=ED=9A=8C=20-=20redis=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=EC=8B=9C=20Db=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/comment/service/CommentService.java | 6 ++++-- .../codin/domain/post/like/LikeService.java | 3 ++- .../domain/post/scrap/ScrapRepository.java | 2 +- .../codin/domain/post/scrap/ScrapService.java | 3 ++- .../domain/post/service/PostService.java | 20 +++++++++++-------- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java index dbf4ec53..b8683414 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java @@ -6,6 +6,7 @@ import inu.codin.codin.domain.post.comment.entity.CommentEntity; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.like.LikeService; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.comment.repository.CommentRepository; import inu.codin.codin.domain.post.comment.repository.ReplyRepository; @@ -26,6 +27,7 @@ public class CommentService { private final CommentRepository commentRepository; private final ReplyRepository replyRepository; private final RedisService redisService; + private final LikeService likeService; // 댓글 추가 public void addComment(String postId, CommentCreateRequestDTO requestDTO) { @@ -138,7 +140,7 @@ public List getCommentsByPostId(String postId) { comment.getUserId(), comment.getContent(), getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 - redisService.getLikeCount("comment", comment.getCommentId()) // 댓글 좋아요 수 + likeService.getLikeCount("comment", comment.getCommentId()) // 댓글 좋아요 수 )) .collect(Collectors.toList()); } @@ -154,7 +156,7 @@ private List getRepliesByCommentId(String commentId) { reply.getUserId(), reply.getContent(), List.of(), // 대댓글은 하위 대댓글이 없음 - redisService.getLikeCount("reply", reply.getReplyId()) + likeService.getLikeCount("reply", reply.getReplyId()) )) .collect(Collectors.toList()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java index 8fde24cd..c3b77b59 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java @@ -53,7 +53,8 @@ public int getLikeCount(String entityType, String entityId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType, entityId); } - return (int) likeRepository.countByEntityTypeAndEntityId(entityType, entityId); + Long count = likeRepository.countByEntityTypeAndEntityId(entityType, entityId); + return (int) Math.max(0, count); } public void recoverRedisFromDB() { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java index 1d999518..62929558 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java @@ -11,7 +11,7 @@ public interface ScrapRepository extends MongoRepository { List findByPostId(String postId); - Object countByPostId(String postId); + long countByPostId(String postId); void deleteByPostIdAndUserId(String postId, String userId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java index 90a5665e..a7fdf183 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java @@ -52,7 +52,8 @@ public int getScrapCount(String postId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getScrapCount(postId); } - return (int) scrapRepository.countByPostId(postId); + long count = scrapRepository.countByPostId(postId); + return (int) Math.max(0, count); } public void recoverRedisFromDB() { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index b5647b99..d7b80e38 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -12,6 +12,8 @@ import inu.codin.codin.domain.post.comment.entity.ReplyEntity; import inu.codin.codin.domain.post.comment.repository.ReplyRepository; +import inu.codin.codin.domain.post.like.LikeService; +import inu.codin.codin.domain.post.scrap.ScrapService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.entity.PostEntity; @@ -33,6 +35,8 @@ public class PostService { private final ReplyRepository replyRepository; private final S3Service s3Service; private final RedisService redisService; + private final LikeService likeService; + private final ScrapService scrapService; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -104,8 +108,8 @@ public List getAllPosts() { post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), // 댓글 수 - redisService.getLikeCount("post",post.getPostId()), // 좋아요 수 - redisService.getScrapCount(post.getPostId()) // 스크랩 수 + likeService.getLikeCount("post",post.getPostId()), // 좋아요 수 + scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )) .collect(Collectors.toList()); } @@ -126,8 +130,8 @@ public List getAllUserPosts(String userId) { post.getPostImageUrls(), post.isAnonymous(), commentRepository.countByPostId(post.getPostId()), // 댓글 수 - redisService.getLikeCount("post",post.getPostId()), // 좋아요 수 - redisService.getScrapCount(post.getPostId()) // 스크랩 수 + likeService.getLikeCount("post",post.getPostId()), // 좋아요 수 + scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )) .collect(Collectors.toList()); } @@ -147,8 +151,8 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { post.getPostImageUrls(), post.isAnonymous(), getCommentsByPostId(postId), // 댓글 및 대댓글 - redisService.getLikeCount("post",post.getPostId()), // 좋아요 수 - redisService.getScrapCount(post.getPostId()) // 스크랩 수 + likeService.getLikeCount("post",post.getPostId()), // 좋아요 수 + scrapService.getScrapCount(post.getPostId()) // 스크랩 수 ); } @@ -164,7 +168,7 @@ private List getCommentsByPostId(String postId) { comment.getUserId(), comment.getContent(), getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 및 변환 - redisService.getLikeCount("comment",comment.getCommentId()) // 댓글 좋아요 수 + likeService.getLikeCount("comment",comment.getCommentId()) // 댓글 좋아요 수 )) .collect(Collectors.toList()); } @@ -181,7 +185,7 @@ private List getRepliesByCommentId(String commentId) { reply.getUserId(), reply.getContent(), List.of(), // 대댓글은 하위 대댓글이 없음 - redisService.getLikeCount("reply",reply.getReplyId()) // 대댓글 좋아요 수 + likeService.getLikeCount("reply",reply.getReplyId()) // 대댓글 좋아요 수 )) .collect(Collectors.toList()); } From c9f1a262d0c3fbc79b10781b2df21de92ad1fb85 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 1 Dec 2024 01:16:44 +0900 Subject: [PATCH 0153/1002] =?UTF-8?q?[SC-68]=20=20Refactor=20:=20Redis=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94(=EC=9E=A5=EC=95=A0)?= =?UTF-8?q?=EC=8B=9C=20Redis=20=ED=83=80=EC=9E=84=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C=EC=A0=81=20=EC=84=A4=EC=A0=95=20->=20?= =?UTF-8?q?=EB=B9=A0=EB=A5=B4=EA=B2=8C=20=EC=8B=A4=ED=8C=A8=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/redis/RedisConfig.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java index 6af6e9b8..5bf8564d 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java @@ -6,11 +6,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import java.time.Duration; + // Redis configuration @Configuration @EnableConfigurationProperties(RedisProperties.class) @@ -30,7 +33,12 @@ public RedisConnectionFactory redisConnectionFactory() { // Lettuce는 비동기 방식을 지원하는 Redis 클라이언트 // 성능상 이점이 있어 기본적으로 사용 - return new LettuceConnectionFactory(redisStandAloneConfiguration); + LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() + .commandTimeout(Duration.ofMillis(500)) // 명령 타임아웃 + .shutdownTimeout(Duration.ofMillis(100)) // 셧다운 타임아웃 + .build(); + + return new LettuceConnectionFactory(redisStandAloneConfiguration, clientConfig); } @Bean From a92d968800d226e79b4ae0a59b121536346e43dc Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 1 Dec 2024 01:17:09 +0900 Subject: [PATCH 0154/1002] =?UTF-8?q?[SC-68]=20=20Refactor=20:=20Redis=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94(=EC=9E=A5=EC=95=A0)?= =?UTF-8?q?=EC=8B=9C=20=EB=8C=80=EC=B2=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/infra/redis/RedisHealthChecker.java | 1 - .../infra/redis/RedisRecoverSyncScheduler.java | 4 ++-- .../codin/codin/infra/redis/RedisService.java | 18 ++++++++++++++---- .../codin/codin/infra/redis/SyncScheduler.java | 12 ++++++++---- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java index c7e94207..30b5ba0a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java @@ -32,7 +32,6 @@ public void checkRedisStatus() { redisAvailable = status; } catch (Exception e) { handleRedisFailure(e); - throw new RedisUnavailableException("Redis 상태 확인 중 오류 발생: " + e.getMessage()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java index 31ccd5b5..682931cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -54,11 +54,11 @@ private void recoverRedisData() { return; } try { - log.info("[Redis 복구 작업] Redis 복구 작업 시작..."); + log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 복구 작업 시작..."); likeService.recoverRedisFromDB(); scrapService.recoverRedisFromDB(); lastRecoveryTime = Instant.now(); - log.info("[Redis 복구 작업] Redis 데이터 복구 완료."); + log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 데이터 복구 완료."); } catch (Exception e) { log.error("[Redis 복구 작업] Redis 복구 작업 실패: {}", e.getMessage(), e); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 2a7efc8f..e3baab37 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -11,6 +12,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class RedisService { /** * Redis 기반 Like/Scrap 관리 Service @@ -22,10 +24,18 @@ public class RedisService { //post, comment, reply 구분 public Set getKeys(String pattern) { - Set keys = redisTemplate.keys(pattern); - return keys != null - ? keys.stream().filter(key -> key != null && !key.isEmpty()).collect(Collectors.toSet()) - : Set.of(); + try { + Set keys = redisTemplate.keys(pattern); + if (keys == null || keys.isEmpty()) { + return Set.of(); // keys가 null이거나 빈 경우 빈 Set 반환 + } + return keys.stream() + .filter(key -> key != null && !key.isEmpty()) // key가 null 또는 빈 문자열이 아닌 경우 필터링 + .collect(Collectors.toSet()); + } catch (Exception e) { + log.warn("Redis 연결 중 오류 발생: {}", e.getMessage()); + return Set.of(); // Redis 예외 발생 시 빈 Set 반환 + } } public void addLike(String entityType, String entityId, String userId) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 21d05210..10212766 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -10,6 +10,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.scrap.ScrapEntity; import inu.codin.codin.domain.post.scrap.ScrapRepository; +import inu.codin.codin.domain.post.scrap.ScrapService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.repository.MongoRepository; @@ -31,21 +32,25 @@ public class SyncScheduler { private final ReplyRepository replyRepository; private final LikeRepository likeRepository; private final ScrapRepository scrapRepository; + private final RedisHealthChecker redisHealthChecker; @Scheduled(fixedRate = 10000) // 매 10초마다 실행(테스트목적) public void syncLikes() { - log.info("좋아요 동기화 작업 시작"); + if (!redisHealthChecker.isRedisAvailable()) { + log.warn("Redis 비활성화 상태, 동기화 작업 중지"); + return; + } + log.info(" 동기화 작업 시작"); syncEntityLikes("post", postRepository); syncEntityLikes("comment", commentRepository); syncEntityLikes("reply", replyRepository); syncPostScraps(); - log.info("좋아요 동기화 작업 완료"); + log.info(" 동기화 작업 완료"); } private void syncEntityLikes(String entityType, MongoRepository repository) { Set redisKeys = redisService.getKeys(entityType + ":likes:*"); if (redisKeys == null || redisKeys.isEmpty()) { - log.info("{} 엔티티에 대한 Redis 키가 존재하지 않음", entityType); return; } @@ -106,7 +111,6 @@ public void syncPostScraps() { Set redisKeys = redisService.getKeys("post:scraps:*"); if (redisKeys == null || redisKeys.isEmpty()) { - log.info("스크랩에 대한 Redis 키가 존재하지 않음"); return; } From 800635bbaa564aec9ffc466020a5ee8d717d0a77 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 1 Dec 2024 02:40:27 +0900 Subject: [PATCH 0155/1002] =?UTF-8?q?[SC-65]=20=20Refactor=20:=20@paramete?= =?UTF-8?q?r=20userId=20->=20SecurityUtils=20=EC=9D=98=20getCurrentUserId?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=20=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SpringContext 사용 --- .../common/security/util/SecurityUtils.java | 29 +++++++++++++++++++ .../comment/controller/CommentController.java | 10 +++---- .../dto}/CommentCreateRequestDTO.java | 8 ++--- .../dto/CommentResponseDTO.java} | 8 ++--- .../dto}/ReplyCreateRequestDTO.java | 8 ++--- .../post/comment/service/CommentService.java | 24 +++++++++------ .../post/controller/PostController.java | 6 ++-- .../dto/request/PostCreateRequestDTO.java | 6 ++-- .../response/PostWithCommentsResponseDTO.java | 5 ++-- .../response/PostWithDetailResponseDTO.java | 7 ++--- .../domain/post/like/LikeController.java | 17 +++++++---- .../domain/post/scrap/ScrapController.java | 17 +++++++---- .../domain/post/service/PostService.java | 20 ++++++++----- 13 files changed, 108 insertions(+), 57 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java rename codin-core/src/main/java/inu/codin/codin/domain/post/{dto/request => comment/dto}/CommentCreateRequestDTO.java (63%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{dto/response/CommentsResponseDTO.java => comment/dto/CommentResponseDTO.java} (76%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{dto/request => comment/dto}/ReplyCreateRequestDTO.java (63%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java new file mode 100644 index 00000000..1484c579 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -0,0 +1,29 @@ +package inu.codin.codin.common.security.util; + +import inu.codin.codin.domain.user.security.CustomUserDetails; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * SecurityContext와 관련된 유틸리티 클래스. + */ +public class SecurityUtils { + + /** + * 현재 인증된 사용자의 ID를 반환. + * + * @return 인증된 사용자의 ID + * @throws IllegalStateException 인증 정보가 없는 경우 예외 발생 + */ + public static String getCurrentUserId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails)) { + throw new IllegalStateException("인증 정보가 없습니다."); + } + + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + return userDetails.getId(); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java index 5f2072a7..1551394f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java @@ -2,9 +2,9 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.dto.request.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.domain.post.comment.dto.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.comment.dto.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -44,8 +44,8 @@ public ResponseEntity> addReply(@PathVariable String commentId @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회") @GetMapping("/post/{postId}") - public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { - List response = commentService.getCommentsByPostId(postId); + public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { + List response = commentService.getCommentsByPostId(postId); return ResponseEntity.ok() .body(new ListResponse<>(200, "해당 게시물의 댓글 및 대댓글 조회 성공", response)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentCreateRequestDTO.java similarity index 63% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentCreateRequestDTO.java index 9de41764..cc105cdd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/CommentCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.dto.request; +package inu.codin.codin.domain.post.comment.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -6,9 +6,9 @@ @Data public class CommentCreateRequestDTO { - @Schema(description = "유저 ID", example = "111111") - @NotBlank - private String userId; +// @Schema(description = "유저 ID", example = "111111") +// @NotBlank +// private String userId; @Schema(description = "댓글 내용", example = "content") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentResponseDTO.java similarity index 76% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentResponseDTO.java index ecc3a89e..56a8bc11 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/CommentsResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentResponseDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.dto.response; +package inu.codin.codin.domain.post.comment.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -7,7 +7,7 @@ import java.util.List; @Data -public class CommentsResponseDTO { +public class CommentResponseDTO { @Schema(description = "댓글 또는 대댓글 ID", example = "111111") @NotBlank private String commentId; @@ -21,12 +21,12 @@ public class CommentsResponseDTO { private String content; @Schema(description = "대댓글 리스트", example = "[...]") - private List replies; + private List replies; @Schema(description = "좋아요 수", example = "5") private int likeCount; - public CommentsResponseDTO(String commentId, String userId, String content, List replies, int likeCount) { + public CommentResponseDTO(String commentId, String userId, String content, List replies, int likeCount) { this.commentId = commentId; this.userId = userId; this.content = content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/ReplyCreateRequestDTO.java similarity index 63% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/ReplyCreateRequestDTO.java index 23f406d8..7efecedd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/ReplyCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.dto.request; +package inu.codin.codin.domain.post.comment.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -6,9 +6,9 @@ @Data public class ReplyCreateRequestDTO { - @Schema(description = "유저 ID", example = "111111") - @NotBlank - private String userId; +// @Schema(description = "유저 ID", example = "111111") +// @NotBlank +// private String userId; @Schema(description = "댓글 내용", example = "content") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java index b8683414..aac2bbef 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java @@ -1,8 +1,9 @@ package inu.codin.codin.domain.post.comment.service; -import inu.codin.codin.domain.post.dto.request.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.comment.dto.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.comment.dto.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.comment.entity.CommentEntity; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.comment.entity.ReplyEntity; @@ -34,9 +35,12 @@ public void addComment(String postId, CommentCreateRequestDTO requestDTO) { PostEntity post = postRepository.findById(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + String userId = SecurityUtils.getCurrentUserId(); + CommentEntity comment = CommentEntity.builder() .postId(postId) - .userId(requestDTO.getUserId()) + .userId(userId) .content(requestDTO.getContent()) .build(); @@ -55,9 +59,11 @@ public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { PostEntity post = postRepository.findById(comment.getPostId()) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + String userId = SecurityUtils.getCurrentUserId(); + ReplyEntity reply = ReplyEntity.builder() .commentId(commentId) - .userId(requestDTO.getUserId()) + .userId(userId) .content(requestDTO.getContent()) .build(); @@ -130,12 +136,12 @@ public void deleteReply(String replyId) { } // 특정 게시물의 댓글 및 대댓글 조회 - public List getCommentsByPostId(String postId) { + public List getCommentsByPostId(String postId) { List comments = commentRepository.findByPostId(postId); return comments.stream() .filter(comment -> !comment.isDeleted()) - .map(comment -> new CommentsResponseDTO( + .map(comment -> new CommentResponseDTO( comment.getCommentId(), comment.getUserId(), comment.getContent(), @@ -146,12 +152,12 @@ public List getCommentsByPostId(String postId) { } // 특정 댓글의 대댓글 조회 - private List getRepliesByCommentId(String commentId) { + private List getRepliesByCommentId(String commentId) { List replies = replyRepository.findByCommentId(commentId); return replies.stream() .filter(reply -> !reply.isDeleted()) - .map(reply -> new CommentsResponseDTO( + .map(reply -> new CommentResponseDTO( reply.getReplyId(), reply.getUserId(), reply.getContent(), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index bcf4fd82..db111672 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -99,9 +99,9 @@ public ResponseEntity> getAllPosts() { @Operation( summary = "해당 사용자 게시물 전체 조회" ) - @GetMapping("/user/{userId}") - public ResponseEntity> getAllUserPosts(@PathVariable String userId) { - List posts = postService.getAllUserPosts(userId); + @GetMapping("/user") + public ResponseEntity> getAllUserPosts() { + List posts = postService.getAllUserPosts(); return ResponseEntity.ok() .body(new ListResponse<>(200, "사용자 게시물 조회 성공", posts)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index b50ff5db..10e034f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -12,9 +12,9 @@ @Data public class PostCreateRequestDTO { - @Schema(description = "유저 ID", example = "111111") - @NotBlank - private String userId; +// @Schema(description = "유저 ID", example = "111111") +// @NotBlank +// private String userId; @Schema(description = "게시물 제목", example = "Example") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java index 211adff8..5aea8653 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.dto.response; +import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; @@ -37,9 +38,9 @@ public class PostWithCommentsResponseDTO { private boolean isAnonymous; @Schema(description = "댓글 및 대댓글", example = "0") - private List comments; + private List comments; - public PostWithCommentsResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, List comments) { + public PostWithCommentsResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, List comments) { this.userId = userId; this.postId = postId; this.content = content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java index b4abcb67..e8e9c12c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java @@ -1,14 +1,13 @@ package inu.codin.codin.domain.post.dto.response; +import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import lombok.NoArgsConstructor; import java.util.List; @@ -47,7 +46,7 @@ public class PostWithDetailResponseDTO { private boolean isAnonymous; @Schema(description = "댓글 및 대댓글 데이터") - private List comments; + private List comments; @Schema(description = "좋아요 수", example = "10") private int likeCount; @@ -65,7 +64,7 @@ public PostWithDetailResponseDTO( PostStatus postStatus, List postImageUrls, boolean isAnonymous, - List comments, + List comments, int likeCount, int scrapCount ) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java index a3a7b44d..8db7ba56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.like; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.common.security.util.SecurityUtils; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -17,11 +18,13 @@ public class LikeController { @Operation(summary = "게시물, 댓글, 대댓글 좋아요 추가"+ "entityType = post,comment,reply" +"entityId = postId, commentId, replyId") - @PostMapping("/{entityType}/{entityId}/{userId}") + @PostMapping("/{entityType}/{entityId}") public ResponseEntity> likeEntity( @PathVariable String entityType, - @PathVariable String entityId, - @PathVariable String userId) { + @PathVariable String entityId) { + + String userId = SecurityUtils.getCurrentUserId(); + likeService.addLike(entityType, entityId, userId); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "좋아요가 추가되었습니다.", null)); @@ -30,11 +33,13 @@ public ResponseEntity> likeEntity( @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 " + "entityType = post,comment,reply" +"entityId = postId, commentId, replyId") - @DeleteMapping("/{entityType}/{entityId}/{userId}") + @DeleteMapping("/{entityType}/{entityId}") public ResponseEntity> unlikeEntity( @PathVariable String entityType, - @PathVariable String entityId, - @PathVariable String userId) { + @PathVariable String entityId) { + + String userId = SecurityUtils.getCurrentUserId(); + likeService.removeLike(entityType, entityId, userId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "좋아요가 취소되었습니다.", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java index 8f23a57c..f9ac5378 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.post.scrap; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -15,20 +17,23 @@ public class ScrapController { private final ScrapService scrapService; @Operation(summary = "게시물 스크랩 추가") - @PostMapping("/{postId}/{userId}") + @PostMapping("/{postId}") public ResponseEntity> addScrap( - @PathVariable String postId, - @PathVariable String userId) { + @PathVariable String postId) { + String userId = SecurityUtils.getCurrentUserId(); + scrapService.addScrap(postId, userId); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "스크랩 성공.", null)); } @Operation(summary = "게시물 스크랩 삭제") - @DeleteMapping("/{postId}/{userId}") + @DeleteMapping("/{postId}") public ResponseEntity> removeScrap( - @PathVariable String postId, - @PathVariable String userId) { + @PathVariable String postId) { + + String userId = SecurityUtils.getCurrentUserId(); + scrapService.removeScrap(postId, userId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "스크랩 취소되었습니다.", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index d7b80e38..34956ff6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,11 +1,12 @@ package inu.codin.codin.domain.post.service; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.comment.repository.CommentRepository; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.CommentsResponseDTO; +import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.comment.entity.CommentEntity; import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; @@ -52,9 +53,11 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List imageUrls = handleImageUpload(postImages); + String userId = SecurityUtils.getCurrentUserId(); + PostEntity postEntity = PostEntity.builder() - .userId(postCreateRequestDTO.getUserId()) + .userId(userId) .title(postCreateRequestDTO.getTitle()) .content(postCreateRequestDTO.getContent()) @@ -116,7 +119,10 @@ public List getAllPosts() { //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public List getAllUserPosts(String userId) { + public List getAllUserPosts() { + + String userId = SecurityUtils.getCurrentUserId(); + List posts = postRepository.findByUserIdNotDeleted(userId); return posts.stream() @@ -158,12 +164,12 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { // 댓글 및 대댓글 조회 로직 - private List getCommentsByPostId(String postId) { + private List getCommentsByPostId(String postId) { List comments = commentRepository.findByPostId(postId); return comments.stream() .filter(comment -> !comment.isDeleted()) - .map(comment -> new CommentsResponseDTO( + .map(comment -> new CommentResponseDTO( comment.getCommentId(), comment.getUserId(), comment.getContent(), @@ -175,12 +181,12 @@ private List getCommentsByPostId(String postId) { // 대댓글 조회 로직 - private List getRepliesByCommentId(String commentId) { + private List getRepliesByCommentId(String commentId) { List replies = replyRepository.findByCommentId(commentId); return replies.stream() .filter(reply -> !reply.isDeleted()) - .map(reply -> new CommentsResponseDTO( + .map(reply -> new CommentResponseDTO( reply.getReplyId(), reply.getUserId(), reply.getContent(), From 39961e133eff566b181e8ae7fbdb6aca1290416f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 28 Nov 2024 14:05:39 +0900 Subject: [PATCH 0156/1002] =?UTF-8?q?style=20:=20response=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatRoomController.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index da2932c8..43eeafb6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -1,12 +1,15 @@ package inu.codin.codin.domain.chat.chatroom.controller; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; @@ -26,34 +29,38 @@ public class ChatRoomController { summary = "채팅방 생성" ) @PostMapping - public ResponseEntity createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ chatRoomService.createChatRoom(chatRoomCreateRequestDto, userDetails); - return ResponseUtils.successMsg("채팅방 생성 완료"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "채팅방 생성 완료", null)); } @Operation( summary = "사용자가 포함된 모든 채팅방 리스트 반환" ) @GetMapping - public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ - return ResponseUtils.success(chatRoomService.getAllChatRoomByUser(userDetails)); + public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "채팅방 리스트 반환 완료",chatRoomService.getAllChatRoomByUser(userDetails))); } @Operation( summary = "채팅방 나가기" ) @DeleteMapping("/{chatRoomId}") - public ResponseEntity leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ chatRoomService.leaveChatRoom(chatRoomId, userDetails); - return ResponseUtils.successMsg("채팅방 나가기 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "채팅방 나가기 완료", null)); } @Operation( summary = "채팅방 알림 여부 수정" ) @GetMapping("/notification/{chatRoomId}") - public ResponseEntity setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ chatRoomService.setNotificationChatRoom(chatRoomId, userDetails); - return ResponseUtils.successMsg("채팅방 알림 여부 수정 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "채팅방 알림 여부 수정 완료", null)); } } From a2c3a61f7a423138fc74330285555ceff018aa77 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:16:11 +0900 Subject: [PATCH 0157/1002] =?UTF-8?q?docs=20:=20Swagger=20docs=20descripti?= =?UTF-8?q?on=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatRoomController.java | 7 +++---- .../dto/ChatRoomCreateRequestDto.java | 9 ++++++++- .../chatroom/dto/ChatRoomListResponseDto.java | 20 +++++++++++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 43eeafb6..26db3dc3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -1,12 +1,12 @@ package inu.codin.codin.domain.chat.chatroom.controller; -import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -15,12 +15,11 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @Slf4j @RequestMapping("/chatroom") +@Tag(name = "ChatRoom API", description = "채팅방 생성, 리스트 반환, 채팅방 나가기, 채팅방 알림 설정") public class ChatRoomController { private final ChatRoomService chatRoomService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java index 5f4c4c82..2a7b94d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @@ -7,6 +9,11 @@ @Setter public class ChatRoomCreateRequestDto { + @NotBlank + @Schema(description = "채팅방 제목", example = "채팅해요") private String roomName; - private String receiverId; //채팅 수신자 + + @NotBlank + @Schema(description = "채팅 수신자", example = "1111111") + private String receiverId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java index a25245d6..8326ee0d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java @@ -1,21 +1,30 @@ package inu.codin.codin.domain.chat.chatroom.dto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatting.Message; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import java.time.LocalDate; import java.time.LocalDateTime; @Getter @Setter public class ChatRoomListResponseDto { + @NotBlank + @Schema(description = "채팅방 제목", example = "채팅해요") private final String roomName; + + @Schema(description = "가장 최근 채팅 내역 메세지", example = "안녕하세요") private final String message; + + @Schema(description = "가장 최근 채팅 내역 시간", example = "2024-11-29") private final LocalDateTime currentMessageDate; + + @Schema(description = "채팅방 알림 설정", example = "true") private final boolean notificationEnabled; @Builder @@ -26,12 +35,11 @@ public ChatRoomListResponseDto(String roomName, String message, LocalDateTime cu this.notificationEnabled = notificationEnabled; } - public static ChatRoomListResponseDto of(ChatRoom chatRoom) { - Message message = chatRoom.getMessages().get(chatRoom.getMessages().size()-1); + public static ChatRoomListResponseDto of(ChatRoom chatRoom, Chatting chatting) { return ChatRoomListResponseDto.builder() .roomName(chatRoom.getRoomName()) - .message(message.getContent()) - .currentMessageDate(message.getCreatedAt()) + .message(chatting.getContent()) + .currentMessageDate(chatting.getCreatedAt()) .build(); } } From 671c423d163461ffebd381047a0ad5d0c2e9f964 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:17:38 +0900 Subject: [PATCH 0158/1002] =?UTF-8?q?feat=20:=20Chatroom,=20Chatting=20ent?= =?UTF-8?q?ity=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/entity/ChatRoom.java | 12 ++---- .../repository/ChatRoomRepository.java | 6 ++- .../chatroom/service/ChatRoomService.java | 14 +++++-- .../domain/chat/chatting/entity/Chatting.java | 42 +++++++++++++++++++ 4 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 958f8f0e..7f694246 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatting.Message; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; @@ -10,12 +10,11 @@ import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.security.core.parameters.P; import java.util.ArrayList; import java.util.List; -@Document(collection = "chat") +@Document(collection = "chatroom") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ChatRoom extends BaseTimeEntity { @@ -29,15 +28,11 @@ public class ChatRoom extends BaseTimeEntity { @NotBlank private List participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) - @NotBlank - private List messages; - @Builder - public ChatRoom(String roomName, List participants, List messages) { + public ChatRoom(String roomName, List participants) { this.roomName = roomName; this.participants = participants; - this.messages = messages; } public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, String senderId){ @@ -47,7 +42,6 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Str return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) .participants(participants) - .messages(new ArrayList<>()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index 5d660b3f..0414883f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -5,9 +5,13 @@ import org.springframework.data.mongodb.repository.Query; import java.util.List; +import java.util.Optional; public interface ChatRoomRepository extends MongoRepository { - @Query("{ 'participants': ?0 }") + @Query("{ '_id': ?0, 'deletedAt': null }") + Optional findById(String id); + + @Query("{ 'participants': { '$elemMatch': { 'userId': ?0 } } , 'deleteAt': null }") List findByParticipant(String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index f90360a5..48b0bf1b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -6,6 +6,8 @@ import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,7 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; + private final ChattingRepository chattingRepository; public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { String senderId = ((CustomUserDetails) userDetails).getId(); @@ -30,14 +33,19 @@ public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Us public List getAllChatRoomByUser(UserDetails userDetails) { String userId = ((CustomUserDetails) userDetails).getId(); List chatRooms = chatRoomRepository.findByParticipant(userId); - return chatRooms.stream().map(ChatRoomListResponseDto::of).toList(); - } + return chatRooms.stream() + .map(chatRoom -> { + Chatting chatting = chattingRepository.findRecentMessageByChatroomId(chatRoom.getId()); + return ChatRoomListResponseDto.of(chatRoom, chatting); + }) + .toList();} public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { String userId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - chatRoom.getParticipants().remove(userId); + if (chatRoom.getParticipants().contains(userId)) chatRoom.getParticipants().remove(userId); + else throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); if (chatRoom.getParticipants().isEmpty()) { chatRoom.delete(); log.info("[LeaveChatRoom] 채팅방에 참여자가 없어 채팅방 삭제"); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java new file mode 100644 index 00000000..f60b6896 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.chat.chatting.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Getter +@Setter +@Document(collection = "chatting") +public class Chatting extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + @NotBlank + private String senderId; + + @NotBlank + private String content; + + private String chatRoomId; + + @Builder + public Chatting(String senderId, String content, String chatRoomId) { + this.senderId = senderId; + this.content = content; + this.chatRoomId = chatRoomId; + } + + public static Chatting of(String chatRoomId, ChattingRequestDto chattingRequestDto) { + return Chatting.builder() + .senderId(chattingRequestDto.getSenderId()) + .content(chattingRequestDto.getContent()) + .chatRoomId(chatRoomId) + .build(); + } +} From 2211e9801fb8d6848298ff7c971c92f4eaca8420 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:20:35 +0900 Subject: [PATCH 0159/1002] =?UTF-8?q?feat=20:=20STOMP=EC=99=80=20ReactiveM?= =?UTF-8?q?ongo=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=9C=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EB=B0=8F=20DB=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5,=20=EC=B1=84=ED=8C=85=20=EB=82=B4=EC=97=AD?= =?UTF-8?q?=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 1 + .../codin/common/config/WebSocketConfig.java | 4 +- .../chat/chatting/ChattingController.java | 23 ------- .../chat/chatting/ChattingRequestDto.java | 11 ---- .../codin/domain/chat/chatting/Message.java | 28 --------- .../controller/ChattingController.java | 60 +++++++++++++++++++ .../dto/request/ChattingRequestDto.java | 25 ++++++++ .../dto/response/ChattingResponseDto.java | 48 +++++++++++++++ .../exception/ChattingNotFoundException.java | 7 +++ .../chatting/service/ChattingService.java | 39 ++++++++++++ 10 files changed, 183 insertions(+), 63 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index ebd9a5b5..37b5b65e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -56,6 +56,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") + .requestMatchers("/ws-stomp/**").permitAll() //잠시 웹소켓 테스트를 위한 open .anyRequest().hasRole("USER") ) // JwtAuthenticationFilter 추가 diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 1b992979..a332fb30 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -13,16 +13,18 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint + .setAllowedOriginPatterns("*") .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic", "/queue"); - //해당 주소를 구독하고 있는 클라이언트들에게 메세지 전달 + //해당 주소를 구독 및 구독하고 있는 클라이언트들에게 메세지 전달 //메세지를 브로커로 라우팅 registry.setApplicationDestinationPrefixes("/pub"); //클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐 } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java deleted file mode 100644 index 03396fde..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java +++ /dev/null @@ -1,23 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.DestinationVariable; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestBody; - -@Controller -@RequiredArgsConstructor -@Slf4j -public class ChattingController { - - @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 - @SendTo("/queue/{chatRoomId}") - public void chat(@DestinationVariable("chatRoomId") String id, @RequestBody ChattingRequestDto chattingRequestDto){ - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); - } - - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java deleted file mode 100644 index d73a7bca..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ChattingRequestDto { - private Long senderId; - private String content; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java deleted file mode 100644 index 9d50122d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java +++ /dev/null @@ -1,28 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Field; - -import java.time.LocalDateTime; - -@Getter -@Setter -public class Message { - - @Id @NotBlank - private String messageId; - - private String senderId; - - private String content; - - private MessageType messageType; - - @CreatedDate - @Field("created_at") - private LocalDateTime createdAt; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java new file mode 100644 index 00000000..0031892a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -0,0 +1,60 @@ +package inu.codin.codin.domain.chat.chatting.controller; + +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; +import inu.codin.codin.domain.chat.chatting.service.ChattingService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import reactor.core.publisher.Mono; + +@Controller +@RequiredArgsConstructor +@Slf4j +@Tag(name = "Chatting API", description = "채팅 보내기, 채팅 내역 반환") +public class ChattingController { + + private final ChattingService chattingService; + private final SimpMessagingTemplate template; + + @Operation( + summary = "채팅 보내기" + ) + @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 + @SendTo("/sub/{chatRoomId}") + public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto){ + log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); + return chattingService.sendMessage(id, chattingRequestDto) + .thenReturn(ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", null))); + } + + + @Operation( + summary = "채팅 내용 리스트 가져오기" + ) + @GetMapping("/chats/list/{chatRoomId}") + public Mono>> getAllMessage(@PathVariable("chatRoomId") String id){ + return chattingService.getAllMessage(id) + .map(chattingList -> ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingList))); + } + + //채팅 테스트를 위한 MVC + @GetMapping("/chat") + public String chatHtml(){ + return "chat"; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java new file mode 100644 index 00000000..27594248 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.chat.chatting.dto.request; + +import inu.codin.codin.domain.chat.chatting.entity.MessageType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChattingRequestDto { + + @NotNull + @Schema(description = "STOMP 프로토콜 type", example = "SEND") + private MessageType type; + + @NotBlank + @Schema(description = "수신자 Id", example = "111111") + private String senderId; + + @NotBlank + @Schema(description = "채팅 내용", example = "안녕하세요") + private String content; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java new file mode 100644 index 00000000..162e14e4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -0,0 +1,48 @@ +package inu.codin.codin.domain.chat.chatting.dto.response; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class ChattingResponseDto { + + @NotBlank + private final String id; + + @NotBlank + private final String senderId; + + @NotBlank + private final String content; + + @NotBlank + private final LocalDateTime createdAt; + + @NotBlank + private final String chatRoomId; + + @Builder + public ChattingResponseDto(String id, String senderId, String content, LocalDateTime createdAt, String chatRoomId) { + this.id = id; + this.senderId = senderId; + this.content = content; + this.createdAt = createdAt; + this.chatRoomId = chatRoomId; + } + + public static ChattingResponseDto of(Chatting chatting){ + return ChattingResponseDto.builder() + .id(chatting.getId()) + .senderId(chatting.getSenderId()) + .content(chatting.getContent()) + .createdAt(chatting.getCreatedAt()) + .chatRoomId(chatting.getChatRoomId()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java new file mode 100644 index 00000000..5d1f296c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.chat.chatting.exception; + +public class ChattingNotFoundException extends RuntimeException{ + public ChattingNotFoundException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java new file mode 100644 index 00000000..51d9025f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -0,0 +1,39 @@ +package inu.codin.codin.domain.chat.chatting.service; + +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; +import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.exception.ChattingNotFoundException; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ChattingService { + + private final ChatRoomRepository chatRoomRepository; + private final ChattingRepository chattingRepository; + + //todo 이미지 채팅에 따른 S3 처리 + + public Mono sendMessage(String id, ChattingRequestDto chattingRequestDto) { + ChatRoom chatRoom = chatRoomRepository.findById(id) + .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + Chatting chatting = Chatting.of(chatRoom.getId(), chattingRequestDto); + return chattingRepository.save(chatting); + } + + public Mono> getAllMessage(String id) { + return chattingRepository.findAllByChatroomId(id) + .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) + .map(ChattingResponseDto::of) + .collectList(); + } +} From c3a271d34776bf1e900bf84d76db570c3c87e747 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:22:06 +0900 Subject: [PATCH 0160/1002] =?UTF-8?q?feat=20:=20Aggregation=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=9C=20=EC=B5=9C=EA=B7=BC=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EB=82=B4=EC=97=AD=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ChattingRepository.java | 17 ++++++++++++ .../ChattingRepositoryCustomImpl.java | 27 +++++++++++++++++++ .../repository/CustomChattingRepository.java | 9 +++++++ 3 files changed, 53 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java new file mode 100644 index 00000000..453889fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.chat.chatting.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ChattingRepository extends ReactiveMongoRepository, CustomChattingRepository { + + @Query("{ '_id': ?0, 'deletedAt': null }") + Mono findById(String id); + + @Query("{ 'chatRoomId': ?0 }") + Flux findAllByChatroomId(String id); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java new file mode 100644 index 00000000..a72a00bf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.chat.chatting.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; + +@RequiredArgsConstructor +public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ + + private final MongoTemplate mongoTemplate; + @Override + public Chatting findRecentMessageByChatroomId(String chatRoomId) { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), + Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), + Aggregation.limit(1) + ); + + AggregationResults result = mongoTemplate.aggregate(aggregation, "chatting", Chatting.class); + + return result.getUniqueMappedResult(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java new file mode 100644 index 00000000..5bc5f6ed --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.chat.chatting.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; + +public interface CustomChattingRepository { + + Chatting findRecentMessageByChatroomId(String id); + +} From cfc5b515044437f2466c81f853807129d398e9fa Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:32:41 +0900 Subject: [PATCH 0161/1002] =?UTF-8?q?refactor=20:=20=EC=B6=94=ED=9B=84=201?= =?UTF-8?q?:N=EC=9D=84=20=EC=9C=84=ED=95=9C=20STOMP=20=EA=B5=AC=EB=8F=85?= =?UTF-8?q?=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/chat/chatting/MessageType.java | 11 ----------- .../chat/chatting/controller/ChattingController.java | 2 +- .../domain/chat/chatting/entity/MessageType.java | 10 ++++++++++ 3 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java deleted file mode 100644 index 9b497d88..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java +++ /dev/null @@ -1,11 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public enum MessageType { - TEXT("텍스트"), - IMAGE("이미지"); - - private final String description; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 0031892a..601b241e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -34,7 +34,7 @@ public class ChattingController { summary = "채팅 보내기" ) @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 - @SendTo("/sub/{chatRoomId}") + @SendTo("/queue/{chatRoomId}") public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto){ log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); return chattingService.sendMessage(id, chattingRequestDto) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java new file mode 100644 index 00000000..505b176e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java @@ -0,0 +1,10 @@ +package inu.codin.codin.domain.chat.chatting.entity; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum MessageType { + SEND, + EXIT + //추후 추가 예정 +} From dd7734ffb3c366056f2d52ea76669438e47a1844 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:33:26 +0900 Subject: [PATCH 0162/1002] =?UTF-8?q?refactor=20:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 2 +- .../domain/chat/chatting/repository/ChattingRepository.java | 2 +- .../chat/chatting/repository/ChattingRepositoryCustomImpl.java | 2 +- .../chat/chatting/repository/CustomChattingRepository.java | 2 +- .../codin/domain/chat/chatting/service/ChattingService.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 48b0bf1b..5d253da4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -35,7 +35,7 @@ public List getAllChatRoomByUser(UserDetails userDetail List chatRooms = chatRoomRepository.findByParticipant(userId); return chatRooms.stream() .map(chatRoom -> { - Chatting chatting = chattingRepository.findRecentMessageByChatroomId(chatRoom.getId()); + Chatting chatting = chattingRepository.findRecentMessageByChatRoomId(chatRoom.getId()); return ChatRoomListResponseDto.of(chatRoom, chatting); }) .toList();} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 453889fa..75cdcf04 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -12,6 +12,6 @@ public interface ChattingRepository extends ReactiveMongoRepository findById(String id); @Query("{ 'chatRoomId': ?0 }") - Flux findAllByChatroomId(String id); + Flux findAllByChatRoomId(String id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java index a72a00bf..84088f75 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java @@ -13,7 +13,7 @@ public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ private final MongoTemplate mongoTemplate; @Override - public Chatting findRecentMessageByChatroomId(String chatRoomId) { + public Chatting findRecentMessageByChatRoomId(String chatRoomId) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 5bc5f6ed..1f4cfbea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -4,6 +4,6 @@ public interface CustomChattingRepository { - Chatting findRecentMessageByChatroomId(String id); + Chatting findRecentMessageByChatRoomId(String id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 51d9025f..8f86f39d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -31,7 +31,7 @@ public Mono sendMessage(String id, ChattingRequestDto chattingRequestD } public Mono> getAllMessage(String id) { - return chattingRepository.findAllByChatroomId(id) + return chattingRepository.findAllByChatRoomId(id) .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) .map(ChattingResponseDto::of) .collectList(); From 496dd8c2ba3a6da651cf9ea3bb6caa7bbf0bbbe1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 28 Nov 2024 14:05:39 +0900 Subject: [PATCH 0163/1002] =?UTF-8?q?[SC-81]=20style=20:=20response=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatRoomController.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index da2932c8..43eeafb6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -1,12 +1,15 @@ package inu.codin.codin.domain.chat.chatroom.controller; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; @@ -26,34 +29,38 @@ public class ChatRoomController { summary = "채팅방 생성" ) @PostMapping - public ResponseEntity createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ chatRoomService.createChatRoom(chatRoomCreateRequestDto, userDetails); - return ResponseUtils.successMsg("채팅방 생성 완료"); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "채팅방 생성 완료", null)); } @Operation( summary = "사용자가 포함된 모든 채팅방 리스트 반환" ) @GetMapping - public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ - return ResponseUtils.success(chatRoomService.getAllChatRoomByUser(userDetails)); + public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "채팅방 리스트 반환 완료",chatRoomService.getAllChatRoomByUser(userDetails))); } @Operation( summary = "채팅방 나가기" ) @DeleteMapping("/{chatRoomId}") - public ResponseEntity leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ chatRoomService.leaveChatRoom(chatRoomId, userDetails); - return ResponseUtils.successMsg("채팅방 나가기 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "채팅방 나가기 완료", null)); } @Operation( summary = "채팅방 알림 여부 수정" ) @GetMapping("/notification/{chatRoomId}") - public ResponseEntity setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ chatRoomService.setNotificationChatRoom(chatRoomId, userDetails); - return ResponseUtils.successMsg("채팅방 알림 여부 수정 완료"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "채팅방 알림 여부 수정 완료", null)); } } From 95e0bff049710b1453975d4a51b82d9c5040def6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:16:11 +0900 Subject: [PATCH 0164/1002] =?UTF-8?q?[SC-81]=20docs=20:=20Swagger=20docs?= =?UTF-8?q?=20description=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatRoomController.java | 7 +++---- .../dto/ChatRoomCreateRequestDto.java | 9 ++++++++- .../chatroom/dto/ChatRoomListResponseDto.java | 20 +++++++++++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 43eeafb6..26db3dc3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -1,12 +1,12 @@ package inu.codin.codin.domain.chat.chatroom.controller; -import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -15,12 +15,11 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @Slf4j @RequestMapping("/chatroom") +@Tag(name = "ChatRoom API", description = "채팅방 생성, 리스트 반환, 채팅방 나가기, 채팅방 알림 설정") public class ChatRoomController { private final ChatRoomService chatRoomService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java index 5f4c4c82..2a7b94d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @@ -7,6 +9,11 @@ @Setter public class ChatRoomCreateRequestDto { + @NotBlank + @Schema(description = "채팅방 제목", example = "채팅해요") private String roomName; - private String receiverId; //채팅 수신자 + + @NotBlank + @Schema(description = "채팅 수신자", example = "1111111") + private String receiverId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java index a25245d6..8326ee0d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java @@ -1,21 +1,30 @@ package inu.codin.codin.domain.chat.chatroom.dto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatting.Message; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import java.time.LocalDate; import java.time.LocalDateTime; @Getter @Setter public class ChatRoomListResponseDto { + @NotBlank + @Schema(description = "채팅방 제목", example = "채팅해요") private final String roomName; + + @Schema(description = "가장 최근 채팅 내역 메세지", example = "안녕하세요") private final String message; + + @Schema(description = "가장 최근 채팅 내역 시간", example = "2024-11-29") private final LocalDateTime currentMessageDate; + + @Schema(description = "채팅방 알림 설정", example = "true") private final boolean notificationEnabled; @Builder @@ -26,12 +35,11 @@ public ChatRoomListResponseDto(String roomName, String message, LocalDateTime cu this.notificationEnabled = notificationEnabled; } - public static ChatRoomListResponseDto of(ChatRoom chatRoom) { - Message message = chatRoom.getMessages().get(chatRoom.getMessages().size()-1); + public static ChatRoomListResponseDto of(ChatRoom chatRoom, Chatting chatting) { return ChatRoomListResponseDto.builder() .roomName(chatRoom.getRoomName()) - .message(message.getContent()) - .currentMessageDate(message.getCreatedAt()) + .message(chatting.getContent()) + .currentMessageDate(chatting.getCreatedAt()) .build(); } } From 6827a82d1f834baa015af0bcc80c501bee6ff16a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:17:38 +0900 Subject: [PATCH 0165/1002] =?UTF-8?q?[SC-81]=20feat=20:=20Chatroom,=20Chat?= =?UTF-8?q?ting=20entity=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/entity/ChatRoom.java | 12 ++---- .../repository/ChatRoomRepository.java | 6 ++- .../chatroom/service/ChatRoomService.java | 14 +++++-- .../domain/chat/chatting/entity/Chatting.java | 42 +++++++++++++++++++ 4 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 958f8f0e..7f694246 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatting.Message; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; @@ -10,12 +10,11 @@ import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.security.core.parameters.P; import java.util.ArrayList; import java.util.List; -@Document(collection = "chat") +@Document(collection = "chatroom") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ChatRoom extends BaseTimeEntity { @@ -29,15 +28,11 @@ public class ChatRoom extends BaseTimeEntity { @NotBlank private List participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) - @NotBlank - private List messages; - @Builder - public ChatRoom(String roomName, List participants, List messages) { + public ChatRoom(String roomName, List participants) { this.roomName = roomName; this.participants = participants; - this.messages = messages; } public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, String senderId){ @@ -47,7 +42,6 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Str return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) .participants(participants) - .messages(new ArrayList<>()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index 5d660b3f..0414883f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -5,9 +5,13 @@ import org.springframework.data.mongodb.repository.Query; import java.util.List; +import java.util.Optional; public interface ChatRoomRepository extends MongoRepository { - @Query("{ 'participants': ?0 }") + @Query("{ '_id': ?0, 'deletedAt': null }") + Optional findById(String id); + + @Query("{ 'participants': { '$elemMatch': { 'userId': ?0 } } , 'deleteAt': null }") List findByParticipant(String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index f90360a5..48b0bf1b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -6,6 +6,8 @@ import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,7 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; + private final ChattingRepository chattingRepository; public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { String senderId = ((CustomUserDetails) userDetails).getId(); @@ -30,14 +33,19 @@ public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Us public List getAllChatRoomByUser(UserDetails userDetails) { String userId = ((CustomUserDetails) userDetails).getId(); List chatRooms = chatRoomRepository.findByParticipant(userId); - return chatRooms.stream().map(ChatRoomListResponseDto::of).toList(); - } + return chatRooms.stream() + .map(chatRoom -> { + Chatting chatting = chattingRepository.findRecentMessageByChatroomId(chatRoom.getId()); + return ChatRoomListResponseDto.of(chatRoom, chatting); + }) + .toList();} public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { String userId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - chatRoom.getParticipants().remove(userId); + if (chatRoom.getParticipants().contains(userId)) chatRoom.getParticipants().remove(userId); + else throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); if (chatRoom.getParticipants().isEmpty()) { chatRoom.delete(); log.info("[LeaveChatRoom] 채팅방에 참여자가 없어 채팅방 삭제"); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java new file mode 100644 index 00000000..f60b6896 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.chat.chatting.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Getter +@Setter +@Document(collection = "chatting") +public class Chatting extends BaseTimeEntity { + + @Id @NotBlank + private String id; + + @NotBlank + private String senderId; + + @NotBlank + private String content; + + private String chatRoomId; + + @Builder + public Chatting(String senderId, String content, String chatRoomId) { + this.senderId = senderId; + this.content = content; + this.chatRoomId = chatRoomId; + } + + public static Chatting of(String chatRoomId, ChattingRequestDto chattingRequestDto) { + return Chatting.builder() + .senderId(chattingRequestDto.getSenderId()) + .content(chattingRequestDto.getContent()) + .chatRoomId(chatRoomId) + .build(); + } +} From 743f877c55aa288df3f7b54a352cbad7b195ae73 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:20:35 +0900 Subject: [PATCH 0166/1002] =?UTF-8?q?[SC-81]=20feat=20:=20STOMP=EC=99=80?= =?UTF-8?q?=20ReactiveMongo=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=9C=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EB=B0=8F=20?= =?UTF-8?q?DB=20=EC=A0=80=EC=9E=A5,=20=EC=B1=84=ED=8C=85=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 1 + .../codin/common/config/WebSocketConfig.java | 4 +- .../chat/chatting/ChattingController.java | 23 ------- .../chat/chatting/ChattingRequestDto.java | 11 ---- .../codin/domain/chat/chatting/Message.java | 28 --------- .../controller/ChattingController.java | 60 +++++++++++++++++++ .../dto/request/ChattingRequestDto.java | 25 ++++++++ .../dto/response/ChattingResponseDto.java | 48 +++++++++++++++ .../exception/ChattingNotFoundException.java | 7 +++ .../chatting/service/ChattingService.java | 39 ++++++++++++ 10 files changed, 183 insertions(+), 63 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index ebd9a5b5..37b5b65e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -56,6 +56,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") + .requestMatchers("/ws-stomp/**").permitAll() //잠시 웹소켓 테스트를 위한 open .anyRequest().hasRole("USER") ) // JwtAuthenticationFilter 추가 diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 1b992979..a332fb30 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -13,16 +13,18 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint + .setAllowedOriginPatterns("*") .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic", "/queue"); - //해당 주소를 구독하고 있는 클라이언트들에게 메세지 전달 + //해당 주소를 구독 및 구독하고 있는 클라이언트들에게 메세지 전달 //메세지를 브로커로 라우팅 registry.setApplicationDestinationPrefixes("/pub"); //클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐 } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java deleted file mode 100644 index 03396fde..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingController.java +++ /dev/null @@ -1,23 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.DestinationVariable; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestBody; - -@Controller -@RequiredArgsConstructor -@Slf4j -public class ChattingController { - - @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 - @SendTo("/queue/{chatRoomId}") - public void chat(@DestinationVariable("chatRoomId") String id, @RequestBody ChattingRequestDto chattingRequestDto){ - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); - } - - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java deleted file mode 100644 index d73a7bca..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChattingRequestDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ChattingRequestDto { - private Long senderId; - private String content; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java deleted file mode 100644 index 9d50122d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/Message.java +++ /dev/null @@ -1,28 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Field; - -import java.time.LocalDateTime; - -@Getter -@Setter -public class Message { - - @Id @NotBlank - private String messageId; - - private String senderId; - - private String content; - - private MessageType messageType; - - @CreatedDate - @Field("created_at") - private LocalDateTime createdAt; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java new file mode 100644 index 00000000..0031892a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -0,0 +1,60 @@ +package inu.codin.codin.domain.chat.chatting.controller; + +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; +import inu.codin.codin.domain.chat.chatting.service.ChattingService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import reactor.core.publisher.Mono; + +@Controller +@RequiredArgsConstructor +@Slf4j +@Tag(name = "Chatting API", description = "채팅 보내기, 채팅 내역 반환") +public class ChattingController { + + private final ChattingService chattingService; + private final SimpMessagingTemplate template; + + @Operation( + summary = "채팅 보내기" + ) + @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 + @SendTo("/sub/{chatRoomId}") + public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto){ + log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); + return chattingService.sendMessage(id, chattingRequestDto) + .thenReturn(ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", null))); + } + + + @Operation( + summary = "채팅 내용 리스트 가져오기" + ) + @GetMapping("/chats/list/{chatRoomId}") + public Mono>> getAllMessage(@PathVariable("chatRoomId") String id){ + return chattingService.getAllMessage(id) + .map(chattingList -> ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingList))); + } + + //채팅 테스트를 위한 MVC + @GetMapping("/chat") + public String chatHtml(){ + return "chat"; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java new file mode 100644 index 00000000..27594248 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.chat.chatting.dto.request; + +import inu.codin.codin.domain.chat.chatting.entity.MessageType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChattingRequestDto { + + @NotNull + @Schema(description = "STOMP 프로토콜 type", example = "SEND") + private MessageType type; + + @NotBlank + @Schema(description = "수신자 Id", example = "111111") + private String senderId; + + @NotBlank + @Schema(description = "채팅 내용", example = "안녕하세요") + private String content; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java new file mode 100644 index 00000000..162e14e4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -0,0 +1,48 @@ +package inu.codin.codin.domain.chat.chatting.dto.response; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class ChattingResponseDto { + + @NotBlank + private final String id; + + @NotBlank + private final String senderId; + + @NotBlank + private final String content; + + @NotBlank + private final LocalDateTime createdAt; + + @NotBlank + private final String chatRoomId; + + @Builder + public ChattingResponseDto(String id, String senderId, String content, LocalDateTime createdAt, String chatRoomId) { + this.id = id; + this.senderId = senderId; + this.content = content; + this.createdAt = createdAt; + this.chatRoomId = chatRoomId; + } + + public static ChattingResponseDto of(Chatting chatting){ + return ChattingResponseDto.builder() + .id(chatting.getId()) + .senderId(chatting.getSenderId()) + .content(chatting.getContent()) + .createdAt(chatting.getCreatedAt()) + .chatRoomId(chatting.getChatRoomId()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java new file mode 100644 index 00000000..5d1f296c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/exception/ChattingNotFoundException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.chat.chatting.exception; + +public class ChattingNotFoundException extends RuntimeException{ + public ChattingNotFoundException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java new file mode 100644 index 00000000..51d9025f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -0,0 +1,39 @@ +package inu.codin.codin.domain.chat.chatting.service; + +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; +import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.exception.ChattingNotFoundException; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ChattingService { + + private final ChatRoomRepository chatRoomRepository; + private final ChattingRepository chattingRepository; + + //todo 이미지 채팅에 따른 S3 처리 + + public Mono sendMessage(String id, ChattingRequestDto chattingRequestDto) { + ChatRoom chatRoom = chatRoomRepository.findById(id) + .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + Chatting chatting = Chatting.of(chatRoom.getId(), chattingRequestDto); + return chattingRepository.save(chatting); + } + + public Mono> getAllMessage(String id) { + return chattingRepository.findAllByChatroomId(id) + .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) + .map(ChattingResponseDto::of) + .collectList(); + } +} From 753df40bbdda917c6fc7fce492d9330dc19be5f9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:22:06 +0900 Subject: [PATCH 0167/1002] =?UTF-8?q?[SC-81]=20feat=20:=20Aggregation?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=9C=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EB=82=B4=EC=97=AD=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ChattingRepository.java | 17 ++++++++++++ .../ChattingRepositoryCustomImpl.java | 27 +++++++++++++++++++ .../repository/CustomChattingRepository.java | 9 +++++++ 3 files changed, 53 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java new file mode 100644 index 00000000..453889fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.chat.chatting.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ChattingRepository extends ReactiveMongoRepository, CustomChattingRepository { + + @Query("{ '_id': ?0, 'deletedAt': null }") + Mono findById(String id); + + @Query("{ 'chatRoomId': ?0 }") + Flux findAllByChatroomId(String id); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java new file mode 100644 index 00000000..a72a00bf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.chat.chatting.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; + +@RequiredArgsConstructor +public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ + + private final MongoTemplate mongoTemplate; + @Override + public Chatting findRecentMessageByChatroomId(String chatRoomId) { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), + Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), + Aggregation.limit(1) + ); + + AggregationResults result = mongoTemplate.aggregate(aggregation, "chatting", Chatting.class); + + return result.getUniqueMappedResult(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java new file mode 100644 index 00000000..5bc5f6ed --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.chat.chatting.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; + +public interface CustomChattingRepository { + + Chatting findRecentMessageByChatroomId(String id); + +} From 349e37de646eabfe3d512375e8fbc23051f51f0d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:32:41 +0900 Subject: [PATCH 0168/1002] =?UTF-8?q?[SC-81]=20refactor=20:=20=EC=B6=94?= =?UTF-8?q?=ED=9B=84=201:N=EC=9D=84=20=EC=9C=84=ED=95=9C=20STOMP=20?= =?UTF-8?q?=EA=B5=AC=EB=8F=85=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/chat/chatting/MessageType.java | 11 ----------- .../chat/chatting/controller/ChattingController.java | 2 +- .../domain/chat/chatting/entity/MessageType.java | 10 ++++++++++ 3 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java deleted file mode 100644 index 9b497d88..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/MessageType.java +++ /dev/null @@ -1,11 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public enum MessageType { - TEXT("텍스트"), - IMAGE("이미지"); - - private final String description; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 0031892a..601b241e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -34,7 +34,7 @@ public class ChattingController { summary = "채팅 보내기" ) @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 - @SendTo("/sub/{chatRoomId}") + @SendTo("/queue/{chatRoomId}") public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto){ log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); return chattingService.sendMessage(id, chattingRequestDto) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java new file mode 100644 index 00000000..505b176e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/MessageType.java @@ -0,0 +1,10 @@ +package inu.codin.codin.domain.chat.chatting.entity; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum MessageType { + SEND, + EXIT + //추후 추가 예정 +} From a1369b87018611720c9348cab3ba2c02eaf9781a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 14:33:26 +0900 Subject: [PATCH 0169/1002] =?UTF-8?q?[SC-81]=20refactor=20:=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 2 +- .../domain/chat/chatting/repository/ChattingRepository.java | 2 +- .../chat/chatting/repository/ChattingRepositoryCustomImpl.java | 2 +- .../chat/chatting/repository/CustomChattingRepository.java | 2 +- .../codin/domain/chat/chatting/service/ChattingService.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 48b0bf1b..5d253da4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -35,7 +35,7 @@ public List getAllChatRoomByUser(UserDetails userDetail List chatRooms = chatRoomRepository.findByParticipant(userId); return chatRooms.stream() .map(chatRoom -> { - Chatting chatting = chattingRepository.findRecentMessageByChatroomId(chatRoom.getId()); + Chatting chatting = chattingRepository.findRecentMessageByChatRoomId(chatRoom.getId()); return ChatRoomListResponseDto.of(chatRoom, chatting); }) .toList();} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 453889fa..75cdcf04 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -12,6 +12,6 @@ public interface ChattingRepository extends ReactiveMongoRepository findById(String id); @Query("{ 'chatRoomId': ?0 }") - Flux findAllByChatroomId(String id); + Flux findAllByChatRoomId(String id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java index a72a00bf..84088f75 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java @@ -13,7 +13,7 @@ public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ private final MongoTemplate mongoTemplate; @Override - public Chatting findRecentMessageByChatroomId(String chatRoomId) { + public Chatting findRecentMessageByChatRoomId(String chatRoomId) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 5bc5f6ed..1f4cfbea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -4,6 +4,6 @@ public interface CustomChattingRepository { - Chatting findRecentMessageByChatroomId(String id); + Chatting findRecentMessageByChatRoomId(String id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 51d9025f..8f86f39d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -31,7 +31,7 @@ public Mono sendMessage(String id, ChattingRequestDto chattingRequestD } public Mono> getAllMessage(String id) { - return chattingRepository.findAllByChatroomId(id) + return chattingRepository.findAllByChatRoomId(id) .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) .map(ChattingResponseDto::of) .collectList(); From 2a67afb8cedf6d795dfbba48d60f0eb47c6c1e91 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 18:44:56 +0900 Subject: [PATCH 0170/1002] =?UTF-8?q?[SC-81]=20fix=20:=20ReativeMongoDB?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20Auditing=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20Date=EA=B0=80=20=EB=B0=B0=EC=97=B4=EB=A1=9C=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=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 --- .../src/main/java/inu/codin/codin/CodinApplication.java | 2 ++ .../chat/chatting/dto/response/ChattingResponseDto.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index 3e3d45b3..279b86ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; +import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import java.util.TimeZone; @@ -10,6 +11,7 @@ @SpringBootApplication @EnableMongoAuditing @EnableMethodSecurity +@EnableReactiveMongoAuditing public class CodinApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java index 162e14e4..27122851 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.chat.chatting.dto.response; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import jakarta.validation.constraints.NotBlank; import lombok.Builder; @@ -22,6 +23,7 @@ public class ChattingResponseDto { private final String content; @NotBlank + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private final LocalDateTime createdAt; @NotBlank @@ -34,7 +36,7 @@ public ChattingResponseDto(String id, String senderId, String content, LocalDate this.content = content; this.createdAt = createdAt; this.chatRoomId = chatRoomId; - } +} public static ChattingResponseDto of(Chatting chatting){ return ChattingResponseDto.builder() From 48c5d1618120edf95f73c402acf70f9d3e0e8aa1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 18:57:04 +0900 Subject: [PATCH 0171/1002] =?UTF-8?q?[SC-81]=20fix=20:=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EB=90=9C=20=EC=B1=84=ED=8C=85=EB=B0=A9=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=98=EC=97=AC=20=EC=B1=84=ED=8C=85=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EB=B0=98=ED=99=98=20=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatting/service/ChattingService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 8f86f39d..015915e2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -31,6 +31,8 @@ public Mono sendMessage(String id, ChattingRequestDto chattingRequestD } public Mono> getAllMessage(String id) { + chatRoomRepository.findById(id) + .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); return chattingRepository.findAllByChatRoomId(id) .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) .map(ChattingResponseDto::of) From 79675f37737927b7ca20dd8f7f2033dc07451fa5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 18:57:45 +0900 Subject: [PATCH 0172/1002] =?UTF-8?q?[SC-81]=20fix=20:=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=9C=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=EC=97=90=20=EB=8C=80=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 5d253da4..044b89ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -44,8 +44,10 @@ public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { String userId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - if (chatRoom.getParticipants().contains(userId)) chatRoom.getParticipants().remove(userId); - else throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); + boolean isRemoved = chatRoom.getParticipants() + .removeIf(participant -> participant.getUserId().equals(userId)); + if (!isRemoved) throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); + if (chatRoom.getParticipants().isEmpty()) { chatRoom.delete(); log.info("[LeaveChatRoom] 채팅방에 참여자가 없어 채팅방 삭제"); From 1f101cd15ca7ce6c1ca7dd69a872b01ee9978484 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 1 Dec 2024 18:58:43 +0900 Subject: [PATCH 0173/1002] =?UTF-8?q?[SC-81]=20refactor=20:=20@NoArgsConst?= =?UTF-8?q?ructor=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/controller/ChattingController.java | 1 - .../codin/codin/domain/chat/chatting/entity/Chatting.java | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 601b241e..4481c556 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -28,7 +28,6 @@ public class ChattingController { private final ChattingService chattingService; - private final SimpMessagingTemplate template; @Operation( summary = "채팅 보내기" diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java index f60b6896..bf1d1425 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -3,15 +3,13 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Getter -@Setter @Document(collection = "chatting") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Chatting extends BaseTimeEntity { @Id @NotBlank From b8c7c1e0beedec65ee4164cae08a654b6ebfff30 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 1 Dec 2024 19:43:34 +0900 Subject: [PATCH 0174/1002] =?UTF-8?q?[SC-86]=20Comment=20:=20Config=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/config/SecurityConfig.java | 8 ++++++++ .../java/inu/codin/codin/common/config/SwaggerConfig.java | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index f50e6dca..d2e27b9d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -90,6 +90,7 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + // 토큰 없이 접근 가능한 URL private static final String[] PERMIT_ALL = { "/auth/login", "/auth/reissue", @@ -100,6 +101,7 @@ public PasswordEncoder passwordEncoder() { "/v3/api/test1", }; + // Swagger 접근 가능한 URL private static final String[] SWAGGER_AUTH_PATHS = { "/swagger-ui/**", "/v3/api-docs/**", @@ -107,20 +109,26 @@ public PasswordEncoder passwordEncoder() { "/swagger-resources/**", }; + // User 권한 URL private static final String[] USER_AUTH_PATHS = { "/v3/api/test2", "/v3/api/test3", }; + // Admin 권한 URL private static final String[] ADMIN_AUTH_PATHS = { "/v3/api/test4", }; + // Manager 권한 URL private static final String[] MANAGER_AUTH_PATHS = { "/v3/api/test5", }; + /** + * CORS 설정 + */ @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index c97c014a..bcd8c36e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -27,6 +27,7 @@ public OpenAPI customOpenAPI() { .description("CODIN API 명세서") .version("v1.0.0"); + // Bearer Auth 설정 SecurityScheme securityScheme = new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") @@ -34,6 +35,7 @@ public OpenAPI customOpenAPI() { .in(SecurityScheme.In.HEADER) .name("Authorization"); + // Bearer Auth를 사용하는 Security Requirement 설정 SecurityRequirement securityRequirement = new SecurityRequirement() .addList("Bearer Auth"); @@ -42,11 +44,12 @@ public OpenAPI customOpenAPI() { .security(List.of(securityRequirement)) .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) .servers(List.of( - new Server().url("http://localhost:8080").description("Local Server"), - new Server().url("https://www.codin.co.kr/api").description("Production Server") + new Server().url("http://localhost:8080").description("Local Server"), // Local Server + new Server().url("https://www.codin.co.kr/api").description("Production Server") // Production Server )); } + // ForwardedHeaderFilter Bean 등록 Nginx 프록시 서버 사용 시 필요 @Bean public ForwardedHeaderFilter forwardedHeaderFilter() { return new ForwardedHeaderFilter(); From f5b4773f164a61fe95b21cb3e2aded91e2ab03ac Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 1 Dec 2024 20:00:02 +0900 Subject: [PATCH 0175/1002] =?UTF-8?q?[SC-86]=20Fix=20:=20Refresh=20Token?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cookie -> Header 사용으로 변경 --- .../filter/JwtAuthenticationFilter.java | 2 +- .../codin/common/security/jwt/JwtUtils.java | 18 ++++++++---------- .../common/security/service/JwtService.java | 12 +++--------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 8058a6f5..a856b1b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -30,7 +30,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String accessToken = jwtUtils.getTokenFromHeader(request); + String accessToken = jwtUtils.getAccessToken(request); // Access Token이 있는 경우 if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index 7bd16313..d9e63b99 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -13,7 +13,7 @@ public class JwtUtils { * HTTP Header : "Authorization" : "Bearer ..." * @return (null, 빈 문자열, "Bearer ")로 시작하지 않는 경우 null 반환 */ - public String getTokenFromHeader(HttpServletRequest request) { + public String getAccessToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); @@ -22,16 +22,14 @@ public String getTokenFromHeader(HttpServletRequest request) { } /** - * 쿠키에서 Refresh 토큰 추출 - * @return 쿠키에 RefreshToken이 없는 경우 null 반환 + * 헤더에서 Refresh 토큰 추출 + * HTTP Header : "X-Refresh-Token" : "..." + * @return RefreshToken이 없는 경우 null 반환 */ - public String getTokenFromCookie(HttpServletRequest request) { - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("RT")) { - return cookie.getValue(); - } - } + public String getRefreshToken(HttpServletRequest request) { + String refreshToken = request.getHeader("X-Refresh-Token"); + if (StringUtils.hasText(refreshToken)) { + return refreshToken; } return null; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 58e309b6..51acfd09 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -5,7 +5,6 @@ import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -44,7 +43,7 @@ public void createToken(HttpServletResponse response) { * @param response */ public void reissueToken(HttpServletRequest request, HttpServletResponse response) { - String refreshToken = jwtUtils.getTokenFromCookie(request); + String refreshToken = jwtUtils.getRefreshToken(request); if (refreshToken == null) { log.error("[reissueToken] Refresh Token이 없습니다."); @@ -90,13 +89,8 @@ private void createBothToken(HttpServletResponse response) { // 응답 헤더에 Access Token 추가 response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); - // 쿠키에 새로운 Refresh Token 추가 - Cookie refreshTokenCookie = new Cookie("RT", newToken.getRefreshToken()); - refreshTokenCookie.setHttpOnly(true); - refreshTokenCookie.setAttribute("SameSite", "None"); // CORS 설정을 통해 SameSite=None 설정 필요 - refreshTokenCookie.setSecure(true); - refreshTokenCookie.setPath("/"); - response.addCookie(refreshTokenCookie); + // 헤더에 Access Token 추가 + response.addHeader("X-Refresh-Token", newToken.getRefreshToken()); log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Refresh : {}",authentication.getName(), newToken.getRefreshToken()); } From 4545d5dbe383dba7df09925e67e2e62ee1372539 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 1 Dec 2024 21:26:58 +0900 Subject: [PATCH 0176/1002] =?UTF-8?q?[SC-86]=20Fix=20:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20Response=20=ED=98=95=EC=8B=9D=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/controller/AuthController.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 44ce1de3..e642dc1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,6 +1,7 @@ package inu.codin.codin.common.security.controller; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.LoginRequestDto; import inu.codin.codin.common.security.service.JwtService; import io.swagger.v3.oas.annotations.Operation; @@ -39,21 +40,21 @@ public ResponseEntity login(@RequestBody LoginRequestDto loginRequestDto, Htt jwtService.createToken(response); - return ResponseUtils.successMsg("로그인 성공 / 토큰 발급 완료"); + return ResponseEntity.ok().body(new SingleResponse<>(200, "로그인 성공", null)); } @Operation(summary = "로그아웃") @PostMapping("/logout") public ResponseEntity logout() { jwtService.deleteToken(); - return ResponseUtils.successMsg("로그아웃 성공 / 토큰 삭제 완료"); + return ResponseEntity.ok().body(new SingleResponse<>(200, "로그아웃 성공", null)); } @Operation(summary = "토큰 재발급") @PostMapping("/reissue") public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { jwtService.reissueToken(request, response); - return ResponseUtils.successMsg("토큰 재발급 완료"); + return ResponseEntity.ok().body(new SingleResponse<>(200, "토큰 재발급 성공", null)); } } From 63bb25085189985f42f4a9b7fd1866e51c1bc554 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 1 Dec 2024 21:36:29 +0900 Subject: [PATCH 0177/1002] =?UTF-8?q?[SC-86]=20Fix=20:=20Filter=20Exceptio?= =?UTF-8?q?n=20Response=20=ED=98=95=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/filter/ExceptionHandlerFilter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java index 24761ce6..bb645324 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.SecurityErrorCode; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -9,6 +10,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -37,7 +39,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } private void sendErrorResponse(HttpServletResponse response, SecurityErrorCode code) throws IOException { - ResponseEntity responseEntity = ResponseUtils.error("Security Fail", code); + ResponseEntity responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ExceptionResponse(code.getMessage(), HttpStatus.UNAUTHORIZED.value())); // Set the HttpServletResponse properties response.setStatus(responseEntity.getStatusCode().value()); From 73b82075402541538c88a28c497cc9d2fd43d3b2 Mon Sep 17 00:00:00 2001 From: kbm Date: Sun, 1 Dec 2024 21:38:31 +0900 Subject: [PATCH 0178/1002] =?UTF-8?q?[SC-86]=20Refactor=20:=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 1 - .../codin/common/security/filter/ExceptionHandlerFilter.java | 1 - .../codin/common/security/filter/JwtAuthenticationFilter.java | 2 -- .../main/java/inu/codin/codin/common/security/jwt/JwtUtils.java | 1 - 4 files changed, 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index e642dc1a..76ea94e1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,6 +1,5 @@ package inu.codin.codin.common.security.controller; -import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.LoginRequestDto; import inu.codin.codin.common.security.service.JwtService; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java index bb645324..64cf4ca7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java @@ -1,7 +1,6 @@ package inu.codin.codin.common.security.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.ResponseUtils; import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.SecurityErrorCode; import jakarta.servlet.FilterChain; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index a856b1b1..ed5d3b1f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -3,7 +3,6 @@ import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.common.security.service.JwtService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -24,7 +23,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; - private final JwtService jwtService; private final JwtUtils jwtUtils; @Override diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index d9e63b99..a590a29d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -1,6 +1,5 @@ package inu.codin.codin.common.security.jwt; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; From a7d1f4fb4f4bd1bc88dc2ea2394f4b6be9be6d8b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 2 Dec 2024 14:19:58 +0900 Subject: [PATCH 0179/1002] =?UTF-8?q?[SC-69]=20=20Refactor=20:=20Scrap=20c?= =?UTF-8?q?ount=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 86f43d51..75f3b30c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -93,7 +93,7 @@ public void updateLikeCount(int likeCount) { } //스크랩 수 업데이트 public void updateScrapCount(int scrapCount) { - this.scrapCount=likeCount; + this.scrapCount=scrapCount; } From f8aa2a8041492af89cb92c752909bb53613d659a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 2 Dec 2024 14:20:51 +0900 Subject: [PATCH 0180/1002] =?UTF-8?q?[SC-69]=20=20Refactor=20:=20LikeType?= =?UTF-8?q?=20entityType=20Enum=20Class=20=EC=B6=94=EA=B0=80.=20DB::=20Enu?= =?UTF-8?q?m=20Redis=20::=20String?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/comment/service/CommentService.java | 5 ++- .../domain/post/like/LikeController.java | 15 ++++--- .../domain/post/like/LikeRepository.java | 10 +++-- .../codin/domain/post/like/LikeService.java | 40 ++++++++++++++----- .../post/like/{ => entity}/LikeEntity.java | 6 +-- .../domain/post/like/entity/LikeType.java | 16 ++++++++ .../exception/InvalidLikeTypeException.java | 7 ++++ .../domain/post/service/PostService.java | 11 ++--- .../codin/infra/redis/SyncScheduler.java | 13 +++--- 9 files changed, 86 insertions(+), 37 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/like/{ => entity}/LikeEntity.java (74%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java index aac2bbef..ca19822b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.comment.entity.ReplyEntity; import inu.codin.codin.domain.post.like.LikeService; +import inu.codin.codin.domain.post.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.comment.repository.CommentRepository; import inu.codin.codin.domain.post.comment.repository.ReplyRepository; @@ -146,7 +147,7 @@ public List getCommentsByPostId(String postId) { comment.getUserId(), comment.getContent(), getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 - likeService.getLikeCount("comment", comment.getCommentId()) // 댓글 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("comment"), comment.getCommentId()) // 댓글 좋아요 수 )) .collect(Collectors.toList()); } @@ -162,7 +163,7 @@ private List getRepliesByCommentId(String commentId) { reply.getUserId(), reply.getContent(), List.of(), // 대댓글은 하위 대댓글이 없음 - likeService.getLikeCount("reply", reply.getReplyId()) + likeService.getLikeCount(LikeType.valueOf("reply"), reply.getReplyId()) )) .collect(Collectors.toList()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java index 8db7ba56..dc015096 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.like.entity.LikeType; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -16,11 +17,12 @@ public class LikeController { private final LikeService likeService; @Operation(summary = "게시물, 댓글, 대댓글 좋아요 추가"+ - "entityType = post,comment,reply" - +"entityId = postId, commentId, replyId") + "entityType = post, comment, reply" + +"entityId = postId, commentId, replyId" + +"예시 :: post/123") @PostMapping("/{entityType}/{entityId}") public ResponseEntity> likeEntity( - @PathVariable String entityType, + @PathVariable LikeType entityType, @PathVariable String entityId) { String userId = SecurityUtils.getCurrentUserId(); @@ -31,11 +33,12 @@ public ResponseEntity> likeEntity( } @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 " + - "entityType = post,comment,reply" - +"entityId = postId, commentId, replyId") + "entityType = post, comment, reply" + +"entityId = postId, commentId, replyId" + +"예시 :: post/123") @DeleteMapping("/{entityType}/{entityId}") public ResponseEntity> unlikeEntity( - @PathVariable String entityType, + @PathVariable LikeType entityType, @PathVariable String entityId) { String userId = SecurityUtils.getCurrentUserId(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java index c2795666..d65e90ee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.post.like; +import inu.codin.codin.domain.post.like.entity.LikeEntity; +import inu.codin.codin.domain.post.like.entity.LikeType; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -9,13 +11,13 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByEntityTypeAndEntityId(String entityType, String entityId); + long countByEntityTypeAndEntityId(LikeType entityType, String entityId); // 특정 엔티티의 좋아요 데이터 조회 - List findByEntityTypeAndEntityId(String entityType, String entityId); + List findByEntityTypeAndEntityId(LikeType entityType, String entityId); // 특정 사용자의 좋아요 삭제 - void deleteByEntityTypeAndEntityIdAndUserId(String entityType, String entityId, String userId); + void deleteByEntityTypeAndEntityIdAndUserId(LikeType entityType, String entityId, String userId); - boolean existsByEntityTypeAndEntityIdAndUserId(String entityType, String entityId, String userId); + boolean existsByEntityTypeAndEntityIdAndUserId(LikeType entityType, String entityId, String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java index c3b77b59..270bab01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java @@ -1,16 +1,19 @@ package inu.codin.codin.domain.post.like; -import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.like.entity.LikeEntity; +import inu.codin.codin.domain.post.like.entity.LikeType; +import inu.codin.codin.domain.post.like.exception.InvalidLikeTypeException; import inu.codin.codin.domain.post.like.exception.LikeCreateFailException; import inu.codin.codin.domain.post.like.exception.LikeRemoveFailException; -import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.util.Arrays; +import java.util.EnumSet; + @Service @RequiredArgsConstructor @Slf4j @@ -20,15 +23,16 @@ public class LikeService { private final LikeRepository likeRepository; private final RedisHealthChecker redisHealthChecker; - public void addLike(String entityType, String entityId, String userId) { - + public void addLike(LikeType entityType, String entityId, String userId) { + //유효한 entityType 검증 + validateLikeType(entityType); // 중복 좋아요 검증 if (likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(entityType, entityId, userId); + redisService.addLike(entityType.name(), entityId, userId); } LikeEntity like = LikeEntity.builder() .entityType(entityType) @@ -38,20 +42,26 @@ public void addLike(String entityType, String entityId, String userId) { likeRepository.save(like); } - public void removeLike(String entityType, String entityId, String userId) { + public void removeLike(LikeType entityType, String entityId, String userId) { + //유효한 entityType 검증 + validateLikeType(entityType); + // 없는 좋아요 방지 if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(entityType, entityId, userId); + redisService.removeLike(entityType.name(), entityId, userId); } likeRepository.deleteByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId); } - public int getLikeCount(String entityType, String entityId) { + public int getLikeCount(LikeType entityType, String entityId) { + //유효한 entityType 검증 + validateLikeType(entityType); + if (redisHealthChecker.isRedisAvailable()) { - return redisService.getLikeCount(entityType, entityId); + return redisService.getLikeCount(entityType.name(), entityId); } Long count = likeRepository.countByEntityTypeAndEntityId(entityType, entityId); return (int) Math.max(0, count); @@ -59,7 +69,15 @@ public int getLikeCount(String entityType, String entityId) { public void recoverRedisFromDB() { likeRepository.findAll().forEach(like -> { - redisService.addLike(like.getEntityType(), like.getEntityId(), like.getUserId()); + redisService.addLike(like.getEntityType().name(), like.getEntityId(), like.getUserId()); }); } + + //EnumSet.allOf 를 사용해 Enum 값 집합 처리 + private void validateLikeType(LikeType entityType) { + if (entityType == null || !EnumSet.allOf(LikeType.class).contains(entityType)) { + throw new InvalidLikeTypeException("유효하지 않은 LikeType입니다: " + entityType); + } + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeEntity.java similarity index 74% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeEntity.java index 2d544ae4..8e81988e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.like; +package inu.codin.codin.domain.post.like.entity; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; @@ -12,11 +12,11 @@ public class LikeEntity extends BaseTimeEntity { @Id private String id; private String entityId; // 게시글, 댓글, 대댓글의 ID - private String entityType; // 엔티티 타입 (post, comment, reply) + private LikeType entityType; // 엔티티 타입 (post, comment, reply) private String userId; // 좋아요를 누른 사용자 ID @Builder - public LikeEntity(String entityId, String entityType, String userId) { + public LikeEntity(String entityId, LikeType entityType, String userId) { this.entityId = entityId; this.entityType = entityType; this.userId = userId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java new file mode 100644 index 00000000..a1edb76c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.post.like.entity; + +import lombok.Getter; + +@Getter +public enum LikeType { + post("게시물"), + comment("댓글"), + reply("대댓글"); + + private final String description; + + LikeType(String description) { + this.description = description; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java new file mode 100644 index 00000000..a69e330e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.like.exception; + +public class InvalidLikeTypeException extends RuntimeException { + public InvalidLikeTypeException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 34956ff6..77aafe19 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -14,6 +14,7 @@ import inu.codin.codin.domain.post.comment.repository.ReplyRepository; import inu.codin.codin.domain.post.like.LikeService; +import inu.codin.codin.domain.post.like.entity.LikeType; import inu.codin.codin.domain.post.scrap.ScrapService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; @@ -111,7 +112,7 @@ public List getAllPosts() { post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), // 댓글 수 - likeService.getLikeCount("post",post.getPostId()), // 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )) .collect(Collectors.toList()); @@ -136,7 +137,7 @@ public List getAllUserPosts() { post.getPostImageUrls(), post.isAnonymous(), commentRepository.countByPostId(post.getPostId()), // 댓글 수 - likeService.getLikeCount("post",post.getPostId()), // 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )) .collect(Collectors.toList()); @@ -157,7 +158,7 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { post.getPostImageUrls(), post.isAnonymous(), getCommentsByPostId(postId), // 댓글 및 대댓글 - likeService.getLikeCount("post",post.getPostId()), // 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 ); } @@ -174,7 +175,7 @@ private List getCommentsByPostId(String postId) { comment.getUserId(), comment.getContent(), getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 및 변환 - likeService.getLikeCount("comment",comment.getCommentId()) // 댓글 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("comment"),comment.getCommentId()) // 댓글 좋아요 수 )) .collect(Collectors.toList()); } @@ -191,7 +192,7 @@ private List getRepliesByCommentId(String commentId) { reply.getUserId(), reply.getContent(), List.of(), // 대댓글은 하위 대댓글이 없음 - likeService.getLikeCount("reply",reply.getReplyId()) // 대댓글 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("reply"),reply.getReplyId()) // 대댓글 좋아요 수 )) .collect(Collectors.toList()); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 10212766..2f955aee 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -4,13 +4,13 @@ import inu.codin.codin.domain.post.comment.entity.ReplyEntity; import inu.codin.codin.domain.post.comment.repository.CommentRepository; import inu.codin.codin.domain.post.comment.repository.ReplyRepository; -import inu.codin.codin.domain.post.like.LikeEntity; +import inu.codin.codin.domain.post.like.entity.LikeEntity; import inu.codin.codin.domain.post.like.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.scrap.ScrapEntity; import inu.codin.codin.domain.post.scrap.ScrapRepository; -import inu.codin.codin.domain.post.scrap.ScrapService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.repository.MongoRepository; @@ -49,17 +49,18 @@ public void syncLikes() { } private void syncEntityLikes(String entityType, MongoRepository repository) { - Set redisKeys = redisService.getKeys(entityType + ":likes:*"); + Set redisKeys = redisService.getKeys(entityType+ ":likes:*"); if (redisKeys == null || redisKeys.isEmpty()) { return; } + LikeType entityTypeEnum = LikeType.valueOf(entityType); for (String redisKey : redisKeys) { String entityId = redisKey.replace(entityType + ":likes:", ""); Set likedUsers = redisService.getLikedUsers(entityType, entityId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByEntityTypeAndEntityId(entityType, entityId); + List dbLikes = likeRepository.findByEntityTypeAndEntityId(entityTypeEnum, entityId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId())) { log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), entityId); @@ -69,10 +70,10 @@ private void syncEntityLikes(String entityType, MongoRepository r // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 for (String userId : likedUsers) { - if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { + if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityTypeEnum, entityId, userId)) { log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, entityId); LikeEntity dbLike = LikeEntity.builder() - .entityType(entityType) + .entityType(entityTypeEnum) .entityId(entityId) .userId(userId) .build(); From caf0adda9206f0fd0e27d73d144827f5ca2d5f52 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 2 Dec 2024 14:24:02 +0900 Subject: [PATCH 0181/1002] =?UTF-8?q?[SC-65]=20=20Refactor=20:=20findAllNo?= =?UTF-8?q?tDeleted()=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/repository/PostRepository.java | 4 +--- .../java/inu/codin/codin/domain/post/service/PostService.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 511952f4..7ad86fef 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -18,7 +18,7 @@ public interface PostRepository extends MongoRepository { List findByUserIdNotDeleted(String userId); @Query("{'isDeleted': false}") - List findALlNotDeleted(); + List findAllNotDeleted(); @Query("{'postId': ?0, 'isDeleted': false}") PostEntity findByPostIdNotDeleted(String postId); @@ -26,6 +26,4 @@ public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'comments.isDeleted': false}") Optional findPostWithActiveComments(String postId); - - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 77aafe19..091d6064 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -99,7 +99,7 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public List getAllPosts() { - List posts = postRepository.findALlNotDeleted(); + List posts = postRepository.findAllNotDeleted(); return posts.stream() .map(post -> new PostWithCountsResponseDTO( From e01a486689ef18229b93b7438317770491a650e7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 16:12:30 +0900 Subject: [PATCH 0182/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 10 +++++----- .../comment/dto/CommentCreateRequestDTO.java | 2 +- .../comment/dto/CommentResponseDTO.java | 2 +- .../comment/dto/ReplyCreateRequestDTO.java | 2 +- .../comment/entity/CommentEntity.java | 2 +- .../comment/entity/ReplyEntity.java | 2 +- .../comment/repository/CommentRepository.java | 4 ++-- .../comment/repository/ReplyRepository.java | 4 ++-- .../comment/service/CommentService.java | 20 +++++++++---------- .../{ => domain}/like/LikeController.java | 4 ++-- .../{ => domain}/like/LikeRepository.java | 6 +++--- .../post/{ => domain}/like/LikeService.java | 13 ++++++------ .../{ => domain}/like/entity/LikeEntity.java | 2 +- .../{ => domain}/like/entity/LikeType.java | 2 +- .../exception/InvalidLikeTypeException.java | 2 +- .../exception/LikeCreateFailException.java | 2 +- .../exception/LikeRemoveFailException.java | 2 +- .../{ => domain}/scrap/ScrapController.java | 3 +-- .../post/{ => domain}/scrap/ScrapEntity.java | 2 +- .../{ => domain}/scrap/ScrapRepository.java | 2 +- .../post/{ => domain}/scrap/ScrapService.java | 11 +++------- .../exception/ScrapCreateFailException.java | 2 +- .../exception/ScrapRemoveFailException.java | 2 +- .../response/PostWithCommentsResponseDTO.java | 2 +- .../response/PostWithDetailResponseDTO.java | 2 +- .../redis/RedisRecoverSyncScheduler.java | 4 ++-- .../codin/infra/redis/SyncScheduler.java | 18 ++++++++--------- 27 files changed, 61 insertions(+), 68 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/controller/CommentController.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/dto/CommentCreateRequestDTO.java (87%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/dto/CommentResponseDTO.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/dto/ReplyCreateRequestDTO.java (87%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/entity/CommentEntity.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/entity/ReplyEntity.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/repository/CommentRepository.java (70%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/repository/ReplyRepository.java (63%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/comment/service/CommentService.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/LikeController.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/LikeRepository.java (81%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/LikeService.java (86%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/entity/LikeEntity.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/entity/LikeType.java (81%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/exception/InvalidLikeTypeException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/exception/LikeCreateFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/like/exception/LikeRemoveFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/scrap/ScrapController.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/scrap/ScrapEntity.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/scrap/ScrapRepository.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/scrap/ScrapService.java (82%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/scrap/exception/ScrapCreateFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{ => domain}/scrap/exception/ScrapRemoveFailException.java (71%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 1551394f..dbb95403 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -1,11 +1,11 @@ -package inu.codin.codin.domain.post.comment.controller; +package inu.codin.codin.domain.post.domain.comment.controller; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.comment.dto.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.comment.dto.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.comment.service.CommentService; +import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java index cc105cdd..96e60271 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.comment.dto; +package inu.codin.codin.domain.post.domain.comment.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentResponseDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java index 56a8bc11..59ece895 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.comment.dto; +package inu.codin.codin.domain.post.domain.comment.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/ReplyCreateRequestDTO.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/ReplyCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/ReplyCreateRequestDTO.java index 7efecedd..35c207f1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/dto/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/ReplyCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.comment.dto; +package inu.codin.codin.domain.post.domain.comment.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 9d622a04..b96759a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.comment.entity; +package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/ReplyEntity.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/ReplyEntity.java index ec2558f7..23de7ed0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/entity/ReplyEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/ReplyEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.comment.entity; +package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index 5e3ca707..f779e3d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.post.comment.repository; +package inu.codin.codin.domain.post.domain.comment.repository; -import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import jakarta.validation.constraints.NotBlank; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java similarity index 63% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java index b9d42416..f4dcc3b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/repository/ReplyRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.comment.repository; +package inu.codin.codin.domain.post.domain.comment.repository; -import inu.codin.codin.domain.post.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; import org.springframework.data.mongodb.repository.MongoRepository; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index ca19822b..4698ad19 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -1,17 +1,17 @@ -package inu.codin.codin.domain.post.comment.service; +package inu.codin.codin.domain.post.domain.comment.service; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.comment.dto.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.comment.dto.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.comment.entity.ReplyEntity; -import inu.codin.codin.domain.post.like.LikeService; -import inu.codin.codin.domain.post.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.comment.repository.ReplyRepository; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.repository.ReplyRepository; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeController.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeController.java index dc015096..310c9789 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeController.java @@ -1,8 +1,8 @@ -package inu.codin.codin.domain.post.like; +package inu.codin.codin.domain.post.domain.like; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeRepository.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeRepository.java index d65e90ee..65aac002 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.like; +package inu.codin.codin.domain.post.domain.like; -import inu.codin.codin.domain.post.like.entity.LikeEntity; -import inu.codin.codin.domain.post.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeService.java similarity index 86% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeService.java index 270bab01..6515f44a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeService.java @@ -1,17 +1,16 @@ -package inu.codin.codin.domain.post.like; +package inu.codin.codin.domain.post.domain.like; -import inu.codin.codin.domain.post.like.entity.LikeEntity; -import inu.codin.codin.domain.post.like.entity.LikeType; -import inu.codin.codin.domain.post.like.exception.InvalidLikeTypeException; -import inu.codin.codin.domain.post.like.exception.LikeCreateFailException; -import inu.codin.codin.domain.post.like.exception.LikeRemoveFailException; +import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; +import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.exception.InvalidLikeTypeException; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.Arrays; import java.util.EnumSet; @Service diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java index 8e81988e..19207b35 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.like.entity; +package inu.codin.codin.domain.post.domain.like.entity; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java index a1edb76c..6381ac20 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/entity/LikeType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.like.entity; +package inu.codin.codin.domain.post.domain.like.entity; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java index a69e330e..b9d6385b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/InvalidLikeTypeException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.like.exception; +package inu.codin.codin.domain.post.domain.like.exception; public class InvalidLikeTypeException extends RuntimeException { public InvalidLikeTypeException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeCreateFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeCreateFailException.java index ae3ebb17..fa878d92 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeCreateFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeCreateFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.like.exception; +package inu.codin.codin.domain.post.domain.like.exception; public class LikeCreateFailException extends RuntimeException{ public LikeCreateFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeRemoveFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeRemoveFailException.java index 47aa85ad..b6613545 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/like/exception/LikeRemoveFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeRemoveFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.like.exception; +package inu.codin.codin.domain.post.domain.like.exception; public class LikeRemoveFailException extends RuntimeException { public LikeRemoveFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapController.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapController.java index f9ac5378..6fa39e84 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapController.java @@ -1,8 +1,7 @@ -package inu.codin.codin.domain.post.scrap; +package inu.codin.codin.domain.post.domain.scrap; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapEntity.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapEntity.java index d58ad7e5..cf91c22b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.scrap; +package inu.codin.codin.domain.post.domain.scrap; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapRepository.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapRepository.java index 62929558..09a785a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapRepository.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.scrap; +package inu.codin.codin.domain.post.domain.scrap; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapService.java similarity index 82% rename from codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapService.java index a7fdf183..f6af1836 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapService.java @@ -1,18 +1,13 @@ -package inu.codin.codin.domain.post.scrap; +package inu.codin.codin.domain.post.domain.scrap; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.like.exception.LikeRemoveFailException; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.scrap.exception.ScrapCreateFailException; +import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; +import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.util.Set; - @Service @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapCreateFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapCreateFailException.java index 807e6b93..aa942d5e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapCreateFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapCreateFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.scrap.exception; +package inu.codin.codin.domain.post.domain.scrap.exception; public class ScrapCreateFailException extends RuntimeException { public ScrapCreateFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapRemoveFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapRemoveFailException.java index 459ab93e..c7b280dc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scrap/exception/ScrapRemoveFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapRemoveFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.scrap.exception; +package inu.codin.codin.domain.post.domain.scrap.exception; public class ScrapRemoveFailException extends RuntimeException { public ScrapRemoveFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java index 5aea8653..12719bbf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.dto.response; -import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java index e8e9c12c..95377920 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.dto.response; -import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java index 682931cd..562d97a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -1,7 +1,7 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.domain.post.like.LikeService; -import inu.codin.codin.domain.post.scrap.ScrapService; +import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.scrap.ScrapService; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 2f955aee..c7c4439c 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -1,16 +1,16 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.domain.post.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.comment.entity.ReplyEntity; -import inu.codin.codin.domain.post.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.comment.repository.ReplyRepository; -import inu.codin.codin.domain.post.like.entity.LikeEntity; -import inu.codin.codin.domain.post.like.LikeRepository; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.repository.ReplyRepository; +import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.post.domain.like.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.scrap.ScrapEntity; -import inu.codin.codin.domain.post.scrap.ScrapRepository; +import inu.codin.codin.domain.post.domain.scrap.ScrapEntity; +import inu.codin.codin.domain.post.domain.scrap.ScrapRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.repository.MongoRepository; From 8a95ac3e40533d1bfa75c5702e8e5f50c472f90d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 16:12:44 +0900 Subject: [PATCH 0183/1002] =?UTF-8?q?[SC-65]=20fix=20:=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=82=B4=EC=97=AD=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/service/PostService.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 091d6064..c88ceaf7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,21 +1,21 @@ package inu.codin.codin.domain.post.service; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; -import inu.codin.codin.domain.post.comment.entity.ReplyEntity; -import inu.codin.codin.domain.post.comment.repository.ReplyRepository; +import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.domain.comment.repository.ReplyRepository; -import inu.codin.codin.domain.post.like.LikeService; -import inu.codin.codin.domain.post.like.entity.LikeType; -import inu.codin.codin.domain.post.scrap.ScrapService; +import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.scrap.ScrapService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.entity.PostEntity; @@ -88,12 +88,14 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req PostEntity post = postRepository.findById(postId) .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); post.updatePostAnonymous(requestDTO.isAnonymous()); + postRepository.save(post); } public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findById(postId) .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); post.updatePostStatus(requestDTO.getPostStatus()); + postRepository.save(post); } From d0a2add8f6b49d1f2167242438cd8ce0810e6679 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 16:30:23 +0900 Subject: [PATCH 0184/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20comment,=20r?= =?UTF-8?q?eplyComment=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 21 +--- .../comment/repository/ReplyRepository.java | 11 --- .../comment/service/CommentService.java | 81 ++------------- .../controller/ReplyCommentController.java | 37 +++++++ .../dto/request}/ReplyCreateRequestDTO.java | 2 +- .../entity/ReplyCommentEntity.java} | 6 +- .../repository/ReplyCommentRepository.java | 11 +++ .../reply/service/ReplyCommentService.java | 98 +++++++++++++++++++ .../domain/post/service/PostService.java | 53 ++-------- .../codin/infra/redis/SyncScheduler.java | 12 +-- 10 files changed, 172 insertions(+), 160 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{comment/dto => reply/dto/request}/ReplyCreateRequestDTO.java (85%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{comment/entity/ReplyEntity.java => reply/entity/ReplyCommentEntity.java} (81%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index dbb95403..ff7960c1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -3,7 +3,6 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; @@ -15,7 +14,7 @@ import java.util.List; @RestController -@RequestMapping("/api/comments") +@RequestMapping("/comments") public class CommentController { private final CommentService commentService; @@ -32,16 +31,6 @@ public ResponseEntity> addComment(@PathVariable String postId, .body(new SingleResponse<>(201, "댓글이 추가되었습니다.", null)); } - @Operation(summary = "대댓글 추가") - @PostMapping("/{commentId}/replies") - public ResponseEntity> addReply(@PathVariable String commentId, - @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { - commentService.addReply(commentId, requestDTO); - return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "대댓글이 추가되었습니다.", null)); - - } - @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회") @GetMapping("/post/{postId}") public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { @@ -58,12 +47,4 @@ public ResponseEntity> deleteComment(@PathVariable String comm return ResponseEntity.ok() .body(new SingleResponse<>(200, "댓글이 삭제되었습니다.", null)); } - - @Operation(summary = "대댓글 삭제") - @DeleteMapping("/replies/{replyId}") - public ResponseEntity> deleteReply(@PathVariable String replyId) { - commentService.deleteReply(replyId); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); - } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java deleted file mode 100644 index f4dcc3b3..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/ReplyRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package inu.codin.codin.domain.post.domain.comment.repository; - - -import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; -import org.springframework.data.mongodb.repository.MongoRepository; - -import java.util.List; - -public interface ReplyRepository extends MongoRepository { - List findByCommentId(String commentId); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 4698ad19..ec921d0d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -2,17 +2,16 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.like.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.comment.repository.ReplyRepository; -import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,9 +26,10 @@ public class CommentService { private final PostRepository postRepository; private final CommentRepository commentRepository; - private final ReplyRepository replyRepository; - private final RedisService redisService; + private final ReplyCommentRepository replyCommentRepository; + private final LikeService likeService; + private final ReplyCommentService replyCommentService; // 댓글 추가 public void addComment(String postId, CommentCreateRequestDTO requestDTO) { @@ -53,30 +53,6 @@ public void addComment(String postId, CommentCreateRequestDTO requestDTO) { log.info("댓글 추가완료 postId: {}.", postId); } - // 대댓글 추가 - public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { - CommentEntity comment = commentRepository.findById(commentId) - .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); - PostEntity post = postRepository.findById(comment.getPostId()) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - - String userId = SecurityUtils.getCurrentUserId(); - - ReplyEntity reply = ReplyEntity.builder() - .commentId(commentId) - .userId(userId) - .content(requestDTO.getContent()) - .build(); - - replyRepository.save(reply); - - // 댓글 수 증가 (대댓글도 댓글 수에 포함) - log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); - post.updateCommentCount(post.getCommentCount() + 1); - postRepository.save(post); - log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); - } - // 댓글 삭제 (Soft Delete) public void deleteComment(String commentId) { CommentEntity comment = commentRepository.findById(commentId) @@ -87,13 +63,13 @@ public void deleteComment(String commentId) { } // 댓글의 대댓글 조회 - List replies = replyRepository.findByCommentId(commentId); + List replies = replyCommentRepository.findByCommentId(commentId); // 대댓글 Soft Delete 처리 replies.forEach(reply -> { if (!reply.isDeleted()) { reply.softDelete(); - replyRepository.save(reply); + replyCommentRepository.save(reply); } }); @@ -111,30 +87,7 @@ public void deleteComment(String commentId) { commentId, replies.size(), (1 + replies.size()), post.getPostId()); } - // 대댓글 삭제 (Soft Delete) - public void deleteReply(String replyId) { - ReplyEntity reply = replyRepository.findById(replyId) - .orElseThrow(() -> new IllegalArgumentException("대댓글을 찾을 수 없습니다.")); - - if (reply.isDeleted()) { - throw new IllegalArgumentException("이미 삭제된 대댓글입니다."); - } - - // 대댓글 삭제 - reply.softDelete(); - replyRepository.save(reply); - // 댓글 수 감소 (대댓글도 댓글 수에서 감소) - CommentEntity comment = commentRepository.findById(reply.getCommentId()) - .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); - - PostEntity post = postRepository.findById(comment.getPostId()) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - post.updateCommentCount(post.getCommentCount() - 1); - postRepository.save(post); - - log.info("대댓글 성공적 삭제 replyId: {} , postId: {}.", replyId, post.getPostId()); - } // 특정 게시물의 댓글 및 대댓글 조회 public List getCommentsByPostId(String postId) { @@ -146,25 +99,9 @@ public List getCommentsByPostId(String postId) { comment.getCommentId(), comment.getUserId(), comment.getContent(), - getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 + replyCommentService.getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 likeService.getLikeCount(LikeType.valueOf("comment"), comment.getCommentId()) // 댓글 좋아요 수 )) .collect(Collectors.toList()); } - - // 특정 댓글의 대댓글 조회 - private List getRepliesByCommentId(String commentId) { - List replies = replyRepository.findByCommentId(commentId); - - return replies.stream() - .filter(reply -> !reply.isDeleted()) - .map(reply -> new CommentResponseDTO( - reply.getReplyId(), - reply.getUserId(), - reply.getContent(), - List.of(), // 대댓글은 하위 대댓글이 없음 - likeService.getLikeCount(LikeType.valueOf("reply"), reply.getReplyId()) - )) - .collect(Collectors.toList()); - } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java new file mode 100644 index 00000000..a6b643f2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -0,0 +1,37 @@ +package inu.codin.codin.domain.post.domain.reply.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/replies") +public class ReplyCommentController { + + private final ReplyCommentService replyCommentService; + + @Operation(summary = "대댓글 추가") + @PostMapping("/{commentId}") + public ResponseEntity> addReply(@PathVariable String commentId, + @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { + replyCommentService.addReply(commentId, requestDTO); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "대댓글이 추가되었습니다.", null)); + + } + + @Operation(summary = "대댓글 삭제") + @DeleteMapping("/{replyId}") + public ResponseEntity> deleteReply(@PathVariable String replyId) { + replyCommentService.deleteReply(replyId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/ReplyCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java index 35c207f1..1d8f999a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.comment.dto; +package inu.codin.codin.domain.post.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/ReplyEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/ReplyEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 23de7ed0..f3ebce8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/ReplyEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.comment.entity; +package inu.codin.codin.domain.post.domain.reply.entity; import inu.codin.codin.common.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; @@ -9,7 +9,7 @@ @Document(collection = "replies") @Getter -public class ReplyEntity extends BaseTimeEntity { +public class ReplyCommentEntity extends BaseTimeEntity { @Id @NotBlank private String replyId; @@ -22,7 +22,7 @@ public class ReplyEntity extends BaseTimeEntity { private int likeCount = 0; // 좋아요 카운트 @Builder - public ReplyEntity(String replyId, String commentId, String userId, String content, int likeCount) { + public ReplyCommentEntity(String replyId, String commentId, String userId, String content, int likeCount) { this.replyId = replyId; this.commentId = commentId; this.userId = userId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java new file mode 100644 index 00000000..9f7670d6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.post.domain.reply.repository; + + +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface ReplyCommentRepository extends MongoRepository { + List findByCommentId(String commentId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java new file mode 100644 index 00000000..f9ed66e2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -0,0 +1,98 @@ +package inu.codin.codin.domain.post.domain.reply.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ReplyCommentService { + + private final CommentRepository commentRepository; + private final PostRepository postRepository; + private final ReplyCommentRepository replyCommentRepository; + + private final LikeService likeService; + + // 대댓글 추가 + public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { + CommentEntity comment = commentRepository.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + PostEntity post = postRepository.findById(comment.getPostId()) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + String userId = SecurityUtils.getCurrentUserId(); + + ReplyCommentEntity reply = ReplyCommentEntity.builder() + .commentId(commentId) + .userId(userId) + .content(requestDTO.getContent()) + .build(); + + replyCommentRepository.save(reply); + + // 댓글 수 증가 (대댓글도 댓글 수에 포함) + log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); + post.updateCommentCount(post.getCommentCount() + 1); + postRepository.save(post); + log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); + } + + // 대댓글 삭제 (Soft Delete) + public void deleteReply(String replyId) { + ReplyCommentEntity reply = replyCommentRepository.findById(replyId) + .orElseThrow(() -> new IllegalArgumentException("대댓글을 찾을 수 없습니다.")); + + if (reply.isDeleted()) { + throw new IllegalArgumentException("이미 삭제된 대댓글입니다."); + } + + // 대댓글 삭제 + reply.softDelete(); + replyCommentRepository.save(reply); + + // 댓글 수 감소 (대댓글도 댓글 수에서 감소) + CommentEntity comment = commentRepository.findById(reply.getCommentId()) + .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + + PostEntity post = postRepository.findById(comment.getPostId()) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + post.updateCommentCount(post.getCommentCount() - 1); + postRepository.save(post); + + log.info("대댓글 성공적 삭제 replyId: {} , postId: {}.", replyId, post.getPostId()); + } + + // 특정 댓글의 대댓글 조회 + public List getRepliesByCommentId(String commentId) { + List replies = replyCommentRepository.findByCommentId(commentId); + + return replies.stream() + .filter(reply -> !reply.isDeleted()) + .map(reply -> new CommentResponseDTO( + reply.getReplyId(), + reply.getUserId(), + reply.getContent(), + List.of(), // 대댓글은 하위 대댓글이 없음 + likeService.getLikeCount(LikeType.valueOf("reply"), reply.getReplyId()) + )) + .collect(Collectors.toList()); + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index c88ceaf7..97c8e7b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -2,21 +2,17 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; -import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; -import inu.codin.codin.domain.post.domain.comment.repository.ReplyRepository; import inu.codin.codin.domain.post.domain.like.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.scrap.ScrapService; -import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; @@ -34,11 +30,11 @@ public class PostService { private final PostRepository postRepository; private final CommentRepository commentRepository; - private final ReplyRepository replyRepository; + private final S3Service s3Service; - private final RedisService redisService; private final LikeService likeService; private final ScrapService scrapService; + private final CommentService commentService; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -53,18 +49,14 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List imageUrls = handleImageUpload(postImages); - String userId = SecurityUtils.getCurrentUserId(); - PostEntity postEntity = PostEntity.builder() .userId(userId) .title(postCreateRequestDTO.getTitle()) .content(postCreateRequestDTO.getContent()) - //이미지 Url List 저장 .postImageUrls(imageUrls) - .isAnonymous(postCreateRequestDTO.isAnonymous()) .postCategory(postCreateRequestDTO.getPostCategory()) //Default Status = Active @@ -159,56 +151,23 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { post.getPostStatus(), post.getPostImageUrls(), post.isAnonymous(), - getCommentsByPostId(postId), // 댓글 및 대댓글 + commentService.getCommentsByPostId(postId), // 댓글 및 대댓글 likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 ); } - // 댓글 및 대댓글 조회 로직 - private List getCommentsByPostId(String postId) { - List comments = commentRepository.findByPostId(postId); - - return comments.stream() - .filter(comment -> !comment.isDeleted()) - .map(comment -> new CommentResponseDTO( - comment.getCommentId(), - comment.getUserId(), - comment.getContent(), - getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 및 변환 - likeService.getLikeCount(LikeType.valueOf("comment"),comment.getCommentId()) // 댓글 좋아요 수 - )) - .collect(Collectors.toList()); - } - - - // 대댓글 조회 로직 - private List getRepliesByCommentId(String commentId) { - List replies = replyRepository.findByCommentId(commentId); - - return replies.stream() - .filter(reply -> !reply.isDeleted()) - .map(reply -> new CommentResponseDTO( - reply.getReplyId(), - reply.getUserId(), - reply.getContent(), - List.of(), // 대댓글은 하위 대댓글이 없음 - likeService.getLikeCount(LikeType.valueOf("reply"),reply.getReplyId()) // 대댓글 좋아요 수 - )) - .collect(Collectors.toList()); - } - public void softDeletePost(String postId) { PostEntity post = postRepository.findById(postId) .orElseThrow(()-> new IllegalArgumentException("게시물을 찾을 수 없음.")); - if (post.isDeleted()) { + if (post.getDeletedAt()!=null) { throw new IllegalArgumentException("이미 삭제된 게시물"); } - post.softDeletePost(); + post.delete(); postRepository.save(post); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index c7c4439c..65a3353f 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -1,9 +1,9 @@ package inu.codin.codin.infra.redis; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.entity.ReplyEntity; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.comment.repository.ReplyRepository; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; @@ -29,7 +29,7 @@ public class SyncScheduler { private final RedisService redisService; private final PostRepository postRepository; private final CommentRepository commentRepository; - private final ReplyRepository replyRepository; + private final ReplyCommentRepository replyCommentRepository; private final LikeRepository likeRepository; private final ScrapRepository scrapRepository; private final RedisHealthChecker redisHealthChecker; @@ -43,7 +43,7 @@ public void syncLikes() { log.info(" 동기화 작업 시작"); syncEntityLikes("post", postRepository); syncEntityLikes("comment", commentRepository); - syncEntityLikes("reply", replyRepository); + syncEntityLikes("reply", replyCommentRepository); syncPostScraps(); log.info(" 동기화 작업 완료"); } @@ -97,8 +97,8 @@ private void syncEntityLikes(String entityType, MongoRepository r comment.updateLikeCount(likeCount); commentRepo.save(comment); } - } else if (repository instanceof ReplyRepository replyRepo) { - ReplyEntity reply = replyRepo.findById(entityId).orElse(null); + } else if (repository instanceof ReplyCommentRepository replyRepo) { + ReplyCommentEntity reply = replyRepo.findById(entityId).orElse(null); if (reply != null && reply.getLikeCount() != likeCount) { log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); reply.updateLikeCount(likeCount); From 22e2ebae3f29f9c3ebcf9c673388d96c826278d9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 16:34:07 +0900 Subject: [PATCH 0185/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20scrap,=20lik?= =?UTF-8?q?e=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/like/{ => controller}/LikeController.java | 3 ++- .../domain/like/{ => repository}/LikeRepository.java | 2 +- .../post/domain/like/{ => service}/LikeService.java | 3 ++- .../domain/scrap/{ => controller}/ScrapController.java | 3 ++- .../post/domain/scrap/{ => entity}/ScrapEntity.java | 2 +- .../domain/scrap/{ => repository}/ScrapRepository.java | 3 ++- .../post/domain/scrap/{ => service}/ScrapService.java | 4 +++- .../codin/infra/redis/RedisRecoverSyncScheduler.java | 4 ++-- .../inu/codin/codin/infra/redis/SyncScheduler.java | 10 +++++----- 9 files changed, 20 insertions(+), 14 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/{ => controller}/LikeController.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/{ => repository}/LikeRepository.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/{ => service}/LikeService.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/{ => controller}/ScrapController.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/{ => entity}/ScrapEntity.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/{ => repository}/ScrapRepository.java (80%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/{ => service}/ScrapService.java (90%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java index 310c9789..915cc9f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java @@ -1,7 +1,8 @@ -package inu.codin.codin.domain.post.domain.like; +package inu.codin.codin.domain.post.domain.like.controller; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 65aac002..b6ea992b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.like; +package inu.codin.codin.domain.post.domain.like.repository; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.entity.LikeType; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 6515f44a..3dd1809a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -1,10 +1,11 @@ -package inu.codin.codin.domain.post.domain.like; +package inu.codin.codin.domain.post.domain.like.service; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.exception.InvalidLikeTypeException; +import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 6fa39e84..605456c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -1,7 +1,8 @@ -package inu.codin.codin.domain.post.domain.scrap; +package inu.codin.codin.domain.post.domain.scrap.controller; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java index cf91c22b..a960caca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.scrap; +package inu.codin.codin.domain.post.domain.scrap.entity; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java similarity index 80% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 09a785a3..788b15b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -1,5 +1,6 @@ -package inu.codin.codin.domain.post.domain.scrap; +package inu.codin.codin.domain.post.domain.scrap.repository; +import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index f6af1836..ebbb806b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -1,7 +1,9 @@ -package inu.codin.codin.domain.post.domain.scrap; +package inu.codin.codin.domain.post.domain.scrap.service; import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; +import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; +import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java index 562d97a4..5c42dc40 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -1,7 +1,7 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.domain.post.domain.like.LikeService; -import inu.codin.codin.domain.post.domain.scrap.ScrapService; +import inu.codin.codin.domain.post.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 65a3353f..df0e023e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -5,12 +5,12 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.LikeRepository; +import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.domain.scrap.ScrapEntity; -import inu.codin.codin.domain.post.domain.scrap.ScrapRepository; +import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.repository.MongoRepository; @@ -84,7 +84,7 @@ private void syncEntityLikes(String entityType, MongoRepository r // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 int likeCount = likedUsers.size(); if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findById(entityId).orElse(null); + PostEntity post = postRepo.findByIdNotDeleted(entityId).orElse(null); if (post != null && post.getLikeCount() != likeCount) { log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); post.updateLikeCount(likeCount); @@ -147,7 +147,7 @@ public void syncPostScraps() { // Redis 사용자 수로 PostEntity의 scrapCount 업데이트 int redisScrapCount = redisScrappedUsers.size(); - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdNotDeleted(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); if (post.getScrapCount() != redisScrapCount) { log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, redisScrapCount); From df2810afd846223dee11968a023426ee789ff35f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 16:57:40 +0900 Subject: [PATCH 0186/1002] =?UTF-8?q?[SC-65]=20fix=20:=20repository=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EC=97=90=20softDelete=20=EC=86=8D=EC=84=B1?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 4 +-- .../domain/comment/entity/CommentEntity.java | 7 ---- .../comment/repository/CommentRepository.java | 10 ++++-- .../comment/service/CommentService.java | 32 ++++++++----------- .../controller/ReplyCommentController.java | 4 +-- .../reply/entity/ReplyCommentEntity.java | 7 ---- .../repository/ReplyCommentRepository.java | 9 +++++- .../reply/service/ReplyCommentService.java | 26 +++++++-------- .../codin/domain/post/entity/PostEntity.java | 18 +---------- .../post/repository/PostRepository.java | 19 ++++------- .../domain/post/service/PostService.java | 24 +++++++------- .../codin/infra/redis/SyncScheduler.java | 8 ++--- 12 files changed, 68 insertions(+), 100 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index ff7960c1..7643d7f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -42,8 +42,8 @@ public ResponseEntity> getCommentsByPostId(@Pat @Operation(summary = "댓글 삭제") @DeleteMapping("/{commentId}") - public ResponseEntity> deleteComment(@PathVariable String commentId) { - commentService.deleteComment(commentId); + public ResponseEntity> softDeleteComment(@PathVariable String commentId) { + commentService.softDeleteComment(commentId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "댓글이 삭제되었습니다.", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index b96759a8..08cace25 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -19,7 +19,6 @@ public class CommentEntity extends BaseTimeEntity { private String postId; //게시글 ID 참조 private String userId; private String content; - private boolean isDeleted = false; // Soft delete 상태 private int likeCount = 0; // 좋아요 수 (Redis에서 관리) @@ -31,12 +30,6 @@ public CommentEntity(String commentId, String postId, String userId, String cont this.content = content; } - // Soft Delete - public void softDelete() { - this.isDeleted = true; - this.delete(); - } - //좋아요 수 업데이트 public void updateLikeCount(int likeCount) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index f779e3d2..0b0831dd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -3,11 +3,17 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import jakarta.validation.constraints.NotBlank; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import java.util.List; +import java.util.Optional; public interface CommentRepository extends MongoRepository { - List findByPostId(String postId); - int countByPostId(@NotBlank String postId); + + @Query("{ '_id': ?0, 'deletedAt': null }") + Optional findByIdAndNotDeleted(String Id); + + @Query("{ 'postId': ?0, 'deletedAt': null }") + List findByPostIdAndNotDeleted(String postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index ec921d0d..f5319832 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -7,7 +7,7 @@ import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -17,7 +17,6 @@ import org.springframework.stereotype.Service; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -33,18 +32,15 @@ public class CommentService { // 댓글 추가 public void addComment(String postId, CommentCreateRequestDTO requestDTO) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); - String userId = SecurityUtils.getCurrentUserId(); - CommentEntity comment = CommentEntity.builder() .postId(postId) .userId(userId) .content(requestDTO.getContent()) .build(); - commentRepository.save(comment); // 댓글 수 증가 @@ -54,31 +50,30 @@ public void addComment(String postId, CommentCreateRequestDTO requestDTO) { } // 댓글 삭제 (Soft Delete) - public void deleteComment(String commentId) { - CommentEntity comment = commentRepository.findById(commentId) + public void softDeleteComment(String commentId) { + CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); - if (comment.isDeleted()) { + if (comment.getDeletedAt()!=null) { throw new IllegalArgumentException("이미 삭제된 댓글입니다."); } // 댓글의 대댓글 조회 - List replies = replyCommentRepository.findByCommentId(commentId); - + List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); // 대댓글 Soft Delete 처리 replies.forEach(reply -> { - if (!reply.isDeleted()) { - reply.softDelete(); + if (reply.getDeletedAt()!=null) { + reply.delete(); replyCommentRepository.save(reply); } }); // 댓글 Soft Delete 처리 - comment.softDelete(); + comment.delete(); commentRepository.save(comment); // 댓글 수 감소 (댓글 + 대댓글 수만큼 감소) - PostEntity post = postRepository.findById(comment.getPostId()) + PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); post.updateCommentCount(post.getCommentCount() - (1 + replies.size())); postRepository.save(post); @@ -91,17 +86,16 @@ public void deleteComment(String commentId) { // 특정 게시물의 댓글 및 대댓글 조회 public List getCommentsByPostId(String postId) { - List comments = commentRepository.findByPostId(postId); + List comments = commentRepository.findByPostIdAndNotDeleted(postId); return comments.stream() - .filter(comment -> !comment.isDeleted()) + .filter(comment -> comment.getDeletedAt() != null) .map(comment -> new CommentResponseDTO( comment.getCommentId(), comment.getUserId(), comment.getContent(), replyCommentService.getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 likeService.getLikeCount(LikeType.valueOf("comment"), comment.getCommentId()) // 댓글 좋아요 수 - )) - .collect(Collectors.toList()); + )).toList(); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index a6b643f2..51872070 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -29,8 +29,8 @@ public ResponseEntity> addReply(@PathVariable String commentId @Operation(summary = "대댓글 삭제") @DeleteMapping("/{replyId}") - public ResponseEntity> deleteReply(@PathVariable String replyId) { - replyCommentService.deleteReply(replyId); + public ResponseEntity> softDeleteReply(@PathVariable String replyId) { + replyCommentService.softDeleteReply(replyId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index f3ebce8c..1a2c6038 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -17,7 +17,6 @@ public class ReplyCommentEntity extends BaseTimeEntity { private String commentId; // 댓글 ID 참조 private String userId; // 작성자 ID private String content; - private boolean isDeleted = false; private int likeCount = 0; // 좋아요 카운트 @@ -30,12 +29,6 @@ public ReplyCommentEntity(String replyId, String commentId, String userId, Strin this.likeCount = likeCount; } - // Soft Delete - public void softDelete() { - this.isDeleted = true; - this.delete(); - } - //좋아요 수 업데이트 public void updateLikeCount(int likeCount) { this.likeCount=likeCount; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java index 9f7670d6..1a19da92 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java @@ -3,9 +3,16 @@ import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import java.util.List; +import java.util.Optional; public interface ReplyCommentRepository extends MongoRepository { - List findByCommentId(String commentId); + + @Query("{'_id': ?0, 'deletedAt': null}") + Optional findByIdAndNotDeleted(String id); + + @Query("{'commentId': ?0, 'deletedAt': null}") + List findByCommentIdAndNotDeleted(String commentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index f9ed66e2..94dfacfb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -4,7 +4,7 @@ import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; @@ -16,7 +16,6 @@ import org.springframework.stereotype.Service; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -31,9 +30,9 @@ public class ReplyCommentService { // 대댓글 추가 public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { - CommentEntity comment = commentRepository.findById(commentId) + CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); - PostEntity post = postRepository.findById(comment.getPostId()) + PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); String userId = SecurityUtils.getCurrentUserId(); @@ -54,23 +53,23 @@ public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { } // 대댓글 삭제 (Soft Delete) - public void deleteReply(String replyId) { - ReplyCommentEntity reply = replyCommentRepository.findById(replyId) + public void softDeleteReply(String replyId) { + ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) .orElseThrow(() -> new IllegalArgumentException("대댓글을 찾을 수 없습니다.")); - if (reply.isDeleted()) { + if (reply.getDeletedAt()!=null) { throw new IllegalArgumentException("이미 삭제된 대댓글입니다."); } // 대댓글 삭제 - reply.softDelete(); + reply.delete(); replyCommentRepository.save(reply); // 댓글 수 감소 (대댓글도 댓글 수에서 감소) - CommentEntity comment = commentRepository.findById(reply.getCommentId()) + CommentEntity comment = commentRepository.findByIdAndNotDeleted(reply.getCommentId()) .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); - PostEntity post = postRepository.findById(comment.getPostId()) + PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); post.updateCommentCount(post.getCommentCount() - 1); postRepository.save(post); @@ -80,18 +79,17 @@ public void deleteReply(String replyId) { // 특정 댓글의 대댓글 조회 public List getRepliesByCommentId(String commentId) { - List replies = replyCommentRepository.findByCommentId(commentId); + List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); return replies.stream() - .filter(reply -> !reply.isDeleted()) + .filter(reply -> reply.getDeletedAt()!=null) .map(reply -> new CommentResponseDTO( reply.getReplyId(), reply.getUserId(), reply.getContent(), List.of(), // 대댓글은 하위 대댓글이 없음 likeService.getLikeCount(LikeType.valueOf("reply"), reply.getReplyId()) - )) - .collect(Collectors.toList()); + )).toList(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 75f3b30c..c19c3b37 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -18,12 +18,10 @@ public class PostEntity extends BaseTimeEntity { private String postId; private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 - private String title; private String content; private List postImageUrls = new ArrayList<>(); private boolean isAnonymous; - private boolean isDeleted = false; private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) @@ -33,18 +31,15 @@ public class PostEntity extends BaseTimeEntity { private int scrapCount = 0; // 스크랩 카운트 (redis) @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, boolean isDeleted, PostStatus postStatus, int commentCount, int likeCount, int scrapCount) { + public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus, int commentCount, int likeCount, int scrapCount) { this.postId = postId; - this.userId = userId; this.title = title; this.content = content; this.postImageUrls = postImageUrls; this.isAnonymous = isAnonymous; - this.isDeleted = isDeleted; this.postCategory = postCategory; this.postStatus = postStatus; - this.commentCount = commentCount; this.likeCount = likeCount; this.scrapCount = scrapCount; @@ -72,17 +67,6 @@ public void removePostImage(String imageUrl) { this.postImageUrls.remove(imageUrl); } - public void removeAllPostImages() { - this.postImageUrls.clear(); - } - - public void softDeletePost() { - this.isDeleted = true; - this.delete(); - } - - - //댓글+대댓글 수 업데이트 public void updateCommentCount(int commentCount) { this.commentCount=commentCount; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 7ad86fef..abd3ebbc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -10,20 +10,13 @@ @Repository public interface PostRepository extends MongoRepository { - Optional findByTitle(String title); - List findByUserId(String userId); + @Query("{'_id': ?0, 'deletedAt': null}") + Optional findByIdAndNotDeleted(String Id); - @Query("{'userId': ?0, 'isDeleted': false}") - List findByUserIdNotDeleted(String userId); - - @Query("{'isDeleted': false}") - List findAllNotDeleted(); - - @Query("{'postId': ?0, 'isDeleted': false}") - PostEntity findByPostIdNotDeleted(String postId); - - @Query("{'_id': ?0, 'comments.isDeleted': false}") - Optional findPostWithActiveComments(String postId); + @Query("{'userId': ?0, 'deletedAt': null}") + List findByUserIdAndNotDeleted(String userId); + @Query("{'deletedAt': null}") + List findAllAndNotDeleted(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 97c8e7b6..2db25f02 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -10,9 +10,9 @@ import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; -import inu.codin.codin.domain.post.domain.like.LikeService; +import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.scrap.ScrapService; +import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; @@ -67,7 +67,7 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); List imageUrls = handleImageUpload(postImages); @@ -77,14 +77,14 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request } public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); } public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); @@ -93,7 +93,7 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public List getAllPosts() { - List posts = postRepository.findAllNotDeleted(); + List posts = postRepository.findAllAndNotDeleted(); return posts.stream() .map(post -> new PostWithCountsResponseDTO( @@ -118,7 +118,7 @@ public List getAllUserPosts() { String userId = SecurityUtils.getCurrentUserId(); - List posts = postRepository.findByUserIdNotDeleted(userId); + List posts = postRepository.findByUserIdAndNotDeleted(userId); return posts.stream() .map(post -> new PostWithCountsResponseDTO( @@ -130,16 +130,16 @@ public List getAllUserPosts() { post.getPostStatus(), post.getPostImageUrls(), post.isAnonymous(), - commentRepository.countByPostId(post.getPostId()), // 댓글 수 + post.getCommentCount(), likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )) - .collect(Collectors.toList()); + .toList(); } //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 public PostWithDetailResponseDTO getPostWithDetail(String postId) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); return new PostWithDetailResponseDTO( @@ -160,7 +160,7 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { public void softDeletePost(String postId) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()-> new IllegalArgumentException("게시물을 찾을 수 없음.")); if (post.getDeletedAt()!=null) { @@ -174,7 +174,7 @@ public void softDeletePost(String postId) { public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postRepository.findById(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); if (!post.getPostImageUrls().contains(imageUrl)) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index df0e023e..9391b659 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -84,21 +84,21 @@ private void syncEntityLikes(String entityType, MongoRepository r // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 int likeCount = likedUsers.size(); if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findByIdNotDeleted(entityId).orElse(null); + PostEntity post = postRepo.findByIdAndNotDeleted(entityId).orElse(null); if (post != null && post.getLikeCount() != likeCount) { log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); post.updateLikeCount(likeCount); postRepo.save(post); } } else if (repository instanceof CommentRepository commentRepo) { - CommentEntity comment = commentRepo.findById(entityId).orElse(null); + CommentEntity comment = commentRepo.findByIdAndNotDeleted(entityId).orElse(null); if (comment != null && comment.getLikeCount() != likeCount) { log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); comment.updateLikeCount(likeCount); commentRepo.save(comment); } } else if (repository instanceof ReplyCommentRepository replyRepo) { - ReplyCommentEntity reply = replyRepo.findById(entityId).orElse(null); + ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(entityId).orElse(null); if (reply != null && reply.getLikeCount() != likeCount) { log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); reply.updateLikeCount(likeCount); @@ -147,7 +147,7 @@ public void syncPostScraps() { // Redis 사용자 수로 PostEntity의 scrapCount 업데이트 int redisScrapCount = redisScrappedUsers.size(); - PostEntity post = postRepository.findByIdNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); if (post.getScrapCount() != redisScrapCount) { log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, redisScrapCount); From 9e0e94c3751567472a7ea57a91ba380e343148f0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 17:05:44 +0900 Subject: [PATCH 0187/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20=EB=B0=98?= =?UTF-8?q?=EB=B3=B5=EB=90=98=EB=8A=94=20SoftDelete=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/comment/entity/CommentEntity.java | 3 --- .../post/domain/comment/repository/CommentRepository.java | 1 - .../post/domain/comment/service/CommentService.java | 5 ----- .../post/domain/reply/service/ReplyCommentService.java | 8 +------- .../inu/codin/codin/domain/post/service/PostService.java | 5 ----- 5 files changed, 1 insertion(+), 21 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 08cace25..9197d536 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -7,9 +7,6 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import java.util.ArrayList; -import java.util.List; - @Document(collection = "comments") @Getter public class CommentEntity extends BaseTimeEntity { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index 0b0831dd..8d650990 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.repository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import jakarta.validation.constraints.NotBlank; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index f5319832..44a46e72 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -54,10 +54,6 @@ public void softDeleteComment(String commentId) { CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); - if (comment.getDeletedAt()!=null) { - throw new IllegalArgumentException("이미 삭제된 댓글입니다."); - } - // 댓글의 대댓글 조회 List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); // 대댓글 Soft Delete 처리 @@ -89,7 +85,6 @@ public List getCommentsByPostId(String postId) { List comments = commentRepository.findByPostIdAndNotDeleted(postId); return comments.stream() - .filter(comment -> comment.getDeletedAt() != null) .map(comment -> new CommentResponseDTO( comment.getCommentId(), comment.getUserId(), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 94dfacfb..8343c0ff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -4,8 +4,8 @@ import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; @@ -56,11 +56,6 @@ public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) .orElseThrow(() -> new IllegalArgumentException("대댓글을 찾을 수 없습니다.")); - - if (reply.getDeletedAt()!=null) { - throw new IllegalArgumentException("이미 삭제된 대댓글입니다."); - } - // 대댓글 삭제 reply.delete(); replyCommentRepository.save(reply); @@ -82,7 +77,6 @@ public List getRepliesByCommentId(String commentId) { List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); return replies.stream() - .filter(reply -> reply.getDeletedAt()!=null) .map(reply -> new CommentResponseDTO( reply.getReplyId(), reply.getUserId(), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 2db25f02..c5afefa8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -162,11 +162,6 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()-> new IllegalArgumentException("게시물을 찾을 수 없음.")); - - if (post.getDeletedAt()!=null) { - throw new IllegalArgumentException("이미 삭제된 게시물"); - } - post.delete(); postRepository.save(post); From b862879f657decce960a9510b8fb311871cdc345 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 17:46:31 +0900 Subject: [PATCH 0188/1002] =?UTF-8?q?[SC-65]=20feat=20:=20NotFoundExceptio?= =?UTF-8?q?n=20common=EC=9C=BC=EB=A1=9C=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/exception/NotFoundException.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/exception/NotFoundException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/NotFoundException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/NotFoundException.java new file mode 100644 index 00000000..6ea57e46 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/NotFoundException.java @@ -0,0 +1,8 @@ +package inu.codin.codin.common.exception; + + +public class NotFoundException extends RuntimeException{ + public NotFoundException(String message) { + super(message); + } +} From d537c53d682e530315e538cf048f70b2c7119e63 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 17:47:44 +0900 Subject: [PATCH 0189/1002] =?UTF-8?q?[SC-65]=20feat=20:=20NotFoundExceptio?= =?UTF-8?q?n=20common=EC=9C=BC=EB=A1=9C=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/{ => exception}/GlobalExceptionHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) rename codin-core/src/main/java/inu/codin/codin/common/{ => exception}/GlobalExceptionHandler.java (64%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java similarity index 64% rename from codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java rename to codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index fc4b9575..a509b9c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common; +package inu.codin.codin.common.exception; import inu.codin.codin.common.response.ExceptionResponse; import org.springframework.http.HttpStatus; @@ -15,4 +15,10 @@ protected ResponseEntity handleException(Exception e) { .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); } + @ExceptionHandler(NotFoundException.class) + protected ResponseEntity handleNotFoundException(NotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); + } + } From 4a33fee3f2049cd0f31f44ef69b39fc9e4e6e7c0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 17:48:54 +0900 Subject: [PATCH 0190/1002] =?UTF-8?q?[SC-65]=20prefix=20:=20LikeRequestDto?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/controller/LikeController.java | 38 ++++------- .../post/domain/like/dto/LikeRequestDto.java | 19 ++++++ .../post/domain/like/entity/LikeEntity.java | 10 +-- .../post/domain/like/entity/LikeType.java | 6 +- .../exception/InvalidLikeTypeException.java | 7 --- .../post/domain/like/service/LikeService.java | 63 ++++++++++--------- 6 files changed, 74 insertions(+), 69 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java index 915cc9f5..f9816c77 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.post.domain.like.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,34 +17,20 @@ public class LikeController { private final LikeService likeService; - @Operation(summary = "게시물, 댓글, 대댓글 좋아요 추가"+ - "entityType = post, comment, reply" - +"entityId = postId, commentId, replyId" - +"예시 :: post/123") - @PostMapping("/{entityType}/{entityId}") - public ResponseEntity> likeEntity( - @PathVariable LikeType entityType, - @PathVariable String entityId) { - - String userId = SecurityUtils.getCurrentUserId(); - - likeService.addLike(entityType, entityId, userId); + @Operation( + summary = "게시물, 댓글, 대댓글 좋아요 추가 " + ) + @PostMapping + public ResponseEntity> addLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { + likeService.addLike(likeRequestDto); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "좋아요가 추가되었습니다.", null)); } - @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 " + - "entityType = post, comment, reply" - +"entityId = postId, commentId, replyId" - +"예시 :: post/123") - @DeleteMapping("/{entityType}/{entityId}") - public ResponseEntity> unlikeEntity( - @PathVariable LikeType entityType, - @PathVariable String entityId) { - - String userId = SecurityUtils.getCurrentUserId(); - - likeService.removeLike(entityType, entityId, userId); + @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 ") + @DeleteMapping + public ResponseEntity> removeLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { + likeService.removeLike(likeRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "좋아요가 취소되었습니다.", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java new file mode 100644 index 00000000..1938cb40 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.post.domain.like.dto; + +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class LikeRequestDto { + + @NotNull + @Schema(description = "좋아요를 반영할 entity 타입(POST, COMMENT, REPLY)", example = "POST") + private LikeType likeType; + + @NotBlank + @Schema(description = "좋아요를 반영할 entity 의 _id 값", example = "111111") + private String id; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java index 19207b35..8eb141f8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java @@ -11,14 +11,14 @@ public class LikeEntity extends BaseTimeEntity { @Id private String id; - private String entityId; // 게시글, 댓글, 대댓글의 ID - private LikeType entityType; // 엔티티 타입 (post, comment, reply) + private String likeTypeId; // 게시글, 댓글, 대댓글의 ID + private LikeType likeType; // 엔티티 타입 (post, comment, reply) private String userId; // 좋아요를 누른 사용자 ID @Builder - public LikeEntity(String entityId, LikeType entityType, String userId) { - this.entityId = entityId; - this.entityType = entityType; + public LikeEntity(String likeTypeId, LikeType likeType, String userId) { + this.likeTypeId = likeTypeId; + this.likeType = likeType; this.userId = userId; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java index 6381ac20..6dff73ee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java @@ -4,9 +4,9 @@ @Getter public enum LikeType { - post("게시물"), - comment("댓글"), - reply("대댓글"); + POST("게시물"), + COMMENT("댓글"), + REPLY("대댓글"); private final String description; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java deleted file mode 100644 index b9d6385b..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/InvalidLikeTypeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.post.domain.like.exception; - -public class InvalidLikeTypeException extends RuntimeException { - public InvalidLikeTypeException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 3dd1809a..1f26aafa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -1,82 +1,89 @@ package inu.codin.codin.domain.post.domain.like.service; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.like.exception.InvalidLikeTypeException; import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.EnumSet; - @Service @RequiredArgsConstructor @Slf4j public class LikeService { + private final LikeRepository likeRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ReplyCommentRepository replyCommentRepository; private final RedisService redisService; - private final LikeRepository likeRepository; private final RedisHealthChecker redisHealthChecker; - public void addLike(LikeType entityType, String entityId, String userId) { - //유효한 entityType 검증 - validateLikeType(entityType); + public void addLike(LikeRequestDto likeRequestDto) { + String userId = SecurityUtils.getCurrentUserId(); + isEntityNotDeleted(likeRequestDto); //해당 entity가 삭제되었는지 확인 // 중복 좋아요 검증 - if (likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { + if (likeRepository.existsByEntityTypeAndEntityIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); } - if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(entityType.name(), entityId, userId); + redisService.addLike(likeRequestDto.getLikeType().name(), likeRequestDto.getId(), userId); } LikeEntity like = LikeEntity.builder() - .entityType(entityType) - .entityId(entityId) + .likeType(likeRequestDto.getLikeType()) + .likeTypeId(likeRequestDto.getId()) .userId(userId) .build(); likeRepository.save(like); } - public void removeLike(LikeType entityType, String entityId, String userId) { - //유효한 entityType 검증 - validateLikeType(entityType); - + public void removeLike(LikeRequestDto likeRequestDto) { + String userId = SecurityUtils.getCurrentUserId(); + isEntityNotDeleted(likeRequestDto); // 없는 좋아요 방지 - if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId)) { + if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(entityType.name(), entityId, userId); + redisService.removeLike(likeRequestDto.getLikeType().name(), likeRequestDto.getId(), userId); } - likeRepository.deleteByEntityTypeAndEntityIdAndUserId(entityType, entityId, userId); + likeRepository.deleteByEntityTypeAndEntityIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId); } public int getLikeCount(LikeType entityType, String entityId) { - //유효한 entityType 검증 - validateLikeType(entityType); - if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType.name(), entityId); } - Long count = likeRepository.countByEntityTypeAndEntityId(entityType, entityId); + long count = likeRepository.countByEntityTypeAndEntityId(entityType, entityId); return (int) Math.max(0, count); } public void recoverRedisFromDB() { likeRepository.findAll().forEach(like -> { - redisService.addLike(like.getEntityType().name(), like.getEntityId(), like.getUserId()); + redisService.addLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); }); } - //EnumSet.allOf 를 사용해 Enum 값 집합 처리 - private void validateLikeType(LikeType entityType) { - if (entityType == null || !EnumSet.allOf(LikeType.class).contains(entityType)) { - throw new InvalidLikeTypeException("유효하지 않은 LikeType입니다: " + entityType); + private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ + String id = likeRequestDto.getId(); + switch(likeRequestDto.getLikeType()){ + case POST -> postRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + case REPLY -> replyCommentRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + case COMMENT -> commentRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + } } From a81d683d2327037dea44449f4c070812085805be Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 17:54:24 +0900 Subject: [PATCH 0191/1002] =?UTF-8?q?[SC-65]=20prefix=20:=20Scrap=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scrap/controller/ScrapController.java | 15 ++++--------- .../scrap/repository/ScrapRepository.java | 1 - .../domain/scrap/service/ScrapService.java | 22 +++++++++++++++---- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 605456c4..a9030100 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -18,23 +18,16 @@ public class ScrapController { @Operation(summary = "게시물 스크랩 추가") @PostMapping("/{postId}") - public ResponseEntity> addScrap( - @PathVariable String postId) { - String userId = SecurityUtils.getCurrentUserId(); - - scrapService.addScrap(postId, userId); + public ResponseEntity> addScrap(@PathVariable String postId) { + scrapService.addScrap(postId); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "스크랩 성공.", null)); } @Operation(summary = "게시물 스크랩 삭제") @DeleteMapping("/{postId}") - public ResponseEntity> removeScrap( - @PathVariable String postId) { - - String userId = SecurityUtils.getCurrentUserId(); - - scrapService.removeScrap(postId, userId); + public ResponseEntity> removeScrap(@PathVariable String postId) { + scrapService.removeScrap(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "스크랩 취소되었습니다.", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 788b15b3..7b18bcd7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -8,7 +8,6 @@ @Repository public interface ScrapRepository extends MongoRepository { - boolean findByPostIdAndUserId(String postId, String userId); List findByPostId(String postId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index ebbb806b..2d65fa6b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -1,9 +1,13 @@ package inu.codin.codin.domain.post.domain.scrap.service; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; +import inu.codin.codin.domain.post.domain.scrap.exception.ScrapRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; @@ -14,12 +18,18 @@ @RequiredArgsConstructor @Slf4j public class ScrapService { + private final ScrapRepository scrapRepository; + private final PostRepository postRepository; private final RedisService redisService; - private final ScrapRepository scrapRepository; private final RedisHealthChecker redisHealthChecker; - public void addScrap(String postId, String userId) { + public void addScrap(String postId) { + postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + + String userId = SecurityUtils.getCurrentUserId(); + if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { throw new ScrapCreateFailException("이미 스크랩 한 상태 입니다."); } @@ -34,9 +44,13 @@ public void addScrap(String postId, String userId) { scrapRepository.save(scrap); } - public void removeScrap(String postId, String userId) { + public void removeScrap(String postId) { + postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + + String userId = SecurityUtils.getCurrentUserId(); if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { - throw new LikeRemoveFailException("스크랩한 적이 없는 게시물입니다."); + throw new ScrapRemoveFailException("스크랩한 적이 없는 게시물입니다."); } if (redisHealthChecker.isRedisAvailable()) { From bfc259d6fd8cc201d9f091b75715e3ab7c10c960 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 18:11:46 +0900 Subject: [PATCH 0192/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20Exception=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20NotFoundException=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/SecurityUtils.java | 7 ++++--- .../comment/service/CommentService.java | 7 ++++--- .../reply/service/ReplyCommentService.java | 11 ++++++----- .../codin/domain/post/entity/PostEntity.java | 5 +++-- .../post/exception/StateUpdateException.java | 7 +++++++ .../domain/post/service/PostService.java | 19 ++++++++++--------- .../inu/codin/codin/infra/s3/S3Service.java | 14 ++++++++------ .../s3/exception/ImageCountException.java | 7 +++++++ .../s3/exception/ImageFileSizeException.java | 7 +++++++ .../s3/exception/ImageRemoveException.java | 7 +++++++ .../s3/exception/ImageTypeException.java | 7 +++++++ .../s3/exception/S3BucketNameException.java | 7 +++++++ 12 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageCountException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageFileSizeException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageRemoveException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageTypeException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/s3/exception/S3BucketNameException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 1484c579..4fe768ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -1,5 +1,7 @@ package inu.codin.codin.common.security.util; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.domain.user.security.CustomUserDetails; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -18,11 +20,10 @@ public class SecurityUtils { public static String getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails)) { - throw new IllegalStateException("인증 정보가 없습니다."); + if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { + throw new JwtException(SecurityErrorCode.INVALID_CREDENTIALS); } - CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); return userDetails.getId(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 44a46e72..31737a0c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; @@ -33,7 +34,7 @@ public class CommentService { // 댓글 추가 public void addComment(String postId, CommentCreateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); String userId = SecurityUtils.getCurrentUserId(); CommentEntity comment = CommentEntity.builder() @@ -52,7 +53,7 @@ public void addComment(String postId, CommentCreateRequestDTO requestDTO) { // 댓글 삭제 (Soft Delete) public void softDeleteComment(String commentId) { CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); // 댓글의 대댓글 조회 List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); @@ -70,7 +71,7 @@ public void softDeleteComment(String commentId) { // 댓글 수 감소 (댓글 + 대댓글 수만큼 감소) PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); post.updateCommentCount(post.getCommentCount() - (1 + replies.size())); postRepository.save(post); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 8343c0ff..6ff2720d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.domain.reply.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -31,9 +32,9 @@ public class ReplyCommentService { // 대댓글 추가 public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); String userId = SecurityUtils.getCurrentUserId(); @@ -55,17 +56,17 @@ public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) - .orElseThrow(() -> new IllegalArgumentException("대댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); // 대댓글 삭제 reply.delete(); replyCommentRepository.save(reply); // 댓글 수 감소 (대댓글도 댓글 수에서 감소) CommentEntity comment = commentRepository.findByIdAndNotDeleted(reply.getCommentId()) - .orElseThrow(() -> new IllegalArgumentException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); post.updateCommentCount(post.getCommentCount() - 1); postRepository.save(post); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index c19c3b37..b11b9f74 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -52,14 +53,14 @@ public void updatePostContent(String content, List postImageUrls) { public void updatePostAnonymous(boolean isAnonymous) { if (this.isAnonymous == isAnonymous) { - throw new IllegalStateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); + throw new StateUpdateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); } this.isAnonymous = isAnonymous; } public void updatePostStatus(PostStatus postStatus) { if (this.postStatus == postStatus) { - throw new IllegalStateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); + throw new StateUpdateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); } this.postStatus = postStatus; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java new file mode 100644 index 00000000..c49a84df --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.exception; + +public class StateUpdateException extends RuntimeException{ + public StateUpdateException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index c5afefa8..f12ad4bc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; @@ -17,6 +18,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -29,7 +31,6 @@ @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; - private final CommentRepository commentRepository; private final S3Service s3Service; private final LikeService likeService; @@ -68,7 +69,7 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); + .orElseThrow(()->new NotFoundException("해당 게시물 없음")); List imageUrls = handleImageUpload(postImages); @@ -78,14 +79,14 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); + .orElseThrow(()->new NotFoundException("해당 게시물 없음")); post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); } public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(()->new IllegalArgumentException("해당 게시물 없음")); + .orElseThrow(()->new NotFoundException("해당 게시물 없음")); post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); } @@ -140,7 +141,7 @@ public List getAllUserPosts() { //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 public PostWithDetailResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); return new PostWithDetailResponseDTO( post.getUserId(), @@ -161,7 +162,7 @@ public PostWithDetailResponseDTO getPostWithDetail(String postId) { public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(()-> new IllegalArgumentException("게시물을 찾을 수 없음.")); + .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); post.delete(); postRepository.save(post); @@ -170,10 +171,10 @@ public void softDeletePost(String postId) { public void deletePostImage(String postId, String imageUrl) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); if (!post.getPostImageUrls().contains(imageUrl)) { - throw new IllegalArgumentException("이미지가 게시물에 존재하지 않습니다."); + throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); } try { @@ -183,7 +184,7 @@ public void deletePostImage(String postId, String imageUrl) { post.removePostImage(imageUrl); postRepository.save(post); } catch (Exception e) { - throw new IllegalStateException("이미지 삭제 중 오류 발생: " + imageUrl, e); + throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java index 9821a578..de45bfd4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java @@ -1,10 +1,12 @@ package inu.codin.codin.infra.s3; import com.amazonaws.services.s3.AmazonS3Client; +import inu.codin.codin.infra.s3.exception.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.awt.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -40,13 +42,13 @@ public List uploadFiles(List multipartFiles) { private void validateImageFileSize(MultipartFile multipartFile) { if (multipartFile.getSize() > MAX_FILE_SIZE) { - throw new IllegalArgumentException("파일 크기가 5MB를 초과할 수 없습니다."); + throw new ImageFileSizeException("파일 크기가 5MB를 초과할 수 없습니다."); } } private void validateFileCount(List multipartFiles) { if (multipartFiles.size() > MAX_FILE_COUNT) { - throw new IllegalArgumentException("이미지 파일 개수는 최대 10개까지 업로드 가능합니다."); + throw new ImageCountException("이미지 파일 개수는 최대 10개까지 업로드 가능합니다."); } } @@ -75,7 +77,7 @@ public void validateImageFileExtension(MultipartFile multipartFile) { List validExtensions = List.of("jpg", "jpeg", "png", "gif"); String extension = getExtension(multipartFile.getOriginalFilename()); if (extension == null || !validExtensions.contains(extension.toLowerCase())) { - throw new IllegalArgumentException("유효한 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다."); + throw new ImageTypeException("유효한 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다."); } } @@ -92,7 +94,7 @@ private String getExtension(String fileName) { //삭제 안됐을경우 에러처리 필요 public void deleteFile(String fileName) { if (bucket == null || bucket.isEmpty()) { - throw new IllegalStateException("S3 버킷 이름이 설정되지 않았습니다."); + throw new S3BucketNameException("S3 버킷 이름이 설정되지 않았습니다."); } try { @@ -100,10 +102,10 @@ public void deleteFile(String fileName) { // 삭제가 제대로 되었는지 추가 검증 if (amazonS3Client.doesObjectExist(bucket, fileName)) { - throw new IllegalStateException("파일 삭제가 실패했습니다. 파일명: " + fileName); + throw new ImageRemoveException("파일 삭제가 실패했습니다. 파일명: " + fileName); } } catch (Exception e) { - throw new IllegalArgumentException("파일 삭제 중 오류가 발생했습니다. 파일명: " + fileName, e); + throw new ImageRemoveException("파일 삭제 중 오류가 발생했습니다. 파일명: " + fileName); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageCountException.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageCountException.java new file mode 100644 index 00000000..d61d7c45 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageCountException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.s3.exception; + +public class ImageCountException extends RuntimeException{ + public ImageCountException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageFileSizeException.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageFileSizeException.java new file mode 100644 index 00000000..b0461507 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageFileSizeException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.s3.exception; + +public class ImageFileSizeException extends RuntimeException{ + public ImageFileSizeException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageRemoveException.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageRemoveException.java new file mode 100644 index 00000000..01693a8b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageRemoveException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.s3.exception; + +public class ImageRemoveException extends RuntimeException{ + public ImageRemoveException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageTypeException.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageTypeException.java new file mode 100644 index 00000000..152edc8d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/ImageTypeException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.s3.exception; + +public class ImageTypeException extends RuntimeException{ + public ImageTypeException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/S3BucketNameException.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/S3BucketNameException.java new file mode 100644 index 00000000..b4d6f57c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/exception/S3BucketNameException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.infra.s3.exception; + +public class S3BucketNameException extends RuntimeException{ + public S3BucketNameException(String message) { + super(message); + } +} From ddfd17103d1f0e95e917d26e78e2781beca2a01a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 19:15:11 +0900 Subject: [PATCH 0193/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20entityType,?= =?UTF-8?q?=20entityId=20=EB=B3=80=EC=88=98=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/repository/LikeRepository.java | 8 ++--- .../post/domain/like/service/LikeService.java | 8 ++--- .../codin/infra/redis/SyncScheduler.java | 33 ++++++++++--------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index b6ea992b..4b12ede9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -11,13 +11,13 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByEntityTypeAndEntityId(LikeType entityType, String entityId); + long countByLikeTypeAndLikeTypeId(LikeType likeType, String id); // 특정 엔티티의 좋아요 데이터 조회 - List findByEntityTypeAndEntityId(LikeType entityType, String entityId); + List findByLikeTypeAndLikeTypeId(LikeType likeType, String id); // 특정 사용자의 좋아요 삭제 - void deleteByEntityTypeAndEntityIdAndUserId(LikeType entityType, String entityId, String userId); + void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String id, String userId); - boolean existsByEntityTypeAndEntityIdAndUserId(LikeType entityType, String entityId, String userId); + boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String id, String userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 1f26aafa..ba22c533 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -33,7 +33,7 @@ public void addLike(LikeRequestDto likeRequestDto) { String userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); //해당 entity가 삭제되었는지 확인 // 중복 좋아요 검증 - if (likeRepository.existsByEntityTypeAndEntityIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { + if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); } if (redisHealthChecker.isRedisAvailable()) { @@ -51,20 +51,20 @@ public void removeLike(LikeRequestDto likeRequestDto) { String userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 없는 좋아요 방지 - if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); } if (redisHealthChecker.isRedisAvailable()) { redisService.removeLike(likeRequestDto.getLikeType().name(), likeRequestDto.getId(), userId); } - likeRepository.deleteByEntityTypeAndEntityIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId); + likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId); } public int getLikeCount(LikeType entityType, String entityId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType.name(), entityId); } - long count = likeRepository.countByEntityTypeAndEntityId(entityType, entityId); + long count = likeRepository.countByLikeTypeAndLikeTypeId(entityType, entityId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 9391b659..16fbd06b 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -1,5 +1,6 @@ package inu.codin.codin.infra.redis; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -53,28 +54,28 @@ private void syncEntityLikes(String entityType, MongoRepository r if (redisKeys == null || redisKeys.isEmpty()) { return; } - LikeType entityTypeEnum = LikeType.valueOf(entityType); + LikeType likeType = LikeType.valueOf(entityType); for (String redisKey : redisKeys) { - String entityId = redisKey.replace(entityType + ":likes:", ""); - Set likedUsers = redisService.getLikedUsers(entityType, entityId); + String likeTypeId = redisKey.replace(entityType + ":likes:", ""); + Set likedUsers = redisService.getLikedUsers(entityType, likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByEntityTypeAndEntityId(entityTypeEnum, entityId); + List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeTypeId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId())) { - log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), entityId); + log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeTypeId); likeRepository.delete(dbLike); } } // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 for (String userId : likedUsers) { - if (!likeRepository.existsByEntityTypeAndEntityIdAndUserId(entityTypeEnum, entityId, userId)) { - log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, entityId); + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeTypeId, userId)) { + log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, likeTypeId); LikeEntity dbLike = LikeEntity.builder() - .entityType(entityTypeEnum) - .entityId(entityId) + .likeType(likeType) + .likeTypeId(likeTypeId) .userId(userId) .build(); likeRepository.save(dbLike); @@ -84,23 +85,23 @@ private void syncEntityLikes(String entityType, MongoRepository r // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 int likeCount = likedUsers.size(); if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findByIdAndNotDeleted(entityId).orElse(null); + PostEntity post = postRepo.findByIdAndNotDeleted(likeTypeId).orElse(null); if (post != null && post.getLikeCount() != likeCount) { - log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); + log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); post.updateLikeCount(likeCount); postRepo.save(post); } } else if (repository instanceof CommentRepository commentRepo) { - CommentEntity comment = commentRepo.findByIdAndNotDeleted(entityId).orElse(null); + CommentEntity comment = commentRepo.findByIdAndNotDeleted(likeTypeId).orElse(null); if (comment != null && comment.getLikeCount() != likeCount) { - log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); + log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); comment.updateLikeCount(likeCount); commentRepo.save(comment); } } else if (repository instanceof ReplyCommentRepository replyRepo) { - ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(entityId).orElse(null); + ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(likeTypeId).orElse(null); if (reply != null && reply.getLikeCount() != likeCount) { - log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityId, likeCount); + log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); reply.updateLikeCount(likeCount); replyRepo.save(reply); } @@ -148,7 +149,7 @@ public void syncPostScraps() { // Redis 사용자 수로 PostEntity의 scrapCount 업데이트 int redisScrapCount = redisScrappedUsers.size(); PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); if (post.getScrapCount() != redisScrapCount) { log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, redisScrapCount); post.updateScrapCount(redisScrapCount); From da206b4a062d3f4c6f324de48fab89c3e5c3610f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 19:16:48 +0900 Subject: [PATCH 0194/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20DTO=20?= =?UTF-8?q?=EC=83=81=EC=86=8D=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8E=99?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 24 +++--- ...nseDTO.java => PostCommonResponseDto.java} | 19 ++--- .../dto/response/PostDetailResponseDto.java | 33 ++++++++ .../dto/response/PostListResponseDto.java | 22 +++++ .../response/PostWithCommentsResponseDTO.java | 53 ------------ .../response/PostWithDetailResponseDTO.java | 83 ------------------- .../domain/post/service/PostService.java | 46 ++++------ 7 files changed, 87 insertions(+), 193 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/{PostWithCountsResponseDTO.java => PostCommonResponseDto.java} (73%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index db111672..cb304d57 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -6,8 +6,8 @@ import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostListResponseDto; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDto; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -20,7 +20,7 @@ import java.util.List; @RestController -@RequestMapping("/api/posts") +@RequestMapping("/posts") public class PostController { private final PostService postService; @@ -39,9 +39,7 @@ public ResponseEntity> createPost( @RequestPart(value = "postImages", required = false) List postImages) { // postImages가 null이면 빈 리스트로 처리 - if (postImages == null) { - postImages = List.of(); - } + if (postImages == null) postImages = List.of(); postService.createPost(postCreateRequestDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED) @@ -49,7 +47,7 @@ public ResponseEntity> createPost( } @Operation( - summary = "게시물 내용 이미지 수정 추가" + summary = "게시물 내용 수정 및 이미지 수정&추가" ) @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> updatePostContent( @@ -90,8 +88,8 @@ public ResponseEntity> updatePostAnonymous( summary = "삭제되지 않은 모든 게시물 조회" ) @GetMapping("") - public ResponseEntity> getAllPosts() { - List posts = postService.getAllPosts(); + public ResponseEntity> getAllPosts() { + List posts = postService.getAllPosts(); return ResponseEntity.ok() .body(new ListResponse<>(200, "삭제되지 않은 모든 게시물 조회 성공", posts)); } @@ -100,16 +98,16 @@ public ResponseEntity> getAllPosts() { summary = "해당 사용자 게시물 전체 조회" ) @GetMapping("/user") - public ResponseEntity> getAllUserPosts() { - List posts = postService.getAllUserPosts(); + public ResponseEntity> getAllUserPosts() { + List posts = postService.getAllUserPosts(); return ResponseEntity.ok() .body(new ListResponse<>(200, "사용자 게시물 조회 성공", posts)); } @Operation(summary = "해당 게시물 상세 조회") @GetMapping("/{postId}") - public ResponseEntity> getPostWithDetail(@PathVariable String postId) { - PostWithDetailResponseDTO post = postService.getPostWithDetail(postId); + public ResponseEntity> getPostWithDetail(@PathVariable String postId) { + PostDetailResponseDto post = postService.getPostWithDetail(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCountsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostCommonResponseDto.java similarity index 73% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCountsResponseDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostCommonResponseDto.java index 60e8fe3c..153bb4b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCountsResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostCommonResponseDto.java @@ -1,18 +1,17 @@ package inu.codin.codin.domain.post.dto.response; import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.Getter; +import lombok.Setter; import java.util.List; -@Data -public class PostWithCountsResponseDTO { +@Getter +@Setter +public class PostCommonResponseDto { @Schema(description = "유저 ID", example = "111111") @NotBlank private String userId; @@ -40,16 +39,13 @@ public class PostWithCountsResponseDTO { @NotNull private boolean isAnonymous; - @Schema(description = "댓글 및 대댓글 count", example = "0") - private int commentCount; - @Schema(description = "좋아요 count", example = "0") private int likeCount; @Schema(description = "스크랩 count", example = "0") private int scrapCount; - public PostWithCountsResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount) { + public PostCommonResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount) { this.userId = userId; this.postId = postId; this.content = content; @@ -57,8 +53,7 @@ public PostWithCountsResponseDTO(String userId, String postId, String content, S this.postCategory = postCategory; this.postImageUrl = postImageUrls; this.isAnonymous = isAnonymous; - this.commentCount = commentCount; this.likeCount = likeCount; this.scrapCount = scrapCount; } -} \ No newline at end of file +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java new file mode 100644 index 00000000..a7b26a71 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java @@ -0,0 +1,33 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.entity.PostCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class PostDetailResponseDto extends PostCommonResponseDto{ + @Schema(description = "댓글 및 대댓글 데이터") + private List comments; + + // Constructor for partial initialization + public PostDetailResponseDto( + String userId, + String postId, + String content, + String title, + PostCategory postCategory, + List postImageUrls, + boolean isAnonymous, + List comments, + int likeCount, + int scrapCount + ) { + super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount); + this.comments = comments; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java new file mode 100644 index 00000000..cd460679 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class PostListResponseDto extends PostCommonResponseDto{ + + @Schema(description = "댓글 및 대댓글 count", example = "0") + private int commentCount; + + public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount) { + super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount); + this.commentCount = commentCount; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java deleted file mode 100644 index 12719bbf..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithCommentsResponseDTO.java +++ /dev/null @@ -1,53 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostStatus; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -import java.util.List; - -public class PostWithCommentsResponseDTO { - @Schema(description = "유저 ID", example = "111111") - @NotBlank - private String userId; - - @Schema(description = "게시물 ID", example = "111111") - @NotBlank - private String postId; - - @Schema(description = "게시물 종류", example = "구해요") - @NotBlank - private PostCategory postCategory; - - @Schema(description = "게시물 제목", example = "Example") - @NotBlank - private String title; - - @Schema(description = "게시물 내용", example = "example content") - @NotBlank - private String content; - - @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") - private List postImageUrl; - - @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") - @NotNull - private boolean isAnonymous; - - @Schema(description = "댓글 및 대댓글", example = "0") - private List comments; - - public PostWithCommentsResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, List comments) { - this.userId = userId; - this.postId = postId; - this.content = content; - this.title = title; - this.postCategory = postCategory; - this.postImageUrl = postImageUrls; - this.isAnonymous = isAnonymous; - this.comments = comments; - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java deleted file mode 100644 index 95377920..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostWithDetailResponseDTO.java +++ /dev/null @@ -1,83 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostStatus; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import lombok.Data; - -import java.util.List; - -@Data -@Builder -public class PostWithDetailResponseDTO { - - @Schema(description = "게시물 작성자 ID", example = "user123") - @NotBlank - private String userId; - - @Schema(description = "게시물 ID", example = "post123") - @NotBlank - private String postId; - - @Schema(description = "게시물 내용", example = "This is a detailed content of the post.") - @NotBlank - private String content; - - @Schema(description = "게시물 제목", example = "Post Title") - @NotBlank - private String title; - - @Schema(description = "게시물 카테고리", example = "구해요") - @NotNull - private PostCategory postCategory; - - @Schema(description = "게시물 상태", example = "ACTIVE") - @NotNull - private PostStatus postStatus; - - @Schema(description = "게시물 이미지 URL 리스트", example = "[\"image1.jpg\", \"image2.jpg\"]") - private List postImageUrls; - - @Schema(description = "게시물 익명 여부", example = "true") - private boolean isAnonymous; - - @Schema(description = "댓글 및 대댓글 데이터") - private List comments; - - @Schema(description = "좋아요 수", example = "10") - private int likeCount; - - @Schema(description = "스크랩 수", example = "5") - private int scrapCount; - - // Constructor for partial initialization - public PostWithDetailResponseDTO( - String userId, - String postId, - String content, - String title, - PostCategory postCategory, - PostStatus postStatus, - List postImageUrls, - boolean isAnonymous, - List comments, - int likeCount, - int scrapCount - ) { - this.userId = userId; - this.postId = postId; - this.content = content; - this.title = title; - this.postCategory = postCategory; - this.postStatus = postStatus; - this.postImageUrls = postImageUrls; - this.isAnonymous = isAnonymous; - this.comments = comments; - this.likeCount = likeCount; - this.scrapCount = scrapCount; - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index f12ad4bc..2dd77989 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -2,30 +2,26 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostWithCountsResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostWithDetailResponseDTO; - -import inu.codin.codin.domain.post.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; -import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDto; +import inu.codin.codin.domain.post.dto.response.PostListResponseDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; - import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -47,8 +43,6 @@ private List handleImageUpload(List postImages) { public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - validateCreatePostRequest(postCreateRequestDTO); - List imageUrls = handleImageUpload(postImages); String userId = SecurityUtils.getCurrentUserId(); @@ -93,11 +87,11 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public List getAllPosts() { - List posts = postRepository.findAllAndNotDeleted(); + public List getAllPosts() { + List posts = postRepository.findAllAndNotDeletedAndActive(); return posts.stream() - .map(post -> new PostWithCountsResponseDTO( + .map(post -> new PostListResponseDto( post.getUserId(), post.getPostId(), post.getContent(), @@ -109,20 +103,19 @@ public List getAllPosts() { post.getCommentCount(), // 댓글 수 likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 - )) - .collect(Collectors.toList()); + )).toList(); } //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public List getAllUserPosts() { + public List getAllUserPosts() { String userId = SecurityUtils.getCurrentUserId(); List posts = postRepository.findByUserIdAndNotDeleted(userId); return posts.stream() - .map(post -> new PostWithCountsResponseDTO( + .map(post -> new PostListResponseDto( post.getUserId(), post.getPostId(), post.getContent(), @@ -139,17 +132,16 @@ public List getAllUserPosts() { } //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 - public PostWithDetailResponseDTO getPostWithDetail(String postId) { + public PostDetailResponseDto getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - return new PostWithDetailResponseDTO( + return new PostDetailResponseDto( post.getUserId(), post.getPostId(), post.getContent(), post.getTitle(), post.getPostCategory(), - post.getPostStatus(), post.getPostImageUrls(), post.isAnonymous(), commentService.getCommentsByPostId(postId), // 댓글 및 대댓글 @@ -188,15 +180,5 @@ public void deletePostImage(String postId, String imageUrl) { } } - - - - //유효성체크 - private void validateCreatePostRequest(PostCreateRequestDTO postCreateReqDTO) { -// if (postRepository.findByTitle(postCreateReqDTO.getTitle()).isPresent()) { -// throw new PostCreateFailException("이미 존재하는 제목입니다. 다른 제목을 사용해주세요."); -// } - } - } From 04861adc7f5736e8ac070018178470fe76cf322d Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 2 Dec 2024 19:33:31 +0900 Subject: [PATCH 0195/1002] =?UTF-8?q?[SC-86]=20Feat=20:=20RateLimit=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 Request에 1초에 10번씩만 가능 - 1초마다 버킷에 10개의 토큰이 다시 채워짐 - 11개 이상의 나머지 패킷은 버리게됨 --- .../common/config/Bucket4jRateLimitApp.java | 22 +++++ .../codin/common/ratelimit/ClientIpUtil.java | 31 +++++++ .../ratelimit/RateLimitBucketConstants.java | 18 +++++ .../ratelimit/RateLimitInterceptor.java | 66 +++++++++++++++ .../common/ratelimit/RateLimitService.java | 80 +++++++++++++++++++ .../common/response/RateLimitResponse.java | 20 +++++ 6 files changed, 237 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java new file mode 100644 index 00000000..4fd385ef --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java @@ -0,0 +1,22 @@ +package inu.codin.codin.common.config; + +import inu.codin.codin.common.ratelimit.RateLimitInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class Bucket4jRateLimitApp implements WebMvcConfigurer { + + private final RateLimitInterceptor interceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry. + addInterceptor(interceptor). + addPathPatterns("/**") + .excludePathPatterns("/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "/webjars/**"); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java new file mode 100644 index 00000000..e9d30f66 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java @@ -0,0 +1,31 @@ +package inu.codin.codin.common.ratelimit; + +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Arrays; + +public class ClientIpUtil { + private static final String[] IP_HEADER_CANDIDATES = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", + "REMOTE_ADDR" + }; + + public static String getClientIp(HttpServletRequest request) { + for (String header : IP_HEADER_CANDIDATES) { + String ip = request.getHeader(header); + if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0].trim(); + } + } + return request.getRemoteAddr(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java new file mode 100644 index 00000000..941decfb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java @@ -0,0 +1,18 @@ +package inu.codin.codin.common.ratelimit; + +import java.time.Duration; + +/** + * @apiNote # BUCKET_CAPACITY : 토큰 버킷의 최대 용량 + * # BUCKET_TOKEN : 토큰 버킷의 현재 토큰 개수 + * # REFILL_DURATION : 토큰 버킷이 채워지는 주기 + * # REQUEST_PER_COST : 요청당 필요한 토큰 개수 + */ +public class RateLimitBucketConstants { + + public static final long BUCKET_CAPACITY = 10L; + public static final long BUCKET_TOKEN = 10L; + public static final Duration REFILL_DURATION = Duration.ofSeconds(1); + public static final long REQUEST_PER_COST = 1L; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java new file mode 100644 index 00000000..56d22261 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java @@ -0,0 +1,66 @@ +package inu.codin.codin.common.ratelimit; + +import inu.codin.codin.common.response.RateLimitResponse; +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.ConsumptionProbe; +import io.github.bucket4j.Refill; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import static inu.codin.codin.common.ratelimit.RateLimitBucketConstants.*; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Reference : https://velog.io/@whcksdud8/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Rate-limit-%ED%95%B8%EB%93%A4%EB%A7%81-%EB%B0%8F-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class RateLimitInterceptor implements HandlerInterceptor { + + // todo : Redis로 변경 + private final Map cache = new ConcurrentHashMap<>(); + private final RateLimitService rateLimitService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String clientIp = ClientIpUtil.getClientIp(request); + Bucket bucket = cache.computeIfAbsent(clientIp, key -> createNewBucket()); + ConsumptionProbe consumptionProbe = bucket.tryConsumeAndReturnRemaining(REQUEST_PER_COST); + + if (isRateLimitExceeded(request, response, clientIp, consumptionProbe)) { + return false; + } + return true; + } + + private boolean isRateLimitExceeded(HttpServletRequest request, HttpServletResponse response, String clientIp, ConsumptionProbe consumptionProbe) { + + if (consumptionProbe.isConsumed()) { + log.info("IP: {}, remaining tokens: {}", clientIp, consumptionProbe.getRemainingTokens()); + RateLimitResponse.successResponse(response, consumptionProbe.getRemainingTokens(), BUCKET_CAPACITY, REFILL_DURATION); + return false; + } else { + log.warn("IP: {}, rate limit exceeded", clientIp); + RateLimitResponse.errorResponse(response, BUCKET_CAPACITY, REFILL_DURATION, 1); + rateLimitService.isLimitReachedThreshold(clientIp); + return true; + } + } + + private Bucket createNewBucket() { + return Bucket.builder() + .addLimit(Bandwidth.classic( + BUCKET_CAPACITY, + Refill.intervally(BUCKET_TOKEN, REFILL_DURATION) + )) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java new file mode 100644 index 00000000..17286444 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java @@ -0,0 +1,80 @@ +package inu.codin.codin.common.ratelimit; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +@Slf4j +@RequiredArgsConstructor +public class RateLimitService { + + // IP 별로 요청 횟수를 저장하는 Map + private Map rateLimitErrorCounter = new ConcurrentHashMap<>(); + + public boolean isLimitReachedThreshold(String clientIp) { + RequestInfo requestInfo = rateLimitErrorCounter.computeIfAbsent(clientIp, key -> new RequestInfo()); + if (!isApplyHandling(clientIp, requestInfo)) { + requestInfo.saveCount(); + return false; + } + return true; + } + + private boolean isApplyHandling(String clientIp, RequestInfo requestInfo) { + if (requestInfo.isWithinTimeWindow()) { + int count = requestInfo.incrementCount(); + log.info("IP: {}, remaining tokens: {}", clientIp, count); + checkAndResetIfLimitExceeded(clientIp, requestInfo, count); + return true; + } + return false; + } + + private void checkAndResetIfLimitExceeded(String clientIp, RequestInfo requestInfo, int count) { + if (count >= 10) { + log.warn("IP: {}, rate limit exceeded, Count : {}", clientIp, count); + requestInfo.resetCount(); + // todo : 지속적으로 rate limit을 넘는 IP Ban 처리 또는 계정 Ban 처리 필요. + } + } + + private static class RequestInfo { + private AtomicInteger count = new AtomicInteger(0); + private LocalDateTime lastRequestTime; + + // 요청 횟수 증가 + public int incrementCount() { + return this.count.incrementAndGet(); + } + + // 1시간 이내에 요청이 있는지 확인 + public boolean isWithinTimeWindow() { + LocalDateTime now = LocalDateTime.now(); + if (this.lastRequestTime == null || ChronoUnit.HOURS.between(this.lastRequestTime, now) >= 1) { + this.lastRequestTime = now; + return false; + } + return true; + } + + // 요청 횟수 초기화 + public void saveCount() { + this.count.set(1); + lastRequestTime = LocalDateTime.now(); + } + + // 요청 횟수 초기화 + public void resetCount() { + this.count.set(0); + lastRequestTime = LocalDateTime.now(); + } + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java new file mode 100644 index 00000000..caf646ff --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java @@ -0,0 +1,20 @@ +package inu.codin.codin.common.response; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; + +import java.time.Duration; + +public class RateLimitResponse { + + public static void successResponse(HttpServletResponse response, long remainToken, Long bucketCapacity, Duration callsInSeconds) { + response.setHeader("X-RateLimit-Remaining", Long.toString(remainToken)); + response.setHeader("X-RateLimit-Limit", bucketCapacity + ";w=" + callsInSeconds.getSeconds()); + } + + public static void errorResponse(HttpServletResponse response, Long bucketCapacity, Duration callsInSeconds, float waitForRefill) { +// response.setHeader("X-RateLimit-Retry-After", Float.toString(waitForRefill)); + response.setHeader("X-RateLimit-Limit", bucketCapacity + ";w=" + callsInSeconds.getSeconds()); + response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + } +} From d3b941009b6ad5a8398bab86a5ab7701da7bdbfc Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 2 Dec 2024 19:35:31 +0900 Subject: [PATCH 0196/1002] =?UTF-8?q?[SC-86]=20Refactor=20:=20SecurityConf?= =?UTF-8?q?ig=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20Bean=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index d2e27b9d..45fc868e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -36,7 +36,6 @@ public class SecurityConfig { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; - private final JwtService jwtService; private final JwtUtils jwtUtils; @Bean @@ -61,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) // JwtAuthenticationFilter 추가 .addFilterBefore( - new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtService, jwtUtils), + new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtUtils), UsernamePasswordAuthenticationFilter.class ) // 예외 처리 필터 추가 From ef0986249cb958c5177d73d8ae5b138a2183960a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 20:12:29 +0900 Subject: [PATCH 0197/1002] =?UTF-8?q?[SC-65]=20prefix=20:=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=B0=8F=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=B6=84=EB=A6=AC,=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=84=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EB=8C=93=EA=B8=80=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 19 ++++++----- .../comment/controller/CommentController.java | 2 +- .../comment/dto/CommentResponseDTO.java | 6 +++- .../comment/repository/CommentRepository.java | 4 +-- .../comment/service/CommentService.java | 22 ++++++++----- .../dto/request/PostCategoryRequestDto.java | 14 ++++++++ ...nseDto.java => PostDetailResponseDTO.java} | 4 +-- .../dto/response/PostDetailResponseDto.java | 33 ------------------- .../dto/response/PostListResponseDto.java | 2 +- .../domain/post/entity/PostCategory.java | 9 ++--- .../post/repository/PostRepository.java | 9 ++--- 11 files changed, 56 insertions(+), 68 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCategoryRequestDto.java rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/{PostCommonResponseDto.java => PostDetailResponseDTO.java} (94%) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index cb304d57..1f9814f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -6,8 +6,9 @@ import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostListResponseDto; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDto; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -85,13 +86,13 @@ public ResponseEntity> updatePostAnonymous( @Operation( - summary = "삭제되지 않은 모든 게시물 조회" + summary = "카테고리별 삭제 되지 않은 모든 게시물 조회" ) - @GetMapping("") - public ResponseEntity> getAllPosts() { - List posts = postService.getAllPosts(); + @GetMapping("/category") + public ResponseEntity> getAllPosts(@RequestParam PostCategory postCategory) { + List posts = postService.getAllPosts(postCategory); return ResponseEntity.ok() - .body(new ListResponse<>(200, "삭제되지 않은 모든 게시물 조회 성공", posts)); + .body(new ListResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", posts)); } @Operation( @@ -104,10 +105,10 @@ public ResponseEntity> getAllUserPosts() { .body(new ListResponse<>(200, "사용자 게시물 조회 성공", posts)); } - @Operation(summary = "해당 게시물 상세 조회") + @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") @GetMapping("/{postId}") - public ResponseEntity> getPostWithDetail(@PathVariable String postId) { - PostDetailResponseDto post = postService.getPostWithDetail(postId); + public ResponseEntity> getPostWithDetail(@PathVariable String postId) { + PostDetailResponseDTO post = postService.getPostWithDetail(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 7643d7f0..d6864aae 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -31,7 +31,7 @@ public ResponseEntity> addComment(@PathVariable String postId, .body(new SingleResponse<>(201, "댓글이 추가되었습니다.", null)); } - @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회") + @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회 (삭제된 내역도 모두 반환)") @GetMapping("/post/{postId}") public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { List response = commentService.getCommentsByPostId(postId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java index 59ece895..fa478289 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java @@ -26,11 +26,15 @@ public class CommentResponseDTO { @Schema(description = "좋아요 수", example = "5") private int likeCount; - public CommentResponseDTO(String commentId, String userId, String content, List replies, int likeCount) { + @Schema(description = "삭제 여부", example = "false") + private boolean isDeleted; + + public CommentResponseDTO(String commentId, String userId, String content, List replies, int likeCount, boolean isDeleted) { this.commentId = commentId; this.userId = userId; this.content = content; this.replies = replies; this.likeCount = likeCount; + this.isDeleted = isDeleted; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index 8d650990..d20c84ef 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -13,6 +13,6 @@ public interface CommentRepository extends MongoRepository findByIdAndNotDeleted(String Id); - @Query("{ 'postId': ?0, 'deletedAt': null }") - List findByPostIdAndNotDeleted(String postId); + @Query("{ 'postId': ?0 }") + List findByPostId(String postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 31737a0c..b491cb28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -83,15 +83,21 @@ public void softDeleteComment(String commentId) { // 특정 게시물의 댓글 및 대댓글 조회 public List getCommentsByPostId(String postId) { - List comments = commentRepository.findByPostIdAndNotDeleted(postId); + postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + List comments = commentRepository.findByPostId(postId); return comments.stream() - .map(comment -> new CommentResponseDTO( - comment.getCommentId(), - comment.getUserId(), - comment.getContent(), - replyCommentService.getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 - likeService.getLikeCount(LikeType.valueOf("comment"), comment.getCommentId()) // 댓글 좋아요 수 - )).toList(); + .map(comment -> { + boolean isDeleted = comment.getDeletedAt() != null; + return new CommentResponseDTO( + comment.getCommentId(), + comment.getUserId(), + comment.getContent(), + replyCommentService.getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 + likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.getCommentId()), // 댓글 좋아요 수 + isDeleted); + }) + .toList(); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCategoryRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCategoryRequestDto.java new file mode 100644 index 00000000..7fe7c752 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCategoryRequestDto.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.post.dto.request; + +import inu.codin.codin.domain.post.entity.PostCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class PostCategoryRequestDto { + + @NotNull + @Schema(description = "게시글 카테고리", example = "REQEUST_STUDY") + private PostCategory postCategory; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostCommonResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostCommonResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 153bb4b6..435678c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostCommonResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -11,7 +11,7 @@ @Getter @Setter -public class PostCommonResponseDto { +public class PostDetailResponseDTO { @Schema(description = "유저 ID", example = "111111") @NotBlank private String userId; @@ -45,7 +45,7 @@ public class PostCommonResponseDto { @Schema(description = "스크랩 count", example = "0") private int scrapCount; - public PostCommonResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount) { + public PostDetailResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount) { this.userId = userId; this.postId = postId; this.content = content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java deleted file mode 100644 index a7b26a71..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; -import inu.codin.codin.domain.post.entity.PostCategory; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -@Getter -@Setter -public class PostDetailResponseDto extends PostCommonResponseDto{ - @Schema(description = "댓글 및 대댓글 데이터") - private List comments; - - // Constructor for partial initialization - public PostDetailResponseDto( - String userId, - String postId, - String content, - String title, - PostCategory postCategory, - List postImageUrls, - boolean isAnonymous, - List comments, - int likeCount, - int scrapCount - ) { - super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount); - this.comments = comments; - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index cd460679..8b0f1310 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -10,7 +10,7 @@ @Getter @Setter -public class PostListResponseDto extends PostCommonResponseDto{ +public class PostListResponseDto extends PostDetailResponseDTO{ @Schema(description = "댓글 및 대댓글 count", example = "0") private int commentCount; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index a6ab1fc0..a4a37a1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -15,13 +15,9 @@ public enum PostCategory { COMMUNICATION_JOB("소통해요_취업수기"), COMMUNICATION_TIP("소통해요_꿀팁공유"), + EXTRACURRICULAR_STARINU("비교과_STARINU"), EXTRACURRICULAR_OUTER("비교과_교외"), - EXTRACURRICULAR_INNER("비교과_교내"), - - INFO_PROFESSOR("정보대소개_교수_연구실"), - INFO_PHONE_NUMBER("정보대소개_정보대전화번호"), - - USED_BOOK("중고책"); + EXTRACURRICULAR_INNER("비교과_교내"); private final String description; @@ -43,5 +39,4 @@ public static PostCategory fromDescription(String description) { } throw new IllegalArgumentException("Unknown description: " + description); } - } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index abd3ebbc..eed57b24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.repository; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -11,12 +12,12 @@ @Repository public interface PostRepository extends MongoRepository { - @Query("{'_id': ?0, 'deletedAt': null}") + @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(String Id); - @Query("{'userId': ?0, 'deletedAt': null}") + @Query("{'userId': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") List findByUserIdAndNotDeleted(String userId); - @Query("{'deletedAt': null}") - List findAllAndNotDeleted(); + @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': ?0 }") + List findAllAndNotDeletedAndActive(PostCategory postCategory); } From 530a1e5339b1f7e4b6ea2ca7cd3fc8d4325861d4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 20:12:50 +0900 Subject: [PATCH 0198/1002] =?UTF-8?q?[SC-65]=20refactor=20:=20ENUM=20?= =?UTF-8?q?=EA=B0=92=20=EB=8C=80=EB=AC=B8=EC=9E=90=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reply/service/ReplyCommentService.java | 17 +++++++++------- .../domain/post/service/PostService.java | 20 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 6ff2720d..92496dc6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -78,13 +78,16 @@ public List getRepliesByCommentId(String commentId) { List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); return replies.stream() - .map(reply -> new CommentResponseDTO( - reply.getReplyId(), - reply.getUserId(), - reply.getContent(), - List.of(), // 대댓글은 하위 대댓글이 없음 - likeService.getLikeCount(LikeType.valueOf("reply"), reply.getReplyId()) - )).toList(); + .map(reply -> { + boolean isDeleted = reply.getDeletedAt() != null; + return new CommentResponseDTO( + reply.getCommentId(), + reply.getUserId(), + reply.getContent(), + List.of(), //대댓글은 대댓글이 없음 + likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.getCommentId()), // 대댓글 좋아요 수 + isDeleted); + }).toList(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 2dd77989..2c5078af 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; @@ -10,8 +9,9 @@ import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDto; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostListResponseDto; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; @@ -31,7 +31,6 @@ public class PostService { private final S3Service s3Service; private final LikeService likeService; private final ScrapService scrapService; - private final CommentService commentService; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -87,8 +86,8 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public List getAllPosts() { - List posts = postRepository.findAllAndNotDeletedAndActive(); + public List getAllPosts(PostCategory postCategory) { + List posts = postRepository.findAllAndNotDeletedAndActive(postCategory); return posts.stream() .map(post -> new PostListResponseDto( @@ -101,7 +100,7 @@ public List getAllPosts() { post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), // 댓글 수 - likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )).toList(); } @@ -125,18 +124,18 @@ public List getAllUserPosts() { post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), - likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 )) .toList(); } //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 - public PostDetailResponseDto getPostWithDetail(String postId) { + public PostDetailResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - return new PostDetailResponseDto( + return new PostDetailResponseDTO( post.getUserId(), post.getPostId(), post.getContent(), @@ -144,8 +143,7 @@ public PostDetailResponseDto getPostWithDetail(String postId) { post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), - commentService.getCommentsByPostId(postId), // 댓글 및 대댓글 - likeService.getLikeCount(LikeType.valueOf("post"),post.getPostId()), // 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 scrapService.getScrapCount(post.getPostId()) // 스크랩 수 ); } From 723e9a7701203d7f683463356c22f68b4ce622fd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 20:16:58 +0900 Subject: [PATCH 0199/1002] =?UTF-8?q?[SC-65]=20docs=20:=20Swagger=20@Tag?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/controller/PostController.java | 2 ++ .../post/domain/comment/controller/CommentController.java | 2 ++ .../domain/post/domain/like/controller/LikeController.java | 4 +++- .../post/domain/reply/controller/ReplyCommentController.java | 2 ++ .../domain/post/domain/scrap/controller/ScrapController.java | 4 +++- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 1f9814f9..0339b649 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -22,6 +23,7 @@ @RestController @RequestMapping("/posts") +@Tag(name = "POST API", description = "게시글 API") public class PostController { private final PostService postService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index d6864aae..c0c29460 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -6,6 +6,7 @@ import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -15,6 +16,7 @@ @RestController @RequestMapping("/comments") +@Tag(name = "Comment API", description = "댓글 API") public class CommentController { private final CommentService commentService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java index f9816c77..e373af13 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.service.LikeService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -11,8 +12,9 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/likes") +@RequestMapping("/likes") @RequiredArgsConstructor +@Tag(name = "Like API", description = "게시물, 댓글, 대댓글 좋아요 API") public class LikeController { private final LikeService likeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index 51872070..76515c43 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -13,6 +14,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/replies") +@Tag(name = "ReplyComment API", description = "대댓글 API") public class ReplyCommentController { private final ReplyCommentService replyCommentService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index a9030100..4c2b9482 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -4,14 +4,16 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/scraps") +@RequestMapping("/scraps") @RequiredArgsConstructor +@Tag(name = "Scrap API", description = "게시물 스크랩 API") public class ScrapController { private final ScrapService scrapService; From e74ebd0692e1c2f22d0df9732c4f17c1727493af Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 20:20:50 +0900 Subject: [PATCH 0200/1002] =?UTF-8?q?[SC-65]=20perf=20:=20Redis=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC=206=EC=8B=9C=EA=B0=84,=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=2012=EC=8B=9C=EA=B0=84=20=EC=A3=BC=EA=B8=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java | 2 +- .../main/java/inu/codin/codin/infra/redis/SyncScheduler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java index 5c42dc40..ac69a818 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -24,7 +24,7 @@ public class RedisRecoverSyncScheduler { /** * Redis 상태를 주기적으로 확인하고, 필요한 경우 복구 작업을 수행합니다. */ - @Scheduled(fixedRate = 10000) // 10초마다 실행 (테스트용) + @Scheduled(fixedRate = 21600000) // 10초마다 실행 (테스트용) public void monitorRedisAndRecover() { try { redisHealthChecker.checkRedisStatus(); // Redis 상태 확인 diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 16fbd06b..ecd80442 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -35,7 +35,7 @@ public class SyncScheduler { private final ScrapRepository scrapRepository; private final RedisHealthChecker redisHealthChecker; - @Scheduled(fixedRate = 10000) // 매 10초마다 실행(테스트목적) + @Scheduled(fixedRate = 43200000) // 매 10초마다 실행(테스트목적) public void syncLikes() { if (!redisHealthChecker.isRedisAvailable()) { log.warn("Redis 비활성화 상태, 동기화 작업 중지"); From d69ba73686227294dabb39180fe07f7610cf9c05 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 20:53:46 +0900 Subject: [PATCH 0201/1002] =?UTF-8?q?[SC-65]=20docs=20:=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java | 2 +- .../main/java/inu/codin/codin/infra/redis/SyncScheduler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java index ac69a818..26833f86 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -24,7 +24,7 @@ public class RedisRecoverSyncScheduler { /** * Redis 상태를 주기적으로 확인하고, 필요한 경우 복구 작업을 수행합니다. */ - @Scheduled(fixedRate = 21600000) // 10초마다 실행 (테스트용) + @Scheduled(fixedRate = 21600000) // 6시간마다 실행 public void monitorRedisAndRecover() { try { redisHealthChecker.checkRedisStatus(); // Redis 상태 확인 diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index ecd80442..5da1571f 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -35,7 +35,7 @@ public class SyncScheduler { private final ScrapRepository scrapRepository; private final RedisHealthChecker redisHealthChecker; - @Scheduled(fixedRate = 43200000) // 매 10초마다 실행(테스트목적) + @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 public void syncLikes() { if (!redisHealthChecker.isRedisAvailable()) { log.warn("Redis 비활성화 상태, 동기화 작업 중지"); From 351bb730df891479d3816499c7052c5ba9b8cb64 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 21:07:14 +0900 Subject: [PATCH 0202/1002] =?UTF-8?q?[SC-65]=20feat=20:=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=83=9D=EC=84=B1=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/dto/response/PostDetailResponseDTO.java | 9 ++++++++- .../domain/post/dto/response/PostListResponseDto.java | 6 +++--- .../codin/codin/domain/post/service/PostService.java | 11 ++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 435678c6..f0592c56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.dto.response; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -7,6 +8,7 @@ import lombok.Getter; import lombok.Setter; +import java.time.LocalDateTime; import java.util.List; @Getter @@ -45,7 +47,11 @@ public class PostDetailResponseDTO { @Schema(description = "스크랩 count", example = "0") private int scrapCount; - public PostDetailResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount) { + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") + private LocalDateTime createdAt; + + public PostDetailResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { this.userId = userId; this.postId = postId; this.content = content; @@ -55,5 +61,6 @@ public PostDetailResponseDTO(String userId, String postId, String content, Strin this.isAnonymous = isAnonymous; this.likeCount = likeCount; this.scrapCount = scrapCount; + this.createdAt = createdAt; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index 8b0f1310..894f2311 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -1,11 +1,11 @@ package inu.codin.codin.domain.post.dto.response; import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; +import java.time.LocalDateTime; import java.util.List; @Getter @@ -15,8 +15,8 @@ public class PostListResponseDto extends PostDetailResponseDTO{ @Schema(description = "댓글 및 대댓글 count", example = "0") private int commentCount; - public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, PostStatus postStatus, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount) { - super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount); + public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { + super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); this.commentCount = commentCount; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 2c5078af..2b3f7056 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -96,12 +96,12 @@ public List getAllPosts(PostCategory postCategory) { post.getContent(), post.getTitle(), post.getPostCategory(), - post.getPostStatus(), post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), // 댓글 수 likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 - scrapService.getScrapCount(post.getPostId()) // 스크랩 수 + scrapService.getScrapCount(post.getPostId()), // 스크랩 수 + post.getCreatedAt() )).toList(); } @@ -120,12 +120,12 @@ public List getAllUserPosts() { post.getContent(), post.getTitle(), post.getPostCategory(), - post.getPostStatus(), post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 - scrapService.getScrapCount(post.getPostId()) // 스크랩 수 + scrapService.getScrapCount(post.getPostId()), // 스크랩 수 + post.getCreatedAt() )) .toList(); } @@ -144,7 +144,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { post.getPostImageUrls(), post.isAnonymous(), likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 - scrapService.getScrapCount(post.getPostId()) // 스크랩 수 + scrapService.getScrapCount(post.getPostId()), // 스크랩 수 + post.getCreatedAt() ); } From 9f4ac3eaa1145c19e07e4591475b6aafa833162f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 23:09:49 +0900 Subject: [PATCH 0203/1002] =?UTF-8?q?[SC-76]=20pref=20:=20ENUM=20descripti?= =?UTF-8?q?on=20=EC=82=AC=EC=9A=A9=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/dto/request/PostCreateRequestDTO.java | 5 +---- .../codin/domain/post/entity/PostCategory.java | 17 ----------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index 10e034f5..7ed1254a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -5,9 +5,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; @Data public class PostCreateRequestDTO { @@ -30,7 +27,7 @@ public class PostCreateRequestDTO { @NotNull private boolean isAnonymous; - @Schema(description = "게시물 종류", example = "구해요_스터디") + @Schema(description = "게시물 종류", example = "REQUEST_STUDY") @NotNull private PostCategory postCategory; //STATUS 필드 - DEFAULT :: ACTIVE diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index a4a37a1a..4be769cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -1,7 +1,5 @@ package inu.codin.codin.domain.post.entity; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; @Getter @@ -24,19 +22,4 @@ public enum PostCategory { PostCategory(String description) { this.description = description; } - - @JsonValue - public String getDescription() { - return description; - } - - @JsonCreator - public static PostCategory fromDescription(String description) { - for (PostCategory category : PostCategory.values()) { - if (category.description.equals(description)) { - return category; - } - } - throw new IllegalArgumentException("Unknown description: " + description); - } } \ No newline at end of file From 4de116d61ca8c20568b6600b0758db23016803d3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 2 Dec 2024 23:24:39 +0900 Subject: [PATCH 0204/1002] =?UTF-8?q?[SC-76]=20docs=20:=20DB=20collection?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index b11b9f74..e01d9048 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.List; -@Document(collection = "post") +@Document(collection = "posts") @Getter public class PostEntity extends BaseTimeEntity { @Id @NotBlank From 7ec236b6d724736cc077de26ee91ddef2fa03810 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 3 Dec 2024 14:41:55 +0900 Subject: [PATCH 0205/1002] =?UTF-8?q?Perf=20:=20Swagger=20URL=20HttpBasic?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 45fc868e..0e279ef7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -11,6 +11,7 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -58,6 +59,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(USER_AUTH_PATHS).hasRole("USER") .anyRequest().hasRole("USER") ) + // Swagger 접근 시 httpBasic 인증 사용 + .securityMatcher(SWAGGER_AUTH_PATHS) + .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtUtils), From e9df0597c1448ca13390ba9491d4608e95c2e6bb Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 3 Dec 2024 14:42:39 +0900 Subject: [PATCH 0206/1002] =?UTF-8?q?Test=20:=20TestController=20Null=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=EB=A1=9C=EA=B9=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/inu/codin/codin/TestController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index 2c672c94..692aac75 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -2,11 +2,13 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Slf4j @RestController @RequestMapping(value = "/v3/api", produces = "plain/text; charset=utf-8") @Tag(name = "Test API", description = "[관리자] 유저 테스트용 API") @@ -43,6 +45,10 @@ public String test5() { } private static String getUserData() { + if (SecurityContextHolder.getContext().getAuthentication() == null) { + log.info("[TEST] 로그인 정보 없음"); + return "로그인 정보 없음"; + } // SecurityContextHolder를 이용하여 현재 사용자의 정보를 가져올 수 있습니다. String username = SecurityContextHolder // SecurityContextHolder를 이용하여 현재 사용자의 정보를 가져올 수 있습니다. .getContext() @@ -53,6 +59,7 @@ private static String getUserData() { .getAuthentication() .getAuthorities() .toString(); + log.info("[TEST] 유저 이름 : {}, Role : {}", username, userRole); return "유저 이름 : " + username + ", Role : " + userRole; } } From cee8875538088d3fd7695029afbb0cda288868eb Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 3 Dec 2024 15:38:27 +0900 Subject: [PATCH 0207/1002] =?UTF-8?q?Fix=20:=20Auth=20API=20HttpMediaTypeN?= =?UTF-8?q?otAcceptableException=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 76ea94e1..2376d69e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = "/auth" , produces = "plain/text; charset=utf-8") +@RequestMapping(value = "/auth") @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class AuthController { @@ -55,5 +55,4 @@ public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse jwtService.reissueToken(request, response); return ResponseEntity.ok().body(new SingleResponse<>(200, "토큰 재발급 성공", null)); } - } From 0c64f3d1066624abd4ad5acbb5eb8579781529cc Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 3 Dec 2024 15:58:23 +0900 Subject: [PATCH 0208/1002] =?UTF-8?q?Remove=20:=20ResponseUtils=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20CommonResponse=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/ResponseUtils.java | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java b/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java deleted file mode 100644 index c8b0e68e..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/ResponseUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -package inu.codin.codin.common; - -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import java.util.HashMap; - -public class ResponseUtils { - - /** - * 성공 응답 - * @param data class 형태의 데이터 타입 (e.g. DTO) - * @return ResponseEntity - */ - public static ResponseEntity success(T data) { - return new ResponseEntity<>(data, HttpStatus.OK); - } - - /** - * 성공 응답 - * @param message 성공 응답 메세지 - * @return ResponseEntity - */ - public static ResponseEntity successMsg(String message) { - return new ResponseEntity<>(message, HttpStatus.OK); - } - - /** - * 실패 응답 - * @param message 실패 응답 메세지 (404에러) - * @return ResponseEntity - */ - public static ResponseEntity error(String message) { - return new ResponseEntity<>(message, HttpStatus.NOT_FOUND); - } - - /** - * 실패 응답 - * @param message 실패 응답 메세지 - * @param code SecurityErrorCode 참조 - * @return ResponseEntity - */ - public static ResponseEntity error(String message, SecurityErrorCode code) { - HashMap error = new HashMap<>(); - error.put("message", message); - error.put("errorCode", code.getErrorCode()); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } -} From 0acdf3aad9585919b372e6b0cdffe01703a1581c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 3 Dec 2024 16:03:00 +0900 Subject: [PATCH 0209/1002] =?UTF-8?q?docs=20:=20Info=20@Tag=20(Lab,=20Offi?= =?UTF-8?q?ce,=20Professor)=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/info/domain/lab/controller/LabController.java | 2 +- .../domain/info/domain/office/controller/OfficeController.java | 2 +- .../info/domain/professor/controller/ProfessorController.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java index ec7e103f..0f96274f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java @@ -18,7 +18,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/lab") -@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") +@Tag(name = "Lab API", description = "연구실 정보 API") public class LabController { private final LabService labService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index bb018024..dc40e8bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -18,7 +18,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/office") -@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") +@Tag(name = "Office API", description = "학과 사무실 정보 API") public class OfficeController { private final OfficeService officeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index 468e9545..05b3f74d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -18,7 +18,7 @@ @RestController @RequiredArgsConstructor @RequestMapping(value = "/info/professor") -@Tag(name = "Info API", description = "연구실, 사무실, 교수 / 정보 API") +@Tag(name = "Professor API", description = "교수 정보 API") public class ProfessorController { private final ProfessorService professorService; From 473fcf57255dc8b8f751760ce1a74245e4f2e4e7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 3 Dec 2024 16:49:19 +0900 Subject: [PATCH 0210/1002] =?UTF-8?q?fix=20:=20Swagger=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20JWT=20=EB=8F=99=EC=9E=91=20=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EC=98=A4=EB=A5=98,=20=EC=9A=B0=EC=84=A0?= =?UTF-8?q?=20HttpBasic=EC=9C=BC=EB=A1=9C=EB=A7=8C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 0e279ef7..8cb5259c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -60,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().hasRole("USER") ) // Swagger 접근 시 httpBasic 인증 사용 - .securityMatcher(SWAGGER_AUTH_PATHS) +// .securityMatcher(SWAGGER_AUTH_PATHS) .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( From 612ea6e64ccb9bd664e2b60756dd1e1ba798e7cb Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 3 Dec 2024 17:04:46 +0900 Subject: [PATCH 0211/1002] =?UTF-8?q?Comment=20:=20TestController=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/java/inu/codin/codin/TestController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index 692aac75..66819252 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -45,6 +45,7 @@ public String test5() { } private static String getUserData() { + // 로그인 정보 인식 추가 필요 if (SecurityContextHolder.getContext().getAuthentication() == null) { log.info("[TEST] 로그인 정보 없음"); return "로그인 정보 없음"; From add6c65cc44adbb9f92a69b6e671a930ac13840e Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 4 Dec 2024 11:35:25 +0900 Subject: [PATCH 0212/1002] =?UTF-8?q?Comment=20:=20TestController=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/java/inu/codin/codin/TestController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/TestController.java b/codin-core/src/main/java/inu/codin/codin/TestController.java index 66819252..c095c8a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/TestController.java +++ b/codin-core/src/main/java/inu/codin/codin/TestController.java @@ -45,6 +45,7 @@ public String test5() { } private static String getUserData() { + // todo : HttpBasic을 통한 인증 X // 로그인 정보 인식 추가 필요 if (SecurityContextHolder.getContext().getAuthentication() == null) { log.info("[TEST] 로그인 정보 없음"); From 7a4421ea696950062d2edf91f2fbaf48a0d0c521 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 15:37:08 +0900 Subject: [PATCH 0213/1002] =?UTF-8?q?refactor=20:=20resource=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index ae5ad123..d2989edc 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit ae5ad1239a01c75f724c8ab74f67c41e25db7e6b +Subproject commit d2989edc5128951e9bd7460f2c148c3c7b07b07e From 69f9e7dedcbbe462c5fcc5575aaa553c0ca33efa Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 19:23:52 +0900 Subject: [PATCH 0214/1002] =?UTF-8?q?[SC-91]=20perf=20:=20=EB=B9=84?= =?UTF-8?q?=EA=B5=90=EA=B3=BC=EB=A5=BC=20=EB=8B=A4=EB=A3=A8=EB=8A=94=20POS?= =?UTF-8?q?T=20API=EC=9D=98=20=EA=B6=8C=ED=95=9C=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/SecurityUtils.java | 11 +++++ .../domain/post/service/PostService.java | 41 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 4fe768ea..2d7d1a06 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.security.CustomUserDetails; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -27,4 +28,14 @@ public static String getCurrentUserId() { return userDetails.getId(); } + public static UserRole getCurrentUserRole(){ + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { + throw new JwtException(SecurityErrorCode.INVALID_CREDENTIALS); + } + + return userDetails.getRole(); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 2b3f7056..18cc7ca2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.post.service; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.service.LikeService; @@ -15,6 +17,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; @@ -45,6 +48,11 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List imageUrls = handleImageUpload(postImages); String userId = SecurityUtils.getCurrentUserId(); + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + } + PostEntity postEntity = PostEntity.builder() .userId(userId) .title(postCreateRequestDTO.getTitle()) @@ -61,9 +69,15 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + } + List imageUrls = handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); @@ -73,6 +87,12 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + } + post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); } @@ -80,6 +100,12 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + } + post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); } @@ -154,6 +180,12 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); + + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + } + post.delete(); postRepository.save(post); @@ -164,9 +196,14 @@ public void deletePostImage(String postId, String imageUrl) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - if (!post.getPostImageUrls().contains(imageUrl)) { + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")) + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + + + if (!post.getPostImageUrls().contains(imageUrl)) throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); - } + try { // S3에서 이미지 삭제 From 6da6641f600e52f1111333a4f590e45b402d17a9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 19:33:06 +0900 Subject: [PATCH 0215/1002] =?UTF-8?q?[SC-91]=20refactor=20:=20Exception=20?= =?UTF-8?q?message=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20GlobalHandler?= =?UTF-8?q?=EC=97=90=20JwtException=20=EC=B6=94=EA=B0=80,=20403=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalExceptionHandler.java | 7 +++++++ .../codin/codin/domain/post/service/PostService.java | 12 ++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index a509b9c5..2f7c4ab4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package inu.codin.codin.common.exception; import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.codin.common.security.exception.JwtException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -21,4 +22,10 @@ protected ResponseEntity handleNotFoundException(NotFoundExce .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); } + @ExceptionHandler(JwtException.class) + protected ResponseEntity handleJwtException(NotFoundException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.value())); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 18cc7ca2..8ec53fcf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -50,7 +50,7 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List imageUrls = handleImageUpload(postImages); @@ -90,7 +90,7 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } post.updatePostAnonymous(requestDTO.isAnonymous()); @@ -103,7 +103,7 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } post.updatePostStatus(requestDTO.getPostStatus()); @@ -183,7 +183,7 @@ public void softDeletePost(String postId) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } post.delete(); @@ -198,7 +198,7 @@ public void deletePostImage(String postId, String imageUrl) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")) - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "게시물을 업로드 할 권한이 없습니다."); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); if (!post.getPostImageUrls().contains(imageUrl)) From efee712afd07f95f2e7d67deda72c60d326a77ef Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 19:39:04 +0900 Subject: [PATCH 0216/1002] =?UTF-8?q?[SC-91]=20perf=20:=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=82=A0=EC=A7=9C=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/service/PostService.java | 23 +++++-------------- codin-core/src/main/resources | 2 +- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 8ec53fcf..80dbadaf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.util.Comparator; import java.util.List; @Service @@ -114,32 +115,20 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public List getAllPosts(PostCategory postCategory) { List posts = postRepository.findAllAndNotDeletedAndActive(postCategory); - - return posts.stream() - .map(post -> new PostListResponseDto( - post.getUserId(), - post.getPostId(), - post.getContent(), - post.getTitle(), - post.getPostCategory(), - post.getPostImageUrls(), - post.isAnonymous(), - post.getCommentCount(), // 댓글 수 - likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 - scrapService.getScrapCount(post.getPostId()), // 스크랩 수 - post.getCreatedAt() - )).toList(); + return getPostListResponseDtos(posts); } //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public List getAllUserPosts() { - String userId = SecurityUtils.getCurrentUserId(); - List posts = postRepository.findByUserIdAndNotDeleted(userId); + return getPostListResponseDtos(posts); + } + private List getPostListResponseDtos(List posts) { return posts.stream() + .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) .map(post -> new PostListResponseDto( post.getUserId(), post.getPostId(), diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index ae5ad123..cd0cb34d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit ae5ad1239a01c75f724c8ab74f67c41e25db7e6b +Subproject commit cd0cb34de8c22acd1129348b34e46251e242e825 From beb603100c3b865fb130bdce49c4c7a1cbe6befb Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 20:49:38 +0900 Subject: [PATCH 0217/1002] =?UTF-8?q?[SC-91]=20refactor=20:=20=5Fid=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9D=84=20ObjectId=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/SecurityUtils.java | 3 +- .../domain/email/entity/EmailAuthEntity.java | 3 +- .../email/repository/EmailAuthRepository.java | 3 +- .../lab/dto/response/LabListResponseDto.java | 3 +- .../domain/info/domain/lab/entity/Lab.java | 3 +- .../info/domain/lab/service/LabService.java | 7 +-- .../response/ProfessorListResponseDto.java | 3 +- .../domain/professor/entity/Professor.java | 3 +- .../professor/service/ProfessorService.java | 7 +-- .../codin/codin/domain/info/entity/Info.java | 7 +-- .../info/repository/InfoRepository.java | 10 ++--- .../comment/dto/CommentCreateRequestDTO.java | 8 ++-- .../comment/dto/CommentResponseDTO.java | 11 +++-- .../domain/comment/entity/CommentEntity.java | 11 ++--- .../comment/repository/CommentRepository.java | 7 +-- .../comment/service/CommentService.java | 22 ++++++---- .../post/domain/like/entity/LikeEntity.java | 9 ++-- .../like/repository/LikeRepository.java | 11 ++--- .../post/domain/like/service/LikeService.java | 24 ++++++----- .../reply/entity/ReplyCommentEntity.java | 11 ++--- .../repository/ReplyCommentRepository.java | 7 +-- .../reply/service/ReplyCommentService.java | 16 ++++--- .../post/domain/scrap/entity/ScrapEntity.java | 9 ++-- .../scrap/repository/ScrapRepository.java | 11 ++--- .../domain/scrap/service/ScrapService.java | 13 +++--- .../dto/response/PostDetailResponseDTO.java | 13 +++--- .../dto/response/PostListResponseDto.java | 1 + .../codin/domain/post/entity/PostEntity.java | 9 ++-- .../post/repository/PostRepository.java | 7 +-- .../domain/post/service/PostService.java | 33 +++++++------- .../codin/domain/user/entity/UserEntity.java | 3 +- .../user/repository/UserRepository.java | 3 +- .../user/security/CustomUserDetails.java | 7 +-- .../codin/codin/infra/redis/RedisService.java | 27 ++++++------ .../codin/infra/redis/SyncScheduler.java | 43 +++++++++++-------- 35 files changed, 207 insertions(+), 161 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 2d7d1a06..624a62a9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.security.CustomUserDetails; +import org.bson.types.ObjectId; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -18,7 +19,7 @@ public class SecurityUtils { * @return 인증된 사용자의 ID * @throws IllegalStateException 인증 정보가 없는 경우 예외 발생 */ - public static String getCurrentUserId() { + public static ObjectId getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 9a088990..ab29ee6d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -14,7 +15,7 @@ public class EmailAuthEntity extends BaseTimeEntity { @Id @NotBlank - private String id; + private ObjectId _id; @NotBlank private String email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java index 5ca1a667..a3b65844 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java @@ -1,13 +1,14 @@ package inu.codin.codin.domain.email.repository; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface EmailAuthRepository extends MongoRepository { +public interface EmailAuthRepository extends MongoRepository { Optional findByEmail(String email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java index 44b4dda7..9c76d96c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; /* 연구실 리스트 반환 DTO @@ -40,7 +41,7 @@ public LabListResponseDto(String id, String title, String content, String profes public static LabListResponseDto of(Lab lab){ return LabListResponseDto.builder() - .id(lab.getId()) + .id(lab.get_id().toString()) .title(lab.getTitle()) .content(lab.getContent()) .professor(lab.getProfessor()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java index 27f2f111..8fef91cf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -9,6 +9,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "info") @@ -35,7 +36,7 @@ public class Lab extends Info { private String site; @Builder - public Lab(String id, Department department, InfoType infoType, String professor, String title, String content, String professorLoc, String professorNumber, String labLoc, String labNumber, String site) { + public Lab(ObjectId id, Department department, InfoType infoType, String professor, String title, String content, String professorLoc, String professorNumber, String labLoc, String labNumber, String site) { super(id, department, infoType); this.professor = professor; this.title = title; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index d3474c81..f74b411d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @@ -18,7 +19,7 @@ public class LabService { private final InfoRepository infoRepository; public LabThumbnailResponseDto getLabThumbnail(String id) { - Lab lab = infoRepository.findLabById(id) + Lab lab = infoRepository.findLabById(new ObjectId(id)) .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); return LabThumbnailResponseDto.of(lab); } @@ -35,14 +36,14 @@ public void createLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto) { } public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, String id) { - Lab lab = infoRepository.findLabById(id) + Lab lab = infoRepository.findLabById(new ObjectId(id)) .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); lab.update(labCreateUpdateRequestDto); infoRepository.save(lab); } public void deleteLab(String id) { - Lab lab = infoRepository.findLabById(id) + Lab lab = infoRepository.findLabById(new ObjectId(id)) .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); lab.delete(); infoRepository.save(lab); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java index fcf137f7..f435024a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java @@ -7,6 +7,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; /* 교수님 리스트 반환 DTO @@ -34,7 +35,7 @@ public ProfessorListResponseDto(String id, String name, Department department) { public static ProfessorListResponseDto of(Professor professor) { return ProfessorListResponseDto.builder() - .id(professor.getId()) + .id(professor.get_id().toString()) .name(professor.getName()) .department(professor.getDepartment()) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java index 13fa01fe..456646a1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java @@ -9,6 +9,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "info") @@ -38,7 +39,7 @@ public class Professor extends Info { private String labId; @Builder - public Professor(String id, Department department, InfoType infoType, String name, String image, String number, String email, String site, String field, String subject, String labId) { + public Professor(ObjectId id, Department department, InfoType infoType, String name, String image, String number, String email, String site, String field, String subject, String labId) { super(id, department, infoType); this.name = name; this.image = image; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index 4c8aa807..d2cdb51a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @@ -25,7 +26,7 @@ public List getProfessorByDepartment(Department depart } public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { - Professor professor = infoRepository.findProfessorById(id) + Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); return ProfessorThumbnailResponseDto.of(professor); } @@ -39,7 +40,7 @@ public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdat } public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { - Professor professor = infoRepository.findProfessorById(id) + Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); professor.update(professorCreateUpdateRequestDto); infoRepository.save(professor); @@ -48,7 +49,7 @@ public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professor public void deleteProfessor(String id) { - Professor professor = infoRepository.findProfessorById(id) + Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); professor.delete(); infoRepository.save(professor); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java index 22dee228..f2dae04b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -6,6 +6,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -15,7 +16,7 @@ public abstract class Info extends BaseTimeEntity { @Id @NotBlank - protected String id; + protected ObjectId _id; @NotBlank protected Department department; @@ -23,8 +24,8 @@ public abstract class Info extends BaseTimeEntity { @NotBlank protected InfoType infoType; - public Info(String id, Department department, InfoType infoType) { - this.id = id; + public Info(ObjectId id, Department department, InfoType infoType) { + this._id = id; this.department = department; this.infoType = infoType; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 71c9f245..9ef45c87 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -5,22 +5,20 @@ import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import inu.codin.codin.domain.info.entity.Info; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import java.util.List; import java.util.Optional; -public interface InfoRepository extends MongoRepository { +public interface InfoRepository extends MongoRepository { @Query("{ 'infoType': 'LAB', 'deletedAt': null }") List findAllLabs(); @Query("{ 'infoType': 'LAB', '_id': ?0, 'deletedAt': null }") - Optional findLabById(String id); - - @Query("{ 'infoType': 'OFFICE', 'deletedAt': null }") - List findAllOffices(); + Optional findLabById(ObjectId id); @Query("{ 'infoType': 'OFFICE', 'department': ?0, 'deletedAt': null }") Office findOfficeByDepartment(Department department); @@ -29,7 +27,7 @@ public interface InfoRepository extends MongoRepository { List findAllProfessorsByDepartment(Department department); @Query("{ 'infoType': 'PROFESSOR', '_id': ?0, 'deletedAt': null }") - Optional findProfessorById(String id); + Optional findProfessorById(ObjectId id); @Query("{ 'infoType': 'PROFESSOR', 'email': ?0, 'deletedAt': null }") Optional findProfessorByEmail(String email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java index 96e60271..ea1f4445 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java @@ -3,12 +3,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import lombok.Getter; +import lombok.Setter; -@Data +@Getter +@Setter public class CommentCreateRequestDTO { -// @Schema(description = "유저 ID", example = "111111") -// @NotBlank -// private String userId; @Schema(description = "댓글 내용", example = "content") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java index fa478289..6afe5ae3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java @@ -3,14 +3,17 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; import java.util.List; -@Data +@Getter public class CommentResponseDTO { @Schema(description = "댓글 또는 대댓글 ID", example = "111111") @NotBlank - private String commentId; + private String _id; @Schema(description = "유저 ID", example = "user123") @NotBlank @@ -29,8 +32,8 @@ public class CommentResponseDTO { @Schema(description = "삭제 여부", example = "false") private boolean isDeleted; - public CommentResponseDTO(String commentId, String userId, String content, List replies, int likeCount, boolean isDeleted) { - this.commentId = commentId; + public CommentResponseDTO(String _id, String userId, String content, List replies, int likeCount, boolean isDeleted) { + this._id = _id; this.userId = userId; this.content = content; this.replies = replies; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 9197d536..0ce5b84e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -11,17 +12,17 @@ @Getter public class CommentEntity extends BaseTimeEntity { @Id @NotBlank - private String commentId; + private ObjectId _id; - private String postId; //게시글 ID 참조 - private String userId; + private ObjectId postId; //게시글 ID 참조 + private ObjectId userId; private String content; private int likeCount = 0; // 좋아요 수 (Redis에서 관리) @Builder - public CommentEntity(String commentId, String postId, String userId, String content) { - this.commentId = commentId; + public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String content) { + this._id = _id; this.postId = postId; this.userId = userId; this.content = content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index d20c84ef..67ae2635 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -1,18 +1,19 @@ package inu.codin.codin.domain.post.domain.comment.repository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import java.util.List; import java.util.Optional; -public interface CommentRepository extends MongoRepository { +public interface CommentRepository extends MongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") - Optional findByIdAndNotDeleted(String Id); + Optional findByIdAndNotDeleted(ObjectId Id); @Query("{ 'postId': ?0 }") - List findByPostId(String postId); + List findByPostId(ObjectId postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index b491cb28..19eb8f36 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -15,6 +15,7 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @@ -32,11 +33,12 @@ public class CommentService { private final ReplyCommentService replyCommentService; // 댓글 추가 - public void addComment(String postId, CommentCreateRequestDTO requestDTO) { + public void addComment(String id, CommentCreateRequestDTO requestDTO) { + ObjectId postId = new ObjectId(id); PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - String userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); CommentEntity comment = CommentEntity.builder() .postId(postId) .userId(userId) @@ -51,7 +53,8 @@ public void addComment(String postId, CommentCreateRequestDTO requestDTO) { } // 댓글 삭제 (Soft Delete) - public void softDeleteComment(String commentId) { + public void softDeleteComment(String id) { + ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); @@ -76,13 +79,14 @@ public void softDeleteComment(String commentId) { postRepository.save(post); log.info("삭제된 commentId: {} , 대댓글 {} . 총 삭제 수: {} postId: {}", - commentId, replies.size(), (1 + replies.size()), post.getPostId()); + commentId, replies.size(), (1 + replies.size()), post.get_id()); } // 특정 게시물의 댓글 및 대댓글 조회 - public List getCommentsByPostId(String postId) { + public List getCommentsByPostId(String id) { + ObjectId postId = new ObjectId(id); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); List comments = commentRepository.findByPostId(postId); @@ -91,11 +95,11 @@ public List getCommentsByPostId(String postId) { .map(comment -> { boolean isDeleted = comment.getDeletedAt() != null; return new CommentResponseDTO( - comment.getCommentId(), - comment.getUserId(), + comment.get_id().toString(), + comment.getUserId().toString(), comment.getContent(), - replyCommentService.getRepliesByCommentId(comment.getCommentId()), // 대댓글 조회 - likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.getCommentId()), // 댓글 좋아요 수 + replyCommentService.getRepliesByCommentId(comment.get_id()), // 대댓글 조회 + likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), // 댓글 좋아요 수 isDeleted); }) .toList(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java index 8eb141f8..aaa3827c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -10,13 +11,13 @@ @Getter public class LikeEntity extends BaseTimeEntity { @Id - private String id; - private String likeTypeId; // 게시글, 댓글, 대댓글의 ID + private ObjectId _id; + private ObjectId likeTypeId; // 게시글, 댓글, 대댓글의 ID private LikeType likeType; // 엔티티 타입 (post, comment, reply) - private String userId; // 좋아요를 누른 사용자 ID + private ObjectId userId; // 좋아요를 누른 사용자 ID @Builder - public LikeEntity(String likeTypeId, LikeType likeType, String userId) { + public LikeEntity(ObjectId likeTypeId, LikeType likeType, ObjectId userId) { this.likeTypeId = likeTypeId; this.likeType = likeType; this.userId = userId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 4b12ede9..80e9c8b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -9,15 +10,15 @@ @Repository -public interface LikeRepository extends MongoRepository { +public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByLikeTypeAndLikeTypeId(LikeType likeType, String id); + long countByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); // 특정 엔티티의 좋아요 데이터 조회 - List findByLikeTypeAndLikeTypeId(LikeType likeType, String id); + List findByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); // 특정 사용자의 좋아요 삭제 - void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String id, String userId); + void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String id, String userId); + boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index ba22c533..25533ff4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -15,7 +15,9 @@ import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import org.springframework.validation.ObjectError; @Service @RequiredArgsConstructor @@ -30,37 +32,39 @@ public class LikeService { private final RedisHealthChecker redisHealthChecker; public void addLike(LikeRequestDto likeRequestDto) { - String userId = SecurityUtils.getCurrentUserId(); + ObjectId likeId = new ObjectId(likeRequestDto.getId()); + ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); //해당 entity가 삭제되었는지 확인 // 중복 좋아요 검증 - if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { + if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId)) { throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeRequestDto.getLikeType().name(), likeRequestDto.getId(), userId); + redisService.addLike(likeRequestDto.getLikeType().name(), likeId, userId); } LikeEntity like = LikeEntity.builder() .likeType(likeRequestDto.getLikeType()) - .likeTypeId(likeRequestDto.getId()) + .likeTypeId(likeId) .userId(userId) .build(); likeRepository.save(like); } public void removeLike(LikeRequestDto likeRequestDto) { - String userId = SecurityUtils.getCurrentUserId(); + ObjectId likeId = new ObjectId(likeRequestDto.getId()); + ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 없는 좋아요 방지 - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId)) { + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId)) { throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(likeRequestDto.getLikeType().name(), likeRequestDto.getId(), userId); + redisService.removeLike(likeRequestDto.getLikeType().name(), likeId, userId); } - likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeRequestDto.getId(), userId); + likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); } - public int getLikeCount(LikeType entityType, String entityId) { + public int getLikeCount(LikeType entityType, ObjectId entityId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType.name(), entityId); } @@ -75,7 +79,7 @@ public void recoverRedisFromDB() { } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ - String id = likeRequestDto.getId(); + ObjectId id = new ObjectId(likeRequestDto.getId()); switch(likeRequestDto.getLikeType()){ case POST -> postRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 1a2c6038..96702703 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,17 +13,17 @@ public class ReplyCommentEntity extends BaseTimeEntity { @Id @NotBlank - private String replyId; + private ObjectId _id; - private String commentId; // 댓글 ID 참조 - private String userId; // 작성자 ID + private ObjectId commentId; // 댓글 ID 참조 + private ObjectId userId; // 작성자 ID private String content; private int likeCount = 0; // 좋아요 카운트 @Builder - public ReplyCommentEntity(String replyId, String commentId, String userId, String content, int likeCount) { - this.replyId = replyId; + public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, String content, int likeCount) { + this._id = _id; this.commentId = commentId; this.userId = userId; this.content = content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java index 1a19da92..c7a3d598 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java @@ -2,17 +2,18 @@ import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import java.util.List; import java.util.Optional; -public interface ReplyCommentRepository extends MongoRepository { +public interface ReplyCommentRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null}") - Optional findByIdAndNotDeleted(String id); + Optional findByIdAndNotDeleted(ObjectId id); @Query("{'commentId': ?0, 'deletedAt': null}") - List findByCommentIdAndNotDeleted(String commentId); + List findByCommentIdAndNotDeleted(ObjectId commentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 92496dc6..a6d77484 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -14,6 +14,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @@ -30,13 +31,14 @@ public class ReplyCommentService { private final LikeService likeService; // 대댓글 추가 - public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { + public void addReply(String id, ReplyCreateRequestDTO requestDTO) { + ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - String userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); ReplyCommentEntity reply = ReplyCommentEntity.builder() .commentId(commentId) @@ -55,7 +57,7 @@ public void addReply(String commentId, ReplyCreateRequestDTO requestDTO) { // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { - ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) + ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); // 대댓글 삭제 reply.delete(); @@ -70,19 +72,19 @@ public void softDeleteReply(String replyId) { post.updateCommentCount(post.getCommentCount() - 1); postRepository.save(post); - log.info("대댓글 성공적 삭제 replyId: {} , postId: {}.", replyId, post.getPostId()); + log.info("대댓글 성공적 삭제 replyId: {} , postId: {}.", replyId, post.get_id()); } // 특정 댓글의 대댓글 조회 - public List getRepliesByCommentId(String commentId) { + public List getRepliesByCommentId(ObjectId commentId) { List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); return replies.stream() .map(reply -> { boolean isDeleted = reply.getDeletedAt() != null; return new CommentResponseDTO( - reply.getCommentId(), - reply.getUserId(), + reply.getCommentId().toString(), + reply.getUserId().toString(), reply.getContent(), List.of(), //대댓글은 대댓글이 없음 likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.getCommentId()), // 대댓글 좋아요 수 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java index a960caca..9ccfea5a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -10,12 +11,12 @@ @Getter public class ScrapEntity extends BaseTimeEntity { @Id - private String id; - private String postId; - private String userId; + private ObjectId _id; + private ObjectId postId; + private ObjectId userId; @Builder - public ScrapEntity(String postId, String userId) { + public ScrapEntity(ObjectId postId, ObjectId userId) { this.postId = postId; this.userId = userId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 7b18bcd7..1e7de7df 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -1,19 +1,20 @@ package inu.codin.codin.domain.post.domain.scrap.repository; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface ScrapRepository extends MongoRepository { +public interface ScrapRepository extends MongoRepository { - List findByPostId(String postId); + List findByPostId(ObjectId postId); - long countByPostId(String postId); + long countByPostId(ObjectId postId); - void deleteByPostIdAndUserId(String postId, String userId); + void deleteByPostIdAndUserId(ObjectId postId, ObjectId userId); - boolean existsByPostIdAndUserId(String postId, String userId); + boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index 2d65fa6b..56844ba6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -12,6 +12,7 @@ import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; @Service @@ -24,11 +25,12 @@ public class ScrapService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; - public void addScrap(String postId) { + public void addScrap(String id) { + ObjectId postId = new ObjectId(id); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - String userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { throw new ScrapCreateFailException("이미 스크랩 한 상태 입니다."); @@ -44,11 +46,12 @@ public void addScrap(String postId) { scrapRepository.save(scrap); } - public void removeScrap(String postId) { + public void removeScrap(String id) { + ObjectId postId = new ObjectId(id); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - String userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { throw new ScrapRemoveFailException("스크랩한 적이 없는 게시물입니다."); } @@ -59,7 +62,7 @@ public void removeScrap(String postId) { scrapRepository.deleteByPostIdAndUserId(postId, userId); } - public int getScrapCount(String postId) { + public int getScrapCount(ObjectId postId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getScrapCount(postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index f0592c56..e96f4a6e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; import java.time.LocalDateTime; import java.util.List; @@ -14,13 +15,13 @@ @Getter @Setter public class PostDetailResponseDTO { - @Schema(description = "유저 ID", example = "111111") + @Schema(description = "게시물 ID", example = "111111") @NotBlank - private String userId; + private String _id; - @Schema(description = "게시물 ID", example = "111111") + @Schema(description = "유저 ID", example = "111111") @NotBlank - private String postId; + private String userId; @Schema(description = "게시물 종류", example = "구해요") @NotBlank @@ -51,9 +52,9 @@ public class PostDetailResponseDTO { @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") private LocalDateTime createdAt; - public PostDetailResponseDTO(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { + public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { this.userId = userId; - this.postId = postId; + this._id = _id; this.content = content; this.title = title; this.postCategory = postCategory; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index 894f2311..15958737 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; import java.time.LocalDateTime; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index e01d9048..c45035e1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -16,9 +17,9 @@ @Getter public class PostEntity extends BaseTimeEntity { @Id @NotBlank - private String postId; + private ObjectId _id; - private String userId; // User 엔티티와의 관계를 유지하기 위한 필드 + private ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 private String title; private String content; private List postImageUrls = new ArrayList<>(); @@ -32,8 +33,8 @@ public class PostEntity extends BaseTimeEntity { private int scrapCount = 0; // 스크랩 카운트 (redis) @Builder - public PostEntity(String postId, String userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus, int commentCount, int likeCount, int scrapCount) { - this.postId = postId; + public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus, int commentCount, int likeCount, int scrapCount) { + this._id = _id; this.userId = userId; this.title = title; this.content = content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index eed57b24..37974b69 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -10,13 +11,13 @@ import java.util.Optional; @Repository -public interface PostRepository extends MongoRepository { +public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") - Optional findByIdAndNotDeleted(String Id); + Optional findByIdAndNotDeleted(ObjectId Id); @Query("{'userId': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") - List findByUserIdAndNotDeleted(String userId); + List findByUserIdAndNotDeleted(ObjectId userId); @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': ?0 }") List findAllAndNotDeletedAndActive(PostCategory postCategory); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 80dbadaf..925ff14e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -21,6 +21,7 @@ import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -47,7 +48,7 @@ private List handleImageUpload(List postImages) { public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { List imageUrls = handleImageUpload(postImages); - String userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ @@ -71,7 +72,7 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -86,7 +87,7 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request } public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -99,7 +100,7 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req } public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -121,7 +122,7 @@ public List getAllPosts(PostCategory postCategory) { //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public List getAllUserPosts() { - String userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); List posts = postRepository.findByUserIdAndNotDeleted(userId); return getPostListResponseDtos(posts); } @@ -130,16 +131,16 @@ private List getPostListResponseDtos(List posts return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) .map(post -> new PostListResponseDto( - post.getUserId(), - post.getPostId(), + post.getUserId().toString(), + post.get_id().toString(), post.getContent(), post.getTitle(), post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), post.getCommentCount(), - likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 - scrapService.getScrapCount(post.getPostId()), // 스크랩 수 + likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 + scrapService.getScrapCount(post.get_id()), // 스크랩 수 post.getCreatedAt() )) .toList(); @@ -147,19 +148,19 @@ private List getPostListResponseDtos(List posts //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 public PostDetailResponseDTO getPostWithDetail(String postId) { - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); return new PostDetailResponseDTO( - post.getUserId(), - post.getPostId(), + post.getUserId().toString(), + post.get_id().toString(), post.getContent(), post.getTitle(), post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), - likeService.getLikeCount(LikeType.valueOf("POST"),post.getPostId()), // 좋아요 수 - scrapService.getScrapCount(post.getPostId()), // 스크랩 수 + likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 + scrapService.getScrapCount(post.get_id()), // 스크랩 수 post.getCreatedAt() ); } @@ -167,7 +168,7 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { public void softDeletePost(String postId) { - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -182,7 +183,7 @@ public void softDeletePost(String postId) { public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 08310eb7..a03a2850 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -13,7 +14,7 @@ public class UserEntity extends BaseTimeEntity { @Id @NotBlank - private String id; + private ObjectId _id; private String email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index f10a8e24..4fe21623 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -1,13 +1,14 @@ package inu.codin.codin.domain.user.repository; import inu.codin.codin.domain.user.entity.UserEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface UserRepository extends MongoRepository { +public interface UserRepository extends MongoRepository { Optional findByEmail(String email); Optional findByStudentId(String studentId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index c78f297b..c34cda32 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -6,6 +6,7 @@ import inu.codin.codin.domain.user.entity.UserStatus; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -16,7 +17,7 @@ @Getter public class CustomUserDetails implements UserDetails { - private final String id; + private final ObjectId id; private final String email; private final String password; private final String studentId; @@ -30,7 +31,7 @@ public class CustomUserDetails implements UserDetails { private final Collection authorities; @Builder - public CustomUserDetails(String id, String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status, Collection authorities) { + public CustomUserDetails(ObjectId id, String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status, Collection authorities) { this.id = id; this.email = email; this.password = password; @@ -46,7 +47,7 @@ public CustomUserDetails(String id, String email, String password, String studen public static CustomUserDetails from(UserEntity userEntity) { return CustomUserDetails.builder() - .id(userEntity.getId()) + .id(userEntity.get_id()) .email(userEntity.getEmail()) .password(userEntity.getPassword()) .studentId(userEntity.getStudentId()) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index e3baab37..0a58ab78 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -38,17 +39,17 @@ public Set getKeys(String pattern) { } } - public void addLike(String entityType, String entityId, String userId) { + public void addLike(String entityType, ObjectId entityId, ObjectId userId) { String redisKey = entityType + ":likes:" + entityId; - redisTemplate.opsForSet().add(redisKey, userId); + redisTemplate.opsForSet().add(redisKey, String.valueOf(userId)); } - public void removeLike(String entityType, String entityId, String userId) { + public void removeLike(String entityType, ObjectId entityId, ObjectId userId) { String redisKey = entityType + ":likes:" + entityId; - redisTemplate.opsForSet().remove(redisKey, userId); + redisTemplate.opsForSet().remove(redisKey, String.valueOf(userId)); } - public int getLikeCount(String entityType, String entityId) { + public int getLikeCount(String entityType, ObjectId entityId) { String redisKey = entityType + ":likes:" + entityId; Long count = redisTemplate.opsForSet().size(redisKey); return count != null ? count.intValue() : 0; @@ -59,18 +60,18 @@ public Set getLikedUsers(String entityType, String entityId) { return redisTemplate.opsForSet().members(redisKey); } - public void addScrap(String postId, String userId) { - String redisKey = "post:scraps:" + postId; - redisTemplate.opsForSet().add(redisKey, userId); + public void addScrap(ObjectId postId, ObjectId userId) { + String redisKey = "post:scraps:" + postId.toString(); + redisTemplate.opsForSet().add(redisKey, userId.toString()); } - public void removeScrap(String postId, String userId) { - String redisKey = "post:scraps:" + postId; - redisTemplate.opsForSet().remove(redisKey, userId); + public void removeScrap(ObjectId postId, ObjectId userId) { + String redisKey = "post:scraps:" + postId.toString(); + redisTemplate.opsForSet().remove(redisKey, userId.toString()); } - public int getScrapCount(String postId) { - String redisKey = "post:scraps:" + postId; + public int getScrapCount(ObjectId postId) { + String redisKey = "post:scraps:" + postId.toString(); Long scrapCount = redisTemplate.opsForSet().size(redisKey); return scrapCount != null ? scrapCount.intValue() : 0; } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 5da1571f..492fb375 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -14,6 +14,7 @@ import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -49,7 +50,7 @@ public void syncLikes() { log.info(" 동기화 작업 완료"); } - private void syncEntityLikes(String entityType, MongoRepository repository) { + private void syncEntityLikes(String entityType, MongoRepository repository) { Set redisKeys = redisService.getKeys(entityType+ ":likes:*"); if (redisKeys == null || redisKeys.isEmpty()) { return; @@ -59,23 +60,25 @@ private void syncEntityLikes(String entityType, MongoRepository r for (String redisKey : redisKeys) { String likeTypeId = redisKey.replace(entityType + ":likes:", ""); Set likedUsers = redisService.getLikedUsers(entityType, likeTypeId); + ObjectId likeId = new ObjectId(likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeTypeId); + List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeId); for (LikeEntity dbLike : dbLikes) { - if (!likedUsers.contains(dbLike.getUserId())) { - log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeTypeId); + if (!likedUsers.contains(dbLike.getUserId().toString())) { + log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); likeRepository.delete(dbLike); } } // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 - for (String userId : likedUsers) { - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeTypeId, userId)) { - log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, likeTypeId); + for (String id : likedUsers) { + ObjectId userId = new ObjectId(id); + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { + log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, likeId); LikeEntity dbLike = LikeEntity.builder() .likeType(likeType) - .likeTypeId(likeTypeId) + .likeTypeId(likeId) .userId(userId) .build(); likeRepository.save(dbLike); @@ -85,21 +88,21 @@ private void syncEntityLikes(String entityType, MongoRepository r // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 int likeCount = likedUsers.size(); if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findByIdAndNotDeleted(likeTypeId).orElse(null); + PostEntity post = postRepo.findByIdAndNotDeleted(likeId).orElse(null); if (post != null && post.getLikeCount() != likeCount) { log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); post.updateLikeCount(likeCount); postRepo.save(post); } } else if (repository instanceof CommentRepository commentRepo) { - CommentEntity comment = commentRepo.findByIdAndNotDeleted(likeTypeId).orElse(null); + CommentEntity comment = commentRepo.findByIdAndNotDeleted(likeId).orElse(null); if (comment != null && comment.getLikeCount() != likeCount) { log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); comment.updateLikeCount(likeCount); commentRepo.save(comment); } } else if (repository instanceof ReplyCommentRepository replyRepo) { - ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(likeTypeId).orElse(null); + ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(likeId).orElse(null); if (reply != null && reply.getLikeCount() != likeCount) { log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); reply.updateLikeCount(likeCount); @@ -119,17 +122,19 @@ public void syncPostScraps() { for (String redisKey : redisKeys) { String postId = redisKey.replace("post:scraps:", ""); Set redisScrappedUsers = redisService.getLikedUsers("post", postId); + ObjectId id = new ObjectId(postId); // MongoDB의 스크랩 데이터 가져오기 - List dbScraps = scrapRepository.findByPostId(postId); + List dbScraps = scrapRepository.findByPostId(id); Set dbScrappedUsers = dbScraps.stream() .map(ScrapEntity::getUserId) + .map(ObjectId::toString) .collect(Collectors.toSet()); // (스크랩 삭제) MongoDB에 있지만 Redis에 없는 사용자 삭제 for (ScrapEntity dbScrap : dbScraps) { - if (!redisScrappedUsers.contains(dbScrap.getUserId())) { - log.info("MongoDB에서 사용자 삭제: UserID={}, PostID={}", dbScrap.getUserId(), postId); + if (!redisScrappedUsers.contains(dbScrap.getUserId().toString())) { + log.info("MongoDB에서 사용자 삭제: UserID={}, PostID={}", dbScrap.getUserId(), id); scrapRepository.delete(dbScrap); } } @@ -137,10 +142,10 @@ public void syncPostScraps() { // (스크랩 추가) Redis에 있지만 MongoDB에 없는 사용자 추가 for (String redisUser : redisScrappedUsers) { if (!dbScrappedUsers.contains(redisUser)) { - log.info("MongoDB에 사용자 추가: UserID={}, PostID={}", redisUser, postId); + log.info("MongoDB에 사용자 추가: UserID={}, PostID={}", redisUser, id); ScrapEntity dbScrap = ScrapEntity.builder() - .postId(postId) - .userId(redisUser) + .postId(id) + .userId(new ObjectId(redisUser)) .build(); scrapRepository.save(dbScrap); } @@ -148,10 +153,10 @@ public void syncPostScraps() { // Redis 사용자 수로 PostEntity의 scrapCount 업데이트 int redisScrapCount = redisScrappedUsers.size(); - PostEntity post = postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); if (post.getScrapCount() != redisScrapCount) { - log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, redisScrapCount); + log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", id, redisScrapCount); post.updateScrapCount(redisScrapCount); postRepository.save(post); } From b0d56e7df3f5d351f7e7529422609485426fefc5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 22:35:32 +0900 Subject: [PATCH 0218/1002] =?UTF-8?q?[SC-91]=20refactor=20:=20DTO=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20@Getter=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/dto/JoinEmailCheckRequestDto.java | 4 +-- .../email/dto/JoinEmailSendRequestDto.java | 4 +-- .../dto/response/LabThumbnailResponseDto.java | 18 +++++----- .../OfficeMemberCreateUpdateRequestDto.java | 2 -- .../dto/request/OfficeUpdateRequestDto.java | 2 -- .../response/OfficeDetailsResponseDto.java | 5 ++- .../dto/response/OfficeMemberResponseDto.java | 10 +++--- .../ProfessorCreateUpdateRequestDto.java | 36 +++++++++++-------- .../response/ProfessorListResponseDto.java | 15 ++++---- .../ProfessorThumbnailResponseDto.java | 20 +++++------ .../comment/controller/CommentController.java | 4 +-- .../CommentCreateRequestDTO.java | 5 +-- .../{ => response}/CommentResponseDTO.java | 17 ++++----- .../comment/service/CommentService.java | 4 +-- .../dto/request/ReplyCreateRequestDTO.java | 7 ++-- .../reply/entity/ReplyCommentEntity.java | 1 - .../reply/service/ReplyCommentService.java | 4 +-- .../PostAnonymousUpdateRequestDTO.java | 4 +-- .../request/PostContentUpdateRequestDTO.java | 4 +-- .../dto/request/PostCreateRequestDTO.java | 8 ++--- .../request/PostStatusUpdateRequestDTO.java | 4 +-- .../dto/response/PostDetailResponseDTO.java | 23 ++++++------ .../dto/response/PostListResponseDto.java | 5 +-- .../domain/user/dto/UserCreateRequestDto.java | 2 -- .../codin/infra/redis/SyncScheduler.java | 8 ++--- 25 files changed, 95 insertions(+), 121 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/{ => request}/CommentCreateRequestDTO.java (72%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/{ => response}/CommentResponseDTO.java (76%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java index 35af1cf6..6b396143 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java @@ -3,13 +3,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import lombok.Data; +import lombok.Getter; /** * 이메일 인증 코드 확인 요청 DTO * 해당 이메일로 전송된 인증 코드를 확인한다. */ -@Data +@Getter public class JoinEmailCheckRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java index 877198e9..279e6ae8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java @@ -3,13 +3,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import lombok.Data; +import lombok.Getter; /** * 이메일 인증 코드 전송 요청 DTO * 해당 이메일로 인증 코드를 전송한다. */ -@Data +@Getter public class JoinEmailSendRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java index e2cac527..4c6a5c23 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java @@ -18,33 +18,33 @@ public class LabThumbnailResponseDto { @NotBlank @Schema(description = "학과", example = "EMBEDDED") - private Department department; + private final Department department; @NotBlank @Schema(description = "연구실 이름", example = "땡땡연구실") - private String title; + private final String title; @Schema(description = "연구 내용", example = "이것저것 연구합니다.") - private String content; + private final String content; @NotBlank @Schema(description = "담당 교수", example = "홍길동") - private String professor; + private final String professor; @Schema(description = "교수실 위치", example = "7호관 423호") - private String professorLoc; + private final String professorLoc; @Schema(description = "교수실 전화번호", example = "032-123-4567") - private String professorNumber; + private final String professorNumber; @Schema(description = "연구실 위치", example = "7호관 409호") - private String labLoc; + private final String labLoc; @Schema(description = "연구실 전화번호", example = "032-987-0653") - private String labNumber; + private final String labNumber; @Schema(description = "연구실 홈페이지", example = "http://~") - private String site; + private final String site; @Builder public LabThumbnailResponseDto(Department department, String title, String content, String professor, diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java index 42ae288b..13bd04bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java @@ -3,10 +3,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; -import lombok.Setter; @Getter -@Setter public class OfficeMemberCreateUpdateRequestDto { @NotBlank @Schema(description = "성명", example = "홍길동") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java index 956ebf04..a6a4bb3c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java @@ -5,10 +5,8 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.Setter; @Getter -@Setter public class OfficeUpdateRequestDto { @Schema(description = "위치", example = "7호관 329호") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java index 252933ca..3410c67d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java @@ -15,7 +15,6 @@ 학과 사무실 평면도 및 지원 정보들을 모두 반환한다. */ @Getter -@Setter public class OfficeDetailsResponseDto { @Schema(description = "학과", example = "IT_COLLEGE") @@ -41,10 +40,10 @@ public class OfficeDetailsResponseDto { private final String fax; @Schema(description = "사무실 평면도", example = "https://") - private String img; + private final String img; @Schema(description = "학과사무실 직원") - private List officeMember; + private final List officeMember; @Builder public OfficeDetailsResponseDto(Department department, String location, String open, String vacation, String officeNumber, String fax, String img, List officeMember) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java index be5e1862..bf7d0dbf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java @@ -13,21 +13,21 @@ public class OfficeMemberResponseDto { @NotBlank @Schema(description = "성명", example = "홍길동") - private String name; + private final String name; @NotBlank @Schema(description = "직위", example = "조교") - private String position; + private final String position; @Schema(description = "담당 업무", example = "학과사무실 업무") - private String role; + private final String role; @NotBlank @Schema(description = "연락처", example = "032-123-2345") - private String number; + private final String number; @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") - private String email; + private final String email; @Builder public OfficeMemberResponseDto(String name, String position, String role, String number, String email) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java index 234b1a9a..de86a82a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java @@ -4,42 +4,48 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; @Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor public class ProfessorCreateUpdateRequestDto { @NotNull @Schema(description = "학과", example = "COMPUTER_SCI") - Department department; + private Department department; @NotBlank @Schema(description = "성함", example = "홍길동") - String name; + private String name; @NotBlank @Schema(description = "프로필 사진", example = "https://~") - String image; + private String image; @NotBlank @Schema(description = "전화번호", example = "032-123-4567") - String number; + private String number; @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") - String email; + private String email; @Schema(description = "연구실 홈페이지", example = "https://~") - String site; + private String site; @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") - String field; + private String field; @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") - String subject; + private String subject; @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") - String labId; + private String labId; + + public ProfessorCreateUpdateRequestDto(Department department, String name, String image, String number, String email, String site, String field, String subject, String labId) { + this.department = department; + this.name = name; + this.image = image; + this.number = number; + this.email = email; + this.site = site; + this.field = field; + this.subject = subject; + this.labId = labId; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java index f435024a..030c6ea8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java @@ -6,8 +6,6 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.Setter; -import org.bson.types.ObjectId; /* 교수님 리스트 반환 DTO @@ -15,27 +13,26 @@ */ @Getter -@Setter public class ProfessorListResponseDto{ @NotBlank @Schema(description = "교수 _id", example = "67319fe3c4ee25b3adf593a0") - String id; + private final String _id; @NotBlank @Schema(description = "교수 성함", example = "홍길동") - String name; + private final String name; @NotBlank @Schema(description = "학과", example = "CSE") - Department department; + private final Department department; @Builder - public ProfessorListResponseDto(String id, String name, Department department) { - this.id = id; + public ProfessorListResponseDto(String _id, String name, Department department) { + this._id = _id; this.name = name; this.department = department; } public static ProfessorListResponseDto of(Professor professor) { return ProfessorListResponseDto.builder() - .id(professor.get_id().toString()) + ._id(professor.get_id().toString()) .name(professor.getName()) .department(professor.getDepartment()) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java index 6f050733..9e2dc1e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.Setter; /* 교수님 상세 정보 반환 DTO @@ -14,27 +13,26 @@ */ @Getter -@Setter public class ProfessorThumbnailResponseDto{ @NotBlank @Schema(description = "학과", example = "CSE") - Department department; + private final Department department; @NotBlank @Schema(description = "성함", example = "홍길동") - String name; + private final String name; @NotBlank @Schema(description = "프로필 사진", example = "https://~") - String image; + private final String image; @NotBlank @Schema(description = "전화번호", example = "032-123-4567") - String number; + private final String number; @NotBlank @Schema(description = "이메일", example = "test@inu.ac.kr") - String email; + private final String email; @Schema(description = "연구실 홈페이지", example = "https://~") - String site; + private final String site; @Schema(description = "연구 분야", example = "무선 통신 및 머신런닝") - String field; + private final String field; @Schema(description = "담당 과목", example = "대학수학, 이동통신, ..") - String subject; + private final String subject; @Schema(description = "연구실 _id", example = "6731a506c4ee25b3adf593ca") - String labId; + private final String labId; @Builder public ProfessorThumbnailResponseDto(Department department, String name, String image, String number, String email, String site, String field, String subject, String labId) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index c0c29460..0d1d8a63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java similarity index 72% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java index ea1f4445..d1695c27 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java @@ -1,13 +1,10 @@ -package inu.codin.codin.domain.post.domain.comment.dto; +package inu.codin.codin.domain.post.domain.comment.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import lombok.Data; import lombok.Getter; -import lombok.Setter; @Getter -@Setter public class CommentCreateRequestDTO { @Schema(description = "댓글 내용", example = "content") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java similarity index 76% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 6afe5ae3..8118d7e9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -1,11 +1,8 @@ -package inu.codin.codin.domain.post.domain.comment.dto; +package inu.codin.codin.domain.post.domain.comment.dto.response; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import lombok.Data; import lombok.Getter; -import lombok.Setter; -import org.bson.types.ObjectId; import java.util.List; @@ -13,24 +10,24 @@ public class CommentResponseDTO { @Schema(description = "댓글 또는 대댓글 ID", example = "111111") @NotBlank - private String _id; + private final String _id; @Schema(description = "유저 ID", example = "user123") @NotBlank - private String userId; + private final String userId; @Schema(description = "댓글 또는 대댓글 내용", example = "This is a comment.") @NotBlank - private String content; + private final String content; @Schema(description = "대댓글 리스트", example = "[...]") - private List replies; + private final List replies; @Schema(description = "좋아요 수", example = "5") - private int likeCount; + private final int likeCount; @Schema(description = "삭제 여부", example = "false") - private boolean isDeleted; + private final boolean isDeleted; public CommentResponseDTO(String _id, String userId, String content, List replies, int likeCount, boolean isDeleted) { this._id = _id; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 19eb8f36..6e6c6bdc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.comment.dto.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.entity.PostEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java index 1d8f999a..bda6d873 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -2,13 +2,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import lombok.Data; +import lombok.Getter; -@Data +@Getter public class ReplyCreateRequestDTO { -// @Schema(description = "유저 ID", example = "111111") -// @NotBlank -// private String userId; @Schema(description = "댓글 내용", example = "content") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 96702703..cf30b459 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -14,7 +14,6 @@ public class ReplyCommentEntity extends BaseTimeEntity { @Id @NotBlank private ObjectId _id; - private ObjectId commentId; // 댓글 ID 참조 private ObjectId userId; // 작성자 ID private String content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index a6d77484..01ffe474 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.comment.dto.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.like.entity.LikeType; @@ -83,7 +83,7 @@ public List getRepliesByCommentId(ObjectId commentId) { .map(reply -> { boolean isDeleted = reply.getDeletedAt() != null; return new CommentResponseDTO( - reply.getCommentId().toString(), + reply.get_id().toString(), reply.getUserId().toString(), reply.getContent(), List.of(), //대댓글은 대댓글이 없음 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java index 01c1d5d9..6ee02d21 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java @@ -2,9 +2,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import lombok.Data; +import lombok.Getter; -@Data +@Getter public class PostAnonymousUpdateRequestDTO { @Schema(description = "익명 여부", example = "true") @NotNull diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java index a1cc6916..5cae42be 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostContentUpdateRequestDTO.java @@ -2,9 +2,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import lombok.Data; +import lombok.Getter; -@Data +@Getter public class PostContentUpdateRequestDTO { @Schema(description = "게시물 내용", example = "Updated content") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index 7ed1254a..c9023ba8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -4,15 +4,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; +import lombok.Getter; -@Data +@Getter public class PostCreateRequestDTO { -// @Schema(description = "유저 ID", example = "111111") -// @NotBlank -// private String userId; - @Schema(description = "게시물 제목", example = "Example") @NotBlank private String title; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java index 62920680..52553bb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostStatusUpdateRequestDTO.java @@ -3,9 +3,9 @@ import inu.codin.codin.domain.post.entity.PostStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import lombok.Data; +import lombok.Getter; -@Data +@Getter public class PostStatusUpdateRequestDTO { @Schema(description = "게시물 상태", example = "ACTIVE") @NotNull diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index e96f4a6e..5c05c5eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -6,51 +6,48 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; -import lombok.Setter; -import org.bson.types.ObjectId; import java.time.LocalDateTime; import java.util.List; @Getter -@Setter public class PostDetailResponseDTO { @Schema(description = "게시물 ID", example = "111111") @NotBlank - private String _id; + private final String _id; @Schema(description = "유저 ID", example = "111111") @NotBlank - private String userId; + private final String userId; @Schema(description = "게시물 종류", example = "구해요") @NotBlank - private PostCategory postCategory; + private final PostCategory postCategory; @Schema(description = "게시물 제목", example = "Example") @NotBlank - private String title; + private final String title; @Schema(description = "게시물 내용", example = "example content") @NotBlank - private String content; + private final String content; @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") - private List postImageUrl; + private final List postImageUrl; @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") @NotNull - private boolean isAnonymous; + private final boolean isAnonymous; @Schema(description = "좋아요 count", example = "0") - private int likeCount; + private final int likeCount; @Schema(description = "스크랩 count", example = "0") - private int scrapCount; + private final int scrapCount; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") - private LocalDateTime createdAt; + private final LocalDateTime createdAt; public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { this.userId = userId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index 15958737..cf3f6219 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -3,18 +3,15 @@ import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; -import lombok.Setter; -import org.bson.types.ObjectId; import java.time.LocalDateTime; import java.util.List; @Getter -@Setter public class PostListResponseDto extends PostDetailResponseDTO{ @Schema(description = "댓글 및 대댓글 count", example = "0") - private int commentCount; + private final int commentCount; public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java index c549e70a..3c0e3523 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java @@ -3,10 +3,8 @@ import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import lombok.Data; import lombok.Getter; -@Data @Getter public class UserCreateRequestDto { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 492fb375..4c8938b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -66,7 +66,7 @@ private void syncEntityLikes(String entityType, MongoRepository List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId().toString())) { - log.info("MongoDB에서 사용자 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); + log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); likeRepository.delete(dbLike); } } @@ -75,7 +75,7 @@ private void syncEntityLikes(String entityType, MongoRepository for (String id : likedUsers) { ObjectId userId = new ObjectId(id); if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { - log.info("MongoDB에 사용자 추가: UserID={}, EntityID={}", userId, likeId); + log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, likeId); LikeEntity dbLike = LikeEntity.builder() .likeType(likeType) .likeTypeId(likeId) @@ -134,7 +134,7 @@ public void syncPostScraps() { // (스크랩 삭제) MongoDB에 있지만 Redis에 없는 사용자 삭제 for (ScrapEntity dbScrap : dbScraps) { if (!redisScrappedUsers.contains(dbScrap.getUserId().toString())) { - log.info("MongoDB에서 사용자 삭제: UserID={}, PostID={}", dbScrap.getUserId(), id); + log.info("[MongoDB] 스크랩 삭제: UserID={}, PostID={}", dbScrap.getUserId(), id); scrapRepository.delete(dbScrap); } } @@ -142,7 +142,7 @@ public void syncPostScraps() { // (스크랩 추가) Redis에 있지만 MongoDB에 없는 사용자 추가 for (String redisUser : redisScrappedUsers) { if (!dbScrappedUsers.contains(redisUser)) { - log.info("MongoDB에 사용자 추가: UserID={}, PostID={}", redisUser, id); + log.info("[MongoDB] 스크랩 추가: UserID={}, PostID={}", redisUser, id); ScrapEntity dbScrap = ScrapEntity.builder() .postId(id) .userId(new ObjectId(redisUser)) From a4d145b65bbf7c83e525fb4af3c1452168b8e747 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 22:42:02 +0900 Subject: [PATCH 0219/1002] =?UTF-8?q?[SC-91]=20feat=20:=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EC=9C=A0=EC=A0=80=EC=9D=98=20API=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EA=B6=8C=ED=95=9C=20=EA=B2=80=EC=A6=9D=20(?= =?UTF-8?q?=EC=B6=94=ED=9B=84=20JWT=20=EB=8F=99=EC=9E=91=20=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/util/SecurityUtils.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 624a62a9..a6fe4bd8 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -17,7 +17,7 @@ public class SecurityUtils { * 현재 인증된 사용자의 ID를 반환. * * @return 인증된 사용자의 ID - * @throws IllegalStateException 인증 정보가 없는 경우 예외 발생 + * @throws JwtException 인증 정보가 없는 경우 예외 발생 */ public static ObjectId getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -39,4 +39,11 @@ public static UserRole getCurrentUserRole(){ return userDetails.getRole(); } + public void validateUser(ObjectId id){ + ObjectId userId = SecurityUtils.getCurrentUserId(); + if (!id.equals(userId)) { + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "현재 유저에게 권한이 없습니다."); + } + } + } From 6b0f4b2d7945e2cdb540a085131a3c901e8a8570 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 22:50:11 +0900 Subject: [PATCH 0220/1002] =?UTF-8?q?[SC-91]=20perf=20:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EA=B6=8C=ED=95=9C=EA=B3=BC=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=EC=9D=98=20HttpCode=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/exception/GlobalExceptionHandler.java | 6 +++++- .../inu/codin/codin/common/security/util/SecurityUtils.java | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 2f7c4ab4..7bc25fc6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -23,7 +23,11 @@ protected ResponseEntity handleNotFoundException(NotFoundExce } @ExceptionHandler(JwtException.class) - protected ResponseEntity handleJwtException(NotFoundException e) { + protected ResponseEntity handleJwtException(JwtException e) { + if (e.getErrorCode().getErrorCode().equals("SEC_005")){ + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.FORBIDDEN.value())); + } return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new ExceptionResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.value())); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index a6fe4bd8..f7a21526 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -23,7 +23,7 @@ public static ObjectId getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { - throw new JwtException(SecurityErrorCode.INVALID_CREDENTIALS); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED); } return userDetails.getId(); @@ -33,7 +33,7 @@ public static UserRole getCurrentUserRole(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { - throw new JwtException(SecurityErrorCode.INVALID_CREDENTIALS); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED); } return userDetails.getRole(); From 9a034ec50c4fb1fba679d133d9b40ec6c090cf9f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 23:10:27 +0900 Subject: [PATCH 0221/1002] =?UTF-8?q?[SC-81]=20resource=20local=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index b101cf25..cd0cb34d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit b101cf2500e54f902ee06ec6108d045e1d0135c2 +Subproject commit cd0cb34de8c22acd1129348b34e46251e242e825 From 63b6e8373e1d25ce28d7a3d66039eca4c9d770b0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 4 Dec 2024 23:28:38 +0900 Subject: [PATCH 0222/1002] =?UTF-8?q?fix=20:=20Swagger=EC=97=90=EC=84=9C?= =?UTF-8?q?=20JWT=20=ED=86=A0=ED=81=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 1 - .../java/inu/codin/codin/common/config/SwaggerConfig.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 8cb5259c..e96097d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -60,7 +60,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().hasRole("USER") ) // Swagger 접근 시 httpBasic 인증 사용 -// .securityMatcher(SWAGGER_AUTH_PATHS) .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index bcd8c36e..4d9e2f89 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -37,12 +37,12 @@ public OpenAPI customOpenAPI() { // Bearer Auth를 사용하는 Security Requirement 설정 SecurityRequirement securityRequirement = new SecurityRequirement() - .addList("Bearer Auth"); + .addList("JWT"); return new OpenAPI() .info(info) .security(List.of(securityRequirement)) - .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .components(new Components().addSecuritySchemes("JWT", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), // Local Server new Server().url("https://www.codin.co.kr/api").description("Production Server") // Production Server From b2ba23d6ce86a3c05d157a8ecafee28bfcce85c2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 12 Dec 2024 16:23:42 +0900 Subject: [PATCH 0223/1002] =?UTF-8?q?[SC-92]=20feat=20:=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=88=98(hits)=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 1 - .../domain/post/domain/hits/HitsEntity.java | 31 +++++++++++++++++++ .../post/domain/hits/HitsRepository.java | 14 +++++++++ .../dto/response/PostDetailResponseDTO.java | 6 +++- .../dto/response/PostListResponseDto.java | 4 +-- .../domain/post/service/PostService.java | 14 +++++++-- .../codin/codin/infra/redis/RedisService.java | 22 ++++++++++++- 7 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 8cb5259c..db583043 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -4,7 +4,6 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.common.security.service.JwtService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java new file mode 100644 index 00000000..47e323c1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.post.domain.hits; + +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "hits") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class HitsEntity { + + @Id @NotBlank + private ObjectId _id; + + @NotBlank + private ObjectId userId; + + @NotBlank + private ObjectId postId; + + @Builder + public HitsEntity(ObjectId userId, ObjectId postId) { + this.userId = userId; + this.postId = postId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java new file mode 100644 index 00000000..9cc775f8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.post.domain.hits; + +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; +import java.util.Optional; + +public interface HitsRepository extends MongoRepository { + + Optional findByPostIdAndUserId(ObjectId postId, ObjectId userId); + + List findAllByPostId(ObjectId postId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 5c05c5eb..dc013a72 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -45,11 +45,14 @@ public class PostDetailResponseDTO { @Schema(description = "스크랩 count", example = "0") private final int scrapCount; + @Schema(description = "조회수", example = "0") + private final int hits; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") private final LocalDateTime createdAt; - public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { + public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt) { this.userId = userId; this._id = _id; this.content = content; @@ -59,6 +62,7 @@ public PostDetailResponseDTO(String userId, String _id, String content, String t this.isAnonymous = isAnonymous; this.likeCount = likeCount; this.scrapCount = scrapCount; + this.hits = hits; this.createdAt = createdAt; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index cf3f6219..76b48ec5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -13,8 +13,8 @@ public class PostListResponseDto extends PostDetailResponseDTO{ @Schema(description = "댓글 및 대댓글 count", example = "0") private final int commentCount; - public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { - super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); + public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, int hits, LocalDateTime createdAt) { + super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, hits, createdAt); this.commentCount = commentCount; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 925ff14e..952b12b9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -18,6 +18,7 @@ import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; @@ -36,6 +37,7 @@ public class PostService { private final S3Service s3Service; private final LikeService likeService; private final ScrapService scrapService; + private final RedisService redisService; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -113,14 +115,14 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT } - // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 + 조회수 반환 public List getAllPosts(PostCategory postCategory) { List posts = postRepository.findAllAndNotDeletedAndActive(postCategory); return getPostListResponseDtos(posts); } - //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 + 조회수 반환 public List getAllUserPosts() { ObjectId userId = SecurityUtils.getCurrentUserId(); List posts = postRepository.findByUserIdAndNotDeleted(userId); @@ -141,16 +143,21 @@ private List getPostListResponseDtos(List posts post.getCommentCount(), likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 scrapService.getScrapCount(post.get_id()), // 스크랩 수 + redisService.getHitsCount(post.get_id()), post.getCreatedAt() )) .toList(); } - //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 + //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수) + 조회수 반환 public PostDetailResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + ObjectId userId = SecurityUtils.getCurrentUserId(); + if (redisService.validateHits(post.get_id(), userId)) + redisService.addHits(post.get_id(), userId); + return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), @@ -161,6 +168,7 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { post.isAnonymous(), likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 scrapService.getScrapCount(post.get_id()), // 스크랩 수 + redisService.getHitsCount(post.get_id()), post.getCreatedAt() ); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 0a58ab78..3a469a5e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -21,7 +21,6 @@ public class RedisService { * 장애 복구를 대비한 보완 로직 추가 */ private final RedisTemplate redisTemplate; - private final PostRepository postRepository; //post, comment, reply 구분 public Set getKeys(String pattern) { @@ -75,4 +74,25 @@ public int getScrapCount(ObjectId postId) { Long scrapCount = redisTemplate.opsForSet().size(redisKey); return scrapCount != null ? scrapCount.intValue() : 0; } + + public void addHits(ObjectId postId, ObjectId userId){ + String redisKey = "post:hits:" + postId.toString(); + redisTemplate.opsForSet().add(redisKey, userId.toString()); + } + + public boolean validateHits(ObjectId postId, ObjectId userId){ + String redisKey = "post:hits:" + postId.toString(); + return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); //없어야 유효성 검증 통과 + } + + public int getHitsCount(ObjectId postId){ + String redisKey = "post:hits:" + postId.toString(); + Long hitsCount = redisTemplate.opsForSet().size(redisKey); + return hitsCount != null ? hitsCount.intValue() : 0; + } + + public Set getHitsUser(ObjectId postId) { + String redisKey = "post:hits:" + postId.toString(); + return redisTemplate.opsForSet().members(redisKey); + } } From 116ee722b965fc51297c1f4d8e4c59a74ad8e5ec Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 12 Dec 2024 16:23:53 +0900 Subject: [PATCH 0224/1002] =?UTF-8?q?[SC-92]=20feat=20:=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=88=98=20redis&db=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/infra/redis/SyncScheduler.java | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 4c8938b2..06b810d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -2,6 +2,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.hits.HitsEntity; +import inu.codin.codin.domain.post.domain.hits.HitsRepository; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; @@ -20,6 +22,7 @@ import org.springframework.stereotype.Component; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -28,12 +31,14 @@ @Slf4j public class SyncScheduler { - private final RedisService redisService; private final PostRepository postRepository; private final CommentRepository commentRepository; private final ReplyCommentRepository replyCommentRepository; private final LikeRepository likeRepository; private final ScrapRepository scrapRepository; + private final HitsRepository hitsRepository; + + private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 @@ -47,6 +52,7 @@ public void syncLikes() { syncEntityLikes("comment", commentRepository); syncEntityLikes("reply", replyCommentRepository); syncPostScraps(); + synPostHits(); log.info(" 동기화 작업 완료"); } @@ -162,4 +168,49 @@ public void syncPostScraps() { } } } + + public void synPostHits(){ + Map> postHits = fetchAllPostHits(); //하나의 게시글에 조회한 user들 + + //Redis 데이터로 DB 저장 + postHits.forEach((postId, userIds) -> { + userIds.forEach(userId -> { + hitsRepository.findByPostIdAndUserId(postId, new ObjectId(userId)) + .orElseGet(() -> hitsRepository.save( + HitsEntity.builder() + .postId(postId) + .userId(new ObjectId(userId)) + .build() + )); + }); + }); + + //DB 데이터로 Redis 저장 + postHits.keySet().forEach(postId -> { + List dbHits = hitsRepository.findAllByPostId(postId); + + dbHits.forEach(hitsEntity -> { + if (redisService.validateHits(postId, hitsEntity.getUserId())) { + redisService.addHits(postId, hitsEntity.getUserId()); + } + }); + }); + } + + public Map> fetchAllPostHits(){ + Set keys = redisService.getKeys("post:hits:*"); + return keys.stream() + .collect(Collectors.toMap( + key -> { + String postId = key.replace("post:hits:", ""); + return new ObjectId(postId); + }, + key -> { + String postId = key.replace("post:hits:", ""); + return redisService.getHitsUser(new ObjectId(postId)); + } + ) + ); + } + } \ No newline at end of file From 2b0922d8953aed55e0e8ed892ae14887fac21462 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 13 Dec 2024 00:23:49 +0900 Subject: [PATCH 0225/1002] =?UTF-8?q?[SC-92]=20feat=20:=20=EA=B5=AC?= =?UTF-8?q?=ED=95=B4=EC=9A=94,=20=EC=86=8C=ED=86=B5=ED=95=B4=EC=9A=94=20,?= =?UTF-8?q?=EB=B9=84=EA=B5=90=EA=B3=BC=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 4 +- .../dto/response/PostDetailResponseDTO.java | 18 +++++++- .../dto/response/PostListResponseDto.java | 4 +- .../domain/post/entity/PostCategory.java | 4 ++ .../post/repository/PostRepository.java | 2 + .../domain/post/service/PostService.java | 25 ++++++++--- .../codin/codin/infra/redis/RedisService.java | 42 +++++++++++++------ 7 files changed, 77 insertions(+), 22 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 0339b649..83e701e3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -91,8 +91,8 @@ public ResponseEntity> updatePostAnonymous( summary = "카테고리별 삭제 되지 않은 모든 게시물 조회" ) @GetMapping("/category") - public ResponseEntity> getAllPosts(@RequestParam PostCategory postCategory) { - List posts = postService.getAllPosts(postCategory); + public ResponseEntity> getAllPostsByCategory(@RequestParam PostCategory postCategory) { + List posts = postService.getAllPostsByCategory(postCategory); return ResponseEntity.ok() .body(new ListResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", posts)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index dc013a72..90055ade 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.Builder; import lombok.Getter; import java.time.LocalDateTime; @@ -52,7 +53,10 @@ public class PostDetailResponseDTO { @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") private final LocalDateTime createdAt; - public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt) { + @Schema(description = "해당 게시글에 대한 유저 반응 여부") + private final UserInfo userInfo; + + public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, UserInfo userInfo) { this.userId = userId; this._id = _id; this.content = content; @@ -64,5 +68,17 @@ public PostDetailResponseDTO(String userId, String _id, String content, String t this.scrapCount = scrapCount; this.hits = hits; this.createdAt = createdAt; + this.userInfo = userInfo; + } + + @Getter + public static class UserInfo { + private final boolean isLike; + private final boolean isScrap; + @Builder + public UserInfo(boolean isLike, boolean isScrap) { + this.isLike = isLike; + this.isScrap = isScrap; + } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index 76b48ec5..b327e655 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -13,8 +13,8 @@ public class PostListResponseDto extends PostDetailResponseDTO{ @Schema(description = "댓글 및 대댓글 count", example = "0") private final int commentCount; - public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, int hits, LocalDateTime createdAt) { - super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, hits, createdAt); + public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, UserInfo userInfo) { + super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, hits, createdAt, userInfo); this.commentCount = commentCount; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 4be769cb..3d42e4d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -4,15 +4,19 @@ @Getter public enum PostCategory { + + REQUEST("구해요"), REQUEST_STUDY("구해요_스터디"), REQUEST_PROJECT("구해요_프로젝트"), REQUEST_COMPETITION("구해요_공모전_대회"), REQUEST_GROUP("구해요_소모임"), + COMMUNICATION("소통해요"), COMMUNICATION_QUESTION("소통해요_질문"), COMMUNICATION_JOB("소통해요_취업수기"), COMMUNICATION_TIP("소통해요_꿀팁공유"), + EXTRACURRICULAR("비교과"), EXTRACURRICULAR_STARINU("비교과_STARINU"), EXTRACURRICULAR_OUTER("비교과_교외"), EXTRACURRICULAR_INNER("비교과_교내"); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 37974b69..9f997e23 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -21,4 +21,6 @@ public interface PostRepository extends MongoRepository { @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': ?0 }") List findAllAndNotDeletedAndActive(PostCategory postCategory); + + List findByPostCategoryStartingWith(String prefix); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 952b12b9..94f8412e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -12,6 +12,7 @@ import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO.UserInfo; import inu.codin.codin.domain.post.dto.response.PostListResponseDto; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; @@ -116,8 +117,13 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 + 조회수 반환 - public List getAllPosts(PostCategory postCategory) { - List posts = postRepository.findAllAndNotDeletedAndActive(postCategory); + public List getAllPostsByCategory(PostCategory postCategory) { + List posts; + if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)){ + posts = postRepository.findByPostCategoryStartingWith(postCategory.toString()); + } else { + posts = postRepository.findAllAndNotDeletedAndActive(postCategory); + } return getPostListResponseDtos(posts); } @@ -144,7 +150,8 @@ private List getPostListResponseDtos(List posts likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 scrapService.getScrapCount(post.get_id()), // 스크랩 수 redisService.getHitsCount(post.get_id()), - post.getCreatedAt() + post.getCreatedAt(), + getUserInfoAboutPost(post.get_id()) )) .toList(); } @@ -169,8 +176,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 scrapService.getScrapCount(post.get_id()), // 스크랩 수 redisService.getHitsCount(post.get_id()), - post.getCreatedAt() - ); + post.getCreatedAt(), + getUserInfoAboutPost(post.get_id())); } @@ -214,5 +221,13 @@ public void deletePostImage(String postId, String imageUrl) { } } + public UserInfo getUserInfoAboutPost(ObjectId postId){ + ObjectId userId = SecurityUtils.getCurrentUserId(); + return UserInfo.builder() + .isLike(redisService.isPostLiked(postId, userId)) + .isScrap(redisService.isPostScraped(postId, userId)) + .build(); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 3a469a5e..48c96358 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -1,7 +1,7 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -22,6 +22,11 @@ public class RedisService { */ private final RedisTemplate redisTemplate; + private static final String LIKE_KEY=":likes:"; + private static final String SCRAP_KEY = "post:scraps:"; + private static final String HITS_KEY = "post:hits:"; + + //post, comment, reply 구분 public Set getKeys(String pattern) { try { @@ -38,61 +43,74 @@ public Set getKeys(String pattern) { } } + //Like public void addLike(String entityType, ObjectId entityId, ObjectId userId) { - String redisKey = entityType + ":likes:" + entityId; + String redisKey = entityType + LIKE_KEY + entityId; redisTemplate.opsForSet().add(redisKey, String.valueOf(userId)); } public void removeLike(String entityType, ObjectId entityId, ObjectId userId) { - String redisKey = entityType + ":likes:" + entityId; + String redisKey = entityType + LIKE_KEY + entityId; redisTemplate.opsForSet().remove(redisKey, String.valueOf(userId)); } public int getLikeCount(String entityType, ObjectId entityId) { - String redisKey = entityType + ":likes:" + entityId; + String redisKey = entityType + LIKE_KEY + entityId; Long count = redisTemplate.opsForSet().size(redisKey); return count != null ? count.intValue() : 0; } public Set getLikedUsers(String entityType, String entityId) { - String redisKey = entityType + ":likes:" + entityId; + String redisKey = entityType + LIKE_KEY + entityId; return redisTemplate.opsForSet().members(redisKey); } + public boolean isPostLiked(ObjectId postId, ObjectId userId){ + String redisKey = LikeType.POST + LIKE_KEY + postId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + + //Scrap public void addScrap(ObjectId postId, ObjectId userId) { - String redisKey = "post:scraps:" + postId.toString(); + String redisKey = SCRAP_KEY + postId.toString(); redisTemplate.opsForSet().add(redisKey, userId.toString()); } public void removeScrap(ObjectId postId, ObjectId userId) { - String redisKey = "post:scraps:" + postId.toString(); + String redisKey = SCRAP_KEY + postId.toString(); redisTemplate.opsForSet().remove(redisKey, userId.toString()); } public int getScrapCount(ObjectId postId) { - String redisKey = "post:scraps:" + postId.toString(); + String redisKey = SCRAP_KEY + postId.toString(); Long scrapCount = redisTemplate.opsForSet().size(redisKey); return scrapCount != null ? scrapCount.intValue() : 0; } + public boolean isPostScraped(ObjectId postId, ObjectId userId){ + String redisKey = SCRAP_KEY + postId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + + //Hits public void addHits(ObjectId postId, ObjectId userId){ - String redisKey = "post:hits:" + postId.toString(); + String redisKey = HITS_KEY + postId.toString(); redisTemplate.opsForSet().add(redisKey, userId.toString()); } public boolean validateHits(ObjectId postId, ObjectId userId){ - String redisKey = "post:hits:" + postId.toString(); + String redisKey = HITS_KEY + postId.toString(); return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); //없어야 유효성 검증 통과 } public int getHitsCount(ObjectId postId){ - String redisKey = "post:hits:" + postId.toString(); + String redisKey = HITS_KEY + postId.toString(); Long hitsCount = redisTemplate.opsForSet().size(redisKey); return hitsCount != null ? hitsCount.intValue() : 0; } public Set getHitsUser(ObjectId postId) { - String redisKey = "post:hits:" + postId.toString(); + String redisKey = HITS_KEY + postId.toString(); return redisTemplate.opsForSet().members(redisKey); } } From 22f3678e79b9c351113cdf9b8ef69b99a1411749 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 13 Dec 2024 00:34:44 +0900 Subject: [PATCH 0226/1002] =?UTF-8?q?[SC-92]=20feat=20:=20User=20Validate?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/SecurityUtils.java | 2 +- .../comment/service/CommentService.java | 2 +- .../reply/service/ReplyCommentService.java | 1 + .../domain/post/service/PostService.java | 39 ++++++------------- 4 files changed, 14 insertions(+), 30 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index f7a21526..0f11558e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -39,7 +39,7 @@ public static UserRole getCurrentUserRole(){ return userDetails.getRole(); } - public void validateUser(ObjectId id){ + public static void validateUser(ObjectId id){ ObjectId userId = SecurityUtils.getCurrentUserId(); if (!id.equals(userId)) { throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "현재 유저에게 권한이 없습니다."); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 6e6c6bdc..2dc26611 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -57,6 +57,7 @@ public void softDeleteComment(String id) { ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + SecurityUtils.validateUser(comment.getUserId()); // 댓글의 대댓글 조회 List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); @@ -83,7 +84,6 @@ public void softDeleteComment(String id) { } - // 특정 게시물의 댓글 및 대댓글 조회 public List getCommentsByPostId(String id) { ObjectId postId = new ObjectId(id); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 01ffe474..92df82fc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -59,6 +59,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + SecurityUtils.validateUser(reply.getUserId()); // 대댓글 삭제 reply.delete(); replyCommentRepository.save(reply); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 94f8412e..63c86e73 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -74,17 +74,11 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); - - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } + validateUserAndPost(post); List imageUrls = handleImageUpload(postImages); - post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); } @@ -92,30 +86,29 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); - - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } + validateUserAndPost(post); post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); } - public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + validateUserAndPost(post); + post.updatePostStatus(requestDTO.getPostStatus()); + postRepository.save(post); + } + private void validateUserAndPost(PostEntity post) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } - - post.updatePostStatus(requestDTO.getPostStatus()); - postRepository.save(post); + SecurityUtils.validateUser(post.getUserId()); } + // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 + 조회수 반환 public List getAllPostsByCategory(PostCategory postCategory) { List posts; @@ -185,11 +178,7 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); - - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } + validateUserAndPost(post); post.delete(); postRepository.save(post); @@ -197,19 +186,13 @@ public void softDeletePost(String postId) { } public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")) - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - + validateUserAndPost(post); if (!post.getPostImageUrls().contains(imageUrl)) throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); - try { // S3에서 이미지 삭제 s3Service.deleteFile(imageUrl); From c318e92db3e3ee986eac9a3701526a307336601a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 13 Dec 2024 22:06:06 +0900 Subject: [PATCH 0227/1002] =?UTF-8?q?[SC-81]=20feat=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20JWT=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 3 +- .../codin/common/config/WebSocketConfig.java | 10 +++- .../domain/chat/chatroom/entity/ChatRoom.java | 4 +- .../chatroom/service/ChatRoomService.java | 2 +- .../domain/chat/chatting/ChatPreHandler.java | 56 +++++++++++++++++++ .../controller/ChattingController.java | 11 ++-- .../dto/request/ChattingRequestDto.java | 4 -- .../dto/response/ChattingResponseDto.java | 6 +- .../domain/chat/chatting/entity/Chatting.java | 13 +++-- .../ChattingRepositoryCustomImpl.java | 3 +- .../repository/CustomChattingRepository.java | 3 +- .../chatting/service/ChattingService.java | 13 ++++- 12 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index c52fb48d..f63e7dc9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -57,7 +57,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") - .requestMatchers("/ws-stomp/**").permitAll() //잠시 웹소켓 테스트를 위한 open .anyRequest().hasRole("USER") ) // Swagger 접근 시 httpBasic 인증 사용 @@ -103,6 +102,8 @@ public PasswordEncoder passwordEncoder() { "/email/auth/check", "/email/auth/send", "/v3/api/test1", + "/ws-stomp/**", + "/chat" }; // Swagger 접근 가능한 URL diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index a332fb30..b50a6c6c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -1,15 +1,20 @@ package inu.codin.codin.common.config; +import inu.codin.codin.domain.chat.chatting.ChatPreHandler; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration +@RequiredArgsConstructor @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + private final ChatPreHandler chatPreHandler; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint @@ -26,5 +31,8 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { //클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐 } - + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(chatPreHandler); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 7f694246..b07a55b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -2,12 +2,12 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -20,7 +20,7 @@ public class ChatRoom extends BaseTimeEntity { @Id @NotBlank - private String id; + private ObjectId _id; @NotBlank private String roomName; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 044b89ab..d8e4dd73 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -35,7 +35,7 @@ public List getAllChatRoomByUser(UserDetails userDetail List chatRooms = chatRoomRepository.findByParticipant(userId); return chatRooms.stream() .map(chatRoom -> { - Chatting chatting = chattingRepository.findRecentMessageByChatRoomId(chatRoom.getId()); + Chatting chatting = chattingRepository.findRecentMessageByChatRoomId(chatRoom.get_id()); return ChatRoomListResponseDto.of(chatRoom, chatting); }) .toList();} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java new file mode 100644 index 00000000..fffcb15f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java @@ -0,0 +1,56 @@ +package inu.codin.codin.domain.chat.chatting; + +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import inu.codin.codin.domain.user.security.CustomUserDetailsService; +import io.jsonwebtoken.MalformedJwtException; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ChatPreHandler implements ChannelInterceptor { + private final JwtTokenProvider jwtTokenProvider; + private final CustomUserDetailsService customUserDetailsService; + private static final String BEARER_PREFIX="Bearer "; + + @Override + public Message preSend(Message message, MessageChannel messageChannel){ + StompHeaderAccessor headerAccessor = StompHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + + if (headerAccessor.getCommand() == StompCommand.CONNECT) { + // 헤더 토큰 얻기 + String authorizationHeader = String.valueOf(headerAccessor.getNativeHeader("Authorization")); + if (authorizationHeader == null || authorizationHeader.equals("null")) { + throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); + } + String token = authorizationHeader.substring(BEARER_PREFIX.length()); + + // 토큰 인증 + try { + if (jwtTokenProvider.validateAccessToken(token)) { + String email = jwtTokenProvider.getUsername(token); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(email); + headerAccessor.setUser(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())); + } + } catch (MessageDeliveryException e) { + throw new MessageDeliveryException("[Chatting] 메세지 에러"); + } catch (MalformedJwtException e) { + throw new MalformedJwtException("[Chatting] JWT 오류"); + } catch (Exception e) { + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "[Chatting] JWT 오류"); + } + } + + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 4481c556..b4a2425b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -14,7 +14,7 @@ import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -23,7 +23,6 @@ @Controller @RequiredArgsConstructor -@Slf4j @Tag(name = "Chatting API", description = "채팅 보내기, 채팅 내역 반환") public class ChattingController { @@ -34,10 +33,10 @@ public class ChattingController { ) @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 @SendTo("/queue/{chatRoomId}") - public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto){ - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), chattingRequestDto.getSenderId(), id); - return chattingService.sendMessage(id, chattingRequestDto) - .thenReturn(ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", null))); + public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto, + Authentication authentication){ + return chattingService.sendMessage(id, chattingRequestDto, authentication) + .map( chattingResponseDto -> ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", chattingResponseDto))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java index 27594248..c526ba03 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java @@ -15,10 +15,6 @@ public class ChattingRequestDto { @Schema(description = "STOMP 프로토콜 type", example = "SEND") private MessageType type; - @NotBlank - @Schema(description = "수신자 Id", example = "111111") - private String senderId; - @NotBlank @Schema(description = "채팅 내용", example = "안녕하세요") private String content; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java index 27122851..0dc56655 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -40,11 +40,11 @@ public ChattingResponseDto(String id, String senderId, String content, LocalDate public static ChattingResponseDto of(Chatting chatting){ return ChattingResponseDto.builder() - .id(chatting.getId()) - .senderId(chatting.getSenderId()) + .id(chatting.get_id().toString()) + .senderId(chatting.getSenderId().toString()) .content(chatting.getContent()) .createdAt(chatting.getCreatedAt()) - .chatRoomId(chatting.getChatRoomId()) + .chatRoomId(chatting.getChatRoomId().toString()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java index bf1d1425..d34b83f6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.*; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -13,26 +14,26 @@ public class Chatting extends BaseTimeEntity { @Id @NotBlank - private String id; + private ObjectId _id; @NotBlank - private String senderId; + private ObjectId senderId; @NotBlank private String content; - private String chatRoomId; + private ObjectId chatRoomId; @Builder - public Chatting(String senderId, String content, String chatRoomId) { + public Chatting(ObjectId senderId, String content, ObjectId chatRoomId) { this.senderId = senderId; this.content = content; this.chatRoomId = chatRoomId; } - public static Chatting of(String chatRoomId, ChattingRequestDto chattingRequestDto) { + public static Chatting of(ObjectId chatRoomId, ChattingRequestDto chattingRequestDto, ObjectId senderId) { return Chatting.builder() - .senderId(chattingRequestDto.getSenderId()) + .senderId(senderId) .content(chattingRequestDto.getContent()) .chatRoomId(chatRoomId) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java index 84088f75..5ee3a933 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; @@ -13,7 +14,7 @@ public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ private final MongoTemplate mongoTemplate; @Override - public Chatting findRecentMessageByChatRoomId(String chatRoomId) { + public Chatting findRecentMessageByChatRoomId(ObjectId chatRoomId) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 1f4cfbea..42285559 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -1,9 +1,10 @@ package inu.codin.codin.domain.chat.chatting.repository; import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import org.bson.types.ObjectId; public interface CustomChattingRepository { - Chatting findRecentMessageByChatRoomId(String id); + Chatting findRecentMessageByChatRoomId(ObjectId id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 015915e2..2b325484 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -8,7 +8,11 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.exception.ChattingNotFoundException; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -16,6 +20,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class ChattingService { private final ChatRoomRepository chatRoomRepository; @@ -23,11 +28,13 @@ public class ChattingService { //todo 이미지 채팅에 따른 S3 처리 - public Mono sendMessage(String id, ChattingRequestDto chattingRequestDto) { + public Mono sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { ChatRoom chatRoom = chatRoomRepository.findById(id) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - Chatting chatting = Chatting.of(chatRoom.getId(), chattingRequestDto); - return chattingRepository.save(chatting); + String userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); + Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, new ObjectId(userId)); + log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); + return chattingRepository.save(chatting).map(ChattingResponseDto::of); } public Mono> getAllMessage(String id) { From 2040e7ee4ff677053f83a19618f6117200998abd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 13 Dec 2024 22:11:17 +0900 Subject: [PATCH 0228/1002] =?UTF-8?q?[SC-81]=20docs=20:=20https=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/WebSocketConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index b50a6c6c..2ce42c94 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -18,7 +18,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("*") + .setAllowedOriginPatterns("*") //이후 https 주소로 변경 .withSockJS(); } From 317d544d11879672560513b8b1cbec98b62218b7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 17 Dec 2024 21:46:07 +0900 Subject: [PATCH 0229/1002] =?UTF-8?q?[SC-93]=20=20Feat=20:=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/controller/CommentController.java | 10 ++++++++++ .../comment/dto/request/CommentUpdateRequestDTO.java | 12 ++++++++++++ .../post/domain/comment/entity/CommentEntity.java | 3 +++ .../post/domain/comment/service/CommentService.java | 11 +++++++++++ 4 files changed, 36 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentUpdateRequestDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 0d1d8a63..57135262 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; @@ -49,4 +50,13 @@ public ResponseEntity> softDeleteComment(@PathVariable String return ResponseEntity.ok() .body(new SingleResponse<>(200, "댓글이 삭제되었습니다.", null)); } + + @Operation(summary = "댓글 수정") + @PatchMapping("/{commentId}") + public ResponseEntity> updateComment(@PathVariable String commentId, @RequestBody @Valid CommentUpdateRequestDTO requestDTO){ + commentService.updateComment(commentId, requestDTO); + return ResponseEntity.status(HttpStatus.OK). + body(new SingleResponse<>(200, "댓글 수정되었습니다.", null)); + + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentUpdateRequestDTO.java new file mode 100644 index 00000000..273c505e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentUpdateRequestDTO.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.post.domain.comment.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class CommentUpdateRequestDTO { + @Schema(description = "댓글 내용", example = "content") + @NotBlank + private String content; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 0ce5b84e..88bd27ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -28,6 +28,9 @@ public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String cont this.content = content; } + public void updateComment(String content) { + this.content = content; + } //좋아요 수 업데이트 public void updateLikeCount(int likeCount) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 6e6c6bdc..339925f2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; @@ -104,4 +105,14 @@ public List getCommentsByPostId(String id) { }) .toList(); } + + public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { + + ObjectId commentId = new ObjectId(id); + CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + + comment.updateComment(requestDTO.getContent()); + commentRepository.save(comment); + } } \ No newline at end of file From c9dc8b84ca98920b60c7b13e5ac69787b4fe9618 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 17 Dec 2024 21:46:13 +0900 Subject: [PATCH 0230/1002] =?UTF-8?q?[SC-93]=20=20Feat=20:=20=EB=8C=80?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reply/controller/ReplyCommentController.java | 11 +++++++++++ .../reply/dto/request/ReplyUpdateRequestDTO.java | 14 ++++++++++++++ .../domain/reply/entity/ReplyCommentEntity.java | 3 +++ .../domain/reply/service/ReplyCommentService.java | 11 +++++++++++ 4 files changed, 39 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index 76515c43..ca684d57 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.post.domain.reply.controller; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; @@ -36,4 +38,13 @@ public ResponseEntity> softDeleteReply(@PathVariable String re return ResponseEntity.ok() .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); } + + @Operation(summary = "대댓글 수정") + @PatchMapping("/{replyId}") + public ResponseEntity> updateReply(@PathVariable String replyId, @RequestBody @Valid ReplyUpdateRequestDTO requestDTO){ + replyCommentService.updateReply(replyId, requestDTO); + return ResponseEntity.status(HttpStatus.OK). + body(new SingleResponse<>(200, "댓글 수정되었습니다.", null)); + + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java new file mode 100644 index 00000000..3d152789 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.post.domain.reply.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class ReplyUpdateRequestDTO { + + @Schema(description = "댓글 내용", example = "content") + @NotBlank + private String content; +} + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index cf30b459..07b8d8f8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -29,6 +29,9 @@ public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, Str this.likeCount = likeCount; } + public void updateReply(String content) { + this.content = content; + } //좋아요 수 업데이트 public void updateLikeCount(int likeCount) { this.likeCount=likeCount; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 01ffe474..fbe6baad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -8,10 +8,12 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -93,4 +95,13 @@ public List getRepliesByCommentId(ObjectId commentId) { } + public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { + + ObjectId replyId = new ObjectId(id); + ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + + reply.updateReply(requestDTO.getContent()); + replyCommentRepository.save(reply); + } } From 5f3ea7d03536d458de8690efccaee33cb163dd3a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 18 Dec 2024 04:32:05 +0900 Subject: [PATCH 0231/1002] =?UTF-8?q?[SC-94]=20=20Refactor=20:=20=EC=9D=B5?= =?UTF-8?q?=EB=AA=85=20->=20"=EC=9D=B5=EB=AA=85"=20,=20=EC=9D=B5=EB=AA=85?= =?UTF-8?q?=20x=20->=20=EC=8B=A4=EC=A0=9C=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostAnonymousUpdateRequestDTO.java | 2 +- .../dto/request/PostCreateRequestDTO.java | 2 +- .../dto/response/PostDetailResponseDTO.java | 10 +++-- .../dto/response/PostListResponseDto.java | 4 +- .../domain/post/service/PostService.java | 41 ++++++++++++------- .../domain/user/service/UserService.java | 9 ++++ 6 files changed, 47 insertions(+), 21 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java index 6ee02d21..0474ccc0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostAnonymousUpdateRequestDTO.java @@ -8,5 +8,5 @@ public class PostAnonymousUpdateRequestDTO { @Schema(description = "익명 여부", example = "true") @NotNull - private boolean isAnonymous; + private boolean anonymous; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index c9023ba8..8dc4606c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -21,7 +21,7 @@ public class PostCreateRequestDTO { @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "true") @NotNull - private boolean isAnonymous; + private boolean anonymous; @Schema(description = "게시물 종류", example = "REQUEST_STUDY") @NotNull diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 5c05c5eb..69409f43 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -32,10 +32,13 @@ public class PostDetailResponseDTO { @NotBlank private final String content; + @Schema(description = "유저 nickname 익명시 익명으로 표시됨") + private final String nickname; + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") private final List postImageUrl; - @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "0") + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") @NotNull private final boolean isAnonymous; @@ -49,11 +52,12 @@ public class PostDetailResponseDTO { @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") private final LocalDateTime createdAt; - public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { + public PostDetailResponseDTO(String userId, String _id, String title ,String content, String nickname, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { this.userId = userId; this._id = _id; - this.content = content; this.title = title; + this.content = content; + this.nickname = nickname; this.postCategory = postCategory; this.postImageUrl = postImageUrls; this.isAnonymous = isAnonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java index cf3f6219..f8631dab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java @@ -13,8 +13,8 @@ public class PostListResponseDto extends PostDetailResponseDTO{ @Schema(description = "댓글 및 대댓글 count", example = "0") private final int commentCount; - public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { - super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); + public PostListResponseDto(String userId, String postId, String content, String title, String nickname, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { + super(userId, postId, content, title,nickname, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); this.commentCount = commentCount; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 925ff14e..b209c1ca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -18,6 +18,8 @@ import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; @@ -36,6 +38,8 @@ public class PostService { private final S3Service s3Service; private final LikeService likeService; private final ScrapService scrapService; + private final UserRepository userRepository; + private final UserService userService; //이미지 업로드 메소드 private List handleImageUpload(List postImages) { @@ -128,21 +132,28 @@ public List getAllUserPosts() { } private List getPostListResponseDtos(List posts) { + return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) - .map(post -> new PostListResponseDto( - post.getUserId().toString(), - post.get_id().toString(), - post.getContent(), - post.getTitle(), - post.getPostCategory(), - post.getPostImageUrls(), - post.isAnonymous(), - post.getCommentCount(), - likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 - scrapService.getScrapCount(post.get_id()), // 스크랩 수 - post.getCreatedAt() - )) + .map(post -> + { + String nickname = userService.getNicknameByUserId(post.getUserId()); // 닉네임 조회 + return new PostListResponseDto( + post.getUserId().toString(), + post.get_id().toString(), + post.getTitle(), + post.getContent(), + post.isAnonymous() ? "익명" : nickname, + post.getPostCategory(), + post.getPostImageUrls(), + post.isAnonymous(), + post.getCommentCount(), + likeService.getLikeCount(LikeType.valueOf("POST"), post.get_id()), // 좋아요 수 + scrapService.getScrapCount(post.get_id()), // 스크랩 수 + post.getCreatedAt() + ); + + }) .toList(); } @@ -151,11 +162,13 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + String nickname = post.isAnonymous() ? "익명" : userService.getNicknameByUserId(post.getUserId()); return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), - post.getContent(), post.getTitle(), + post.getContent(), + nickname, post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 1a739d47..f3cafb1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -10,6 +10,7 @@ import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -57,4 +58,12 @@ private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) throw new UserCreateFailException("이미 존재하는 학번입니다."); } + + + //user id 기반 nickname 반환 + public String getNicknameByUserId(ObjectId userId) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + return user.getNickname(); + } } From 17ed33b73d8f414e60ef8b4a520ab4ba9ecc5bec Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 22 Dec 2024 22:12:05 +0900 Subject: [PATCH 0232/1002] =?UTF-8?q?[SC-93]=20refactor=20:=20Response=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/reply/controller/ReplyCommentController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index ca684d57..6b0f00c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -44,7 +44,7 @@ public ResponseEntity> softDeleteReply(@PathVariable String re public ResponseEntity> updateReply(@PathVariable String replyId, @RequestBody @Valid ReplyUpdateRequestDTO requestDTO){ replyCommentService.updateReply(replyId, requestDTO); return ResponseEntity.status(HttpStatus.OK). - body(new SingleResponse<>(200, "댓글 수정되었습니다.", null)); + body(new SingleResponse<>(200, "대댓글이 수정되었습니다.", null)); } } From 9c91c1abcd2a99b2f60e5f73c6bfab27b48ca9a6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 22 Dec 2024 22:23:19 +0900 Subject: [PATCH 0233/1002] =?UTF-8?q?[SC-93]=20refactor=20:=20https=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/WebSocketConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 2ce42c94..78d5211a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -18,7 +18,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("*") //이후 https 주소로 변경 + .setAllowedOriginPatterns("https://www.codin.co.kr/", "http://localhost:3000") .withSockJS(); } From a0eb512d35026c815fffaa0a9ef121f46507cc58 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 22 Dec 2024 23:58:42 +0900 Subject: [PATCH 0234/1002] =?UTF-8?q?[SC-81]=20fix=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20=EB=B0=98=ED=99=98=20=EC=8B=9C,=20?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=20=EC=B1=84=ED=8C=85=20=EB=82=B4=EC=97=AD=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EB=B0=8F=20ObjectId=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/dto/ChatRoomListResponseDto.java | 14 +++++++--- .../domain/chat/chatroom/entity/ChatRoom.java | 4 +-- .../chat/chatroom/entity/Participants.java | 5 ++-- .../repository/ChatRoomRepository.java | 5 ++-- .../chatroom/service/ChatRoomService.java | 26 ++++++++++--------- .../controller/ChattingController.java | 7 ++--- .../repository/ChattingRepository.java | 8 +++--- .../ChattingRepositoryCustomImpl.java | 7 +++-- .../repository/CustomChattingRepository.java | 3 ++- .../chatting/service/ChattingService.java | 13 +++++----- 10 files changed, 56 insertions(+), 36 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java index 8326ee0d..124002e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import io.swagger.v3.oas.annotations.media.Schema; @@ -14,6 +15,10 @@ @Setter public class ChatRoomListResponseDto { + @NotBlank + @Schema(description = "채팅방 _id", example = "1111111") + private final String chatRoomId; + @NotBlank @Schema(description = "채팅방 제목", example = "채팅해요") private final String roomName; @@ -22,13 +27,15 @@ public class ChatRoomListResponseDto { private final String message; @Schema(description = "가장 최근 채팅 내역 시간", example = "2024-11-29") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private final LocalDateTime currentMessageDate; @Schema(description = "채팅방 알림 설정", example = "true") private final boolean notificationEnabled; @Builder - public ChatRoomListResponseDto(String roomName, String message, LocalDateTime currentMessageDate, boolean notificationEnabled) { + public ChatRoomListResponseDto(String chatRoomId, String roomName, String message, LocalDateTime currentMessageDate, boolean notificationEnabled) { + this.chatRoomId = chatRoomId; this.roomName = roomName; this.message = message; this.currentMessageDate = currentMessageDate; @@ -37,9 +44,10 @@ public ChatRoomListResponseDto(String roomName, String message, LocalDateTime cu public static ChatRoomListResponseDto of(ChatRoom chatRoom, Chatting chatting) { return ChatRoomListResponseDto.builder() + .chatRoomId(chatRoom.get_id().toString()) .roomName(chatRoom.getRoomName()) - .message(chatting.getContent()) - .currentMessageDate(chatting.getCreatedAt()) + .message(chatting==null ? null : chatting.getContent()) + .currentMessageDate(chatting==null ? null : chatting.getCreatedAt()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index b07a55b5..e3a301a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -35,9 +35,9 @@ public ChatRoom(String roomName, List participants) { this.participants = participants; } - public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, String senderId){ + public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, ObjectId senderId){ ArrayList participants = new ArrayList<>(2); - participants.add(new Participants(chatRoomCreateRequestDto.getReceiverId(), true)); + participants.add(new Participants(new ObjectId(chatRoomCreateRequestDto.getReceiverId()), true)); participants.add(new Participants(senderId, true)); return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java index da7419d7..e9e510b9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java @@ -3,16 +3,17 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; @Getter @Setter public class Participants { - private final String userId; + private final ObjectId userId; private boolean notificationsEnabled; @Builder - public Participants(String userId, boolean notificationsEnabled) { + public Participants(ObjectId userId, boolean notificationsEnabled) { this.userId = userId; this.notificationsEnabled = notificationsEnabled; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index 0414883f..cb0f06ee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.repository; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -10,8 +11,8 @@ public interface ChatRoomRepository extends MongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") - Optional findById(String id); + Optional findById(ObjectId id); @Query("{ 'participants': { '$elemMatch': { 'userId': ?0 } } , 'deleteAt': null }") - List findByParticipant(String userId); + List findByParticipant(ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index d8e4dd73..7a410ce0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,18 +1,19 @@ package inu.codin.codin.domain.chat.chatroom.service; -import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; import java.util.List; @@ -25,23 +26,24 @@ public class ChatRoomService { private final ChattingRepository chattingRepository; public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { - String senderId = ((CustomUserDetails) userDetails).getId(); + ObjectId senderId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); } public List getAllChatRoomByUser(UserDetails userDetails) { - String userId = ((CustomUserDetails) userDetails).getId(); + ObjectId userId = ((CustomUserDetails) userDetails).getId(); List chatRooms = chatRoomRepository.findByParticipant(userId); - return chatRooms.stream() - .map(chatRoom -> { - Chatting chatting = chattingRepository.findRecentMessageByChatRoomId(chatRoom.get_id()); - return ChatRoomListResponseDto.of(chatRoom, chatting); - }) - .toList();} + return Flux.fromIterable(chatRooms) + .flatMap(chatRoom -> + chattingRepository.findRecentMessageByChatRoomId(chatRoom.get_id()) // Retrieve the most recent message + .map(chatting -> ChatRoomListResponseDto.of(chatRoom, chatting)) // Map to response DTO + .defaultIfEmpty(ChatRoomListResponseDto.of(chatRoom, null)) // Default to null if no message found + ).collectList().block(); + } public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { - String userId = ((CustomUserDetails) userDetails).getId(); + ObjectId userId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); boolean isRemoved = chatRoom.getParticipants() @@ -56,7 +58,7 @@ public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { } public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) { - String userId = ((CustomUserDetails) userDetails).getId(); + ObjectId userId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); chatRoom.getParticipants().stream() diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index b4a2425b..78084f4f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -9,7 +9,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; @@ -21,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestBody; import reactor.core.publisher.Mono; +import java.awt.print.Pageable; + @Controller @RequiredArgsConstructor @Tag(name = "Chatting API", description = "채팅 보내기, 채팅 내역 반환") @@ -44,8 +45,8 @@ public Mono>> sendMessage(@DestinationVariable( summary = "채팅 내용 리스트 가져오기" ) @GetMapping("/chats/list/{chatRoomId}") - public Mono>> getAllMessage(@PathVariable("chatRoomId") String id){ - return chattingService.getAllMessage(id) + public Mono>> getAllMessage(@PathVariable("chatRoomId") String id, Pageable pageable){ + return chattingService.getAllMessage(id, pageable) .map(chattingList -> ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingList))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 75cdcf04..250cbe6a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -1,17 +1,19 @@ package inu.codin.codin.domain.chat.chatting.repository; import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.awt.print.Pageable; + public interface ChattingRepository extends ReactiveMongoRepository, CustomChattingRepository { @Query("{ '_id': ?0, 'deletedAt': null }") - Mono findById(String id); + Mono findById(ObjectId id); - @Query("{ 'chatRoomId': ?0 }") - Flux findAllByChatRoomId(String id); + Flux findAllByChatRoomIdOrderByCreatedAt(ObjectId id, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java index 5ee3a933..eb72ee5c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java @@ -8,13 +8,14 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; +import reactor.core.publisher.Mono; @RequiredArgsConstructor public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ private final MongoTemplate mongoTemplate; @Override - public Chatting findRecentMessageByChatRoomId(ObjectId chatRoomId) { + public Mono findRecentMessageByChatRoomId(ObjectId chatRoomId) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), @@ -23,6 +24,8 @@ public Chatting findRecentMessageByChatRoomId(ObjectId chatRoomId) { AggregationResults result = mongoTemplate.aggregate(aggregation, "chatting", Chatting.class); - return result.getUniqueMappedResult(); + // Use Mono.justOrEmpty to ensure it returns Mono.empty() when there is no result + return Mono.justOrEmpty(result.getUniqueMappedResult()); } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 42285559..d2b1cbb8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -2,9 +2,10 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import org.bson.types.ObjectId; +import reactor.core.publisher.Mono; public interface CustomChattingRepository { - Chatting findRecentMessageByChatRoomId(ObjectId id); + Mono findRecentMessageByChatRoomId(ObjectId id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 2b325484..7f65e2ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -16,6 +16,7 @@ import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; +import java.awt.print.Pageable; import java.util.List; @Service @@ -29,18 +30,18 @@ public class ChattingService { //todo 이미지 채팅에 따른 S3 처리 public Mono sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { - ChatRoom chatRoom = chatRoomRepository.findById(id) + ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - String userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); - Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, new ObjectId(userId)); + ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); + Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId); log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); return chattingRepository.save(chatting).map(ChattingResponseDto::of); } - public Mono> getAllMessage(String id) { - chatRoomRepository.findById(id) + public Mono> getAllMessage(String id, Pageable pageable) { + chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - return chattingRepository.findAllByChatRoomId(id) + return chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) .map(ChattingResponseDto::of) .collectList(); From 24d4393f70932853adeaafe4e6d82f778ddea241 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 23 Dec 2024 18:21:46 +0900 Subject: [PATCH 0235/1002] =?UTF-8?q?[SC-81]=20feat=20:=20Chatting=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20Pageable=EC=9D=84=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/WebSocketConfig.java | 2 +- .../chatroom/service/ChatRoomService.java | 6 ++-- .../controller/ChattingController.java | 9 +++--- .../repository/ChattingRepository.java | 6 ++-- .../ChattingRepositoryCustomImpl.java | 31 ------------------- .../repository/CustomChattingRepository.java | 21 +++++++++++-- .../chatting/service/ChattingService.java | 7 +++-- 7 files changed, 34 insertions(+), 48 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 78d5211a..6a144ec5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -18,7 +18,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("https://www.codin.co.kr/", "http://localhost:3000") + .setAllowedOriginPatterns("https://www.codin.co.kr/", "http://localhost:3000", "http://localhost:8080") .withSockJS(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 7a410ce0..f3c753bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -6,7 +6,7 @@ import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,7 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; - private final ChattingRepository chattingRepository; + private final CustomChattingRepository customChattingRepository; public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { ObjectId senderId = ((CustomUserDetails) userDetails).getId(); @@ -36,7 +36,7 @@ public List getAllChatRoomByUser(UserDetails userDetail List chatRooms = chatRoomRepository.findByParticipant(userId); return Flux.fromIterable(chatRooms) .flatMap(chatRoom -> - chattingRepository.findRecentMessageByChatRoomId(chatRoom.get_id()) // Retrieve the most recent message + customChattingRepository.findMostRecentByChatRoomId(chatRoom.get_id()) // Retrieve the most recent message .map(chatting -> ChatRoomListResponseDto.of(chatRoom, chatting)) // Map to response DTO .defaultIfEmpty(ChatRoomListResponseDto.of(chatRoom, null)) // Default to null if no message found ).collectList().block(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 78084f4f..beae73e2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -20,8 +20,6 @@ import org.springframework.web.bind.annotation.RequestBody; import reactor.core.publisher.Mono; -import java.awt.print.Pageable; - @Controller @RequiredArgsConstructor @Tag(name = "Chatting API", description = "채팅 보내기, 채팅 내역 반환") @@ -42,11 +40,12 @@ public Mono>> sendMessage(@DestinationVariable( @Operation( - summary = "채팅 내용 리스트 가져오기" + summary = "채팅 내용 리스트 가져오기", + description = "Pageable에 해당하는 page, size, sort 내역에 맞게 반환" ) @GetMapping("/chats/list/{chatRoomId}") - public Mono>> getAllMessage(@PathVariable("chatRoomId") String id, Pageable pageable){ - return chattingService.getAllMessage(id, pageable) + public Mono>> getAllMessage(@PathVariable("chatRoomId") String id){ + return chattingService.getAllMessage(id) .map(chattingList -> ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingList))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 250cbe6a..44674a56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -2,18 +2,16 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import org.bson.types.ObjectId; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.awt.print.Pageable; - -public interface ChattingRepository extends ReactiveMongoRepository, CustomChattingRepository { +public interface ChattingRepository extends ReactiveMongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") Mono findById(ObjectId id); Flux findAllByChatRoomIdOrderByCreatedAt(ObjectId id, Pageable pageable); - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java deleted file mode 100644 index eb72ee5c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepositoryCustomImpl.java +++ /dev/null @@ -1,31 +0,0 @@ -package inu.codin.codin.domain.chat.chatting.repository; - -import inu.codin.codin.domain.chat.chatting.entity.Chatting; -import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.aggregation.Aggregation; -import org.springframework.data.mongodb.core.aggregation.AggregationResults; -import org.springframework.data.mongodb.core.query.Criteria; -import reactor.core.publisher.Mono; - -@RequiredArgsConstructor -public class ChattingRepositoryCustomImpl implements CustomChattingRepository{ - - private final MongoTemplate mongoTemplate; - @Override - public Mono findRecentMessageByChatRoomId(ObjectId chatRoomId) { - Aggregation aggregation = Aggregation.newAggregation( - Aggregation.match(Criteria.where("chatRoomId").is(chatRoomId)), - Aggregation.sort(Sort.by(Sort.Order.desc("createdAt"))), - Aggregation.limit(1) - ); - - AggregationResults result = mongoTemplate.aggregate(aggregation, "chatting", Chatting.class); - - // Use Mono.justOrEmpty to ensure it returns Mono.empty() when there is no result - return Mono.justOrEmpty(result.getUniqueMappedResult()); - } - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index d2b1cbb8..137ffe86 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -2,10 +2,27 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import org.bson.types.ObjectId; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Repository; import reactor.core.publisher.Mono; -public interface CustomChattingRepository { +@Repository +public class CustomChattingRepository { - Mono findRecentMessageByChatRoomId(ObjectId id); + private final ReactiveMongoTemplate mongoTemplate; + + public CustomChattingRepository(ReactiveMongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } + + public Mono findMostRecentByChatRoomId(ObjectId chatRoomId) { + Query query = new Query(Criteria.where("chatRoomId").is(chatRoomId)) + .with(Sort.by(Sort.Direction.DESC, "createdAt")) + .limit(1); + return mongoTemplate.findOne(query, Chatting.class); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 7f65e2ec..e13002b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -12,11 +12,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import java.awt.print.Pageable; import java.util.List; @Service @@ -38,7 +40,8 @@ public Mono sendMessage(String id, ChattingRequestDto chatt return chattingRepository.save(chatting).map(ChattingResponseDto::of); } - public Mono> getAllMessage(String id, Pageable pageable) { + public Mono> getAllMessage(String id) { + Pageable pageable = PageRequest.of(0, 20, Sort.by("createdAt").descending()); chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); return chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) From 1a9eb2c666cadb5f0c70361a7db31045d915f06a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 23 Dec 2024 18:31:08 +0900 Subject: [PATCH 0236/1002] =?UTF-8?q?[SC-81]=20refactor=20:=20pageNumber?= =?UTF-8?q?=EB=A7=8C=20RequestParam=EC=9C=BC=EB=A1=9C=20=EB=B0=9B=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/controller/ChattingController.java | 6 ++++-- .../codin/domain/chat/chatting/service/ChattingService.java | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index beae73e2..6082a229 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import reactor.core.publisher.Mono; @Controller @@ -44,8 +45,9 @@ public Mono>> sendMessage(@DestinationVariable( description = "Pageable에 해당하는 page, size, sort 내역에 맞게 반환" ) @GetMapping("/chats/list/{chatRoomId}") - public Mono>> getAllMessage(@PathVariable("chatRoomId") String id){ - return chattingService.getAllMessage(id) + public Mono>> getAllMessage(@PathVariable("chatRoomId") String id, + @RequestParam("page") int page){ + return chattingService.getAllMessage(id, page) .map(chattingList -> ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingList))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index e13002b8..920f44c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -40,8 +40,8 @@ public Mono sendMessage(String id, ChattingRequestDto chatt return chattingRepository.save(chatting).map(ChattingResponseDto::of); } - public Mono> getAllMessage(String id) { - Pageable pageable = PageRequest.of(0, 20, Sort.by("createdAt").descending()); + public Mono> getAllMessage(String id, int page) { + Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); return chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) From 5e28e29e23aed76f9e3a91522b475938be89e11a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 17:50:25 +0900 Subject: [PATCH 0237/1002] =?UTF-8?q?[SC-81]=20feat=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20Image=20=EB=B3=B4=EB=82=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 4 +-- .../codin/common/config/WebSocketConfig.java | 6 +++++ .../controller/ChattingController.java | 25 +++++++++++++++---- .../domain/chat/chatting/dto/ContentType.java | 8 ++++++ .../dto/request/ChattingRequestDto.java | 5 ++++ .../dto/response/ChattingResponseDto.java | 9 ++++++- .../domain/chat/chatting/entity/Chatting.java | 17 +++++++++---- .../chatting/service/ChattingService.java | 9 ++++++- .../domain/post/service/PostService.java | 13 ++-------- .../inu/codin/codin/infra/s3/S3Service.java | 10 ++++++-- 10 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/ContentType.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index f63e7dc9..9a3c22d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -4,7 +4,6 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.common.security.service.JwtService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -103,7 +102,8 @@ public PasswordEncoder passwordEncoder() { "/email/auth/send", "/v3/api/test1", "/ws-stomp/**", - "/chat" + "/chat", + "/chat/image", }; // Swagger 접근 가능한 URL diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 6a144ec5..ed6f2fc5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -8,6 +8,7 @@ import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; @Configuration @RequiredArgsConstructor @@ -31,6 +32,11 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { //클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐 } + @Override + public void configureWebSocketTransport(WebSocketTransportRegistration registration) { + registration.setMessageSizeLimit(50 * 1024 * 1024); // 메세지 크기 제한 오류 방지(이 코드가 없으면 byte code를 보낼때 소켓 연결이 끊길 수 있음) + } + @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(chatPreHandler); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 6082a229..70ae6a66 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -9,18 +9,20 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import reactor.core.publisher.Mono; +import java.util.List; + @Controller @RequiredArgsConstructor @Tag(name = "Chatting API", description = "채팅 보내기, 채팅 내역 반환") @@ -34,11 +36,19 @@ public class ChattingController { @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 @SendTo("/queue/{chatRoomId}") public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto, - Authentication authentication){ + @AuthenticationPrincipal Authentication authentication){ return chattingService.sendMessage(id, chattingRequestDto, authentication) .map( chattingResponseDto -> ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", chattingResponseDto))); } + @Operation( + summary = "채팅으로 사진 보내기" + ) + @PostMapping(value = "/chats/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> sendImageMessage(List chatImages){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "채팅 사진 업로드 완료", chattingService.sendImageMessage(chatImages))); + } @Operation( summary = "채팅 내용 리스트 가져오기", @@ -57,4 +67,9 @@ public String chatHtml(){ return "chat"; } + @GetMapping("/chat/image") + public String chatImageHtml(){ + return "chatImage"; + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/ContentType.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/ContentType.java new file mode 100644 index 00000000..836f69ce --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/ContentType.java @@ -0,0 +1,8 @@ +package inu.codin.codin.domain.chat.chatting.dto; + +import lombok.Getter; + +@Getter +public enum ContentType { + TEXT, IMAGE +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java index c526ba03..390b434e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/request/ChattingRequestDto.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.chat.chatting.dto.request; +import inu.codin.codin.domain.chat.chatting.dto.ContentType; import inu.codin.codin.domain.chat.chatting.entity.MessageType; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -18,4 +19,8 @@ public class ChattingRequestDto { @NotBlank @Schema(description = "채팅 내용", example = "안녕하세요") private String content; + + @NotNull + @Schema(description = "채팅 타입", example = "TEXT") + private ContentType contentType; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java index 0dc56655..792ac128 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -1,8 +1,10 @@ package inu.codin.codin.domain.chat.chatting.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.domain.chat.chatting.dto.ContentType; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -22,6 +24,9 @@ public class ChattingResponseDto { @NotBlank private final String content; + @NotEmpty + private final ContentType contentType; + @NotBlank @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private final LocalDateTime createdAt; @@ -30,10 +35,11 @@ public class ChattingResponseDto { private final String chatRoomId; @Builder - public ChattingResponseDto(String id, String senderId, String content, LocalDateTime createdAt, String chatRoomId) { + public ChattingResponseDto(String id, String senderId, String content, ContentType contentType, LocalDateTime createdAt, String chatRoomId) { this.id = id; this.senderId = senderId; this.content = content; + this.contentType = contentType; this.createdAt = createdAt; this.chatRoomId = chatRoomId; } @@ -44,6 +50,7 @@ public static ChattingResponseDto of(Chatting chatting){ .senderId(chatting.getSenderId().toString()) .content(chatting.getContent()) .createdAt(chatting.getCreatedAt()) + .contentType(chatting.getType()) .chatRoomId(chatting.getChatRoomId().toString()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java index d34b83f6..3bc83d03 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -1,9 +1,12 @@ package inu.codin.codin.domain.chat.chatting.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.chat.chatting.dto.ContentType; import jakarta.validation.constraints.NotBlank; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -24,18 +27,22 @@ public class Chatting extends BaseTimeEntity { private ObjectId chatRoomId; + private ContentType type; + @Builder - public Chatting(ObjectId senderId, String content, ObjectId chatRoomId) { + public Chatting(ObjectId senderId, String content, ObjectId chatRoomId, ContentType type) { this.senderId = senderId; this.content = content; this.chatRoomId = chatRoomId; + this.type = type; } - public static Chatting of(ObjectId chatRoomId, ChattingRequestDto chattingRequestDto, ObjectId senderId) { + public static Chatting of(ObjectId chatRoomId, String content, ObjectId senderId, ContentType type) { return Chatting.builder() .senderId(senderId) - .content(chattingRequestDto.getContent()) + .content(content) .chatRoomId(chatRoomId) + .type(type) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 920f44c3..81cd048b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.chat.chatting.exception.ChattingNotFoundException; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; +import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -17,6 +18,7 @@ import org.springframework.data.domain.Sort; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import reactor.core.publisher.Mono; import java.util.List; @@ -28,6 +30,7 @@ public class ChattingService { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; + private final S3Service s3Service; //todo 이미지 채팅에 따른 S3 처리 @@ -35,7 +38,7 @@ public Mono sendMessage(String id, ChattingRequestDto chatt ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); - Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId); + Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto.getContent(), userId, chattingRequestDto.getContentType()); log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); return chattingRepository.save(chatting).map(ChattingResponseDto::of); } @@ -49,4 +52,8 @@ public Mono> getAllMessage(String id, int page) { .map(ChattingResponseDto::of) .collectList(); } + + public List sendImageMessage(List chatImages) { + return s3Service.handleImageUpload(chatImages); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 925ff14e..3957c8d3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -37,17 +37,8 @@ public class PostService { private final LikeService likeService; private final ScrapService scrapService; - //이미지 업로드 메소드 - private List handleImageUpload(List postImages) { - if (postImages != null && !postImages.isEmpty()) { - return s3Service.uploadFiles(postImages); // 실제 업로드 처리 - } - return List.of(); // 이미지가 없을 경우 빈 리스트 반환 - } - - public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - List imageUrls = handleImageUpload(postImages); + List imageUrls = s3Service.handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -80,7 +71,7 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } - List imageUrls = handleImageUpload(postImages); + List imageUrls = s3Service.handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java index de45bfd4..6f278e1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java @@ -6,7 +6,6 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.awt.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -26,9 +25,16 @@ public S3Service(AmazonS3Client amazonS3Client) { private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB private static final int MAX_FILE_COUNT = 10; // 최대 파일 개수 + //이미지 업로드 메소드 + public List handleImageUpload(List postImages) { + if (postImages != null && !postImages.isEmpty()) { + return uploadFiles(postImages); // 실제 업로드 처리 + } + return List.of(); // 이미지가 없을 경우 빈 리스트 반환 + } //모든 이미지 업로드 - public List uploadFiles(List multipartFiles) { + private List uploadFiles(List multipartFiles) { validateFileCount(multipartFiles); List uploadUrls = new ArrayList<>(); for (MultipartFile multipartFile : multipartFiles) { From 363d8315449248100aaf796ba4600fe9d64e909c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 21:39:11 +0900 Subject: [PATCH 0238/1002] =?UTF-8?q?[SC-95]=20feat=20:=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20Pageable=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 16 +++++----- .../post/dto/response/PostPageResponse.java | 32 +++++++++++++++++++ .../post/repository/PostRepository.java | 11 ++++--- .../domain/post/service/PostService.java | 19 +++++++---- 4 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 0339b649..799db5b4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,13 +1,12 @@ package inu.codin.codin.domain.post.controller; -import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostListResponseDto; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; @@ -91,20 +90,21 @@ public ResponseEntity> updatePostAnonymous( summary = "카테고리별 삭제 되지 않은 모든 게시물 조회" ) @GetMapping("/category") - public ResponseEntity> getAllPosts(@RequestParam PostCategory postCategory) { - List posts = postService.getAllPosts(postCategory); + public ResponseEntity> getAllPosts(@RequestParam PostCategory postCategory, + @RequestParam("page") int pageNumber) { + PostPageResponse postpages= postService.getAllPosts(postCategory, pageNumber); return ResponseEntity.ok() - .body(new ListResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", posts)); + .body(new SingleResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); } @Operation( summary = "해당 사용자 게시물 전체 조회" ) @GetMapping("/user") - public ResponseEntity> getAllUserPosts() { - List posts = postService.getAllUserPosts(); + public ResponseEntity> getAllUserPosts(@RequestParam("page") int pageNumber) { + PostPageResponse posts = postService.getAllUserPosts(pageNumber); return ResponseEntity.ok() - .body(new ListResponse<>(200, "사용자 게시물 조회 성공", posts)); + .body(new SingleResponse<>(200, "사용자 게시물 조회 성공", posts)); } @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java new file mode 100644 index 00000000..26b5b6c8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.post.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PostPageResponse { + + private List contents = new ArrayList<>(); + private long lastPage; + private long nextPage; + + private PostPageResponse(List contents, long lastPage, long nextPage) { + this.contents = contents; + this.lastPage = lastPage; + this.nextPage = nextPage; + } + + public static PostPageResponse of(List postPaging, long totalElements, long nextCursor) { + return PostPageResponse.newPagingHasNext(postPaging, totalElements, nextCursor); + } + + private static PostPageResponse newPagingHasNext(List posts, long totalElements, long nextCursor) { + return new PostPageResponse(posts, totalElements, nextCursor); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 37974b69..bbee5326 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -3,6 +3,8 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -15,10 +17,9 @@ public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(ObjectId Id); - - @Query("{'userId': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") - List findByUserIdAndNotDeleted(ObjectId userId); - @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': ?0 }") - List findAllAndNotDeletedAndActive(PostCategory postCategory); + Page findAllByCategoryOrderByCreatedAt(PostCategory postCategory, PageRequest pageRequest); + + @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") + Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 925ff14e..bf6740fc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -13,6 +13,7 @@ import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostListResponseDto; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; @@ -22,6 +23,9 @@ import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -114,17 +118,20 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public List getAllPosts(PostCategory postCategory) { - List posts = postRepository.findAllAndNotDeletedAndActive(postCategory); - return getPostListResponseDtos(posts); + public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 10, Sort.by("createdAt").descending()); + Page page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + } //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public List getAllUserPosts() { + public PostPageResponse getAllUserPosts(int pageNumber) { ObjectId userId = SecurityUtils.getCurrentUserId(); - List posts = postRepository.findByUserIdAndNotDeleted(userId); - return getPostListResponseDtos(posts); + PageRequest pageRequest = PageRequest.of(pageNumber, 10, Sort.by("createdAt").descending()); + Page page = postRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); } private List getPostListResponseDtos(List posts) { From b94d0811e39c27be93ca2f1ce200df489dd9c9a2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 21:41:49 +0900 Subject: [PATCH 0239/1002] =?UTF-8?q?[SC-95]=20refactor=20:=20PageSize=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/service/PostService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index bf6740fc..e8aa1437 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -119,7 +119,7 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { - PageRequest pageRequest = PageRequest.of(pageNumber, 10, Sort.by("createdAt").descending()); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); @@ -129,7 +129,7 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllUserPosts(int pageNumber) { ObjectId userId = SecurityUtils.getCurrentUserId(); - PageRequest pageRequest = PageRequest.of(pageNumber, 10, Sort.by("createdAt").descending()); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); } From 5e8e98e5a09f2d4e9d3ba92c335e3b0b2ae4b98f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 21:51:14 +0900 Subject: [PATCH 0240/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index cd0cb34d..8eeba2c5 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit cd0cb34de8c22acd1129348b34e46251e242e825 +Subproject commit 8eeba2c59cbb68342d128e60d44b561ecd6145df From 8607426120b216709d158103be7ae97eb04686a4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 21:53:34 +0900 Subject: [PATCH 0241/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index d2989edc..8eeba2c5 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit d2989edc5128951e9bd7460f2c148c3c7b07b07e +Subproject commit 8eeba2c59cbb68342d128e60d44b561ecd6145df From 05563272bc18bb78228a9a0fbfb902077a5699db Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 22:22:46 +0900 Subject: [PATCH 0242/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8eeba2c5..462c1068 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8eeba2c59cbb68342d128e60d44b561ecd6145df +Subproject commit 462c106811cf75b26a368128160ac52e7b233697 From 835a3ce5f9961b0cfaf140c1e2abdee3d3d43dbe Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 22:42:36 +0900 Subject: [PATCH 0243/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 462c1068..a56f17ab 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 462c106811cf75b26a368128160ac52e7b233697 +Subproject commit a56f17ab0c2920f7f538e76ebff9364cd7e44d07 From c2f1c6651470d261e4e2ad5d55861251da02e996 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 24 Dec 2024 23:04:06 +0900 Subject: [PATCH 0244/1002] =?UTF-8?q?Update=20resources=20:=20dev=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a56f17ab..288d97c9 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a56f17ab0c2920f7f538e76ebff9364cd7e44d07 +Subproject commit 288d97c9ecbb27962296e4cdf6a0860b77abbd1d From 294740245e709fdb493e5cdbf22c47a5d269353e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 25 Dec 2024 00:31:42 +0900 Subject: [PATCH 0245/1002] =?UTF-8?q?feat=20:=20WebSocket=20Config=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/WebSocketConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index ed6f2fc5..85164d5c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -19,8 +19,8 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("https://www.codin.co.kr/", "http://localhost:3000", "http://localhost:8080") - .withSockJS(); + .setAllowedOriginPatterns("https://www.codin.co.kr/", "http://localhost:3000", "http://localhost:8080"); +// .withSockJS(); } @Override From a8af2193f8abfd41c42598ee1ff8840d7870b98c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 25 Dec 2024 00:40:15 +0900 Subject: [PATCH 0246/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 288d97c9..9173b6dd 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 288d97c9ecbb27962296e4cdf6a0860b77abbd1d +Subproject commit 9173b6dd7bdeb5f66a915275296961d207696330 From 12a18808c9452a627667198637b070865bd0f689 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 00:05:19 +0900 Subject: [PATCH 0247/1002] =?UTF-8?q?[SC-96]=20feat=20:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=9D=98=20=EC=A2=8B=EC=95=84=EC=9A=94,=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=9E=A9=ED=95=9C=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/repository/LikeRepository.java | 5 +++ .../scrap/repository/ScrapRepository.java | 6 +++ .../domain/post/service/PostService.java | 2 +- .../user/controller/UserController.java | 28 ++++++++++-- .../domain/user/service/UserService.java | 43 +++++++++++++++++++ 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 80e9c8b1..00e373bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -3,6 +3,9 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -21,4 +24,6 @@ public interface LikeRepository extends MongoRepository { void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + + Page findAllByUserIdAndLikeTypeOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 1e7de7df..4af571f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -1,7 +1,11 @@ package inu.codin.codin.domain.post.domain.scrap.repository; +import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -17,4 +21,6 @@ public interface ScrapRepository extends MongoRepository void deleteByPostIdAndUserId(ObjectId postId, ObjectId userId); boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); + + Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 36f9c291..4ee6433d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -125,7 +125,7 @@ public PostPageResponse getAllUserPosts(int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); } - private List getPostListResponseDtos(List posts) { + public List getPostListResponseDtos(List posts) { return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) .map(post -> new PostListResponseDto( diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index b6094101..fbb73535 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -1,17 +1,17 @@ package inu.codin.codin.domain.user.controller; +import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.Getter; 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; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/users") @@ -29,4 +29,24 @@ public ResponseEntity> signUpUser( return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원가입 성공", null)); } + + @Operation( + summary = "유저가 좋아요 누른 게시글 반환" + ) + @GetMapping("/like") + public ResponseEntity> getUserLike(@RequestParam("page") int pageNumber){ + PostPageResponse posts = userService.getPostUserLike(pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "사용자가 좋아요 누른 게시물 조회 성공", posts)); + } + + @Operation( + summary = "유저가 스크랩한 게시글 반환" + ) + @GetMapping("/scrap") + public ResponseEntity> getUserScrap(@RequestParam("page") int pageNumber){ + PostPageResponse posts = userService.getPostUserScrap(pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "사용자가 좋아요 누른 게시물 조회 성공", posts)); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 1a739d47..55c65ae7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -1,7 +1,18 @@ package inu.codin.codin.domain.user.service; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; +import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; @@ -10,9 +21,15 @@ import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -20,7 +37,11 @@ public class UserService { private final EmailAuthRepository emailAuthRepository; private final UserRepository userRepository; + private final LikeRepository likeRepository; + private final PostRepository postRepository; + private final ScrapRepository scrapRepository; private final PasswordEncoder passwordEncoder; + private final PostService postService; public void createUser(UserCreateRequestDto userCreateRequestDto) { @@ -57,4 +78,26 @@ private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) throw new UserCreateFailException("이미 존재하는 학번입니다."); } + + public PostPageResponse getPostUserLike(int pageNumber) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = likeRepository.findAllByUserIdAndLikeTypeOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); + List postUserLike = page.getContent().stream() + .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) + .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) + .toList(); + return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + } + + public PostPageResponse getPostUserScrap(int pageNumber) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = scrapRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + List postUserScrap = page.getContent().stream() + .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) + .toList(); + return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + } } From ae90cbcde4df8acfd6269c8cdec88ed576a96c01 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 00:11:51 +0900 Subject: [PATCH 0248/1002] =?UTF-8?q?[SC-96]=20style=20:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B0=98=ED=99=98,?= =?UTF-8?q?=20Post=20->=20User=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/controller/PostController.java | 10 ---------- .../codin/codin/domain/post/service/PostService.java | 9 --------- .../codin/domain/user/controller/UserController.java | 12 ++++++++++-- .../codin/codin/domain/user/service/UserService.java | 10 ++++++++++ 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 799db5b4..ee4f11be 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -97,16 +97,6 @@ public ResponseEntity> getAllPosts(@RequestPara .body(new SingleResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); } - @Operation( - summary = "해당 사용자 게시물 전체 조회" - ) - @GetMapping("/user") - public ResponseEntity> getAllUserPosts(@RequestParam("page") int pageNumber) { - PostPageResponse posts = postService.getAllUserPosts(pageNumber); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "사용자 게시물 조회 성공", posts)); - } - @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") @GetMapping("/{postId}") public ResponseEntity> getPostWithDetail(@PathVariable String postId) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 4ee6433d..cc57fba1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -116,15 +116,6 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { } - - //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public PostPageResponse getAllUserPosts(int pageNumber) { - ObjectId userId = SecurityUtils.getCurrentUserId(); - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); - } - public List getPostListResponseDtos(List posts) { return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index fbb73535..0f732bac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.user.controller; -import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; @@ -8,7 +7,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -30,6 +28,16 @@ public ResponseEntity> signUpUser( .body(new SingleResponse<>(200, "회원가입 성공", null)); } + @Operation( + summary = "해당 사용자 게시물 전체 조회" + ) + @GetMapping("/post") + public ResponseEntity> getAllUserPosts(@RequestParam("page") int pageNumber) { + PostPageResponse posts = userService.getAllUserPosts(pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "사용자 게시물 조회 성공", posts)); + } + @Operation( summary = "유저가 좋아요 누른 게시글 반환" ) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 55c65ae7..c8d06daf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -79,6 +79,14 @@ private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto throw new UserCreateFailException("이미 존재하는 학번입니다."); } + //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + public PostPageResponse getAllUserPosts(int pageNumber) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = postRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + return PostPageResponse.of(postService.getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + } + public PostPageResponse getPostUserLike(int pageNumber) { ObjectId userId = SecurityUtils.getCurrentUserId(); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); @@ -100,4 +108,6 @@ public PostPageResponse getPostUserScrap(int pageNumber) { .toList(); return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); } + + } From e4efa856ca565ad30f856a7569cfddb77aaa5866 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 00:31:38 +0900 Subject: [PATCH 0249/1002] =?UTF-8?q?[SC-96]=20feat=20:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EA=B0=80=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=EC=9D=98=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/repository/CommentRepository.java | 4 ++ .../user/controller/UserController.java | 14 ++++- .../domain/user/service/UserService.java | 52 +++++++++++++------ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index 67ae2635..80bbfe83 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -2,6 +2,8 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -16,4 +18,6 @@ public interface CommentRepository extends MongoRepository findByPostId(ObjectId postId); + + Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 0f732bac..a310734b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -43,7 +43,7 @@ public ResponseEntity> getAllUserPosts(@Request ) @GetMapping("/like") public ResponseEntity> getUserLike(@RequestParam("page") int pageNumber){ - PostPageResponse posts = userService.getPostUserLike(pageNumber); + PostPageResponse posts = userService.getPostUserInteraction(pageNumber, UserService.InteractionType.LIKE); return ResponseEntity.ok() .body(new SingleResponse<>(200, "사용자가 좋아요 누른 게시물 조회 성공", posts)); } @@ -53,8 +53,18 @@ public ResponseEntity> getUserLike(@RequestPara ) @GetMapping("/scrap") public ResponseEntity> getUserScrap(@RequestParam("page") int pageNumber){ - PostPageResponse posts = userService.getPostUserScrap(pageNumber); + PostPageResponse posts = userService.getPostUserInteraction(pageNumber, UserService.InteractionType.SCRAP); return ResponseEntity.ok() .body(new SingleResponse<>(200, "사용자가 좋아요 누른 게시물 조회 성공", posts)); } + + @Operation( + summary = "유저가 작성한 댓글의 게시글 반환" + ) + @GetMapping("/comment") + public ResponseEntity> getUserComment(@RequestParam("page") int pageNumber){ + PostPageResponse posts = userService.getPostUserInteraction(pageNumber, UserService.InteractionType.COMMENT); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "사용자가 작성한 댓글의 게시물 조회 성공", posts)); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index c8d06daf..42edb56e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -4,6 +4,8 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; @@ -28,6 +30,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import javax.xml.stream.events.Comment; import java.util.List; @Slf4j @@ -40,6 +43,7 @@ public class UserService { private final LikeRepository likeRepository; private final PostRepository postRepository; private final ScrapRepository scrapRepository; + private final CommentRepository commentRepository; private final PasswordEncoder passwordEncoder; private final PostService postService; @@ -87,27 +91,43 @@ public PostPageResponse getAllUserPosts(int pageNumber) { return PostPageResponse.of(postService.getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); } - public PostPageResponse getPostUserLike(int pageNumber) { + public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType interactionType) { ObjectId userId = SecurityUtils.getCurrentUserId(); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = likeRepository.findAllByUserIdAndLikeTypeOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); - List postUserLike = page.getContent().stream() - .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) - .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) - .toList(); - return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + switch (interactionType) { + case LIKE: + Page likePage = likeRepository.findAllByUserIdAndLikeTypeOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); + List postUserLike = likePage.getContent().stream() + .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) + .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) + .toList(); + return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages()-1, likePage.hasNext()? likePage.getPageable().getPageNumber() + 1 : -1); + + case SCRAP: + Page scrapPage = scrapRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + List postUserScrap = scrapPage.getContent().stream() + .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) + .toList(); + return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages()-1, scrapPage.hasNext()? scrapPage.getPageable().getPageNumber() + 1 : -1); + + case COMMENT: + Page commentPage = commentRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + List postUserComment = commentPage.getContent().stream() + .map(commentEntity -> postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("유저가 작성한 댓글의 게시글을 찾을 수 없습니다."))) + .toList(); + return PostPageResponse.of(postService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages()-1, commentPage.hasNext()? commentPage.getPageable().getPageNumber() + 1 : -1); + + default: + throw new IllegalArgumentException("지원하지 않는 타입입니다."); + } } - public PostPageResponse getPostUserScrap(int pageNumber) { - ObjectId userId = SecurityUtils.getCurrentUserId(); - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = scrapRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); - List postUserScrap = page.getContent().stream() - .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) - .toList(); - return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + public enum InteractionType { + LIKE, SCRAP, COMMENT } + } From 3084982b889f5c71fc8719d41200c1f901775746 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 00:37:17 +0900 Subject: [PATCH 0250/1002] =?UTF-8?q?[SC-96]=20docs=20:=20response=20messa?= =?UTF-8?q?ge=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/user/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index a310734b..d4dbf264 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -55,7 +55,7 @@ public ResponseEntity> getUserLike(@RequestPara public ResponseEntity> getUserScrap(@RequestParam("page") int pageNumber){ PostPageResponse posts = userService.getPostUserInteraction(pageNumber, UserService.InteractionType.SCRAP); return ResponseEntity.ok() - .body(new SingleResponse<>(200, "사용자가 좋아요 누른 게시물 조회 성공", posts)); + .body(new SingleResponse<>(200, "사용자가 스크랩한 게시물 조회 성공", posts)); } @Operation( From a13bbe561b83e20a78c94f89889af7cbf2b42ad2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 00:46:23 +0900 Subject: [PATCH 0251/1002] =?UTF-8?q?refactor=20:=20comment,reply=20respon?= =?UTF-8?q?se=EC=97=90=20createdAt=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dto/response/CommentResponseDTO.java | 9 ++++++++- .../post/domain/comment/service/CommentService.java | 3 ++- .../post/domain/reply/service/ReplyCommentService.java | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 8118d7e9..2f561ae8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -1,9 +1,11 @@ package inu.codin.codin.domain.post.domain.comment.dto.response; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import java.time.LocalDateTime; import java.util.List; @Getter @@ -29,12 +31,17 @@ public class CommentResponseDTO { @Schema(description = "삭제 여부", example = "false") private final boolean isDeleted; - public CommentResponseDTO(String _id, String userId, String content, List replies, int likeCount, boolean isDeleted) { + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @Schema(description = "댓글 작성 시간", example = "2024-12-05 02:22:48") + private final LocalDateTime createdAt; + + public CommentResponseDTO(String _id, String userId, String content, List replies, int likeCount, boolean isDeleted, LocalDateTime createdAt) { this._id = _id; this.userId = userId; this.content = content; this.replies = replies; this.likeCount = likeCount; this.isDeleted = isDeleted; + this.createdAt = createdAt; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 339925f2..b56586f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -101,7 +101,8 @@ public List getCommentsByPostId(String id) { comment.getContent(), replyCommentService.getRepliesByCommentId(comment.get_id()), // 대댓글 조회 likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), // 댓글 좋아요 수 - isDeleted); + isDeleted, + comment.getCreatedAt()); }) .toList(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index fbe6baad..48d7878a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -90,7 +90,8 @@ public List getRepliesByCommentId(ObjectId commentId) { reply.getContent(), List.of(), //대댓글은 대댓글이 없음 likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.getCommentId()), // 대댓글 좋아요 수 - isDeleted); + isDeleted, + reply.getCreatedAt()); }).toList(); } From 1c7fc59995b7c7ee972cb134e36de2f2391e13da Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 01:53:26 +0900 Subject: [PATCH 0252/1002] =?UTF-8?q?feat=20:=20Password=20=EC=9E=AC?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20=EC=9E=AC=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?email=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 3 ++ .../common/security/util/SecurityUtils.java | 2 +- .../email/controller/EmailController.java | 24 ++++++++++- .../email/service/EmailAuthService.java | 42 +++++++++++++++++++ .../email/service/EmailSendService.java | 23 ++++++++++ .../user/controller/UserController.java | 25 +++++++++++ .../domain/user/dto/UserDeleteRequestDto.java | 12 ++++++ .../user/dto/UserPasswordRequestDto.java | 19 +++++++++ .../codin/domain/user/entity/UserEntity.java | 10 +++++ .../UserPasswordChangeFailException.java | 7 ++++ .../user/repository/UserRepository.java | 4 ++ .../domain/user/service/UserService.java | 30 ++++++++++++- 12 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 9a3c22d7..0aea6aa2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -100,6 +100,9 @@ public PasswordEncoder passwordEncoder() { "/users/sign-up", "/email/auth/check", "/email/auth/send", + "/email/auth/password", + "/email/auth/password/check", + "/users/password", "/v3/api/test1", "/ws-stomp/**", "/chat", diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index f7a21526..0f11558e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -39,7 +39,7 @@ public static UserRole getCurrentUserRole(){ return userDetails.getRole(); } - public void validateUser(ObjectId id){ + public static void validateUser(ObjectId id){ ObjectId userId = SecurityUtils.getCurrentUserId(); if (!id.equals(userId)) { throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "현재 유저에게 권한이 없습니다."); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 556f2efd..5c90cc80 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -39,6 +39,28 @@ public ResponseEntity> checkAuthNum( ) { emailAuthService.checkAuthNum(joinEmailCheckRequestDto); return ResponseEntity.ok() - .body(new SingleResponse<>(200, "이메일 인증 성공 - 회원가입 가능", null)); + .body(new SingleResponse<>(200, "이메일 인증 성공", null)); + } + + @Operation( + summary = "비밀번호 찾기를 위한 이메일 인증" + ) + @PostMapping("/auth/password") + public ResponseEntity> sendPasswordEmail( + @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto + ) { + emailAuthService.sendPasswordEmail(emailAuthRequestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "이메일 인증 코드 전송 성공", null)); + } + + @Operation(summary = "비밀번호 재설정 인증 코드 확인 - 학교인증 X") + @PostMapping("/auth/password/check") + public ResponseEntity> checkPasswordAuthNum( + @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto + ) { + emailAuthService.checkPasswordAuthNum(joinEmailCheckRequestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "이메일 인증 성공", null)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 72c75a1c..7dfc8e5e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,10 +1,13 @@ package inu.codin.codin.domain.email.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -19,6 +22,7 @@ public class EmailAuthService { private final EmailAuthRepository emailAuthRepository; private final EmailSendService emailSendService; + private final UserRepository userRepository; public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { @@ -59,6 +63,44 @@ private String generateAuthNum() { public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { + checkEmailAndAuthNum(joinEmailCheckRequestDto); + } + + public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { + + String email = joinEmailSendRequestDto.getEmail(); + log.info("[sendAuthEmail] email : {}", email); + + Optional emailAuth = emailAuthRepository.findByEmail(email); + EmailAuthEntity emailAuthEntity; + + // 재인증 로직 + if (emailAuth.isPresent()) { + emailAuthEntity = emailAuth.get(); + emailAuthEntity.changeAuthNum(generateAuthNum()); + } + else { + // 인증 생성 로직 + emailAuthEntity = EmailAuthEntity.builder() + .email(email) + .authNum(generateAuthNum()) + .build(); + } + emailAuthRepository.save(emailAuthEntity); + + // 비동기 이메일 전송 로직 + emailSendService.sendPasswordEmail(email, emailAuthEntity.getAuthNum()); + } + + public void checkPasswordAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { + checkEmailAndAuthNum(joinEmailCheckRequestDto); + UserEntity user = userRepository.findByEmail(joinEmailCheckRequestDto.getEmail()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + user.changePassword(); + userRepository.save(user); + } + + private void checkEmailAndAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { String email = joinEmailCheckRequestDto.getEmail(); String authNum = joinEmailCheckRequestDto.getAuthNum(); log.info("[checkAuthNum] email : {}, authNum : {}", email, authNum); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java index 3c24b4da..faeb4673 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java @@ -46,4 +46,27 @@ public void sendAuthEmail(String email, String authNum) { throw new RuntimeException(e); } } + + public void sendPasswordEmail(String email, String authNum) { + MimeMessage message = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); + + Context context = new Context(); + context.setVariable("authNum", authNum); + + // 템플릿 엔진을 사용하여 HTML 내용을 생성 + String htmlContent = templateEngine.process("password-email", context); + + helper.setTo(email); + helper.setSubject("[CODIN] 비밀번호 찾기 인증번호입니다."); + helper.setText(htmlContent, true); + + javaMailSender.send(message); + log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); + } catch (MessagingException e) { + log.error("[sendAuthEmail] 인증 이메일 전송 실패, email : {}", email); + throw new RuntimeException(e); + } + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index d4dbf264..ab7846b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -3,12 +3,17 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; +import inu.codin.codin.domain.user.dto.UserDeleteRequestDto; +import inu.codin.codin.domain.user.dto.UserPasswordRequestDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController @@ -67,4 +72,24 @@ public ResponseEntity> getUserComment(@RequestP return ResponseEntity.ok() .body(new SingleResponse<>(200, "사용자가 작성한 댓글의 게시물 조회 성공", posts)); } + + @Operation( + summary = "비밀번호 재설정" + ) + @PutMapping("/password") + public ResponseEntity> setUserPassword(@RequestBody @Valid UserPasswordRequestDto userPasswordRequestDto){ + userService.setUserPassword(userPasswordRequestDto); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "비밀번호 재설정 완료", null)); + } + + @Operation( + summary = "회원 탈퇴" + ) + @DeleteMapping + public ResponseEntity> deleteUser(@RequestBody @Valid UserDeleteRequestDto userDeleteRequestDto){ + userService.deleteUser(userDeleteRequestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "회원 탈퇴 완료", null)); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java new file mode 100644 index 00000000..17390152 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.user.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class UserDeleteRequestDto { + @Schema(description = "이메일 주소", example = "codin@inu.ac.kr") + @NotBlank + private String email; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java new file mode 100644 index 00000000..0af2c9ba --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.user.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class UserPasswordRequestDto { + + @Schema(description = "이메일 주소", example = "example@inu.ac.kr") + @Email + @NotBlank + private String email; + + @Schema(description = "변경된 비밀번호", example = "password1234") + private String password; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index a03a2850..a4081702 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -34,6 +34,8 @@ public class UserEntity extends BaseTimeEntity { private UserStatus status; + private boolean changePassword = false; + @Builder public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status) { this.email = email; @@ -46,4 +48,12 @@ public UserEntity(String email, String password, String studentId, String name, this.role = role; this.status = status; } + + public void updatePassword(String password){ + this.password = password; + } + + public void changePassword(){ + this.changePassword = !this.changePassword; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java new file mode 100644 index 00000000..1b6473f1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.user.exception; + +public class UserPasswordChangeFailException extends RuntimeException{ + public UserPasswordChangeFailException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 4fe21623..8cea3e39 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -10,7 +11,10 @@ @Repository public interface UserRepository extends MongoRepository { + @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByEmail(String email); + + @Query("{'studentId': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByStudentId(String studentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 42edb56e..571bea8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -16,21 +16,28 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.user.dto.UserCreateRequestDto; +import inu.codin.codin.domain.user.dto.UserDeleteRequestDto; +import inu.codin.codin.domain.user.dto.UserPasswordRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserDisabledException; +import inu.codin.codin.domain.user.exception.UserPasswordChangeFailException; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.security.CustomUserDetails; +import io.jsonwebtoken.Jwt; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import javax.xml.stream.events.Comment; import java.util.List; @Slf4j @@ -124,6 +131,27 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } } + public void setUserPassword(@Valid UserPasswordRequestDto userPasswordRequestDto) { + UserEntity user = userRepository.findByEmail(userPasswordRequestDto.getEmail()) + .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); + if (user.isChangePassword()){ + String encodedPassword = passwordEncoder.encode(userPasswordRequestDto.getPassword()); + user.updatePassword(encodedPassword); + user.changePassword(); + userRepository.save(user); + } else { + throw new UserPasswordChangeFailException("유저의 비밀번호를 변경할 수 없습니다. 이메일 인증을 먼저 진행해주세요."); + } + } + + public void deleteUser(UserDeleteRequestDto userDeleteRequestDto) { + UserEntity user = userRepository.findByEmail(userDeleteRequestDto.getEmail()) + .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); + SecurityUtils.validateUser(user.get_id()); + user.delete(); + userRepository.save(user); + } + public enum InteractionType { LIKE, SCRAP, COMMENT } From 9384f9c1bee0fff6d77d4a877e9ddb594de17146 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 02:05:15 +0900 Subject: [PATCH 0253/1002] =?UTF-8?q?feat=20:=20password-email.html=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8eeba2c5..56ed44cb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8eeba2c59cbb68342d128e60d44b561ecd6145df +Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a From b3130ea219d7a9a99271a8be59b0d38aa2ec0066 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 02:07:22 +0900 Subject: [PATCH 0254/1002] =?UTF-8?q?feat=20:=20password-email.html=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 9173b6dd..56ed44cb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 9173b6dd7bdeb5f66a915275296961d207696330 +Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a From d26082d52be93366d1e3f4f5f98a246519a768b8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 02:21:44 +0900 Subject: [PATCH 0255/1002] =?UTF-8?q?feat=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 18 ++++++++--- .../{ => request}/UserCreateRequestDto.java | 2 +- .../{ => request}/UserDeleteRequestDto.java | 2 +- .../{ => request}/UserPasswordRequestDto.java | 2 +- .../dto/response/UserInfoResponseDto.java | 32 +++++++++++++++++++ .../domain/user/service/UserService.java | 19 +++++++---- 6 files changed, 60 insertions(+), 15 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/user/dto/{ => request}/UserCreateRequestDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/user/dto/{ => request}/UserDeleteRequestDto.java (85%) rename codin-core/src/main/java/inu/codin/codin/domain/user/dto/{ => request}/UserPasswordRequestDto.java (90%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index ab7846b7..07036707 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -2,9 +2,10 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.user.dto.UserCreateRequestDto; -import inu.codin.codin.domain.user.dto.UserDeleteRequestDto; -import inu.codin.codin.domain.user.dto.UserPasswordRequestDto; +import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; +import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; +import inu.codin.codin.domain.user.dto.request.UserPasswordRequestDto; +import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -12,8 +13,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController @@ -92,4 +91,13 @@ public ResponseEntity> deleteUser(@RequestBody @Valid UserDele return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 탈퇴 완료", null)); } + + @Operation( + summary = "유저 정보 반환" + ) + @GetMapping + public ResponseEntity> getUserInfo(){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "유저 정보 반환 완료", userService.getUserInfo())); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java index 3c0e3523..604c57b0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.dto; +package inu.codin.codin.domain.user.dto.request; import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java index 17390152..56d10322 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserDeleteRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.dto; +package inu.codin.codin.domain.user.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java index 0af2c9ba..d89cf97a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/UserPasswordRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.dto; +package inu.codin.codin.domain.user.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java new file mode 100644 index 00000000..910bb07b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.user.dto.response; + +import inu.codin.codin.domain.user.entity.UserEntity; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserInfoResponseDto { + private String _id; + private String email; + private String name; + private String profileImageUrl; + + @Builder + public UserInfoResponseDto(String _id, String email, String name, String profileImageUrl) { + this._id = _id; + this.email = email; + this.name = name; + this.profileImageUrl = profileImageUrl; + } + + public static UserInfoResponseDto of(UserEntity user) { + return UserInfoResponseDto.builder() + ._id(user.get_id().toString()) + .email(user.getEmail()) + .name(user.getName()) + .profileImageUrl(user.getProfileImageUrl()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 571bea8f..b1e56f7b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -15,26 +15,24 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; -import inu.codin.codin.domain.user.dto.UserCreateRequestDto; -import inu.codin.codin.domain.user.dto.UserDeleteRequestDto; -import inu.codin.codin.domain.user.dto.UserPasswordRequestDto; +import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; +import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; +import inu.codin.codin.domain.user.dto.request.UserPasswordRequestDto; +import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserDisabledException; import inu.codin.codin.domain.user.exception.UserPasswordChangeFailException; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.security.CustomUserDetails; -import io.jsonwebtoken.Jwt; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.User; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -152,6 +150,13 @@ public void deleteUser(UserDeleteRequestDto userDeleteRequestDto) { userRepository.save(user); } + public UserInfoResponseDto getUserInfo() { + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + return UserInfoResponseDto.of(user); + } + public enum InteractionType { LIKE, SCRAP, COMMENT } From 13af8296cf2453e6c8c24c723e338b23448ce61a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 11:05:30 +0900 Subject: [PATCH 0256/1002] =?UTF-8?q?refactor=20:=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=EB=90=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EB=B3=B4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/email/service/EmailAuthService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 7dfc8e5e..c48cec16 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -36,10 +36,10 @@ public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { if (emailAuth.isPresent()) { emailAuthEntity = emailAuth.get(); - // 이미 인증된 이메일 체크 - if (emailAuthEntity.isVerified()) { - throw new EmailAuthFailException("이미 인증된 이메일입니다.", email); - } +// // 이미 인증된 이메일 체크 +// if (emailAuthEntity.isVerified()) { +// throw new EmailAuthFailException("이미 인증된 이메일입니다.", email); +// } emailAuthEntity.changeAuthNum(generateAuthNum()); } From 34431203bd397052ef7fb41385a694f86b7379ca Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 11:13:09 +0900 Subject: [PATCH 0257/1002] =?UTF-8?q?refactor=20:=20DetailResponse?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PostDetailResponseDTO.java | 6 +++++- .../dto/response/PostListResponseDto.java | 20 ------------------- .../post/dto/response/PostPageResponse.java | 8 ++++---- .../domain/post/service/PostService.java | 16 +++++++-------- 4 files changed, 16 insertions(+), 34 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 5c05c5eb..449e4713 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -49,7 +49,10 @@ public class PostDetailResponseDTO { @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") private final LocalDateTime createdAt; - public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt) { + @Schema(description = "댓글 및 대댓글 count", example = "0") + private final int commentCount; + + public PostDetailResponseDTO(String userId, String _id, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int likeCount, int scrapCount, LocalDateTime createdAt, int commentCount) { this.userId = userId; this._id = _id; this.content = content; @@ -60,5 +63,6 @@ public PostDetailResponseDTO(String userId, String _id, String content, String t this.likeCount = likeCount; this.scrapCount = scrapCount; this.createdAt = createdAt; + this.commentCount = commentCount; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java deleted file mode 100644 index cf3f6219..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.post.entity.PostCategory; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; - -import java.time.LocalDateTime; -import java.util.List; - -@Getter -public class PostListResponseDto extends PostDetailResponseDTO{ - - @Schema(description = "댓글 및 대댓글 count", example = "0") - private final int commentCount; - - public PostListResponseDto(String userId, String postId, String content, String title, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { - super(userId, postId, content, title, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); - this.commentCount = commentCount; - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java index 26b5b6c8..e033c02c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java @@ -11,21 +11,21 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostPageResponse { - private List contents = new ArrayList<>(); + private List contents = new ArrayList<>(); private long lastPage; private long nextPage; - private PostPageResponse(List contents, long lastPage, long nextPage) { + private PostPageResponse(List contents, long lastPage, long nextPage) { this.contents = contents; this.lastPage = lastPage; this.nextPage = nextPage; } - public static PostPageResponse of(List postPaging, long totalElements, long nextCursor) { + public static PostPageResponse of(List postPaging, long totalElements, long nextCursor) { return PostPageResponse.newPagingHasNext(postPaging, totalElements, nextCursor); } - private static PostPageResponse newPagingHasNext(List posts, long totalElements, long nextCursor) { + private static PostPageResponse newPagingHasNext(List posts, long totalElements, long nextCursor) { return new PostPageResponse(posts, totalElements, nextCursor); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index cc57fba1..6a62852a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -12,7 +12,6 @@ import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostListResponseDto; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; @@ -116,10 +115,10 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { } - public List getPostListResponseDtos(List posts) { + public List getPostListResponseDtos(List posts) { return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) - .map(post -> new PostListResponseDto( + .map(post -> new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), post.getContent(), @@ -127,12 +126,11 @@ public List getPostListResponseDtos(List posts) post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), - post.getCommentCount(), likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 scrapService.getScrapCount(post.get_id()), // 스크랩 수 - post.getCreatedAt() - )) - .toList(); + post.getCreatedAt(), + post.getCommentCount() + )).toList(); } //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 @@ -150,8 +148,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { post.isAnonymous(), likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 scrapService.getScrapCount(post.get_id()), // 스크랩 수 - post.getCreatedAt() - ); + post.getCreatedAt(), + post.getCommentCount()); } From 886a973eab940651e16ef96b41f085b6f140698b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 11:40:24 +0900 Subject: [PATCH 0258/1002] =?UTF-8?q?refactor=20:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EB=B3=84=20=EC=A0=84=EC=B2=B4=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/repository/PostRepository.java | 4 +--- .../codin/domain/post/service/PostService.java | 17 ++++------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index dfd8e221..9d3e1cf3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -23,7 +23,5 @@ public interface PostRepository extends MongoRepository { @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); - List findAllAndNotDeletedAndActive(PostCategory postCategory); - - List findByPostCategoryStartingWith(String prefix); + Page findByPostCategoryStartingWithOrderByCreatedAt(String prefix, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 51176c25..e9169216 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -103,22 +103,13 @@ private void validateUserAndPost(PostEntity post) { SecurityUtils.validateUser(post.getUserId()); } - - - // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 + 조회수 반환 - public List getAllPostsByCategory(PostCategory postCategory) { - List posts; - if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)){ - posts = postRepository.findByPostCategoryStartingWith(postCategory.toString()); - } else { - posts = postRepository.findAllAndNotDeletedAndActive(postCategory); - } - return getPostListResponseDtos(posts); - } // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); + Page page; + if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) + page = postRepository.findByPostCategoryStartingWithOrderByCreatedAt(postCategory.toString(), pageRequest); + else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } From 11bb5a1d83f93f82ee884b4a09742111b4f66604 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 12:55:33 +0900 Subject: [PATCH 0259/1002] =?UTF-8?q?docs=20:=20api=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 73f21f87..0fe4a5cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -96,7 +96,7 @@ public PasswordEncoder passwordEncoder() { "/auth/login", "/auth/reissue", "/auth/logout", - "/users/sign-up", + "/users/signup", "/email/auth/check", "/email/auth/send", "/email/auth/password", From 1544ca42fc4a501331a16af1b48593f2bbd21b28 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 27 Dec 2024 17:54:52 +0900 Subject: [PATCH 0260/1002] =?UTF-8?q?[SC-94]=20=20Refactor=20::=20?= =?UTF-8?q?=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=8A=A4=ED=8A=B8=EB=A6=BC=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=A7=A4=ED=95=91(UserId,=20Nickname)=20?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=95=9C=EB=B2=88=EC=97=90=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PostListResponseDto.java | 20 ----------- .../domain/post/service/PostService.java | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 30 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java deleted file mode 100644 index f8631dab..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostListResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.post.entity.PostCategory; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; - -import java.time.LocalDateTime; -import java.util.List; - -@Getter -public class PostListResponseDto extends PostDetailResponseDTO{ - - @Schema(description = "댓글 및 대댓글 count", example = "0") - private final int commentCount; - - public PostListResponseDto(String userId, String postId, String content, String title, String nickname, PostCategory postCategory, List postImageUrls , boolean isAnonymous, int commentCount, int likeCount, int scrapCount, LocalDateTime createdAt) { - super(userId, postId, content, title,nickname, postCategory, postImageUrls, isAnonymous, likeCount, scrapCount, createdAt); - this.commentCount = commentCount; - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 3009f8b9..07ddd900 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -18,6 +18,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.service.UserService; @@ -34,6 +35,8 @@ import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -44,7 +47,7 @@ public class PostService { private final LikeService likeService; private final ScrapService scrapService; private final RedisService redisService; - private final UserService userService; + private final UserRepository userRepository; public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { List imageUrls = s3Service.handleImageUpload(postImages); @@ -117,27 +120,33 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { } public List getPostListResponseDtos(List posts) { + // 1. 사용자 ID와 닉네임을 한 번에 조회하여 Map으로 변환 + // 닉네임 조회를 스트림 내부에서 진행하지 않고 매핑을 통해 한번에 처리 (중복 호출 최소화) + Map userNicknameMap = userRepository.findAllById( + posts.stream().map(PostEntity::getUserId).distinct().toList() + ).stream().collect(Collectors.toMap(UserEntity::get_id, UserEntity::getNickname)); + + // 2. 게시글 처리 return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) - .map(post -> - { - String nickname = userService.getNicknameByUserId(post.getUserId()); // 닉네임 조회 + .map(post -> { + String nickname = post.isAnonymous() ? "익명" : userNicknameMap.get(post.getUserId()); return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), post.getTitle(), post.getContent(), - post.isAnonymous() ? "익명" : nickname, + nickname, post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), - likeService.getLikeCount(LikeType.valueOf("POST"), post.get_id()), // 좋아요 수 - scrapService.getScrapCount(post.get_id()), // 스크랩 수 + likeService.getLikeCount(LikeType.valueOf("POST"), post.get_id()), // 좋아요 수 + scrapService.getScrapCount(post.get_id()), // 스크랩 수 redisService.getHitsCount(post.get_id()), post.getCreatedAt(), post.getCommentCount(), - getUserInfoAboutPost(post.get_id()) - ); + getUserInfoAboutPost(post.get_id()) + ); }) .toList(); } @@ -151,7 +160,7 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { if (redisService.validateHits(post.get_id(), userId)) redisService.addHits(post.get_id(), userId); - String nickname = post.isAnonymous() ? "익명" : userService.getNicknameByUserId(post.getUserId()); + String nickname = post.isAnonymous() ? "익명" : getNicknameByUserId(post.getUserId()); return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), @@ -207,5 +216,12 @@ public UserInfo getUserInfoAboutPost(ObjectId postId){ .build(); } + //user id 기반 nickname 반환 + public String getNicknameByUserId(ObjectId userId) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + return user.getNickname(); + } + } From 4ab05ecc952945d9c341a6d1cad50d0506e1aed5 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 27 Dec 2024 18:46:56 +0900 Subject: [PATCH 0261/1002] =?UTF-8?q?[SC-94]=20=20feat=20::=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80,=20=EB=8C=80=EB=8C=93=EA=B8=80=20=EC=9D=B5=EB=AA=85?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=ED=99=95=EC=9E=A5=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentAnonnymousUpdateRequestDTO.java | 12 +++++++++++ .../dto/request/CommentCreateRequestDTO.java | 5 +++++ .../dto/response/CommentResponseDTO.java | 12 ++++++++++- .../domain/comment/entity/CommentEntity.java | 6 +++++- .../comment/service/CommentService.java | 20 +++++++++++++++++++ .../ReplyAnonnymousUpdateRequestDTO.java | 12 +++++++++++ .../dto/request/ReplyCreateRequestDTO.java | 5 +++++ .../reply/entity/ReplyCommentEntity.java | 7 ++++++- .../reply/service/ReplyCommentService.java | 16 ++++++++++++++- .../dto/request/PostCreateRequestDTO.java | 2 +- 10 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentAnonnymousUpdateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentAnonnymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentAnonnymousUpdateRequestDTO.java new file mode 100644 index 00000000..a5c40722 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentAnonnymousUpdateRequestDTO.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.post.domain.comment.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class CommentAnonnymousUpdateRequestDTO { + @Schema(description = "익명 여부", example = "true") + @NotNull + private boolean anonymous; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java index d1695c27..d279d6fa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/request/CommentCreateRequestDTO.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @@ -10,4 +11,8 @@ public class CommentCreateRequestDTO { @Schema(description = "댓글 내용", example = "content") @NotBlank private String content; + + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") + @NotNull + private boolean anonymous; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 2f561ae8..5e5bcaba 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import java.time.LocalDateTime; @@ -22,6 +23,13 @@ public class CommentResponseDTO { @NotBlank private final String content; + @Schema(description = "유저 nickname 익명시 익명으로 표시됨") + private final String nickname; + + @Schema(description = "익명 여부", example = "true") + @NotNull + private final boolean anonymous; + @Schema(description = "대댓글 리스트", example = "[...]") private final List replies; @@ -35,10 +43,12 @@ public class CommentResponseDTO { @Schema(description = "댓글 작성 시간", example = "2024-12-05 02:22:48") private final LocalDateTime createdAt; - public CommentResponseDTO(String _id, String userId, String content, List replies, int likeCount, boolean isDeleted, LocalDateTime createdAt) { + public CommentResponseDTO(String _id, String userId, String content, String nickname, Boolean anonymous ,List replies, int likeCount, boolean isDeleted, LocalDateTime createdAt) { this._id = _id; this.userId = userId; this.content = content; + this.nickname = nickname; + this.anonymous = anonymous; this.replies = replies; this.likeCount = likeCount; this.isDeleted = isDeleted; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 88bd27ea..3260b1e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -18,14 +19,17 @@ public class CommentEntity extends BaseTimeEntity { private ObjectId userId; private String content; + private boolean anonymous; + private int likeCount = 0; // 좋아요 수 (Redis에서 관리) @Builder - public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String content) { + public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String content, Boolean anonymous) { this._id = _id; this.postId = postId; this.userId = userId; this.content = content; + this.anonymous = anonymous; } public void updateComment(String content) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 8552c494..0944bb5f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -14,12 +14,16 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -30,6 +34,7 @@ public class CommentService { private final CommentRepository commentRepository; private final ReplyCommentRepository replyCommentRepository; + private final UserRepository userRepository; private final LikeService likeService; private final ReplyCommentService replyCommentService; @@ -43,6 +48,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { CommentEntity comment = CommentEntity.builder() .postId(postId) .userId(userId) + .anonymous(requestDTO.isAnonymous()) .content(requestDTO.getContent()) .build(); commentRepository.save(comment); @@ -92,13 +98,20 @@ public List getCommentsByPostId(String id) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); List comments = commentRepository.findByPostId(postId); + Map userNicknameMap = userRepository.findAllById( + comments.stream().map(CommentEntity::getUserId).distinct().toList() + ).stream().collect(Collectors.toMap(UserEntity::get_id, UserEntity::getNickname)); + return comments.stream() .map(comment -> { + String nickname = comment.isAnonymous() ? "익명" : userNicknameMap.get(comment.getUserId()); boolean isDeleted = comment.getDeletedAt() != null; return new CommentResponseDTO( comment.get_id().toString(), comment.getUserId().toString(), comment.getContent(), + nickname, + comment.isAnonymous(), replyCommentService.getRepliesByCommentId(comment.get_id()), // 대댓글 조회 likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), // 댓글 좋아요 수 isDeleted, @@ -116,4 +129,11 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); } + + //user id 기반 nickname 반환 + public String getNicknameByUserId(ObjectId userId) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + return user.getNickname(); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java new file mode 100644 index 00000000..0e4774f7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.post.domain.reply.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class ReplyAnonnymousUpdateRequestDTO { + @Schema(description = "익명 여부", example = "true") + @NotNull + private boolean anonymous; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java index bda6d873..8170e365 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @@ -10,4 +11,8 @@ public class ReplyCreateRequestDTO { @Schema(description = "댓글 내용", example = "content") @NotBlank private String content; + + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") + @NotNull + private boolean anonymous; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 07b8d8f8..558dad54 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.domain.reply.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -18,20 +19,24 @@ public class ReplyCommentEntity extends BaseTimeEntity { private ObjectId userId; // 작성자 ID private String content; + private boolean anonymous; + private int likeCount = 0; // 좋아요 카운트 @Builder - public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, String content, int likeCount) { + public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, boolean anonymous, String content, int likeCount) { this._id = _id; this.commentId = commentId; this.userId = userId; this.content = content; + this.anonymous = anonymous; this.likeCount = likeCount; } public void updateReply(String content) { this.content = content; } + //좋아요 수 업데이트 public void updateLikeCount(int likeCount) { this.likeCount=likeCount; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index cd1bd252..b677b671 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -13,6 +13,8 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,8 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -29,7 +33,7 @@ public class ReplyCommentService { private final CommentRepository commentRepository; private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; - + private final UserRepository userRepository; private final LikeService likeService; // 대댓글 추가 @@ -46,6 +50,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { .commentId(commentId) .userId(userId) .content(requestDTO.getContent()) + .anonymous(requestDTO.isAnonymous()) .build(); replyCommentRepository.save(reply); @@ -82,13 +87,22 @@ public void softDeleteReply(String replyId) { public List getRepliesByCommentId(ObjectId commentId) { List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); + Map userNicknameMap = userRepository.findAllById( + replies.stream().map(ReplyCommentEntity::getUserId).distinct().toList() + ).stream().collect(Collectors.toMap(UserEntity::get_id, UserEntity::getNickname)); + + return replies.stream() .map(reply -> { + String nickname = reply.isAnonymous() ? "익명" : userNicknameMap.get(reply.getUserId()); + boolean isDeleted = reply.getDeletedAt() != null; return new CommentResponseDTO( reply.get_id().toString(), reply.getUserId().toString(), reply.getContent(), + nickname, + reply.isAnonymous(), List.of(), //대댓글은 대댓글이 없음 likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.getCommentId()), // 대댓글 좋아요 수 isDeleted, diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index 8dc4606c..75691cf1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -19,7 +19,7 @@ public class PostCreateRequestDTO { //이미지 별도 Multipart (RequestPart 사용) - @Schema(description = "게시물 익명 여부 default = 0 (익명)", example = "true") + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") @NotNull private boolean anonymous; From ff634e38b3b48b4d91819574b891844cb72c3123 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 27 Dec 2024 19:23:18 +0900 Subject: [PATCH 0262/1002] =?UTF-8?q?[SC-101]=20=20Refactor=20::=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=86=A0=EA=B8=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/controller/LikeController.java | 17 +++------ .../post/domain/like/service/LikeService.java | 35 ++++++++++++------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java index e373af13..5a5e020b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java @@ -19,21 +19,12 @@ public class LikeController { private final LikeService likeService; - @Operation( - summary = "게시물, 댓글, 대댓글 좋아요 추가 " - ) + @Operation(summary = "게시물, 댓글, 대댓글 좋아요 토글") @PostMapping - public ResponseEntity> addLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { - likeService.addLike(likeRequestDto); + public ResponseEntity> toggleLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { + likeService.toggleLike(likeRequestDto); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "좋아요가 추가되었습니다.", null)); + .body(new SingleResponse<>(201, "좋아요 상태가 변경되었습니다.", null)); } - @Operation(summary = "게시물, 댓글, 대댓글 좋아요 삭제 ") - @DeleteMapping - public ResponseEntity> removeLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { - likeService.removeLike(likeRequestDto); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "좋아요가 취소되었습니다.", null)); - } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 25533ff4..db0804a9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -31,37 +31,46 @@ public class LikeService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; - public void addLike(LikeRequestDto likeRequestDto) { + public void toggleLike(LikeRequestDto likeRequestDto) { ObjectId likeId = new ObjectId(likeRequestDto.getId()); ObjectId userId = SecurityUtils.getCurrentUserId(); - isEntityNotDeleted(likeRequestDto); //해당 entity가 삭제되었는지 확인 + isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 + + // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 + boolean alreadyLiked = likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + + if (alreadyLiked) { + removeLike(likeRequestDto.getLikeType(), likeId, userId); + } else { + addLike(likeRequestDto.getLikeType(), likeId, userId); + } + } + + public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { // 중복 좋아요 검증 - if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId)) { + if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeRequestDto.getLikeType().name(), likeId, userId); + redisService.addLike(likeType.name(), likeId, userId); } LikeEntity like = LikeEntity.builder() - .likeType(likeRequestDto.getLikeType()) + .likeType(likeType) .likeTypeId(likeId) .userId(userId) .build(); likeRepository.save(like); - } + } - public void removeLike(LikeRequestDto likeRequestDto) { - ObjectId likeId = new ObjectId(likeRequestDto.getId()); - ObjectId userId = SecurityUtils.getCurrentUserId(); - isEntityNotDeleted(likeRequestDto); + public void removeLike(LikeType likeType,ObjectId likeId, ObjectId userId) { // 없는 좋아요 방지 - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId)) { + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); } if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(likeRequestDto.getLikeType().name(), likeId, userId); + redisService.removeLike(likeType.name(), likeId, userId); } - likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); } public int getLikeCount(LikeType entityType, ObjectId entityId) { From 2a41ab6563c5949b14fead5c96ca9322a8782a1f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 27 Dec 2024 19:51:12 +0900 Subject: [PATCH 0263/1002] =?UTF-8?q?[SC-101]=20=20Refactor=20::=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=9E=A9=20=ED=86=A0=EA=B8=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scrap/controller/ScrapController.java | 17 +++++------- .../domain/scrap/service/ScrapService.java | 26 +++++++++++++------ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 4c2b9482..9c939e25 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -2,9 +2,11 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,20 +20,13 @@ public class ScrapController { private final ScrapService scrapService; - @Operation(summary = "게시물 스크랩 추가") + @Operation(summary = "게시물 스크랩 토글") @PostMapping("/{postId}") - public ResponseEntity> addScrap(@PathVariable String postId) { - scrapService.addScrap(postId); + public ResponseEntity> toggleLike(@PathVariable String postId) { + scrapService.toggleScrap(postId); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "스크랩 성공.", null)); + .body(new SingleResponse<>(201, "스크랩 상태가 변경되었습니다.", null)); } - @Operation(summary = "게시물 스크랩 삭제") - @DeleteMapping("/{postId}") - public ResponseEntity> removeScrap(@PathVariable String postId) { - scrapService.removeScrap(postId); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "스크랩 취소되었습니다.", null)); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index 56844ba6..1e26a87b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -25,20 +25,33 @@ public class ScrapService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; - public void addScrap(String id) { + public void toggleScrap(String id) { ObjectId postId = new ObjectId(id); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); + // 이미 스크랩한 게시물인지 확인 + boolean alreadyScrapped = scrapRepository.existsByPostIdAndUserId(postId, userId); + + if (alreadyScrapped) { + removeScrap(postId, userId); + } else { + addScrap(postId, userId); + } + } + + private void addScrap(ObjectId postId, ObjectId userId) { + // 중복 스크랩 방지 if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { - throw new ScrapCreateFailException("이미 스크랩 한 상태 입니다."); + throw new ScrapCreateFailException("이미 스크랩한 게시물입니다."); } if (redisHealthChecker.isRedisAvailable()) { redisService.addScrap(postId, userId); } + ScrapEntity scrap = ScrapEntity.builder() .postId(postId) .userId(userId) @@ -46,12 +59,8 @@ public void addScrap(String id) { scrapRepository.save(scrap); } - public void removeScrap(String id) { - ObjectId postId = new ObjectId(id); - postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - - ObjectId userId = SecurityUtils.getCurrentUserId(); + private void removeScrap(ObjectId postId, ObjectId userId) { + // 스크랩하지 않은 게시물이라면 오류 처리 if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { throw new ScrapRemoveFailException("스크랩한 적이 없는 게시물입니다."); } @@ -59,6 +68,7 @@ public void removeScrap(String id) { if (redisHealthChecker.isRedisAvailable()) { redisService.removeScrap(postId, userId); } + scrapRepository.deleteByPostIdAndUserId(postId, userId); } From b9c50dc52174ec029fe9ecf096d903faa9565b39 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 22:14:38 +0900 Subject: [PATCH 0264/1002] =?UTF-8?q?[SC-100]=20feat=20:=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=97=94=EC=A7=84=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/controller/PostController.java | 16 +++++++++++++++- .../domain/post/repository/PostRepository.java | 4 +++- .../codin/domain/post/service/PostService.java | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index ee4f11be..a7a9d370 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -12,9 +12,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -22,6 +25,7 @@ @RestController @RequestMapping("/posts") +@Validated @Tag(name = "POST API", description = "게시글 API") public class PostController { @@ -91,7 +95,7 @@ public ResponseEntity> updatePostAnonymous( ) @GetMapping("/category") public ResponseEntity> getAllPosts(@RequestParam PostCategory postCategory, - @RequestParam("page") int pageNumber) { + @RequestParam("page") @NotNull int pageNumber) { PostPageResponse postpages= postService.getAllPosts(postCategory, pageNumber); return ResponseEntity.ok() .body(new SingleResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); @@ -123,4 +127,14 @@ public ResponseEntity> softDeletePost(@PathVariable String pos return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); } + + @Operation( + summary = "검색 엔진" + ) + @GetMapping("/search") + public ResponseEntity> searchPosts(@RequestParam("keyword") @Size(min = 2) String keyword, + @RequestParam("pageNumber") @NotNull int pageNumber){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "'"+keyword+"'"+"으로 검색된 게시글 반환 완료", postService.searchPosts(keyword, pageNumber))); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 9d3e1cf3..5836702b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -9,7 +9,6 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository @@ -24,4 +23,7 @@ public interface PostRepository extends MongoRepository { Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); Page findByPostCategoryStartingWithOrderByCreatedAt(String prefix, PageRequest pageRequest); + + @Query("{ '$or': [ { 'content': { $regex: ?0, $options: 'i' } }, { 'title': { $regex: ?0, $options: 'i' } } ] }") + Page findAllByKeyword(String keyword, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 6ad97a3c..8eee85ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -197,5 +197,10 @@ public UserInfo getUserInfoAboutPost(ObjectId postId){ .build(); } + public PostPageResponse searchPosts(String keyword, int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = postRepository.findAllByKeyword(keyword, pageRequest); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } } From dcbb677abff3c7e77a4ac51f778180fa9a62a83a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Dec 2024 23:05:11 +0900 Subject: [PATCH 0265/1002] =?UTF-8?q?refactor=20:=20User=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C,=20multipart/f?= =?UTF-8?q?orm-data=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/user/controller/UserController.java | 9 ++++++--- .../user/dto/request/UserCreateRequestDto.java | 5 ----- .../codin/codin/domain/user/service/UserService.java | 12 +++++++++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 07036707..c140cf7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -12,8 +12,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping(value = "/users") @@ -24,10 +26,11 @@ public class UserController { private final UserService userService; @Operation(summary = "회원가입") - @PostMapping("/signup") + @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> signUpUser( - @RequestBody @Valid UserCreateRequestDto userCreateRequestDto) { - userService.createUser(userCreateRequestDto); + @RequestPart @Valid UserCreateRequestDto userCreateRequestDto, + @RequestPart(value = "userImage", required = false) MultipartFile userImage) { + userService.createUser(userCreateRequestDto, userImage); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원가입 성공", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java index 604c57b0..2ca598d8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java @@ -30,11 +30,6 @@ public class UserCreateRequestDto { @NotBlank private String nickname; - //todo 프로필 이미지 업로드 처리 ex) 이미지 크기 제한.. - @Schema(description = "프로필 이미지 URL", example = "https://avatars.githubusercontent.com/u/77490521?v=4") - @NotBlank - private String profileImageUrl; - @Schema(description = "소속", example = "IT_COLLEGE") private Department department; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index b1e56f7b..1ad24205 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -25,16 +25,17 @@ import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserPasswordChangeFailException; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.catalina.User; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -49,10 +50,15 @@ public class UserService { private final PostRepository postRepository; private final ScrapRepository scrapRepository; private final CommentRepository commentRepository; + private final PasswordEncoder passwordEncoder; private final PostService postService; + private final S3Service s3Service; + + public void createUser(UserCreateRequestDto userCreateRequestDto, MultipartFile userImage) { - public void createUser(UserCreateRequestDto userCreateRequestDto) { + String imageUrl = null; + if (userImage != null) imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); String encodedPassword = passwordEncoder.encode(userCreateRequestDto.getPassword()); @@ -66,7 +72,7 @@ public void createUser(UserCreateRequestDto userCreateRequestDto) { .studentId(userCreateRequestDto.getStudentId()) .name(userCreateRequestDto.getName()) .nickname(userCreateRequestDto.getNickname()) - .profileImageUrl(userCreateRequestDto.getProfileImageUrl()) + .profileImageUrl(imageUrl) .department(userCreateRequestDto.getDepartment()) .status(UserStatus.ACTIVE) .role(UserRole.USER) From 4359c673382b76a8baa9a8760670f4607e8433b6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 28 Dec 2024 16:00:11 +0900 Subject: [PATCH 0266/1002] =?UTF-8?q?[SC-94]=20=20Refactor=20::=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/comment/service/CommentService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 0944bb5f..3f32f390 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -130,10 +130,4 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { commentRepository.save(comment); } - //user id 기반 nickname 반환 - public String getNicknameByUserId(ObjectId userId) { - UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); - return user.getNickname(); - } } \ No newline at end of file From 7808b5d1631dafec5197aae8e2b0ae0901fefb4f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 28 Dec 2024 16:06:43 +0900 Subject: [PATCH 0267/1002] =?UTF-8?q?[SC-101]=20=20Refactor=20::=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94/=EC=8A=A4=ED=81=AC=EB=9E=A9=20Respo?= =?UTF-8?q?nse=20Message=20=EC=83=81=ED=83=9C=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/like/controller/LikeController.java | 4 ++-- .../codin/domain/post/domain/like/service/LikeService.java | 4 +++- .../domain/post/domain/scrap/controller/ScrapController.java | 4 ++-- .../codin/domain/post/domain/scrap/service/ScrapService.java | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java index 5a5e020b..90ceb084 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java @@ -22,9 +22,9 @@ public class LikeController { @Operation(summary = "게시물, 댓글, 대댓글 좋아요 토글") @PostMapping public ResponseEntity> toggleLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { - likeService.toggleLike(likeRequestDto); + String message = likeService.toggleLike(likeRequestDto); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "좋아요 상태가 변경되었습니다.", null)); + .body(new SingleResponse<>(201, message, null)); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index db0804a9..6a57ac70 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -31,7 +31,7 @@ public class LikeService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; - public void toggleLike(LikeRequestDto likeRequestDto) { + public String toggleLike(LikeRequestDto likeRequestDto) { ObjectId likeId = new ObjectId(likeRequestDto.getId()); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 @@ -41,8 +41,10 @@ public void toggleLike(LikeRequestDto likeRequestDto) { if (alreadyLiked) { removeLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 삭제되었습니다"; } else { addLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 추가되었습니다."; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 9c939e25..30b6060e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -23,9 +23,9 @@ public class ScrapController { @Operation(summary = "게시물 스크랩 토글") @PostMapping("/{postId}") public ResponseEntity> toggleLike(@PathVariable String postId) { - scrapService.toggleScrap(postId); + String message = scrapService.toggleScrap(postId); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "스크랩 상태가 변경되었습니다.", null)); + .body(new SingleResponse<>(201, message, null)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index 1e26a87b..4872accd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -25,7 +25,7 @@ public class ScrapService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; - public void toggleScrap(String id) { + public String toggleScrap(String id) { ObjectId postId = new ObjectId(id); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); @@ -37,8 +37,10 @@ public void toggleScrap(String id) { if (alreadyScrapped) { removeScrap(postId, userId); + return "스크랩이 취소되었습니다. "; } else { addScrap(postId, userId); + return "스크랩이 추가되었습니다. "; } } From ca8b8ccee3a0cbf5b227681085201fa941025114 Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 20:04:45 +0900 Subject: [PATCH 0268/1002] =?UTF-8?q?Feat=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/user/controller/UserController.java | 10 ++++++++++ .../codin/codin/domain/user/entity/UserEntity.java | 10 ++++++++++ .../codin/codin/domain/user/service/UserService.java | 12 ++++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 07036707..384b57b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -100,4 +100,14 @@ public ResponseEntity> getUserInfo(){ return ResponseEntity.ok() .body(new SingleResponse<>(200, "유저 정보 반환 완료", userService.getUserInfo())); } + + @Operation( + summary = "유저 정보 수정" + ) + @PutMapping + public ResponseEntity> updateUserInfo(@RequestBody @Valid UserCreateRequestDto userCreateRequestDto){ + userService.updateUserInfo(userCreateRequestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "유저 정보 수정 완료", null)); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index a4081702..f465fc85 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -56,4 +57,13 @@ public void updatePassword(String password){ public void changePassword(){ this.changePassword = !this.changePassword; } + + public void updateUserInfo(UserCreateRequestDto userCreateRequestDto) { + this.email = userCreateRequestDto.getEmail(); + this.studentId = userCreateRequestDto.getStudentId(); + this.name = userCreateRequestDto.getName(); + this.nickname = userCreateRequestDto.getNickname(); + this.profileImageUrl = userCreateRequestDto.getProfileImageUrl(); + this.department = userCreateRequestDto.getDepartment(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 7a6b1ff6..3df11d70 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -158,14 +158,18 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } + public void updateUserInfo(@Valid UserCreateRequestDto userCreateRequestDto) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + user.updateUserInfo(userCreateRequestDto); + userRepository.save(user); + } + public enum InteractionType { LIKE, SCRAP, COMMENT } - - - - //user id 기반 nickname 반환 public String getNicknameByUserId(ObjectId userId) { UserEntity user = userRepository.findById(userId) From 43edbc269fc7628216a335e8bac99cdffb13dbee Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 20:06:02 +0900 Subject: [PATCH 0269/1002] =?UTF-8?q?feat=20:=20Best=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=EC=9D=84=20=EC=9C=84=ED=95=9C=20ZSet=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 9 ++++++ .../domain/post/service/PostService.java | 4 +++ .../codin/codin/infra/redis/RedisService.java | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index ee4f11be..288f420c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.controller; +import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; @@ -12,6 +13,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import org.apache.coyote.Response; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -123,4 +125,11 @@ public ResponseEntity> softDeletePost(@PathVariable String pos return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); } + + @Operation(summary = "Top 3 베스트 게시글 가져오기") + @GetMapping("/top3") + public ResponseEntity> getTop3BestPosts(){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postService.getTop3BestPosts())); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 6ad97a3c..a92dfa34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -197,5 +197,9 @@ public UserInfo getUserInfoAboutPost(ObjectId postId){ .build(); } + public List getTop3BestPosts() { + redisService.getTopNPosts(3); + return null; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 48c96358..d79acd74 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -2,12 +2,15 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.entity.PostEntity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Set; import java.util.stream.Collectors; @@ -113,4 +116,33 @@ public Set getHitsUser(ObjectId postId) { String redisKey = HITS_KEY + postId.toString(); return redisTemplate.opsForSet().members(redisKey); } + + + // 점수 업데이트 Sorted Set 사용 + public void updateScore(String postId, double score) { + redisTemplate.opsForZSet().add("post:ranking", postId, score); + } + + // Top N 게시물 조회 + public Set getTopNPosts(int N) { + return redisTemplate.opsForZSet().reverseRange("post:ranking", 0, N - 1); + } + + public void applyBestScore(int score, PostEntity post){ + LocalDateTime now = LocalDateTime.now(); + int hour = now.toLocalTime().getHour(); + int day = now.toLocalDate().getDayOfMonth(); + String rediskey; + for (int i=0; i<24; i++){ + if ((hour-i) < 0){ hour = hour+24; day= day-1; } + rediskey = now.format(DateTimeFormatter.ofPattern("yyyyMM")) + day + "/" + (hour-i); + Double scoreOfBest = redisTemplate.opsForZSet().score(rediskey, post.get_id().toString()); + if (scoreOfBest != null){ + redisTemplate.opsForZSet().incrementScore(rediskey, post.get_id().toString(), score); + break; + } + } + rediskey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); + redisTemplate.opsForZSet().add(rediskey, post.get_id().toString(), score); + } } From e1e9c8b90c772a44fe1b8c5204bcf4ea0b15ce4d Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 20:06:06 +0900 Subject: [PATCH 0270/1002] =?UTF-8?q?Refactor=20:=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/user/service/UserService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 3df11d70..6422ef61 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -28,12 +28,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.catalina.User; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.bson.types.ObjectId; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; From 45782a56516320abc5a2a75ac1826c2e2acae7c7 Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 20:12:57 +0900 Subject: [PATCH 0271/1002] =?UTF-8?q?Perf=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 5 ++-- .../dto/request/UserUpdateRequestDto.java | 28 +++++++++++++++++++ .../codin/domain/user/entity/UserEntity.java | 15 +++++----- .../domain/user/service/UserService.java | 5 ++-- 4 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 384b57b3..85489100 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; import inu.codin.codin.domain.user.dto.request.UserPasswordRequestDto; +import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; @@ -105,8 +106,8 @@ public ResponseEntity> getUserInfo(){ summary = "유저 정보 수정" ) @PutMapping - public ResponseEntity> updateUserInfo(@RequestBody @Valid UserCreateRequestDto userCreateRequestDto){ - userService.updateUserInfo(userCreateRequestDto); + public ResponseEntity> updateUserInfo(@RequestBody @Valid UserUpdateRequestDto userUpdateRequestDto){ + userService.updateUserInfo(userUpdateRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "유저 정보 수정 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java new file mode 100644 index 00000000..d39d4796 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.user.dto.request; + +import inu.codin.codin.common.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class UserUpdateRequestDto { + @Schema(description = "학번", example = "20210000") + @NotBlank + private String studentId; + + @Schema(description = "이름", example = "홍길동") + @NotBlank + private String name; + + @Schema(description = "닉네임", example = "코딩") + @NotBlank + private String nickname; + + @Schema(description = "프로필 이미지 URL", example = "https://avatars.githubusercontent.com/u/77490521?v=4") + @NotBlank + private String profileImageUrl; + + @Schema(description = "소속", example = "IT_COLLEGE") + private Department department; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index f465fc85..cb33a473 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; +import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -58,12 +58,11 @@ public void changePassword(){ this.changePassword = !this.changePassword; } - public void updateUserInfo(UserCreateRequestDto userCreateRequestDto) { - this.email = userCreateRequestDto.getEmail(); - this.studentId = userCreateRequestDto.getStudentId(); - this.name = userCreateRequestDto.getName(); - this.nickname = userCreateRequestDto.getNickname(); - this.profileImageUrl = userCreateRequestDto.getProfileImageUrl(); - this.department = userCreateRequestDto.getDepartment(); + public void updateUserInfo(UserUpdateRequestDto userUpdateRequestDto) { + this.studentId = userUpdateRequestDto.getStudentId(); + this.name = userUpdateRequestDto.getName(); + this.nickname = userUpdateRequestDto.getNickname(); + this.profileImageUrl = userUpdateRequestDto.getProfileImageUrl(); + this.department = userUpdateRequestDto.getDepartment(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 6422ef61..fe14ef20 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -18,6 +18,7 @@ import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; import inu.codin.codin.domain.user.dto.request.UserPasswordRequestDto; +import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; @@ -156,11 +157,11 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } - public void updateUserInfo(@Valid UserCreateRequestDto userCreateRequestDto) { + public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { ObjectId userId = SecurityUtils.getCurrentUserId(); UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); - user.updateUserInfo(userCreateRequestDto); + user.updateUserInfo(userUpdateRequestDto); userRepository.save(user); } From eea8cc6c1b283001c349a661ca3acf206432541e Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 20:35:17 +0900 Subject: [PATCH 0272/1002] =?UTF-8?q?Feat=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=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 | 14 ++++++++++++++ .../user/dto/request/UserUpdateRequestDto.java | 4 ---- .../codin/codin/domain/user/entity/UserEntity.java | 5 ++++- .../codin/domain/user/service/UserService.java | 12 ++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 85489100..3f278571 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -13,8 +13,12 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @RequestMapping(value = "/users") @@ -111,4 +115,14 @@ public ResponseEntity> updateUserInfo(@RequestBody @Valid User return ResponseEntity.ok() .body(new SingleResponse<>(200, "유저 정보 수정 완료", null)); } + + @Operation( + summary = "유저 사진 수정" + ) + @PostMapping(value = "/profile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> updateUserProfile(@RequestPart(value = "postImages", required = false) MultipartFile postImage){ + userService.updateUserProfile(postImage); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "유저 사진 수정 완료", null)); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java index d39d4796..94c76c56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java @@ -19,10 +19,6 @@ public class UserUpdateRequestDto { @NotBlank private String nickname; - @Schema(description = "프로필 이미지 URL", example = "https://avatars.githubusercontent.com/u/77490521?v=4") - @NotBlank - private String profileImageUrl; - @Schema(description = "소속", example = "IT_COLLEGE") private Department department; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index cb33a473..4946ad6c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -62,7 +62,10 @@ public void updateUserInfo(UserUpdateRequestDto userUpdateRequestDto) { this.studentId = userUpdateRequestDto.getStudentId(); this.name = userUpdateRequestDto.getName(); this.nickname = userUpdateRequestDto.getNickname(); - this.profileImageUrl = userUpdateRequestDto.getProfileImageUrl(); this.department = userUpdateRequestDto.getDepartment(); } + + public void updateProfileImageUrl(String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index fe14ef20..c1a6b09c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -26,6 +26,7 @@ import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserPasswordChangeFailException; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,6 +36,7 @@ import org.springframework.data.domain.Sort; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -51,6 +53,7 @@ public class UserService { private final CommentRepository commentRepository; private final PasswordEncoder passwordEncoder; private final PostService postService; + private final S3Service s3Service; public void createUser(UserCreateRequestDto userCreateRequestDto) { @@ -165,6 +168,15 @@ public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { userRepository.save(user); } + public void updateUserProfile(MultipartFile postImage) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + String profileImageUrl = s3Service.handleImageUpload(List.of(postImage)).get(0); + user.updateProfileImageUrl(profileImageUrl); + userRepository.save(user); + } + public enum InteractionType { LIKE, SCRAP, COMMENT } From 98556b2896e592344e1c70a613400c5b19c8bbc4 Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 20:42:35 +0900 Subject: [PATCH 0273/1002] =?UTF-8?q?Refactor=20:=20Import=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/user/controller/UserController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 3f278571..6b8a2447 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -18,8 +18,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.util.List; - @RestController @RequestMapping(value = "/users") @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") From e83f6ec5da345b4a8958139886a39e8f49f1d86a Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 20:58:33 +0900 Subject: [PATCH 0274/1002] =?UTF-8?q?Perf=20:=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20Mapping=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/user/controller/UserController.java | 6 +++--- .../inu/codin/codin/domain/user/service/UserService.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 6b8a2447..1defff6e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -117,9 +117,9 @@ public ResponseEntity> updateUserInfo(@RequestBody @Valid User @Operation( summary = "유저 사진 수정" ) - @PostMapping(value = "/profile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> updateUserProfile(@RequestPart(value = "postImages", required = false) MultipartFile postImage){ - userService.updateUserProfile(postImage); + @PutMapping(value = "/profile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> updateUserProfile(@RequestPart(value = "postImages", required = true) MultipartFile profileImage){ + userService.updateUserProfile(profileImage); return ResponseEntity.ok() .body(new SingleResponse<>(200, "유저 사진 수정 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index c1a6b09c..bff07794 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -168,11 +168,11 @@ public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { userRepository.save(user); } - public void updateUserProfile(MultipartFile postImage) { + public void updateUserProfile(MultipartFile profileImage) { ObjectId userId = SecurityUtils.getCurrentUserId(); UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); - String profileImageUrl = s3Service.handleImageUpload(List.of(postImage)).get(0); + String profileImageUrl = s3Service.handleImageUpload(List.of(profileImage)).get(0); user.updateProfileImageUrl(profileImageUrl); userRepository.save(user); } From a9a1b075ab713e3c956654da6bca9e9b91b11697 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 20:59:51 +0900 Subject: [PATCH 0275/1002] =?UTF-8?q?refactor=20:=20Like=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=98=EC=97=AC=20softDelete=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/BaseTimeEntity.java | 4 ++ .../like/repository/LikeRepository.java | 3 +- .../post/domain/like/service/LikeService.java | 54 ++++++++++--------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java index 62ebd48e..b10b05a2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -28,4 +28,8 @@ public void restore() { this.deletedAt = null; } + public void recreatedAt(){ + this.createdAt = LocalDateTime.now(); + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 00e373bf..61db88ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -4,7 +4,6 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -25,5 +24,7 @@ public interface LikeRepository extends MongoRepository { boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + Page findAllByUserIdAndLikeTypeOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 6a57ac70..e112c1c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -37,42 +37,44 @@ public String toggleLike(LikeRequestDto likeRequestDto) { isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 - boolean alreadyLiked = likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - if (alreadyLiked) { - removeLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 삭제되었습니다"; - } else { - addLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 추가되었습니다."; + if (like != null && like.getDeletedAt() == null) { + removeLike(like); + return "좋아요가 삭제되었습니다."; } + addLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 추가되었습니다."; } public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { - // 중복 좋아요 검증 - if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { - throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); - } - if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeType.name(), likeId, userId); + LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); + + if (like != null){ + if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 + like.recreatedAt(); + like.restore(); + likeRepository.save(like); + } else throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); + } else { //좋아요 내역이 없으면 새로 생성 + if (redisHealthChecker.isRedisAvailable()) { + redisService.addLike(likeType.name(), likeId, userId); + } + likeRepository.save(LikeEntity.builder() + .likeType(likeType) + .likeTypeId(likeId) + .userId(userId) + .build()); + redisService.applyBestScore(1, likeId); } - LikeEntity like = LikeEntity.builder() - .likeType(likeType) - .likeTypeId(likeId) - .userId(userId) - .build(); - likeRepository.save(like); } - public void removeLike(LikeType likeType,ObjectId likeId, ObjectId userId) { - // 없는 좋아요 방지 - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { - throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); - } + public void removeLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(likeType.name(), likeId, userId); + redisService.removeLike(like.getLikeType().name(), like.get_id(), like.getUserId()); } - likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); + like.delete(); + likeRepository.save(like); } public int getLikeCount(LikeType entityType, ObjectId entityId) { From e39d30559f251eaf8a5ff99e161bd735bae3d5f6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 21:00:18 +0900 Subject: [PATCH 0276/1002] =?UTF-8?q?chore=20:=20=EB=A7=A4=EA=B0=9C?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/redis/RedisService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index d79acd74..16876a81 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -128,7 +128,7 @@ public Set getTopNPosts(int N) { return redisTemplate.opsForZSet().reverseRange("post:ranking", 0, N - 1); } - public void applyBestScore(int score, PostEntity post){ + public void applyBestScore(int score, ObjectId id){ LocalDateTime now = LocalDateTime.now(); int hour = now.toLocalTime().getHour(); int day = now.toLocalDate().getDayOfMonth(); @@ -136,13 +136,13 @@ public void applyBestScore(int score, PostEntity post){ for (int i=0; i<24; i++){ if ((hour-i) < 0){ hour = hour+24; day= day-1; } rediskey = now.format(DateTimeFormatter.ofPattern("yyyyMM")) + day + "/" + (hour-i); - Double scoreOfBest = redisTemplate.opsForZSet().score(rediskey, post.get_id().toString()); + Double scoreOfBest = redisTemplate.opsForZSet().score(rediskey, id.toString()); if (scoreOfBest != null){ - redisTemplate.opsForZSet().incrementScore(rediskey, post.get_id().toString(), score); + redisTemplate.opsForZSet().incrementScore(rediskey, id.toString(), score); break; } } rediskey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); - redisTemplate.opsForZSet().add(rediskey, post.get_id().toString(), score); + redisTemplate.opsForZSet().add(rediskey, id.toString(), score); } } From 1d15c73d361bcc3a3b558e6a8503bb895f1583c1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 21:53:22 +0900 Subject: [PATCH 0277/1002] =?UTF-8?q?refactor=20:=20Scrap=EC=97=90=20softD?= =?UTF-8?q?elete=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scrap/repository/ScrapRepository.java | 6 +- .../domain/scrap/service/ScrapService.java | 55 +++++++++---------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 4af571f3..ce6d9fb8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -1,7 +1,5 @@ package inu.codin.codin.domain.post.domain.scrap.repository; -import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; @@ -18,9 +16,7 @@ public interface ScrapRepository extends MongoRepository long countByPostId(ObjectId postId); - void deleteByPostIdAndUserId(ObjectId postId, ObjectId userId); - - boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); + ScrapEntity findByPostIdAndUserId(ObjectId postId, ObjectId userId); Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index 4872accd..517ff106 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -2,10 +2,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; -import inu.codin.codin.domain.post.domain.scrap.exception.ScrapRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; @@ -33,45 +31,44 @@ public String toggleScrap(String id) { ObjectId userId = SecurityUtils.getCurrentUserId(); // 이미 스크랩한 게시물인지 확인 - boolean alreadyScrapped = scrapRepository.existsByPostIdAndUserId(postId, userId); + ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); - if (alreadyScrapped) { - removeScrap(postId, userId); + if (scrap != null && scrap.getDeletedAt() == null) { + removeScrap(scrap); return "스크랩이 취소되었습니다. "; - } else { - addScrap(postId, userId); - return "스크랩이 추가되었습니다. "; } + addScrap(postId, userId); + return "스크랩이 추가되었습니다. "; + } private void addScrap(ObjectId postId, ObjectId userId) { - // 중복 스크랩 방지 - if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { - throw new ScrapCreateFailException("이미 스크랩한 게시물입니다."); - } + ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); - if (redisHealthChecker.isRedisAvailable()) { - redisService.addScrap(postId, userId); + if (scrap != null){ + if (scrap.getDeletedAt() != null){ + scrap.recreatedAt(); + scrap.restore(); + scrapRepository.save(scrap); + } else throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); + } else { + if (redisHealthChecker.isRedisAvailable()) { + redisService.addScrap(postId, userId); + } + scrapRepository.save(ScrapEntity.builder() + .postId(postId) + .userId(userId) + .build()); + redisService.applyBestScore(2, postId); } - - ScrapEntity scrap = ScrapEntity.builder() - .postId(postId) - .userId(userId) - .build(); - scrapRepository.save(scrap); } - private void removeScrap(ObjectId postId, ObjectId userId) { - // 스크랩하지 않은 게시물이라면 오류 처리 - if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { - throw new ScrapRemoveFailException("스크랩한 적이 없는 게시물입니다."); - } - + private void removeScrap(ScrapEntity scrap) { if (redisHealthChecker.isRedisAvailable()) { - redisService.removeScrap(postId, userId); + redisService.removeScrap(scrap.getPostId(), scrap.getUserId()); } - - scrapRepository.deleteByPostIdAndUserId(postId, userId); + scrap.delete(); + scrapRepository.save(scrap); } public int getScrapCount(ObjectId postId) { From 4eba686b23e2bdbb346d215123d5da939589cf36 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 21:53:51 +0900 Subject: [PATCH 0278/1002] =?UTF-8?q?refactor=20:=20POST=EC=97=90=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=95=9C=20=EA=B2=83=EB=A7=8C=20?= =?UTF-8?q?redis=20ranking=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/like/repository/LikeRepository.java | 3 --- .../codin/domain/post/domain/like/service/LikeService.java | 7 +++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 61db88ec..1dbeecb3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -19,9 +19,6 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티의 좋아요 데이터 조회 List findByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); - // 특정 사용자의 좋아요 삭제 - void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index e112c1c6..4e41d7ca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -5,9 +5,8 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; -import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; @@ -17,7 +16,6 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import org.springframework.validation.ObjectError; @Service @RequiredArgsConstructor @@ -65,7 +63,8 @@ public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { .likeTypeId(likeId) .userId(userId) .build()); - redisService.applyBestScore(1, likeId); + if (likeType == LikeType.POST) + redisService.applyBestScore(1, likeId); } } From e42bed775075dfa4d71d0362b24aff257001d544 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 21:54:15 +0900 Subject: [PATCH 0279/1002] =?UTF-8?q?feat=20:=2024=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=9D=B4=EB=82=B4=EC=9D=98=20ranking=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20TOP3=20=EB=BD=91=EC=95=84=EB=82=B4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/service/PostService.java | 10 ++++- .../codin/codin/infra/redis/RedisService.java | 41 +++++++++++-------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index cd1e791e..8b8cd302 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -36,6 +36,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Service @@ -224,8 +225,13 @@ public String getNicknameByUserId(ObjectId userId) { } public List getTop3BestPosts() { - redisService.getTopNPosts(3); - return null; + Set postIds = redisService.getTopNPosts(3); + List bestPosts = postIds.stream() + .map(postId -> + postRepository.findById(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")) + ).toList(); + return getPostListResponseDtos(bestPosts); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 16876a81..49c63d9e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.entity.PostEntity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -11,6 +10,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -117,32 +117,37 @@ public Set getHitsUser(ObjectId postId) { return redisTemplate.opsForSet().members(redisKey); } - - // 점수 업데이트 Sorted Set 사용 - public void updateScore(String postId, double score) { - redisTemplate.opsForZSet().add("post:ranking", postId, score); - } - // Top N 게시물 조회 public Set getTopNPosts(int N) { - return redisTemplate.opsForZSet().reverseRange("post:ranking", 0, N - 1); + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + Set result = new HashSet<>(); + for (int i = 0; i < 24; i++) { + String redisKey = now.minusHours(i).format(formatter); + Set members = redisTemplate.opsForZSet().reverseRange(redisKey, 0, N - 1); + if (members != null) { + result.addAll(members); + } + } + + return result.stream().limit(N).collect(Collectors.toSet()); } public void applyBestScore(int score, ObjectId id){ LocalDateTime now = LocalDateTime.now(); - int hour = now.toLocalTime().getHour(); - int day = now.toLocalDate().getDayOfMonth(); - String rediskey; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + String redisKey; for (int i=0; i<24; i++){ - if ((hour-i) < 0){ hour = hour+24; day= day-1; } - rediskey = now.format(DateTimeFormatter.ofPattern("yyyyMM")) + day + "/" + (hour-i); - Double scoreOfBest = redisTemplate.opsForZSet().score(rediskey, id.toString()); + redisKey = now.minusHours(i).format(formatter); + Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, id.toString()); if (scoreOfBest != null){ - redisTemplate.opsForZSet().incrementScore(rediskey, id.toString(), score); - break; + redisTemplate.opsForZSet().incrementScore(redisKey, id.toString(), score); + return; } } - rediskey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); - redisTemplate.opsForZSet().add(rediskey, id.toString(), score); + redisKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); + redisTemplate.opsForZSet().add(redisKey, id.toString(), score); } } From e96624ed21a47072d380457e4447b886a8f7e812 Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 22:17:16 +0900 Subject: [PATCH 0280/1002] =?UTF-8?q?Feat=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=82=B4=EC=97=90=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=EC=84=A4=EC=A0=95=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/user/entity/UserEntity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index a4081702..4d22e1bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; +import inu.codin.codin.domain.notification.entity.NotificationPreference; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -36,6 +37,8 @@ public class UserEntity extends BaseTimeEntity { private boolean changePassword = false; + private NotificationPreference notificationPreference = new NotificationPreference(); + @Builder public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status) { this.email = email; From 2d3cd7cfa1ff9c4689673d517669c2b38b0f6dbf Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 22:22:13 +0900 Subject: [PATCH 0281/1002] =?UTF-8?q?Feat=20:=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FCM 메세지 전송 예외처리 추가 - 토픽 구독 및 구독 해제 로직 기본 틀 추가 - FCM 토큰 만료시 삭제 로직 추가 --- .../domain/user/service/UserService.java | 10 +-- .../infra/fcm/controller/FcmController.java | 29 +++++-- .../infra/fcm/entity/FcmTokenEntity.java | 17 +++-- .../codin/infra/fcm/service/FcmService.java | 75 +++++++++++++++---- 4 files changed, 98 insertions(+), 33 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 496c4548..82f01a7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -33,7 +33,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.bson.types.ObjectId; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -164,14 +163,15 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } + public UserEntity getUserEntityFromUserId(ObjectId userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); + } + public enum InteractionType { LIKE, SCRAP, COMMENT } - - - - //user id 기반 nickname 반환 public String getNicknameByUserId(ObjectId userId) { UserEntity user = userRepository.findById(userId) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java index 7c1b3db2..3d1cd4de 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java @@ -1,14 +1,13 @@ package inu.codin.codin.infra.fcm.controller; -import inu.codin.codin.common.ResponseUtils; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; import inu.codin.codin.infra.fcm.service.FcmService; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,10 +23,26 @@ public class FcmController { @PostMapping("/save") public ResponseEntity sendFcmMessage( - @RequestBody @Valid FcmTokenRequest fcmTokenRequest, - @AuthenticationPrincipal UserDetails userDetails + @RequestBody @Valid FcmTokenRequest fcmTokenRequest ) { - fcmService.saveFcmToken(fcmTokenRequest, userDetails); - return ResponseUtils.successMsg("FCM 토큰 저장 성공"); + fcmService.saveFcmToken(fcmTokenRequest); + return ResponseEntity.status(HttpStatus.ACCEPTED).body(new SingleResponse<>(202, "FCM 토큰 저장 성공", null)); } + + @PostMapping("/subscribe") + public ResponseEntity subscribeTopic( + @RequestBody String topic + ) { + fcmService.subscribeTopic(topic); + return ResponseEntity.status(HttpStatus.ACCEPTED).body(new SingleResponse<>(202, "FCM 토픽 구독 성공", null)); + } + + @PostMapping("/unsubscribe") + public ResponseEntity unsubscribeTopic( + @RequestBody String topic + ) { + fcmService.unsubscribeTopic(topic); + return ResponseEntity.status(HttpStatus.ACCEPTED).body(new SingleResponse<>(202, "FCM 토픽 구독 해제 성공", null)); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index c9089e51..2445dfa0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,11 +1,14 @@ package inu.codin.codin.infra.fcm.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; @@ -15,18 +18,18 @@ public class FcmTokenEntity extends BaseTimeEntity { @Id - private String id; + private ObjectId id; - // username와 같음 - private String email; + @DBRef + private UserEntity user; private List fcmTokenList; private String deviceType; @Builder - public FcmTokenEntity(String email, List fcmTokenList, String deviceType) { - this.email = email; + public FcmTokenEntity(UserEntity user, List fcmTokenList, String deviceType) { + this.user = user; this.fcmTokenList = fcmTokenList; this.deviceType = deviceType; } @@ -47,4 +50,8 @@ private void checkDuplicatedFcmToken(String fcmToken) { throw new FcmDuplicatedTokenException("이미 등록된 FCM 토큰입니다."); } } + + public void deleteFcmToken(String fcmToken) { + fcmTokenList.remove(fcmToken); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 1072df3a..ac2e378c 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -1,10 +1,10 @@ package inu.codin.codin.infra.fcm.service; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.Notification; +import com.google.firebase.messaging.*; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.notification.entity.NotificationPreference; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; @@ -14,7 +14,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.userdetails.UserDetails; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @@ -26,16 +26,17 @@ public class FcmService { private final FcmTokenRepository fcmTokenRepository; + private final UserService userService; /** * 클라이언트로부터 받은 FCM 토큰을 저장하는 로직 * @param fcmTokenRequest FCM 토큰 요청 DTO - * @param userDetails 로그인한 유저 정보 */ - public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails userDetails) { + public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { // 유저의 FCM 토큰이 존재하는지 확인 - String email = userDetails.getUsername(); - Optional fcmToken = fcmTokenRepository.findByEmail(email); + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userService.getUserEntityFromUserId(userId); + Optional fcmToken = fcmTokenRepository.findByUser(user); if (fcmToken.isPresent()) { // 이미 존재하는 유저라면 토큰 추가 FcmTokenEntity fcmTokenEntity = fcmToken.get(); @@ -44,7 +45,7 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails use } else { // 존재하지 않는 FCM 토큰이라면 저장 FcmTokenEntity newFcmTokenEntity = FcmTokenEntity.builder() - .email(email) + .user(user) .fcmTokenList(List.of(fcmTokenRequest.getFcmToken())) .deviceType(fcmTokenRequest.getDeviceType()) .build(); @@ -58,16 +59,16 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest, UserDetails use */ public void sendFcmMessage(FcmMessageUserDto fcmMessageUserDto) { // 유저의 알림 설정 조회 - String email = fcmMessageUserDto.getUser().getEmail(); + UserEntity user = fcmMessageUserDto.getUser(); NotificationPreference userPreference = fcmMessageUserDto.getUser().getNotificationPreference(); // 유저의 FCM 토큰 조회 - FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByEmail(email).orElseThrow(() + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user).orElseThrow(() -> new FcmTokenNotFoundException("유저에게 FCM 토큰이 존재하지 않습니다.")); // 알림 설정에 따라 알림 전송 if (!userPreference.isAllowPush()) { - log.info("[sendFcmMessage] 알림 설정에서 푸시 알림을 허용하지 않았습니다. : {}", email); + log.info("[sendFcmMessage] 알림 설정에서 푸시 알림을 허용하지 않았습니다. : {}", user.getEmail()); return; } for (String fcmToken : fcmTokenEntity.getFcmTokenList()) { @@ -86,9 +87,7 @@ public void sendFcmMessage(FcmMessageUserDto fcmMessageUserDto) { log.info("[sendFcmMessage] 알림 전송 성공 : {}", response); } catch (FirebaseMessagingException e) { log.error("[sendFcmMessage] 알림 전송 실패, errorCode : {}, msg : {}", e.getErrorCode(), e.getMessage()); - // todo : 에러 관리 및 리포팅 기능 추가 - // todo : 알림 전송 실패 시 로직 추가 FCM 토큰 만료시 삭제 로직 추가 - // todo : 토큰 만료 관리 추가 + handleFirebaseMessagingException(e, fcmTokenEntity, fcmToken); } } } @@ -121,4 +120,48 @@ public void sendFcmMessageByTopic(FcmMessageTopicDto fcmMessageTopicDto) { } } + /** + * FCM 메시지 전송 중 발생한 예외를 처리하는 로직 + * @param e FirebaseMessagingException + * @param fcmTokenEntity FcmTokenEntity + * @param fcmToken FCM 토큰 + */ + private void handleFirebaseMessagingException(FirebaseMessagingException e, FcmTokenEntity fcmTokenEntity, String fcmToken) { + MessagingErrorCode errorCode = e.getMessagingErrorCode(); + switch (errorCode) { + case INVALID_ARGUMENT: // FCM 토큰이 유효하지 않을 때 + log.error("Invalid argument error for token: {}", fcmToken); + break; + case UNREGISTERED: // FCM 토큰이 등록되지 않았을 때 + log.warn("Unregistered token: {}. Removing from database.", fcmToken); + removeFcmToken(fcmTokenEntity, fcmToken); + break; + case QUOTA_EXCEEDED: // FCM 토큰의 전송량이 초과되었을 때 + log.error("Quota exceeded for token: {}", fcmToken); + // 에러관리 및 리포팅 기능 추가 + break; + case SENDER_ID_MISMATCH: // FCM 토큰의 발신자 ID가 일치하지 않을 때 + log.error("Sender ID mismatch for token: {}", fcmToken); + break; + case THIRD_PARTY_AUTH_ERROR: // FCM 토큰의 인증이 실패했을 때 + log.error("Third-party authentication error for token: {}", fcmToken); + break; + default: // 그 외의 에러 + log.error("Unknown error for token: {}", fcmToken); + break; + } + } + + // todo : FCM 토큰 만료시 삭제 로직 추가 + private void removeFcmToken(FcmTokenEntity fcmTokenEntity, String fcmToken) { + fcmTokenEntity.deleteFcmToken(fcmToken); + fcmTokenRepository.save(fcmTokenEntity); + } + + // todo : FCM 토픽 구독 및 구독 해제 로직 추가 + public void unsubscribeTopic(String topic) { + } + public void subscribeTopic(String topic) { + } + } \ No newline at end of file From 04524847dd42938d5fd1a230532cf72778833981 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 22:27:13 +0900 Subject: [PATCH 0282/1002] =?UTF-8?q?feat=20:=20softDelete=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20repository=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/like/repository/LikeRepository.java | 8 ++++---- .../domain/post/domain/like/service/LikeService.java | 6 +++--- .../post/domain/scrap/repository/ScrapRepository.java | 6 +++--- .../domain/post/domain/scrap/service/ScrapService.java | 2 +- .../inu/codin/codin/domain/user/service/UserService.java | 6 ++---- .../java/inu/codin/codin/infra/redis/SyncScheduler.java | 6 +++--- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 1dbeecb3..a67f3873 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -14,14 +14,14 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); + long countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); // 특정 엔티티의 좋아요 데이터 조회 - List findByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); + List findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); - boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, ObjectId id, ObjectId userId); LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - Page findAllByUserIdAndLikeTypeOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); + Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 4e41d7ca..e345b0b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -45,7 +45,7 @@ public String toggleLike(LikeRequestDto likeRequestDto) { return "좋아요가 추가되었습니다."; } - public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { + public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId) { LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); if (like != null){ @@ -70,7 +70,7 @@ public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { public void removeLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(like.getLikeType().name(), like.get_id(), like.getUserId()); + redisService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); } like.delete(); likeRepository.save(like); @@ -80,7 +80,7 @@ public int getLikeCount(LikeType entityType, ObjectId entityId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType.name(), entityId); } - long count = likeRepository.countByLikeTypeAndLikeTypeId(entityType, entityId); + long count = likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index ce6d9fb8..75fe507f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -12,11 +12,11 @@ @Repository public interface ScrapRepository extends MongoRepository { - List findByPostId(ObjectId postId); + List findByPostIdAndDeletedAtIsNull(ObjectId postId); - long countByPostId(ObjectId postId); + long countByPostIdAndDeletedAtIsNull(ObjectId postId); ScrapEntity findByPostIdAndUserId(ObjectId postId, ObjectId userId); - Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); + Page findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index 517ff106..dfd8c2e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -75,7 +75,7 @@ public int getScrapCount(ObjectId postId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getScrapCount(postId); } - long count = scrapRepository.countByPostId(postId); + long count = scrapRepository.countByPostIdAndDeletedAtIsNull(postId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 7a6b1ff6..412acd3b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -28,12 +28,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.catalina.User; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.bson.types.ObjectId; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -102,7 +100,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); switch (interactionType) { case LIKE: - Page likePage = likeRepository.findAllByUserIdAndLikeTypeOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); + Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); List postUserLike = likePage.getContent().stream() .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) @@ -110,7 +108,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages()-1, likePage.hasNext()? likePage.getPageable().getPageNumber() + 1 : -1); case SCRAP: - Page scrapPage = scrapRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + Page scrapPage = scrapRepository.findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(userId, pageRequest); List postUserScrap = scrapPage.getContent().stream() .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 06b810d4..773ecc7d 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -69,7 +69,7 @@ private void syncEntityLikes(String entityType, MongoRepository ObjectId likeId = new ObjectId(likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeId); + List dbLikes = likeRepository.findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId().toString())) { log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); @@ -80,7 +80,7 @@ private void syncEntityLikes(String entityType, MongoRepository // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 for (String id : likedUsers) { ObjectId userId = new ObjectId(id); - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeId, userId)) { log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, likeId); LikeEntity dbLike = LikeEntity.builder() .likeType(likeType) @@ -131,7 +131,7 @@ public void syncPostScraps() { ObjectId id = new ObjectId(postId); // MongoDB의 스크랩 데이터 가져오기 - List dbScraps = scrapRepository.findByPostId(id); + List dbScraps = scrapRepository.findByPostIdAndDeletedAtIsNull(id); Set dbScrappedUsers = dbScraps.stream() .map(ScrapEntity::getUserId) .map(ObjectId::toString) From 92f2fe328c3a9f7cde4aee646234f527c8c6b451 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 22:45:43 +0900 Subject: [PATCH 0283/1002] =?UTF-8?q?feat=20:=20Top3=20=EB=B2=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/RedisService.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 49c63d9e..efa01df0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -6,11 +6,14 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -122,16 +125,30 @@ public Set getTopNPosts(int N) { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - Set result = new HashSet<>(); + Map result = new HashMap<>(); for (int i = 0; i < 24; i++) { String redisKey = now.minusHours(i).format(formatter); - Set members = redisTemplate.opsForZSet().reverseRange(redisKey, 0, N - 1); + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); if (members != null) { - result.addAll(members); + for (ZSetOperations.TypedTuple member :members){ + String postId = member.getValue(); + Double score = member.getScore(); + result.put(postId, score); + } } } - return result.stream().limit(N).collect(Collectors.toSet()); + return result.entrySet().stream() + .sorted((e1, e2) -> { + int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); + if (scoreComparison != 0) { + return scoreComparison; + } + return Integer.compare(getHitsCount(new ObjectId(e2.getKey())), getHitsCount(new ObjectId(e1.getKey()))); + }) + .limit(N) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); } public void applyBestScore(int score, ObjectId id){ From dfd309cbf01cce9d02bcecf015f6cdf3fdca1389 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 22:46:51 +0900 Subject: [PATCH 0284/1002] =?UTF-8?q?feat=20:=20Top3=20=EB=B2=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/inu/codin/codin/infra/redis/RedisService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index efa01df0..ba67ae4e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -12,7 +12,6 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; From 9c0fc050fd24ac4655bcaa04cb55638ef0084ac6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 22:47:08 +0900 Subject: [PATCH 0285/1002] =?UTF-8?q?docs=20:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=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 --- .../codin/domain/post/controller/PostController.java | 1 - .../domain/post/domain/comment/entity/CommentEntity.java | 1 - .../post/domain/reply/entity/ReplyCommentEntity.java | 1 - .../post/domain/scrap/controller/ScrapController.java | 8 ++++---- .../inu/codin/codin/domain/post/service/PostService.java | 1 - 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 288f420c..109c4625 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -13,7 +13,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import org.apache.coyote.Response; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 3260b1e7..6df1cd7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 558dad54..8ce269f8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.reply.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 30b6060e..5b45bee4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -1,16 +1,16 @@ package inu.codin.codin.domain.post.domain.scrap.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/scraps") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 8b8cd302..0fcb66e0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -21,7 +21,6 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; From 70952603887f2e37281e78695c523f59304d0e54 Mon Sep 17 00:00:00 2001 From: kbm Date: Sat, 28 Dec 2024 22:48:33 +0900 Subject: [PATCH 0286/1002] =?UTF-8?q?Feat=20:=20NotificationEntity=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=ED=99=9C=EC=9A=A9=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationEntity.java | 24 ++++--- .../repository/NotificationRepository.java | 7 ++- .../service/NotificationService.java | 63 +++++++++++++------ .../fcm/repository/FcmTokenRepository.java | 8 ++- .../codin/infra/fcm/service/FcmService.java | 40 +++++++++++- 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index 154a861d..b1b013c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -4,42 +4,48 @@ import inu.codin.codin.domain.user.entity.UserEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; +import java.time.LocalDateTime; + @Document(collection = "notification") public class NotificationEntity extends BaseTimeEntity { @Id @NotBlank - private String id; + private ObjectId id; @DBRef(lazy = true) private UserEntity user; - private String topic; + private String title; private String message; - // 미사용중 - private String type; - - // 미사용중 private boolean isRead = false; + private LocalDateTime readAt; + + // push, email ... + private String type; + // 알림 중요도 - 미사용중 private String priority; @Builder - public NotificationEntity(UserEntity user, String type, String message, String priority) { + public NotificationEntity(UserEntity user, String title, String message, String type, String priority) { this.user = user; - this.type = type; + this.title = title; this.message = message; + this.type = type; this.priority = priority; } - public void read() { + public void markAsRead() { this.isRead = true; + this.readAt = LocalDateTime.now(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java index 133f97b9..f033952b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java @@ -1,9 +1,14 @@ package inu.codin.codin.domain.notification.repository; import inu.codin.codin.domain.notification.entity.NotificationEntity; +import inu.codin.codin.domain.user.entity.UserEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @Repository -public interface NotificationRepository extends MongoRepository { +public interface NotificationRepository extends MongoRepository { + @Query("{ 'user': ?0, 'isRead': false, deletedAt: null }") + long countUnreadNotificationsByUser(UserEntity user); } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 061dc467..496361a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -17,8 +17,17 @@ @Slf4j public class NotificationService { - private NotificationRepository notificationRepository; - private FcmService fcmService; + private final NotificationRepository notificationRepository; + private final FcmService fcmService; + + /** + * 특정 유저의 읽지 않은 알림 개수를 반환 + * @param user 알림 수신자 + * @return 읽지 않은 알림 개수 + */ + public long getUnreadNotificationCount(UserEntity user) { + return notificationRepository.countUnreadNotificationsByUser(user); + } /** * FCM 메시지를 특정 사용자에게 전송하는 로직 @@ -33,17 +42,23 @@ public void sendFcmMessageToUser(String title, String body, UserEntity user) { .body(body) .build(); - // FCM 메시지 전송 try { fcmService.sendFcmMessage(msgDto); log.info("[sendFcmMessage] 알림 전송 성공"); + saveNotificationLog(msgDto); } catch (Exception e) { log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); } - // 알림 로그 저장 -// saveNotificationLog(msgDto); } + /** + * FCM 메시지를 Topic을 구독한 사람들에게 전송하는 로직 + * @param title + * @param body + * @param data + * @param imageUrl + * @param topic + */ public void sendFcmMessageToTopic(String title, String body, Map data, String imageUrl, String topic) { FcmMessageTopicDto msgDto = FcmMessageTopicDto.builder() .topic(topic) @@ -53,26 +68,36 @@ public void sendFcmMessageToTopic(String title, String body, Map .imageUrl(imageUrl) .build(); - // FCM 메시지 전송 try { fcmService.sendFcmMessageByTopic(msgDto); log.info("[sendFcmMessage] 알림 전송 성공"); + saveNotificationLog(msgDto); } catch (Exception e) { log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); } - // 알림 로그 저장 -// saveNotificationLog(msgDto); } - // todo : 알림로그 저장하는 방식에 대한 고찰 필요 - // 알림 로그를 저장하는 로직 -// private void saveNotificationLog(FcmMessageUserDto msgDto) { -// NotificationEntity notificationEntity = NotificationEntity.builder() -// .user(msgDto.getUser()) -// .type("push") -// .message(msgDto.getBody()) -// .priority("high") -// .build(); -// notificationRepository.save(notificationEntity); -// } + // 알림 로그를 저장하는 로직 (특정 사용자 대상) + private void saveNotificationLog(FcmMessageUserDto msgDto) { + NotificationEntity notificationEntity = NotificationEntity.builder() + .user(msgDto.getUser()) + .title(msgDto.getTitle()) + .message(msgDto.getBody()) + .type("push") + .priority("high") + .build(); + notificationRepository.save(notificationEntity); + } + + // 알림 로그를 저장하는 로직 (토픽 대상) + private void saveNotificationLog(FcmMessageTopicDto msgDto) { + NotificationEntity notificationEntity = NotificationEntity.builder() + .title(msgDto.getTitle()) + .message(msgDto.getBody()) + .type("topic") + .priority("high") + .build(); + notificationRepository.save(notificationEntity); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java index 5b843ae0..ec11c7b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java @@ -1,6 +1,8 @@ package inu.codin.codin.infra.fcm.repository; +import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -8,7 +10,7 @@ import java.util.Optional; @Repository -public interface FcmTokenRepository extends MongoRepository { - @Query("{ 'email': ?0, deletedAt: null }") - Optional findByEmail(String email); +public interface FcmTokenRepository extends MongoRepository { + @Query("{ 'user': ?0, deletedAt: null }") + Optional findByUser(UserEntity user); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index ac2e378c..58827c47 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -158,10 +158,44 @@ private void removeFcmToken(FcmTokenEntity fcmTokenEntity, String fcmToken) { fcmTokenRepository.save(fcmTokenEntity); } - // todo : FCM 토픽 구독 및 구독 해제 로직 추가 - public void unsubscribeTopic(String topic) { - } + /** + * FCM 토픽 구독 로직 + * @param topic 구독할 토픽 이름 + */ public void subscribeTopic(String topic) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userService.getUserEntityFromUserId(userId); + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user) + .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); + + for (String token : fcmTokenEntity.getFcmTokenList()) { + try { + FirebaseMessaging.getInstance().subscribeToTopic(List.of(token), topic); + log.info("FCM 토픽 구독 성공: 토픽={}, 토큰={}", topic, token); + } catch (FirebaseMessagingException e) { + log.error("FCM 토픽 구독 실패: 토픽={}, 토큰={}, 에러={}", topic, token, e.getMessage()); + } + } + } + + /** + * FCM 토픽 구독 해제 로직 + * @param topic 구독 해제할 토픽 이름 + */ + public void unsubscribeTopic(String topic) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userService.getUserEntityFromUserId(userId); + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user) + .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); + + for (String token : fcmTokenEntity.getFcmTokenList()) { + try { + FirebaseMessaging.getInstance().unsubscribeFromTopic(List.of(token), topic); + log.info("FCM 토픽 구독 해제 성공: 토픽={}, 토큰={}", topic, token); + } catch (FirebaseMessagingException e) { + log.error("FCM 토픽 구독 해제 실패: 토픽={}, 토큰={}, 에러={}", topic, token, e.getMessage()); + } + } } } \ No newline at end of file From 58ef595f1b767c1c68ee84ff951f34f17c6b63cc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 28 Dec 2024 22:53:17 +0900 Subject: [PATCH 0287/1002] =?UTF-8?q?feat=20:=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EB=8C=80=EB=8C=93=EA=B8=80=EC=97=90=20=EB=8C=80=ED=95=9C=20sco?= =?UTF-8?q?re=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/comment/service/CommentService.java | 3 +++ .../post/domain/reply/controller/ReplyCommentController.java | 3 +-- .../domain/post/domain/reply/service/ReplyCommentService.java | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 3f32f390..80fc8621 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -16,6 +16,7 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -37,6 +38,7 @@ public class CommentService { private final UserRepository userRepository; private final LikeService likeService; private final ReplyCommentService replyCommentService; + private final RedisService redisService; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -55,6 +57,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 수 증가 post.updateCommentCount(post.getCommentCount() + 1); + redisService.applyBestScore(1, postId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index 6b0f00c4..b2529210 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -1,10 +1,9 @@ package inu.codin.codin.domain.post.domain.reply.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index b677b671..2460207c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -15,6 +15,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.RedisService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -34,7 +35,9 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; + private final LikeService likeService; + private final RedisService redisService; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -58,6 +61,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { // 댓글 수 증가 (대댓글도 댓글 수에 포함) log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); post.updateCommentCount(post.getCommentCount() + 1); + redisService.applyBestScore(1, post.get_id()); postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); } From b2f38e2cfc4dd1609fcf94f61d67bab9c2c0cd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=EA=B5=AD?= <121088189+X1n9fU@users.noreply.github.com> Date: Sat, 28 Dec 2024 23:04:43 +0900 Subject: [PATCH 0288/1002] =?UTF-8?q?Revert=20"SC-102=20Top3=20Best=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B0=98=ED=99=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/BaseTimeEntity.java | 4 -- .../post/controller/PostController.java | 8 --- .../domain/comment/entity/CommentEntity.java | 1 + .../comment/service/CommentService.java | 3 - .../like/repository/LikeRepository.java | 12 ++-- .../post/domain/like/service/LikeService.java | 63 +++++++++---------- .../controller/ReplyCommentController.java | 3 +- .../reply/entity/ReplyCommentEntity.java | 1 + .../reply/service/ReplyCommentService.java | 4 -- .../scrap/controller/ScrapController.java | 8 +-- .../scrap/repository/ScrapRepository.java | 12 ++-- .../domain/scrap/service/ScrapService.java | 57 +++++++++-------- .../domain/post/service/PostService.java | 11 +--- .../domain/user/service/UserService.java | 5 +- .../codin/codin/infra/redis/RedisService.java | 53 ---------------- .../codin/infra/redis/SyncScheduler.java | 6 +- codin-core/src/main/resources | 2 +- 17 files changed, 92 insertions(+), 161 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java index b10b05a2..62ebd48e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -28,8 +28,4 @@ public void restore() { this.deletedAt = null; } - public void recreatedAt(){ - this.createdAt = LocalDateTime.now(); - } - } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 109c4625..ee4f11be 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.post.controller; -import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; @@ -124,11 +123,4 @@ public ResponseEntity> softDeletePost(@PathVariable String pos return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); } - - @Operation(summary = "Top 3 베스트 게시글 가져오기") - @GetMapping("/top3") - public ResponseEntity> getTop3BestPosts(){ - return ResponseEntity.ok() - .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postService.getTop3BestPosts())); - } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 6df1cd7f..3260b1e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 80fc8621..3f32f390 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -16,7 +16,6 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -38,7 +37,6 @@ public class CommentService { private final UserRepository userRepository; private final LikeService likeService; private final ReplyCommentService replyCommentService; - private final RedisService redisService; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -57,7 +55,6 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 수 증가 post.updateCommentCount(post.getCommentCount() + 1); - redisService.applyBestScore(1, postId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index a67f3873..00e373bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -14,14 +15,15 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); + long countByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); // 특정 엔티티의 좋아요 데이터 조회 - List findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); + List findByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); - boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, ObjectId id, ObjectId userId); + // 특정 사용자의 좋아요 삭제 + void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); + Page findAllByUserIdAndLikeTypeOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index e345b0b6..6a57ac70 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -5,8 +5,9 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; +import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; @@ -16,6 +17,7 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import org.springframework.validation.ObjectError; @Service @RequiredArgsConstructor @@ -35,52 +37,49 @@ public String toggleLike(LikeRequestDto likeRequestDto) { isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 - LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + boolean alreadyLiked = likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - if (like != null && like.getDeletedAt() == null) { - removeLike(like); - return "좋아요가 삭제되었습니다."; + if (alreadyLiked) { + removeLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 삭제되었습니다"; + } else { + addLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 추가되었습니다."; } - addLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 추가되었습니다."; } - public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId) { - LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); - - if (like != null){ - if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 - like.recreatedAt(); - like.restore(); - likeRepository.save(like); - } else throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); - } else { //좋아요 내역이 없으면 새로 생성 - if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeType.name(), likeId, userId); - } - likeRepository.save(LikeEntity.builder() - .likeType(likeType) - .likeTypeId(likeId) - .userId(userId) - .build()); - if (likeType == LikeType.POST) - redisService.applyBestScore(1, likeId); + public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { + // 중복 좋아요 검증 + if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { + throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); + } + if (redisHealthChecker.isRedisAvailable()) { + redisService.addLike(likeType.name(), likeId, userId); } + LikeEntity like = LikeEntity.builder() + .likeType(likeType) + .likeTypeId(likeId) + .userId(userId) + .build(); + likeRepository.save(like); } - public void removeLike(LikeEntity like) { + public void removeLike(LikeType likeType,ObjectId likeId, ObjectId userId) { + // 없는 좋아요 방지 + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { + throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); + } if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); + redisService.removeLike(likeType.name(), likeId, userId); } - like.delete(); - likeRepository.save(like); + likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); } public int getLikeCount(LikeType entityType, ObjectId entityId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType.name(), entityId); } - long count = likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); + long count = likeRepository.countByLikeTypeAndLikeTypeId(entityType, entityId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index b2529210..6b0f00c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -1,9 +1,10 @@ package inu.codin.codin.domain.post.domain.reply.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 8ce269f8..558dad54 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.domain.reply.entity; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 2460207c..b677b671 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -15,7 +15,6 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.RedisService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,9 +34,7 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; - private final LikeService likeService; - private final RedisService redisService; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -61,7 +58,6 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { // 댓글 수 증가 (대댓글도 댓글 수에 포함) log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); post.updateCommentCount(post.getCommentCount() + 1); - redisService.applyBestScore(1, post.get_id()); postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 5b45bee4..30b6060e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -1,16 +1,16 @@ package inu.codin.codin.domain.post.domain.scrap.controller; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/scraps") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 75fe507f..4af571f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -1,5 +1,7 @@ package inu.codin.codin.domain.post.domain.scrap.repository; +import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; @@ -12,11 +14,13 @@ @Repository public interface ScrapRepository extends MongoRepository { - List findByPostIdAndDeletedAtIsNull(ObjectId postId); + List findByPostId(ObjectId postId); - long countByPostIdAndDeletedAtIsNull(ObjectId postId); + long countByPostId(ObjectId postId); - ScrapEntity findByPostIdAndUserId(ObjectId postId, ObjectId userId); + void deleteByPostIdAndUserId(ObjectId postId, ObjectId userId); - Page findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); + boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); + + Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index dfd8c2e4..4872accd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -2,8 +2,10 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; +import inu.codin.codin.domain.post.domain.scrap.exception.ScrapRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; @@ -31,51 +33,52 @@ public String toggleScrap(String id) { ObjectId userId = SecurityUtils.getCurrentUserId(); // 이미 스크랩한 게시물인지 확인 - ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); + boolean alreadyScrapped = scrapRepository.existsByPostIdAndUserId(postId, userId); - if (scrap != null && scrap.getDeletedAt() == null) { - removeScrap(scrap); + if (alreadyScrapped) { + removeScrap(postId, userId); return "스크랩이 취소되었습니다. "; + } else { + addScrap(postId, userId); + return "스크랩이 추가되었습니다. "; } - addScrap(postId, userId); - return "스크랩이 추가되었습니다. "; - } private void addScrap(ObjectId postId, ObjectId userId) { - ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); + // 중복 스크랩 방지 + if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { + throw new ScrapCreateFailException("이미 스크랩한 게시물입니다."); + } - if (scrap != null){ - if (scrap.getDeletedAt() != null){ - scrap.recreatedAt(); - scrap.restore(); - scrapRepository.save(scrap); - } else throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); - } else { - if (redisHealthChecker.isRedisAvailable()) { - redisService.addScrap(postId, userId); - } - scrapRepository.save(ScrapEntity.builder() - .postId(postId) - .userId(userId) - .build()); - redisService.applyBestScore(2, postId); + if (redisHealthChecker.isRedisAvailable()) { + redisService.addScrap(postId, userId); } + + ScrapEntity scrap = ScrapEntity.builder() + .postId(postId) + .userId(userId) + .build(); + scrapRepository.save(scrap); } - private void removeScrap(ScrapEntity scrap) { + private void removeScrap(ObjectId postId, ObjectId userId) { + // 스크랩하지 않은 게시물이라면 오류 처리 + if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { + throw new ScrapRemoveFailException("스크랩한 적이 없는 게시물입니다."); + } + if (redisHealthChecker.isRedisAvailable()) { - redisService.removeScrap(scrap.getPostId(), scrap.getUserId()); + redisService.removeScrap(postId, userId); } - scrap.delete(); - scrapRepository.save(scrap); + + scrapRepository.deleteByPostIdAndUserId(postId, userId); } public int getScrapCount(ObjectId postId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getScrapCount(postId); } - long count = scrapRepository.countByPostIdAndDeletedAtIsNull(postId); + long count = scrapRepository.countByPostId(postId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 0fcb66e0..07ddd900 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -21,6 +21,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; @@ -35,7 +36,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; @Service @@ -223,14 +223,5 @@ public String getNicknameByUserId(ObjectId userId) { return user.getNickname(); } - public List getTop3BestPosts() { - Set postIds = redisService.getTopNPosts(3); - List bestPosts = postIds.stream() - .map(postId -> - postRepository.findById(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")) - ).toList(); - return getPostListResponseDtos(bestPosts); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 6870117f..496c4548 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -33,6 +33,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.bson.types.ObjectId; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -107,7 +108,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); switch (interactionType) { case LIKE: - Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); + Page likePage = likeRepository.findAllByUserIdAndLikeTypeOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); List postUserLike = likePage.getContent().stream() .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) @@ -115,7 +116,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages()-1, likePage.hasNext()? likePage.getPageable().getPageNumber() + 1 : -1); case SCRAP: - Page scrapPage = scrapRepository.findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(userId, pageRequest); + Page scrapPage = scrapRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); List postUserScrap = scrapPage.getContent().stream() .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index ba67ae4e..48c96358 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -6,13 +6,8 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -118,52 +113,4 @@ public Set getHitsUser(ObjectId postId) { String redisKey = HITS_KEY + postId.toString(); return redisTemplate.opsForSet().members(redisKey); } - - // Top N 게시물 조회 - public Set getTopNPosts(int N) { - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - - Map result = new HashMap<>(); - for (int i = 0; i < 24; i++) { - String redisKey = now.minusHours(i).format(formatter); - Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); - if (members != null) { - for (ZSetOperations.TypedTuple member :members){ - String postId = member.getValue(); - Double score = member.getScore(); - result.put(postId, score); - } - } - } - - return result.entrySet().stream() - .sorted((e1, e2) -> { - int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); - if (scoreComparison != 0) { - return scoreComparison; - } - return Integer.compare(getHitsCount(new ObjectId(e2.getKey())), getHitsCount(new ObjectId(e1.getKey()))); - }) - .limit(N) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - } - - public void applyBestScore(int score, ObjectId id){ - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - - String redisKey; - for (int i=0; i<24; i++){ - redisKey = now.minusHours(i).format(formatter); - Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, id.toString()); - if (scoreOfBest != null){ - redisTemplate.opsForZSet().incrementScore(redisKey, id.toString(), score); - return; - } - } - redisKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); - redisTemplate.opsForZSet().add(redisKey, id.toString(), score); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 773ecc7d..06b810d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -69,7 +69,7 @@ private void syncEntityLikes(String entityType, MongoRepository ObjectId likeId = new ObjectId(likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeId); + List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId().toString())) { log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); @@ -80,7 +80,7 @@ private void syncEntityLikes(String entityType, MongoRepository // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 for (String id : likedUsers) { ObjectId userId = new ObjectId(id); - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeId, userId)) { + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, likeId); LikeEntity dbLike = LikeEntity.builder() .likeType(likeType) @@ -131,7 +131,7 @@ public void syncPostScraps() { ObjectId id = new ObjectId(postId); // MongoDB의 스크랩 데이터 가져오기 - List dbScraps = scrapRepository.findByPostIdAndDeletedAtIsNull(id); + List dbScraps = scrapRepository.findByPostId(id); Set dbScrappedUsers = dbScraps.stream() .map(ScrapEntity::getUserId) .map(ObjectId::toString) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 136b0f56..56ed44cb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 136b0f562fad2480e9194f2e7467a0dda1991178 +Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a From 2aecc2cc50906d5b4caaa5c51d4b4312a9f27414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=EA=B5=AD?= <121088189+X1n9fU@users.noreply.github.com> Date: Sat, 28 Dec 2024 23:26:19 +0900 Subject: [PATCH 0289/1002] =?UTF-8?q?Revert=20"Revert=20"SC-102=20Top3=20B?= =?UTF-8?q?est=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B0=98=ED=99=98""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/BaseTimeEntity.java | 4 ++ .../post/controller/PostController.java | 8 +++ .../domain/comment/entity/CommentEntity.java | 1 - .../comment/service/CommentService.java | 3 + .../like/repository/LikeRepository.java | 12 ++-- .../post/domain/like/service/LikeService.java | 63 ++++++++++--------- .../controller/ReplyCommentController.java | 3 +- .../reply/entity/ReplyCommentEntity.java | 1 - .../reply/service/ReplyCommentService.java | 4 ++ .../scrap/controller/ScrapController.java | 8 +-- .../scrap/repository/ScrapRepository.java | 12 ++-- .../domain/scrap/service/ScrapService.java | 57 ++++++++--------- .../domain/post/service/PostService.java | 11 +++- .../domain/user/service/UserService.java | 5 +- .../codin/codin/infra/redis/RedisService.java | 53 ++++++++++++++++ .../codin/infra/redis/SyncScheduler.java | 6 +- codin-core/src/main/resources | 2 +- 17 files changed, 161 insertions(+), 92 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java index 62ebd48e..b10b05a2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -28,4 +28,8 @@ public void restore() { this.deletedAt = null; } + public void recreatedAt(){ + this.createdAt = LocalDateTime.now(); + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index ee4f11be..109c4625 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.controller; +import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; @@ -123,4 +124,11 @@ public ResponseEntity> softDeletePost(@PathVariable String pos return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); } + + @Operation(summary = "Top 3 베스트 게시글 가져오기") + @GetMapping("/top3") + public ResponseEntity> getTop3BestPosts(){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postService.getTop3BestPosts())); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 3260b1e7..6df1cd7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 3f32f390..80fc8621 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -16,6 +16,7 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -37,6 +38,7 @@ public class CommentService { private final UserRepository userRepository; private final LikeService likeService; private final ReplyCommentService replyCommentService; + private final RedisService redisService; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -55,6 +57,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 수 증가 post.updateCommentCount(post.getCommentCount() + 1); + redisService.applyBestScore(1, postId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java index 00e373bf..a67f3873 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java @@ -4,7 +4,6 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -15,15 +14,14 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); + long countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); // 특정 엔티티의 좋아요 데이터 조회 - List findByLikeTypeAndLikeTypeId(LikeType likeType, ObjectId id); + List findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); - // 특정 사용자의 좋아요 삭제 - void deleteByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, ObjectId id, ObjectId userId); - boolean existsByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - Page findAllByUserIdAndLikeTypeOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); + Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index 6a57ac70..e345b0b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -5,9 +5,8 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; -import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; @@ -17,7 +16,6 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import org.springframework.validation.ObjectError; @Service @RequiredArgsConstructor @@ -37,49 +35,52 @@ public String toggleLike(LikeRequestDto likeRequestDto) { isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 - boolean alreadyLiked = likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - if (alreadyLiked) { - removeLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 삭제되었습니다"; - } else { - addLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 추가되었습니다."; + if (like != null && like.getDeletedAt() == null) { + removeLike(like); + return "좋아요가 삭제되었습니다."; } + addLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 추가되었습니다."; } - public void addLike(LikeType likeType,ObjectId likeId, ObjectId userId) { - // 중복 좋아요 검증 - if (likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { - throw new LikeCreateFailException("이미 좋아요를 누른 상태입니다."); - } - if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeType.name(), likeId, userId); + public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId) { + LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); + + if (like != null){ + if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 + like.recreatedAt(); + like.restore(); + likeRepository.save(like); + } else throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); + } else { //좋아요 내역이 없으면 새로 생성 + if (redisHealthChecker.isRedisAvailable()) { + redisService.addLike(likeType.name(), likeId, userId); + } + likeRepository.save(LikeEntity.builder() + .likeType(likeType) + .likeTypeId(likeId) + .userId(userId) + .build()); + if (likeType == LikeType.POST) + redisService.applyBestScore(1, likeId); } - LikeEntity like = LikeEntity.builder() - .likeType(likeType) - .likeTypeId(likeId) - .userId(userId) - .build(); - likeRepository.save(like); } - public void removeLike(LikeType likeType,ObjectId likeId, ObjectId userId) { - // 없는 좋아요 방지 - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { - throw new LikeRemoveFailException(" 좋아요를 누른적이 없습니다."); - } + public void removeLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(likeType.name(), likeId, userId); + redisService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); } - likeRepository.deleteByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); + like.delete(); + likeRepository.save(like); } public int getLikeCount(LikeType entityType, ObjectId entityId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getLikeCount(entityType.name(), entityId); } - long count = likeRepository.countByLikeTypeAndLikeTypeId(entityType, entityId); + long count = likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index 6b0f00c4..b2529210 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -1,10 +1,9 @@ package inu.codin.codin.domain.post.domain.reply.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 558dad54..8ce269f8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.reply.entity; import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index b677b671..2460207c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -15,6 +15,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.RedisService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -34,7 +35,9 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; + private final LikeService likeService; + private final RedisService redisService; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -58,6 +61,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { // 댓글 수 증가 (대댓글도 댓글 수에 포함) log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); post.updateCommentCount(post.getCommentCount() + 1); + redisService.applyBestScore(1, post.get_id()); postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java index 30b6060e..5b45bee4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java @@ -1,16 +1,16 @@ package inu.codin.codin.domain.post.domain.scrap.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/scraps") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java index 4af571f3..75fe507f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java @@ -1,7 +1,5 @@ package inu.codin.codin.domain.post.domain.scrap.repository; -import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; @@ -14,13 +12,11 @@ @Repository public interface ScrapRepository extends MongoRepository { - List findByPostId(ObjectId postId); + List findByPostIdAndDeletedAtIsNull(ObjectId postId); - long countByPostId(ObjectId postId); + long countByPostIdAndDeletedAtIsNull(ObjectId postId); - void deleteByPostIdAndUserId(ObjectId postId, ObjectId userId); + ScrapEntity findByPostIdAndUserId(ObjectId postId, ObjectId userId); - boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); - - Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); + Page findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index 4872accd..dfd8c2e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -2,10 +2,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.like.exception.LikeRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; -import inu.codin.codin.domain.post.domain.scrap.exception.ScrapRemoveFailException; import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; @@ -33,52 +31,51 @@ public String toggleScrap(String id) { ObjectId userId = SecurityUtils.getCurrentUserId(); // 이미 스크랩한 게시물인지 확인 - boolean alreadyScrapped = scrapRepository.existsByPostIdAndUserId(postId, userId); + ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); - if (alreadyScrapped) { - removeScrap(postId, userId); + if (scrap != null && scrap.getDeletedAt() == null) { + removeScrap(scrap); return "스크랩이 취소되었습니다. "; - } else { - addScrap(postId, userId); - return "스크랩이 추가되었습니다. "; } + addScrap(postId, userId); + return "스크랩이 추가되었습니다. "; + } private void addScrap(ObjectId postId, ObjectId userId) { - // 중복 스크랩 방지 - if (scrapRepository.existsByPostIdAndUserId(postId, userId)) { - throw new ScrapCreateFailException("이미 스크랩한 게시물입니다."); - } + ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); - if (redisHealthChecker.isRedisAvailable()) { - redisService.addScrap(postId, userId); + if (scrap != null){ + if (scrap.getDeletedAt() != null){ + scrap.recreatedAt(); + scrap.restore(); + scrapRepository.save(scrap); + } else throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); + } else { + if (redisHealthChecker.isRedisAvailable()) { + redisService.addScrap(postId, userId); + } + scrapRepository.save(ScrapEntity.builder() + .postId(postId) + .userId(userId) + .build()); + redisService.applyBestScore(2, postId); } - - ScrapEntity scrap = ScrapEntity.builder() - .postId(postId) - .userId(userId) - .build(); - scrapRepository.save(scrap); } - private void removeScrap(ObjectId postId, ObjectId userId) { - // 스크랩하지 않은 게시물이라면 오류 처리 - if (!scrapRepository.existsByPostIdAndUserId(postId, userId)) { - throw new ScrapRemoveFailException("스크랩한 적이 없는 게시물입니다."); - } - + private void removeScrap(ScrapEntity scrap) { if (redisHealthChecker.isRedisAvailable()) { - redisService.removeScrap(postId, userId); + redisService.removeScrap(scrap.getPostId(), scrap.getUserId()); } - - scrapRepository.deleteByPostIdAndUserId(postId, userId); + scrap.delete(); + scrapRepository.save(scrap); } public int getScrapCount(ObjectId postId) { if (redisHealthChecker.isRedisAvailable()) { return redisService.getScrapCount(postId); } - long count = scrapRepository.countByPostId(postId); + long count = scrapRepository.countByPostIdAndDeletedAtIsNull(postId); return (int) Math.max(0, count); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 07ddd900..0fcb66e0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -21,7 +21,6 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; @@ -36,6 +35,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Service @@ -223,5 +223,14 @@ public String getNicknameByUserId(ObjectId userId) { return user.getNickname(); } + public List getTop3BestPosts() { + Set postIds = redisService.getTopNPosts(3); + List bestPosts = postIds.stream() + .map(postId -> + postRepository.findById(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")) + ).toList(); + return getPostListResponseDtos(bestPosts); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 496c4548..6870117f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -33,7 +33,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.bson.types.ObjectId; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -108,7 +107,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); switch (interactionType) { case LIKE: - Page likePage = likeRepository.findAllByUserIdAndLikeTypeOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); + Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); List postUserLike = likePage.getContent().stream() .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) @@ -116,7 +115,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages()-1, likePage.hasNext()? likePage.getPageable().getPageNumber() + 1 : -1); case SCRAP: - Page scrapPage = scrapRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); + Page scrapPage = scrapRepository.findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(userId, pageRequest); List postUserScrap = scrapPage.getContent().stream() .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 48c96358..ba67ae4e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -6,8 +6,13 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -113,4 +118,52 @@ public Set getHitsUser(ObjectId postId) { String redisKey = HITS_KEY + postId.toString(); return redisTemplate.opsForSet().members(redisKey); } + + // Top N 게시물 조회 + public Set getTopNPosts(int N) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + Map result = new HashMap<>(); + for (int i = 0; i < 24; i++) { + String redisKey = now.minusHours(i).format(formatter); + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); + if (members != null) { + for (ZSetOperations.TypedTuple member :members){ + String postId = member.getValue(); + Double score = member.getScore(); + result.put(postId, score); + } + } + } + + return result.entrySet().stream() + .sorted((e1, e2) -> { + int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); + if (scoreComparison != 0) { + return scoreComparison; + } + return Integer.compare(getHitsCount(new ObjectId(e2.getKey())), getHitsCount(new ObjectId(e1.getKey()))); + }) + .limit(N) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + public void applyBestScore(int score, ObjectId id){ + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + String redisKey; + for (int i=0; i<24; i++){ + redisKey = now.minusHours(i).format(formatter); + Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, id.toString()); + if (scoreOfBest != null){ + redisTemplate.opsForZSet().incrementScore(redisKey, id.toString(), score); + return; + } + } + redisKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); + redisTemplate.opsForZSet().add(redisKey, id.toString(), score); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 06b810d4..773ecc7d 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -69,7 +69,7 @@ private void syncEntityLikes(String entityType, MongoRepository ObjectId likeId = new ObjectId(likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByLikeTypeAndLikeTypeId(likeType, likeId); + List dbLikes = likeRepository.findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId().toString())) { log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); @@ -80,7 +80,7 @@ private void syncEntityLikes(String entityType, MongoRepository // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 for (String id : likedUsers) { ObjectId userId = new ObjectId(id); - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId)) { + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeId, userId)) { log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, likeId); LikeEntity dbLike = LikeEntity.builder() .likeType(likeType) @@ -131,7 +131,7 @@ public void syncPostScraps() { ObjectId id = new ObjectId(postId); // MongoDB의 스크랩 데이터 가져오기 - List dbScraps = scrapRepository.findByPostId(id); + List dbScraps = scrapRepository.findByPostIdAndDeletedAtIsNull(id); Set dbScrappedUsers = dbScraps.stream() .map(ScrapEntity::getUserId) .map(ObjectId::toString) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 56ed44cb..136b0f56 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a +Subproject commit 136b0f562fad2480e9194f2e7467a0dda1991178 From bc8f0a54c05eeff1f2133198e4715d08004a4d5f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 28 Dec 2024 23:55:33 +0900 Subject: [PATCH 0290/1002] =?UTF-8?q?[SC-103]=20Feat=20::=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C(Poll)=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../poll/controller/PollController.java | 42 +++++++ .../domain/poll/dto/PollCreateRequestDTO.java | 47 ++++++++ .../domain/poll/dto/PollVotingRequestDTO.java | 16 +++ .../post/domain/poll/entity/PollEntity.java | 47 ++++++++ .../exception/PollOptionChoiceException.java | 7 ++ .../poll/exception/PollTimeFailException.java | 8 ++ .../poll/repository/PollRepository.java | 14 +++ .../post/domain/poll/service/PollService.java | 105 ++++++++++++++++++ .../response/PostPollDetailResponseDTO.java | 50 +++++++++ .../domain/post/entity/PostCategory.java | 4 +- .../domain/post/service/PostService.java | 65 ++++++++++- 11 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java new file mode 100644 index 00000000..49768ef0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.post.domain.poll.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; + +@RestController +@RequestMapping("/api/polls") +@RequiredArgsConstructor +public class PollController { + + private final PollService pollService; + + @Operation(summary = "투표 생성") + @PostMapping + public ResponseEntity createPoll( + @Validated @RequestBody PollCreateRequestDTO pollRequestDTO) { + + pollService.createPoll(pollRequestDTO); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "투표 생성 완료", null)); + } + + @Operation(summary = "투표 실시") + @PostMapping("/voting/{postId}") + public ResponseEntity votingPoll( + @PathVariable String postId, + @Validated @RequestBody PollVotingRequestDTO pollRequestDTO) { + + pollService.votingPoll(postId, pollRequestDTO); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "투표 실시 완료", null)); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java new file mode 100644 index 00000000..80c9626b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java @@ -0,0 +1,47 @@ +package inu.codin.codin.domain.post.domain.poll.dto; + +import inu.codin.codin.domain.post.entity.PostCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + + +@Getter +@NoArgsConstructor +public class PollCreateRequestDTO { + + @Schema(description = "투표 제목", example = "투표 제목") + @NotBlank + //투표 제목 = 게시글 제목 + private String title; + + @Schema(description = "투표 내용", example = "투표 내용") + @NotBlank + //투표 내용 = 게시글 내용 + private String content; + + @Schema(description = "투표 옵션 리스트", example = "[\"a\", \"b\", \"c\"]") + @NotNull + private List<@NotBlank String> pollOptions; + + @Schema(description = "복수 선택 가능 여부", example = "false") + private boolean multipleChoice; + + @Schema(description = "설문조사 종료 시간 (ISO8601 format)", example = "2024-01-21T23:59:59") + @NotNull + private LocalDateTime pollEndTime; + + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") + @NotNull + private boolean anonymous; + + @Schema(description = "게시물 종류", example = "POLL") + @NotNull + private PostCategory postCategory; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java new file mode 100644 index 00000000..1cfb97fb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.post.domain.poll.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class PollVotingRequestDTO { + + @Schema(description = "사용자가 선택한 옵션 인덱스 리스트 (복수 투표 가능)", example = "[1, 2]") + @NotNull + private List selectedOptions; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java new file mode 100644 index 00000000..ddcd8904 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -0,0 +1,47 @@ +package inu.codin.codin.domain.post.domain.poll.entity; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Document(collection = "polls") +@Getter +public class PollEntity { + @Id + @NotBlank + private ObjectId _id; + + private ObjectId postId; // PostEntity와의 관계를 유지하기 위한 필드 + private List pollOptions = new ArrayList<>(); // 설문조사 선택지 + private List pollVotes = new ArrayList<>(); // 선택지별 투표 수 + //Map 구조로 변환 + private LocalDateTime pollEndTime; // 설문조사 종료 시간 + private boolean multipleChoice; // 복수 선택 가능 여부 + + @Builder + public PollEntity(ObjectId _id, ObjectId postId, List pollOptions, + LocalDateTime pollEndTime, boolean multipleChoice) { + this._id = _id; + this.postId = postId; + this.pollOptions = pollOptions; + this.pollVotes = pollOptions != null ? new ArrayList<>(pollOptions.stream().map(option -> 0).toList()) : new ArrayList<>(); + this.pollEndTime = pollEndTime; + this.multipleChoice = multipleChoice; + } + + + //각 옵션의 투표 수 증가 + public void vote(int optionIndex) { + if (optionIndex < 0 || optionIndex >= pollOptions.size()) { + throw new IllegalArgumentException("잘못된 선택지입니다."); + } + pollVotes.set(optionIndex, pollVotes.get(optionIndex) + 1); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java new file mode 100644 index 00000000..b9ec960a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.domain.poll.exception; + +public class PollOptionChoiceException extends RuntimeException{ + public PollOptionChoiceException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java new file mode 100644 index 00000000..ba4cce7f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java @@ -0,0 +1,8 @@ +package inu.codin.codin.domain.post.domain.poll.exception; + + +public class PollTimeFailException extends RuntimeException{ + public PollTimeFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java new file mode 100644 index 00000000..2eb58387 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.post.domain.poll.repository; + +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PollRepository extends MongoRepository { + Optional findByPostId(ObjectId postId); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java new file mode 100644 index 00000000..cf5fb55a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -0,0 +1,105 @@ +package inu.codin.codin.domain.post.domain.poll.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; + +import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; +import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.user.entity.UserRole; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PollService { + + private final PostRepository postRepository; + private final PollRepository pollRepository; + + @Transactional + public void createPoll(PollCreateRequestDTO pollRequestDTO) { + + ObjectId userId = SecurityUtils.getCurrentUserId(); + //권한 확인 처리 로직 추가 + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + pollRequestDTO.getPostCategory().toString().split("_")[0].equals("POLL")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표에 대한 권한이 없습니다."); + } + + // 1. PostEntity 생성 및 저장 + PostEntity postEntity = PostEntity.builder() + .title(pollRequestDTO.getTitle()) + .content(pollRequestDTO.getContent()) + .userId(userId) + .postCategory(pollRequestDTO.getPostCategory()) + .isAnonymous(pollRequestDTO.isAnonymous()) + .postStatus(PostStatus.ACTIVE) + .build(); + postEntity = postRepository.save(postEntity); + + // 2. PollEntity 생성 및 저장 + PollEntity pollEntity = PollEntity.builder() + .postId(postEntity.get_id()) + .pollOptions(pollRequestDTO.getPollOptions()) + .pollEndTime(pollRequestDTO.getPollEndTime()) + .multipleChoice(pollRequestDTO.isMultipleChoice()) + .build(); + pollRepository.save(pollEntity); + } + + public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { + // 1. 게시글 조회 및 검증 + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("해당 게시물을 찾을 수 없습니다.")); + validateUserAndPost(post); + + // 2. 투표 데이터(PollEntity) 조회 + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 데이터가 존재하지 않습니다.")); + + // 3. 투표 종료 여부 확인 + if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { + throw new PollTimeFailException("이미 종료된 투표입니다."); + } + + // 4. 사용자의 선택 항목 검증 + List selectedOptions = pollRequestDTO.getSelectedOptions(); + if (!poll.isMultipleChoice() && selectedOptions.size() > 1) { + throw new PollOptionChoiceException("복수 선택이 허용되지 않은 투표입니다."); + } + for (Integer index : selectedOptions) { + if (index < 0 || index >= poll.getPollOptions().size()) { + throw new PollOptionChoiceException("잘못된 선택지입니다."); + } + } + + // 5. 투표 항목 DB 반영 + for (Integer index : selectedOptions) { + poll.vote(index); + } + pollRepository.save(poll); + } + + private void validateUserAndPost(PostEntity post) { + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("POLL")){ + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표 게시글에 대한 권한이 없습니다."); + } + SecurityUtils.validateUser(post.getUserId()); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java new file mode 100644 index 00000000..9939b2d4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -0,0 +1,50 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.post.entity.PostCategory; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +public class PostPollDetailResponseDTO extends PostDetailResponseDTO { + + // Poll 관련 필드 + private List pollOptions; + private LocalDateTime pollEndTime; + private boolean multipleChoice; + private List pollVotes; // 선택지별 투표 수 + private boolean isPollFinished; + + public PostPollDetailResponseDTO( + String userId, + String postId, + String title, + String content, + String nickname, + boolean isAnonymous, + int likeCount, + int scrapCount, + int hitsCount, + LocalDateTime createdAt, + int commentCount, + UserInfo userInfo, + List pollOptions, + LocalDateTime pollEndTime, + boolean multipleChoice, + List pollVotes, + boolean isPollFinished + ) { + // 상위 클래스(PostDetailResponseDTO)의 생성자 호출 + super(userId, postId, title, content, nickname, PostCategory.POLL, null, isAnonymous, likeCount, scrapCount, hitsCount, createdAt, commentCount, userInfo); + + this.pollOptions = pollOptions; + this.pollEndTime = pollEndTime; + this.multipleChoice = multipleChoice; + this.pollVotes = pollVotes; + this.isPollFinished = isPollFinished; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 3d42e4d4..19118be5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -19,7 +19,9 @@ public enum PostCategory { EXTRACURRICULAR("비교과"), EXTRACURRICULAR_STARINU("비교과_STARINU"), EXTRACURRICULAR_OUTER("비교과_교외"), - EXTRACURRICULAR_INNER("비교과_교내"); + EXTRACURRICULAR_INNER("비교과_교내"), + + POLL("설문조사"); private final String description; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 07ddd900..0f6d1e24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -6,6 +6,8 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; @@ -14,6 +16,7 @@ import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO.UserInfo; import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.PostPollDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; @@ -33,6 +36,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -48,6 +52,7 @@ public class PostService { private final ScrapService scrapService; private final RedisService redisService; private final UserRepository userRepository; + private final PollRepository pollRepository; public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { List imageUrls = s3Service.handleImageUpload(postImages); @@ -157,10 +162,66 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); + if (redisService.validateHits(post.get_id(), userId)) redisService.addHits(post.get_id(), userId); String nickname = post.isAnonymous() ? "익명" : getNicknameByUserId(post.getUserId()); + + // POLL 게시물 처리 + if (post.getPostCategory().equals(PostCategory.POLL)) { + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); + + // 투표 종료 여부 판단 + boolean hasPollFinished = LocalDateTime.now().isAfter(poll.getPollEndTime()); + + if (!hasPollFinished) { + // 투표 진행 중 + return new PostPollDetailResponseDTO( + post.getUserId().toString(), + post.get_id().toString(), + post.getTitle(), + post.getContent(), + nickname, + post.isAnonymous(), + likeService.getLikeCount(LikeType.POST, post.get_id()), + scrapService.getScrapCount(post.get_id()), + redisService.getHitsCount(post.get_id()), + post.getCreatedAt(), + post.getCommentCount(), + getUserInfoAboutPost(post.get_id()), + poll.getPollOptions(), + poll.getPollEndTime(), + poll.isMultipleChoice(), + null, // 진행 중이므로 투표 수는 반환하지 않음 + false // 투표 종료 여부 + ); + } else { + // 투표 종료 + return new PostPollDetailResponseDTO( + post.getUserId().toString(), + post.get_id().toString(), + post.getTitle(), + post.getContent(), + nickname, + post.isAnonymous(), + likeService.getLikeCount(LikeType.POST, post.get_id()), + scrapService.getScrapCount(post.get_id()), + redisService.getHitsCount(post.get_id()), + post.getCreatedAt(), + post.getCommentCount(), + getUserInfoAboutPost(post.get_id()), + poll.getPollOptions(), + poll.getPollEndTime(), + poll.isMultipleChoice(), + poll.getPollVotes(), // 투표 종료 시 투표 수 반환 + true // 투표 종료 여부 + ); + } + } + + // 5. 일반 게시물 처리 return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), @@ -170,8 +231,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { post.getPostCategory(), post.getPostImageUrls(), post.isAnonymous(), - likeService.getLikeCount(LikeType.valueOf("POST"),post.get_id()), // 좋아요 수 - scrapService.getScrapCount(post.get_id()), // 스크랩 수 + likeService.getLikeCount(LikeType.POST, post.get_id()), + scrapService.getScrapCount(post.get_id()), redisService.getHitsCount(post.get_id()), post.getCreatedAt(), post.getCommentCount(), From 298079d00cac25e9890d1d5e9bdb2d16f8376ca3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 00:10:04 +0900 Subject: [PATCH 0291/1002] =?UTF-8?q?perf=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=95=99=EB=B2=88=20=EC=88=98=EC=A0=95=20=EA=B8=88=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/user/dto/request/UserUpdateRequestDto.java | 3 --- .../java/inu/codin/codin/domain/user/entity/UserEntity.java | 1 - 2 files changed, 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java index 94c76c56..6a4655c9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java @@ -7,9 +7,6 @@ @Getter public class UserUpdateRequestDto { - @Schema(description = "학번", example = "20210000") - @NotBlank - private String studentId; @Schema(description = "이름", example = "홍길동") @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 4946ad6c..f7dea137 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -59,7 +59,6 @@ public void changePassword(){ } public void updateUserInfo(UserUpdateRequestDto userUpdateRequestDto) { - this.studentId = userUpdateRequestDto.getStudentId(); this.name = userUpdateRequestDto.getName(); this.nickname = userUpdateRequestDto.getNickname(); this.department = userUpdateRequestDto.getDepartment(); From 2ab7730d0614b1e84d5e358549a5849a297454e8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 00:20:50 +0900 Subject: [PATCH 0292/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 136b0f56..56ed44cb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 136b0f562fad2480e9194f2e7467a0dda1991178 +Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a From abe53ae2c0d942a9c72c2d7cda391839f04610b0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 00:25:50 +0900 Subject: [PATCH 0293/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=5Fid=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/controller/ChatRoomController.java | 3 +-- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 26db3dc3..c86323bd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -29,9 +29,8 @@ public class ChatRoomController { ) @PostMapping public ResponseEntity> createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ - chatRoomService.createChatRoom(chatRoomCreateRequestDto, userDetails); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "채팅방 생성 완료", null)); + .body(new SingleResponse<>(201, "채팅방 생성 완료", chatRoomService.createChatRoom(chatRoomCreateRequestDto, userDetails))); } @Operation( diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 044b89ab..02a28982 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -24,10 +24,11 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; - public void createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { + public String createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { String senderId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); + return chatRoom.getId().toString(); } public List getAllChatRoomByUser(UserDetails userDetails) { From d3396aee61a6ad1122e9fcad1ccfe9c03a6570df Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 01:10:00 +0900 Subject: [PATCH 0294/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=5Fid=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/service/ChatRoomService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 02a28982..7bf231f6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -14,7 +14,9 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -24,11 +26,13 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; - public String createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { + public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { String senderId = ((CustomUserDetails) userDetails).getId(); ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); - return chatRoom.getId().toString(); + Map response = new HashMap<>(); + response.put("chatRoomId", chatRoom.getId().toString()); + return response; } public List getAllChatRoomByUser(UserDetails userDetails) { From 37732146bef9ea3831d3c9c13d037a906f6394b0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 01:18:55 +0900 Subject: [PATCH 0295/1002] =?UTF-8?q?perf=20:=20ReceiveId=EC=9D=98=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index fabcb279..0c05dd8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; @@ -7,6 +8,8 @@ import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,9 +29,12 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; private final CustomChattingRepository customChattingRepository; + private final UserRepository userRepository; public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { ObjectId senderId = ((CustomUserDetails) userDetails).getId(); + userRepository.findById(new ObjectId(chatRoomCreateRequestDto.getReceiverId())) + .orElseThrow(() -> new NotFoundException("Receive 유저를 찾을 수 없습니다.")); //Receive 유저에 대한 유효성 검사 ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); Map response = new HashMap<>(); From 6a4f16954f8917ffb779346cc6c989b6fb605c60 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 02:31:05 +0900 Subject: [PATCH 0296/1002] =?UTF-8?q?feat=20:=20BestEntity=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20best=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=B0=8F=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 7 ++++ .../domain/post/domain/best/BestEntity.java | 39 +++++++++++++++++++ .../post/domain/best/BestRepository.java | 8 ++++ .../post/repository/PostRepository.java | 6 ++- .../domain/post/service/PostService.java | 35 +++++++++++++---- .../codin/codin/infra/redis/RedisService.java | 18 +++++---- 6 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 8395d404..e4c2ca16 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -145,4 +145,11 @@ public ResponseEntity> getTop3BestPosts(){ return ResponseEntity.ok() .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postService.getTop3BestPosts())); } + + @Operation(summary = "Top3로 선정된 게시글들 모두 가져오기") + @GetMapping("/best") + public ResponseEntity> getBestPosts(@RequestParam("pageNumber") int pageNumber){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "Top3로 선정된 게시글들 모두 반환 완료", postService.getBestPosts(pageNumber))); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java new file mode 100644 index 00000000..08fd3012 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java @@ -0,0 +1,39 @@ +package inu.codin.codin.domain.post.domain.best; + +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDateTime; + +@Document(collection = "bests") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BestEntity { + + @Id @NotBlank + private ObjectId _id; + + private ObjectId postId; + + private LocalDateTime createdAt; + + @CreatedDate + private LocalDateTime selectedAt; + + private int score; + + @Builder + public BestEntity(ObjectId postId, LocalDateTime createdAt, LocalDateTime selectedAt, int score) { + this.postId = postId; + this.createdAt = createdAt; + this.selectedAt = selectedAt; + this.score = score; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java new file mode 100644 index 00000000..967c35df --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java @@ -0,0 +1,8 @@ +package inu.codin.codin.domain.post.domain.best; + +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface BestRepository extends MongoRepository { + BestEntity findByPostId(ObjectId postId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 5836702b..0ef02cff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -22,8 +22,10 @@ public interface PostRepository extends MongoRepository { @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); - Page findByPostCategoryStartingWithOrderByCreatedAt(String prefix, PageRequest pageRequest); + Page findByPostCategoryStartingWithAndDeletedAtIsNullOrderByCreatedAt(String prefix, PageRequest pageRequest); @Query("{ '$or': [ { 'content': { $regex: ?0, $options: 'i' } }, { 'title': { $regex: ?0, $options: 'i' } } ] }") - Page findAllByKeyword(String keyword, PageRequest pageRequest); + Page findAllByKeywordAndDeletedAtIsNull(String keyword, PageRequest pageRequest); + + Page findAllByBestIsTrueAndDeletedAtIsNull(PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 4bb684a1..cc065650 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -4,6 +4,8 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; @@ -42,6 +44,7 @@ @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; + private final BestRepository bestRepository; private final S3Service s3Service; private final LikeService likeService; @@ -114,7 +117,7 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page; if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) - page = postRepository.findByPostCategoryStartingWithOrderByCreatedAt(postCategory.toString(), pageRequest); + page = postRepository.findByPostCategoryStartingWithAndDeletedAtIsNullOrderByCreatedAt(postCategory.toString(), pageRequest); else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } @@ -225,17 +228,35 @@ public String getNicknameByUserId(ObjectId userId) { public PostPageResponse searchPosts(String keyword, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByKeyword(keyword, pageRequest); + Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, pageRequest); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } public List getTop3BestPosts() { - Set postIds = redisService.getTopNPosts(3); - List bestPosts = postIds.stream() - .map(postId -> - postRepository.findById(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")) + Map posts = redisService.getTopNPosts(3); + List bestPosts = posts.entrySet().stream() + .map(post -> { + BestEntity bestPost = bestRepository.findByPostId(new ObjectId(post.getKey())); + PostEntity postEntity = postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) + .orElseThrow(() -> new NotFoundException("해당 게시글을 찾을 수 없습니다.")); + if (bestPost == null) { + bestRepository.save(BestEntity.builder() + .postId(new ObjectId(post.getKey())) + .createdAt(postEntity.getCreatedAt()) + .score(post.getValue().intValue()) + .build()); + } + return postEntity; + } ).toList(); return getPostListResponseDtos(bestPosts); } + + public PostPageResponse getBestPosts(int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page bests = bestRepository.findAll(pageRequest); + Page page = bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index ba67ae4e..71c98cd3 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -1,7 +1,10 @@ package inu.codin.codin.infra.redis; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.entity.PostEntity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -11,9 +14,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Service @@ -120,7 +121,7 @@ public Set getHitsUser(ObjectId postId) { } // Top N 게시물 조회 - public Set getTopNPosts(int N) { + public Map getTopNPosts(int N) { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); @@ -145,9 +146,12 @@ public Set getTopNPosts(int N) { } return Integer.compare(getHitsCount(new ObjectId(e2.getKey())), getHitsCount(new ObjectId(e1.getKey()))); }) - .limit(N) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); + .limit(N).collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (existing, replacement) -> existing, // Merge function (not needed here) + LinkedHashMap::new // Use LinkedHashMap to preserve the order + )); } public void applyBestScore(int score, ObjectId id){ From 84169cb26da997f33ea5fc6135d5a625cb6a4cdd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 02:31:25 +0900 Subject: [PATCH 0297/1002] =?UTF-8?q?feat=20:=20Scheduler=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=2012=EC=8B=9C=EA=B0=84=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20Top3=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/infra/redis/SyncScheduler.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 773ecc7d..dadc0af3 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -1,6 +1,8 @@ package inu.codin.codin.infra.redis; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.hits.HitsEntity; import inu.codin.codin.domain.post.domain.hits.HitsRepository; @@ -9,6 +11,7 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; @@ -18,9 +21,13 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,6 +44,7 @@ public class SyncScheduler { private final LikeRepository likeRepository; private final ScrapRepository scrapRepository; private final HitsRepository hitsRepository; + private final BestRepository bestRepository; private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; @@ -212,5 +220,20 @@ public Map> fetchAllPostHits(){ ) ); } + @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 + public void getTop3BestPosts() { + Map posts = redisService.getTopNPosts(3); + posts.entrySet().stream() + .peek(post -> { + BestEntity bestPost = bestRepository.findByPostId(new ObjectId(post.getKey())); + if (bestPost == null) { + bestRepository.save(BestEntity.builder() + .postId(new ObjectId(post.getKey())) + .score(post.getValue().intValue()) + .build()); + } + } + ); + } } \ No newline at end of file From e8cf94ab0348743e63eec2495e77256df9a72054 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 02:42:45 +0900 Subject: [PATCH 0298/1002] =?UTF-8?q?/chats/**=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=A0=EC=8B=9C=20=EC=97=B4=EC=96=B4=EB=86=93=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 0fe4a5cb..fb2d190e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -106,6 +106,7 @@ public PasswordEncoder passwordEncoder() { "/ws-stomp/**", "/chat", "/chat/image", + "/chats/**" }; // Swagger 접근 가능한 URL From 32f64d5f5920442ecbfe3adc339f93c2234474bb Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 29 Dec 2024 03:03:53 +0900 Subject: [PATCH 0299/1002] =?UTF-8?q?[SC-103]=20Feat=20::=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/poll/entity/PollEntity.java | 21 ++- .../domain/poll/entity/PollVoteEntity.java | 34 +++++ .../exception/PollDuplicateVoteException.java | 7 + .../poll/repository/PollVoteRepository.java | 19 +++ .../post/domain/poll/service/PollService.java | 37 ++++- .../dto/response/PostDetailResponseDTO.java | 20 +++ .../response/PostPollDetailResponseDTO.java | 77 +++++++--- .../domain/post/service/PostService.java | 142 +++++------------- 8 files changed, 219 insertions(+), 138 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index ddcd8904..01a74deb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.entity; +import inu.codin.codin.common.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -9,29 +10,37 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @Document(collection = "polls") @Getter -public class PollEntity { +public class PollEntity extends BaseTimeEntity { @Id @NotBlank private ObjectId _id; private ObjectId postId; // PostEntity와의 관계를 유지하기 위한 필드 private List pollOptions = new ArrayList<>(); // 설문조사 선택지 - private List pollVotes = new ArrayList<>(); // 선택지별 투표 수 - //Map 구조로 변환 + private List pollVotesCounts = new ArrayList<>(); // 선택지별 투표 수 + private LocalDateTime pollEndTime; // 설문조사 종료 시간 private boolean multipleChoice; // 복수 선택 가능 여부 + @Builder public PollEntity(ObjectId _id, ObjectId postId, List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice) { this._id = _id; this.postId = postId; - this.pollOptions = pollOptions; - this.pollVotes = pollOptions != null ? new ArrayList<>(pollOptions.stream().map(option -> 0).toList()) : new ArrayList<>(); + + // pollOptions가 null일 경우 빈 리스트로 초기화 + this.pollOptions = (pollOptions != null) ? pollOptions : new ArrayList<>(); + + // pollVotesCounts가 null일 경우, pollOptions의 크기만큼 0으로 초기화 + this.pollVotesCounts = (pollVotesCounts != null && !pollVotesCounts.isEmpty()) ? + pollVotesCounts : new ArrayList<>(Collections.nCopies(this.pollOptions.size(), 0)); + this.pollEndTime = pollEndTime; this.multipleChoice = multipleChoice; } @@ -42,6 +51,6 @@ public void vote(int optionIndex) { if (optionIndex < 0 || optionIndex >= pollOptions.size()) { throw new IllegalArgumentException("잘못된 선택지입니다."); } - pollVotes.set(optionIndex, pollVotes.get(optionIndex) + 1); + pollVotesCounts.set(optionIndex, pollVotesCounts.get(optionIndex) + 1); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java new file mode 100644 index 00000000..4a85aea7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java @@ -0,0 +1,34 @@ +package inu.codin.codin.domain.post.domain.poll.entity; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.List; + +@Document(collection = "poll_votes") +@Getter +public class PollVoteEntity { + @Id + private ObjectId _id; + + @NotNull + private ObjectId pollId; + @NotNull + private ObjectId userId; + @NotNull + private List selectedOptions; // 선택한 옵션 인덱스 + + @Builder + public PollVoteEntity(ObjectId _id, ObjectId pollId, ObjectId userId, List selectedOptions) { + this._id = _id; + this.pollId = pollId; + this.userId = userId; + this.selectedOptions = selectedOptions; + + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java new file mode 100644 index 00000000..819be7cf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.post.domain.poll.exception; + +public class PollDuplicateVoteException extends RuntimeException { + public PollDuplicateVoteException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java new file mode 100644 index 00000000..abc24ba5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.post.domain.poll.repository; + +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import jakarta.validation.constraints.NotBlank; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PollVoteRepository extends MongoRepository { + boolean existsByPollIdAndUserId(ObjectId pollId, ObjectId userId); + + long countByPollId(@NotBlank ObjectId id); + + Optional findByPollIdAndUserId(@NotBlank ObjectId id, ObjectId userId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index cf5fb55a..0d9905f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -8,8 +8,11 @@ import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.user.entity.UserRole; @@ -30,18 +33,19 @@ public class PollService { private final PostRepository postRepository; private final PollRepository pollRepository; + private final PollVoteRepository pollVoteRepository; @Transactional public void createPoll(PollCreateRequestDTO pollRequestDTO) { ObjectId userId = SecurityUtils.getCurrentUserId(); - //권한 확인 처리 로직 추가 + // 권한 확인 처리 로직 추가 if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && pollRequestDTO.getPostCategory().toString().split("_")[0].equals("POLL")){ throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표에 대한 권한이 없습니다."); } - // 1. PostEntity 생성 및 저장 + // PostEntity 생성 및 저장 PostEntity postEntity = PostEntity.builder() .title(pollRequestDTO.getTitle()) .content(pollRequestDTO.getContent()) @@ -52,7 +56,7 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { .build(); postEntity = postRepository.save(postEntity); - // 2. PollEntity 생성 및 저장 + // PollEntity 생성 및 저장 PollEntity pollEntity = PollEntity.builder() .postId(postEntity.get_id()) .pollOptions(pollRequestDTO.getPollOptions()) @@ -63,21 +67,29 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { } public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { - // 1. 게시글 조회 및 검증 + + // 게시글 조회 및 검증 PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("해당 게시물을 찾을 수 없습니다.")); validateUserAndPost(post); - // 2. 투표 데이터(PollEntity) 조회 + // 투표 데이터(PollEntity) 조회 PollEntity poll = pollRepository.findByPostId(post.get_id()) .orElseThrow(() -> new NotFoundException("투표 데이터가 존재하지 않습니다.")); - // 3. 투표 종료 여부 확인 + // 투표 종료 여부 확인 if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { throw new PollTimeFailException("이미 종료된 투표입니다."); } - // 4. 사용자의 선택 항목 검증 + // 사용자 투표 여부 검증 + ObjectId userId = SecurityUtils.getCurrentUserId(); + boolean hasAlreadyVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); + if (hasAlreadyVoted) { + throw new PollDuplicateVoteException("이미 투표하셨습니다."); + } + + //투표 항목 검증 List selectedOptions = pollRequestDTO.getSelectedOptions(); if (!poll.isMultipleChoice() && selectedOptions.size() > 1) { throw new PollOptionChoiceException("복수 선택이 허용되지 않은 투표입니다."); @@ -88,7 +100,16 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { } } - // 5. 투표 항목 DB 반영 + //투표 기록 + PollVoteEntity vote = PollVoteEntity.builder() + .pollId(poll.get_id()) + .userId(userId) + .selectedOptions(selectedOptions) // 다중 선택 지원 + .build(); + pollVoteRepository.save(vote); + + + // 투표 항목 DB 반영 for (Integer index : selectedOptions) { poll.vote(index); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index b87633f6..daa75041 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -91,5 +92,24 @@ public UserInfo(boolean isLike, boolean isScrap) { this.isScrap = isScrap; } } + + public static PostDetailResponseDTO of(PostEntity post, String nickname, int likeCount, int scrapCount, int hitsCount, int commentCount, UserInfo userInfo) { + return new PostDetailResponseDTO( + post.getUserId().toString(), + post.get_id().toString(), + post.getTitle(), + post.getContent(), + nickname, + post.getPostCategory(), + post.getPostImageUrls(), + post.isAnonymous(), + likeCount, + scrapCount, + hitsCount, + post.getCreatedAt(), + commentCount, + userInfo + ); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 9939b2d4..f9387ae3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.post.dto.response; import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import java.time.LocalDateTime; @@ -12,12 +12,7 @@ @Setter public class PostPollDetailResponseDTO extends PostDetailResponseDTO { - // Poll 관련 필드 - private List pollOptions; - private LocalDateTime pollEndTime; - private boolean multipleChoice; - private List pollVotes; // 선택지별 투표 수 - private boolean isPollFinished; + private PollInfo poll; public PostPollDetailResponseDTO( String userId, @@ -32,19 +27,61 @@ public PostPollDetailResponseDTO( LocalDateTime createdAt, int commentCount, UserInfo userInfo, - List pollOptions, - LocalDateTime pollEndTime, - boolean multipleChoice, - List pollVotes, - boolean isPollFinished + PollInfo poll ) { - // 상위 클래스(PostDetailResponseDTO)의 생성자 호출 - super(userId, postId, title, content, nickname, PostCategory.POLL, null, isAnonymous, likeCount, scrapCount, hitsCount, createdAt, commentCount, userInfo); - - this.pollOptions = pollOptions; - this.pollEndTime = pollEndTime; - this.multipleChoice = multipleChoice; - this.pollVotes = pollVotes; - this.isPollFinished = isPollFinished; + super(userId, postId, title, content, nickname, + PostCategory.POLL, null, isAnonymous, + likeCount, scrapCount, hitsCount, createdAt, commentCount, + userInfo); + + this.poll = poll; + } + + public static PostPollDetailResponseDTO of(PostEntity post, String nickname, + int likeCount, int scrapCount, int hitsCount, + int commentCount, UserInfo userInfo, + List pollOptions, LocalDateTime pollEndTime, + boolean multipleChoice, List pollVotesCounts, + boolean isPollFinished, List userVotes, + Long totalVotes) { + + PollInfo pollInfo = new PollInfo(pollOptions, pollEndTime, multipleChoice, pollVotesCounts, userVotes, totalVotes, isPollFinished); + return new PostPollDetailResponseDTO( + post.getUserId().toString(), + post.get_id().toString(), + post.getTitle(), + post.getContent(), + nickname, + post.isAnonymous(), + likeCount, + scrapCount, + hitsCount, + post.getCreatedAt(), + commentCount, + userInfo, + pollInfo + ); + } + + @Getter + public static class PollInfo { + private List pollOptions; + private LocalDateTime pollEndTime; + private boolean multipleChoice; + private List pollVotesCounts; + private List userVotesOptions; + private Long totalVotes; + private boolean isPollFinished; + + public PollInfo(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, + List pollVotesCounts, List userVotesOptions, Long totalVotes, boolean isPollFinished) { + this.pollOptions = pollOptions; + this.pollEndTime = pollEndTime; + this.multipleChoice = multipleChoice; + this.pollVotesCounts = pollVotesCounts; + this.userVotesOptions = userVotesOptions; + this.totalVotes = totalVotes; + this.isPollFinished = isPollFinished; + } } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 0f6d1e24..bcb55f15 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; @@ -24,7 +25,6 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.redis.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; @@ -36,11 +36,9 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.time.LocalDateTime; + import java.util.Comparator; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -53,6 +51,7 @@ public class PostService { private final RedisService redisService; private final UserRepository userRepository; private final PollRepository pollRepository; + private final PollVoteRepository pollVoteRepository; public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { List imageUrls = s3Service.handleImageUpload(postImages); @@ -124,120 +123,55 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - public List getPostListResponseDtos(List posts) { - // 1. 사용자 ID와 닉네임을 한 번에 조회하여 Map으로 변환 - // 닉네임 조회를 스트림 내부에서 진행하지 않고 매핑을 통해 한번에 처리 (중복 호출 최소화) - Map userNicknameMap = userRepository.findAllById( - posts.stream().map(PostEntity::getUserId).distinct().toList() - ).stream().collect(Collectors.toMap(UserEntity::get_id, UserEntity::getNickname)); + // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 + private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { + + String nickname = post.isAnonymous() ? "익명" : getNicknameByUserId(post.getUserId()); + UserInfo userInfo = getUserInfoAboutPost(post.get_id()); + int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); + int scrapCount = scrapService.getScrapCount(post.get_id()); + int hitsCount = redisService.getHitsCount(post.get_id()); + int commentCount = post.getCommentCount(); + + // POLL 게시물 처리 + if (post.getPostCategory().equals(PostCategory.POLL)) { + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); + long totalVotes = pollVoteRepository.countByPollId(poll.get_id()); + List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), post.getUserId()) + .stream().flatMap(vote -> vote.getSelectedOptions().stream()).toList(); + boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), post.getUserId()); + + return PostPollDetailResponseDTO.of( + post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo, + poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), + poll.getPollVotesCounts(), hasUserVoted, userVotes, totalVotes + ); + } + // 일반 게시물 처리 + return PostDetailResponseDTO.of( + post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo + ); + } - // 2. 게시글 처리 + // 게시물 리스트 가져오기 + public List getPostListResponseDtos(List posts) { return posts.stream() .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) - .map(post -> { - String nickname = post.isAnonymous() ? "익명" : userNicknameMap.get(post.getUserId()); - return new PostDetailResponseDTO( - post.getUserId().toString(), - post.get_id().toString(), - post.getTitle(), - post.getContent(), - nickname, - post.getPostCategory(), - post.getPostImageUrls(), - post.isAnonymous(), - likeService.getLikeCount(LikeType.valueOf("POST"), post.get_id()), // 좋아요 수 - scrapService.getScrapCount(post.get_id()), // 스크랩 수 - redisService.getHitsCount(post.get_id()), - post.getCreatedAt(), - post.getCommentCount(), - getUserInfoAboutPost(post.get_id()) - ); - }) + .map(this::createPostDetailResponse) .toList(); } - //게시물 상세 조회 :: 게시글 (내용 + 좋아요,스크랩 count 수) + 댓글 +대댓글 (내용 +좋아요,스크랩 count 수 ) 반환 + // 게시물 상세 조회 public PostDetailResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - if (redisService.validateHits(post.get_id(), userId)) redisService.addHits(post.get_id(), userId); - String nickname = post.isAnonymous() ? "익명" : getNicknameByUserId(post.getUserId()); - - // POLL 게시물 처리 - if (post.getPostCategory().equals(PostCategory.POLL)) { - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); - - // 투표 종료 여부 판단 - boolean hasPollFinished = LocalDateTime.now().isAfter(poll.getPollEndTime()); - - if (!hasPollFinished) { - // 투표 진행 중 - return new PostPollDetailResponseDTO( - post.getUserId().toString(), - post.get_id().toString(), - post.getTitle(), - post.getContent(), - nickname, - post.isAnonymous(), - likeService.getLikeCount(LikeType.POST, post.get_id()), - scrapService.getScrapCount(post.get_id()), - redisService.getHitsCount(post.get_id()), - post.getCreatedAt(), - post.getCommentCount(), - getUserInfoAboutPost(post.get_id()), - poll.getPollOptions(), - poll.getPollEndTime(), - poll.isMultipleChoice(), - null, // 진행 중이므로 투표 수는 반환하지 않음 - false // 투표 종료 여부 - ); - } else { - // 투표 종료 - return new PostPollDetailResponseDTO( - post.getUserId().toString(), - post.get_id().toString(), - post.getTitle(), - post.getContent(), - nickname, - post.isAnonymous(), - likeService.getLikeCount(LikeType.POST, post.get_id()), - scrapService.getScrapCount(post.get_id()), - redisService.getHitsCount(post.get_id()), - post.getCreatedAt(), - post.getCommentCount(), - getUserInfoAboutPost(post.get_id()), - poll.getPollOptions(), - poll.getPollEndTime(), - poll.isMultipleChoice(), - poll.getPollVotes(), // 투표 종료 시 투표 수 반환 - true // 투표 종료 여부 - ); - } - } - - // 5. 일반 게시물 처리 - return new PostDetailResponseDTO( - post.getUserId().toString(), - post.get_id().toString(), - post.getTitle(), - post.getContent(), - nickname, - post.getPostCategory(), - post.getPostImageUrls(), - post.isAnonymous(), - likeService.getLikeCount(LikeType.POST, post.get_id()), - scrapService.getScrapCount(post.get_id()), - redisService.getHitsCount(post.get_id()), - post.getCreatedAt(), - post.getCommentCount(), - getUserInfoAboutPost(post.get_id()) - ); + return createPostDetailResponse(post); } public void softDeletePost(String postId) { From a8a4952892f4c37d6c2d1b3f306a2b786910d190 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 03:34:58 +0900 Subject: [PATCH 0300/1002] =?UTF-8?q?AllowedOriginPattern("*")=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/WebSocketConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 85164d5c..e1433631 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -19,7 +19,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("https://www.codin.co.kr/", "http://localhost:3000", "http://localhost:8080"); + .setAllowedOriginPatterns("*"); // .withSockJS(); } From 44d8259b1a7f6a05cf8f1ff3ccee85944545553a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 29 Dec 2024 03:41:34 +0900 Subject: [PATCH 0301/1002] =?UTF-8?q?Feat=20::=20=EB=8C=93=EA=B8=80/?= =?UTF-8?q?=EB=8C=80=EB=8C=93=EA=B8=80=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=99=95=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/CommentResponseDTO.java | 21 ++++++++++++++++++- .../comment/service/CommentService.java | 15 +++++++++++-- .../dto/request/ReplyCreateRequestDTO.java | 3 +++ .../reply/service/ReplyCommentService.java | 11 +++++++++- .../codin/codin/infra/redis/RedisService.java | 8 +++++++ 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 5e5bcaba..dc31d872 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.Builder; import lombok.Getter; import java.time.LocalDateTime; @@ -43,7 +44,13 @@ public class CommentResponseDTO { @Schema(description = "댓글 작성 시간", example = "2024-12-05 02:22:48") private final LocalDateTime createdAt; - public CommentResponseDTO(String _id, String userId, String content, String nickname, Boolean anonymous ,List replies, int likeCount, boolean isDeleted, LocalDateTime createdAt) { + @Schema(description = "해당 댓글 대한 유저 반응 여부") + private final CommnetUserInfo CommnetUserInfo; + + public CommentResponseDTO(String _id, String userId, String content, + String nickname, Boolean anonymous , + List replies, int likeCount, + boolean isDeleted, LocalDateTime createdAt, CommnetUserInfo CommnetUserInfo) { this._id = _id; this.userId = userId; this.content = content; @@ -53,5 +60,17 @@ public CommentResponseDTO(String _id, String userId, String content, String nick this.likeCount = likeCount; this.isDeleted = isDeleted; this.createdAt = createdAt; + this.CommnetUserInfo = CommnetUserInfo; } + + @Getter + public static class CommnetUserInfo { + private final boolean isLike; + + @Builder + public CommnetUserInfo(boolean isLike) { + this.isLike = isLike; + } + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 80fc8621..1bbf8b6f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; - +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.CommnetUserInfo; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -118,7 +118,11 @@ public List getCommentsByPostId(String id) { replyCommentService.getRepliesByCommentId(comment.get_id()), // 대댓글 조회 likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), // 댓글 좋아요 수 isDeleted, - comment.getCreatedAt()); + comment.getCreatedAt(), + getUserInfoAboutPost(comment.get_id()) + ); + + }) .toList(); } @@ -133,4 +137,11 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { commentRepository.save(comment); } + public CommnetUserInfo getUserInfoAboutPost(ObjectId commentId) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + return CommnetUserInfo.builder() + .isLike(redisService.isCommentLiked(commentId, userId)) + .build(); + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java index 8170e365..f00ae843 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.Builder; import lombok.Getter; @Getter @@ -15,4 +16,6 @@ public class ReplyCreateRequestDTO { @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") @NotNull private boolean anonymous; + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 2460207c..29eead01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -110,9 +110,18 @@ public List getRepliesByCommentId(ObjectId commentId) { List.of(), //대댓글은 대댓글이 없음 likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.getCommentId()), // 대댓글 좋아요 수 isDeleted, - reply.getCreatedAt()); + reply.getCreatedAt(), + getUserInfoAboutPost(reply.get_id()) + + ); }).toList(); } + public CommentResponseDTO.CommnetUserInfo getUserInfoAboutPost(ObjectId replyId) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + return CommentResponseDTO.CommnetUserInfo.builder() + .isLike(redisService.isReplyLiked(replyId, userId)) + .build(); + } public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index ba67ae4e..920fa4e3 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -74,6 +74,14 @@ public boolean isPostLiked(ObjectId postId, ObjectId userId){ String redisKey = LikeType.POST + LIKE_KEY + postId.toString(); return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); } + public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ + String redisKey = LikeType.COMMENT + LIKE_KEY + commentId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + public boolean isReplyLiked(ObjectId replyId, ObjectId userId){ + String redisKey = LikeType.REPLY + LIKE_KEY + replyId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } //Scrap public void addScrap(ObjectId postId, ObjectId userId) { From ab115dcfb7ddf3a68b0b6e1606bfaa4c56696321 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 29 Dec 2024 03:52:59 +0900 Subject: [PATCH 0302/1002] =?UTF-8?q?Feat=20::=20=EB=8C=93=EA=B8=80/?= =?UTF-8?q?=EB=8C=80=EB=8C=93=EA=B8=80=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=99=95=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/redis/SyncScheduler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 773ecc7d..bcfbce58 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -48,9 +48,9 @@ public void syncLikes() { return; } log.info(" 동기화 작업 시작"); - syncEntityLikes("post", postRepository); - syncEntityLikes("comment", commentRepository); - syncEntityLikes("reply", replyCommentRepository); + syncEntityLikes("POST", postRepository); + syncEntityLikes("COMMENT", commentRepository); + syncEntityLikes("REPLY", replyCommentRepository); syncPostScraps(); synPostHits(); log.info(" 동기화 작업 완료"); From f389b4063350d67949e187a727b5e95e099c792a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 29 Dec 2024 03:54:51 +0900 Subject: [PATCH 0303/1002] =?UTF-8?q?Fix=20::=20=EB=8C=80=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/reply/service/ReplyCommentService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 29eead01..f69ba469 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -108,7 +108,7 @@ public List getRepliesByCommentId(ObjectId commentId) { nickname, reply.isAnonymous(), List.of(), //대댓글은 대댓글이 없음 - likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.getCommentId()), // 대댓글 좋아요 수 + likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.get_id()), // 대댓글 좋아요 수 isDeleted, reply.getCreatedAt(), getUserInfoAboutPost(reply.get_id()) From 1f3673340723e24d70a31e6527995a6517be5e14 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 05:06:10 +0900 Subject: [PATCH 0304/1002] =?UTF-8?q?SockJs()=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/WebSocketConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index e1433631..63fc7455 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -19,8 +19,8 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("*"); -// .withSockJS(); + .setAllowedOriginPatterns("http://localhost:3000", "http://localhost:8080", "https://www.codin.co.kr") + .withSockJS(); } @Override From 6dad357dee9aac323fba1c4c6f957c185649457a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 05:26:22 +0900 Subject: [PATCH 0305/1002] =?UTF-8?q?refactor=20:=20Repository=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/repository/PostRepository.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 0ef02cff..e77bc8ac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -26,6 +26,4 @@ public interface PostRepository extends MongoRepository { @Query("{ '$or': [ { 'content': { $regex: ?0, $options: 'i' } }, { 'title': { $regex: ?0, $options: 'i' } } ] }") Page findAllByKeywordAndDeletedAtIsNull(String keyword, PageRequest pageRequest); - - Page findAllByBestIsTrueAndDeletedAtIsNull(PageRequest pageRequest); } From 73d38189a147c4746dec5d9b76471743870a9364 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 05:44:58 +0900 Subject: [PATCH 0306/1002] =?UTF-8?q?fix=20:=20ACTIVE=EC=9D=98=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=EB=A7=8C=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/repository/PostRepository.java | 3 ++- .../java/inu/codin/codin/domain/post/service/PostService.java | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index e77bc8ac..a99b3753 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -22,7 +23,7 @@ public interface PostRepository extends MongoRepository { @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); - Page findByPostCategoryStartingWithAndDeletedAtIsNullOrderByCreatedAt(String prefix, PageRequest pageRequest); + Page findByPostCategoryStartingWithAndDeletedAtIsNullAndPostStatusInOrderByCreatedAt(String prefix, PostStatus postStatus, PageRequest pageRequest); @Query("{ '$or': [ { 'content': { $regex: ?0, $options: 'i' } }, { 'title': { $regex: ?0, $options: 'i' } } ] }") Page findAllByKeywordAndDeletedAtIsNull(String keyword, PageRequest pageRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index cc065650..84a539ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -37,7 +37,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; @Service @@ -117,7 +116,7 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page; if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) - page = postRepository.findByPostCategoryStartingWithAndDeletedAtIsNullOrderByCreatedAt(postCategory.toString(), pageRequest); + page = postRepository.findByPostCategoryStartingWithAndDeletedAtIsNullAndPostStatusInOrderByCreatedAt(postCategory.toString(), PostStatus.ACTIVE, pageRequest); else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } From 96e934507713b68c13e0ef74312db5800d5b193c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 06:50:49 +0900 Subject: [PATCH 0307/1002] =?UTF-8?q?refactor=20:=20WebFlux=20=EC=B2=A0?= =?UTF-8?q?=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/CodinApplication.java | 1 - .../chat/chatting/controller/ChattingController.java | 12 ++++-------- .../chat/chatting/repository/ChattingRepository.java | 8 ++++---- .../chat/chatting/service/ChattingService.java | 12 ++++++------ 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index 7563a763..07567624 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -12,7 +12,6 @@ @SpringBootApplication @EnableMongoAuditing @EnableMethodSecurity -@EnableReactiveMongoAuditing @EnableScheduling public class CodinApplication { public static void main(String[] args) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 70ae6a66..450d4e1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -3,7 +3,6 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; -import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; import inu.codin.codin.domain.chat.chatting.service.ChattingService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,7 +18,6 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import reactor.core.publisher.Mono; import java.util.List; @@ -35,10 +33,9 @@ public class ChattingController { ) @MessageMapping("/chats/{chatRoomId}") //앞에 '/pub' 를 붙여서 요청 @SendTo("/queue/{chatRoomId}") - public Mono>> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto, + public ResponseEntity> sendMessage(@DestinationVariable("chatRoomId") String id, @RequestBody @Valid ChattingRequestDto chattingRequestDto, @AuthenticationPrincipal Authentication authentication){ - return chattingService.sendMessage(id, chattingRequestDto, authentication) - .map( chattingResponseDto -> ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", chattingResponseDto))); + return ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 송신 완료", chattingService.sendMessage(id, chattingRequestDto, authentication))); } @Operation( @@ -55,10 +52,9 @@ public ResponseEntity> sendImageMessage(List ch description = "Pageable에 해당하는 page, size, sort 내역에 맞게 반환" ) @GetMapping("/chats/list/{chatRoomId}") - public Mono>> getAllMessage(@PathVariable("chatRoomId") String id, + public ResponseEntity> getAllMessage(@PathVariable("chatRoomId") String id, @RequestParam("page") int page){ - return chattingService.getAllMessage(id, page) - .map(chattingList -> ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingList))); + return ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingService.getAllMessage(id, page))); } //채팅 테스트를 위한 MVC diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 44674a56..6c74b632 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -3,15 +3,15 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import org.bson.types.ObjectId; import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -public interface ChattingRepository extends ReactiveMongoRepository { +import java.util.List; - @Query("{ '_id': ?0, 'deletedAt': null }") - Mono findById(ObjectId id); +public interface ChattingRepository extends MongoRepository { - Flux findAllByChatRoomIdOrderByCreatedAt(ObjectId id, Pageable pageable); + List findAllByChatRoomIdOrderByCreatedAt(ObjectId id, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 81cd048b..5d0159da 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -34,23 +34,23 @@ public class ChattingService { //todo 이미지 채팅에 따른 S3 처리 - public Mono sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { + public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto.getContent(), userId, chattingRequestDto.getContentType()); log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); - return chattingRepository.save(chatting).map(ChattingResponseDto::of); + chattingRepository.save(chatting); + return ChattingResponseDto.of(chatting); } - public Mono> getAllMessage(String id, int page) { + public List getAllMessage(String id, int page) { Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); return chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) - .switchIfEmpty(Mono.error(new ChattingNotFoundException("채팅 내역을 찾을 수 없습니다."))) - .map(ChattingResponseDto::of) - .collectList(); + .stream().map(ChattingResponseDto::of) + .toList(); } public List sendImageMessage(List chatImages) { From ae93fbefcc2b217ae14ab8bd343e7f15aeba525c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 06:51:14 +0900 Subject: [PATCH 0308/1002] =?UTF-8?q?docs=20:=20import=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/repository/ChattingRepository.java | 4 ---- .../codin/domain/chat/chatting/service/ChattingService.java | 2 -- 2 files changed, 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 6c74b632..4689bdfc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -4,10 +4,6 @@ import org.bson.types.ObjectId; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.mongodb.repository.Query; -import org.springframework.data.mongodb.repository.ReactiveMongoRepository; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 5d0159da..85d3999c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -6,7 +6,6 @@ import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; import inu.codin.codin.domain.chat.chatting.entity.Chatting; -import inu.codin.codin.domain.chat.chatting.exception.ChattingNotFoundException; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import inu.codin.codin.infra.s3.S3Service; @@ -19,7 +18,6 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import reactor.core.publisher.Mono; import java.util.List; From 5eeb856457f785aa75e310d4494d20194cc27916 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 07:41:25 +0900 Subject: [PATCH 0309/1002] =?UTF-8?q?perf=20:=20=ED=98=84=EC=9E=AC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=90=9C=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EC=9D=98=20=5Fid=20=EA=B0=92=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/service/ChatRoomService.java | 12 +++++------ .../controller/ChattingController.java | 4 ++-- .../ChattingAndUserIdResponseDto.java | 20 +++++++++++++++++++ .../dto/response/ChattingResponseDto.java | 20 +++++++++++++++++-- .../repository/CustomChattingRepository.java | 7 ++++--- .../chatting/service/ChattingService.java | 11 ++++++---- 6 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingAndUserIdResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 0c05dd8c..a8c7e197 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; @@ -45,12 +46,11 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat public List getAllChatRoomByUser(UserDetails userDetails) { ObjectId userId = ((CustomUserDetails) userDetails).getId(); List chatRooms = chatRoomRepository.findByParticipant(userId); - return Flux.fromIterable(chatRooms) - .flatMap(chatRoom -> - customChattingRepository.findMostRecentByChatRoomId(chatRoom.get_id()) // Retrieve the most recent message - .map(chatting -> ChatRoomListResponseDto.of(chatRoom, chatting)) // Map to response DTO - .defaultIfEmpty(ChatRoomListResponseDto.of(chatRoom, null)) // Default to null if no message found - ).collectList().block(); + return chatRooms.stream() + .map(chatRoom -> { + Chatting chat = customChattingRepository.findMostRecentByChatRoomId(chatRoom.get_id()); + return ChatRoomListResponseDto.of(chatRoom, chat); + }).toList(); } public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 450d4e1a..75b15895 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -52,9 +52,9 @@ public ResponseEntity> sendImageMessage(List ch description = "Pageable에 해당하는 page, size, sort 내역에 맞게 반환" ) @GetMapping("/chats/list/{chatRoomId}") - public ResponseEntity> getAllMessage(@PathVariable("chatRoomId") String id, + public ResponseEntity> getAllMessage(@PathVariable("chatRoomId") String id, @RequestParam("page") int page){ - return ResponseEntity.ok().body(new ListResponse<>(200, "채팅 내용 리스트 반환 완료", chattingService.getAllMessage(id, page))); + return ResponseEntity.ok().body(new SingleResponse<>(200, "채팅 내용 리스트 반환 완료", chattingService.getAllMessage(id, page))); } //채팅 테스트를 위한 MVC diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingAndUserIdResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingAndUserIdResponseDto.java new file mode 100644 index 00000000..98c0aea0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingAndUserIdResponseDto.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.chat.chatting.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ChattingAndUserIdResponseDto { + + private List chatting; + + private String currentUserId; + + @Builder + public ChattingAndUserIdResponseDto(List chatting, String currentUserId) { + this.chatting = chatting; + this.currentUserId = currentUserId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java index 792ac128..498204ca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -8,6 +8,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; import java.time.LocalDateTime; @@ -34,15 +35,18 @@ public class ChattingResponseDto { @NotBlank private final String chatRoomId; + private final String currentUserId; + @Builder - public ChattingResponseDto(String id, String senderId, String content, ContentType contentType, LocalDateTime createdAt, String chatRoomId) { + public ChattingResponseDto(String id, String senderId, String content, ContentType contentType, LocalDateTime createdAt, String chatRoomId, String currentUserId) { this.id = id; this.senderId = senderId; this.content = content; this.contentType = contentType; this.createdAt = createdAt; this.chatRoomId = chatRoomId; -} + this.currentUserId = currentUserId; + } public static ChattingResponseDto of(Chatting chatting){ return ChattingResponseDto.builder() @@ -54,4 +58,16 @@ public static ChattingResponseDto of(Chatting chatting){ .chatRoomId(chatting.getChatRoomId().toString()) .build(); } + + public static ChattingResponseDto of(Chatting chatting, ObjectId currentUserId){ + return ChattingResponseDto.builder() + .id(chatting.get_id().toString()) + .senderId(chatting.getSenderId().toString()) + .content(chatting.getContent()) + .createdAt(chatting.getCreatedAt()) + .contentType(chatting.getType()) + .chatRoomId(chatting.getChatRoomId().toString()) + .currentUserId(currentUserId.toString()) + .build(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 137ffe86..0aa7e48c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; @@ -12,13 +13,13 @@ @Repository public class CustomChattingRepository { - private final ReactiveMongoTemplate mongoTemplate; + private final MongoTemplate mongoTemplate; - public CustomChattingRepository(ReactiveMongoTemplate mongoTemplate) { + public CustomChattingRepository(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } - public Mono findMostRecentByChatRoomId(ObjectId chatRoomId) { + public Chatting findMostRecentByChatRoomId(ObjectId chatRoomId) { Query query = new Query(Criteria.where("chatRoomId").is(chatRoomId)) .with(Sort.by(Sort.Direction.DESC, "createdAt")) .limit(1); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 85d3999c..382efc1e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -1,9 +1,11 @@ package inu.codin.codin.domain.chat.chatting.service; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.chat.chatting.dto.response.ChattingAndUserIdResponseDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; @@ -20,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -42,13 +45,13 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq return ChattingResponseDto.of(chatting); } - public List getAllMessage(String id, int page) { + public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); - return chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) - .stream().map(ChattingResponseDto::of) - .toList(); + List chattingResponseDto = chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) + .stream().map(ChattingResponseDto::of).toList(); + return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); } public List sendImageMessage(List chatImages) { From c985edcfe7f0244bf86a420d2479a67e1ac6ab05 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 29 Dec 2024 09:11:20 +0900 Subject: [PATCH 0310/1002] =?UTF-8?q?refactor=20:=20UserInfo=EB=A1=9C=20cl?= =?UTF-8?q?ass=EB=AA=85=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dto/response/CommentResponseDTO.java | 10 +++++----- .../post/domain/comment/service/CommentService.java | 6 +++--- .../post/domain/reply/service/ReplyCommentService.java | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index dc31d872..fa1ba085 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -45,12 +45,12 @@ public class CommentResponseDTO { private final LocalDateTime createdAt; @Schema(description = "해당 댓글 대한 유저 반응 여부") - private final CommnetUserInfo CommnetUserInfo; + private final UserInfo userInfo; public CommentResponseDTO(String _id, String userId, String content, String nickname, Boolean anonymous , List replies, int likeCount, - boolean isDeleted, LocalDateTime createdAt, CommnetUserInfo CommnetUserInfo) { + boolean isDeleted, LocalDateTime createdAt, UserInfo userInfo) { this._id = _id; this.userId = userId; this.content = content; @@ -60,15 +60,15 @@ public CommentResponseDTO(String _id, String userId, String content, this.likeCount = likeCount; this.isDeleted = isDeleted; this.createdAt = createdAt; - this.CommnetUserInfo = CommnetUserInfo; + this.userInfo = userInfo; } @Getter - public static class CommnetUserInfo { + public static class UserInfo { private final boolean isLike; @Builder - public CommnetUserInfo(boolean isLike) { + public UserInfo(boolean isLike) { this.isLike = isLike; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 1bbf8b6f..8abd0e18 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.CommnetUserInfo; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -137,9 +137,9 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { commentRepository.save(comment); } - public CommnetUserInfo getUserInfoAboutPost(ObjectId commentId) { + public UserInfo getUserInfoAboutPost(ObjectId commentId) { ObjectId userId = SecurityUtils.getCurrentUserId(); - return CommnetUserInfo.builder() + return UserInfo.builder() .isLike(redisService.isCommentLiked(commentId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index f69ba469..1747bc05 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -116,9 +116,9 @@ public List getRepliesByCommentId(ObjectId commentId) { ); }).toList(); } - public CommentResponseDTO.CommnetUserInfo getUserInfoAboutPost(ObjectId replyId) { + public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); - return CommentResponseDTO.CommnetUserInfo.builder() + return CommentResponseDTO.UserInfo.builder() .isLike(redisService.isReplyLiked(replyId, userId)) .build(); } From 81d9bb64e8eb1e6f7189aa62e067641395d100fe Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 30 Dec 2024 22:16:21 +0900 Subject: [PATCH 0311/1002] =?UTF-8?q?Fix=20::=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=EA=B0=80=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/user/service/UserService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 228416b7..7a6e88d0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -39,6 +39,8 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Slf4j @Service @@ -128,6 +130,16 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i List postUserComment = commentPage.getContent().stream() .map(commentEntity -> postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) .orElseThrow(() -> new NotFoundException("유저가 작성한 댓글의 게시글을 찾을 수 없습니다."))) + //중복 필터링 로직 + //Map(ObjectId, PostEntity) 로 변환 + .collect(Collectors.toMap( + PostEntity::get_id, // Key: postId + postEntity -> postEntity, // Value: PostEntity + (existing, replacement) -> existing // 중복 발생 시 기존 값 유지 - 같은 키 존재 발생시 기존 값 유지 + )) + // 중복 제거된 후 Map 에서 PostEntity 추출 + .values() + .stream() .toList(); return PostPageResponse.of(postService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages()-1, commentPage.hasNext()? commentPage.getPageable().getPageNumber() + 1 : -1); From 8608b2e13f247f9bc62cc89cc96e47ad7f6291a4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 2 Jan 2025 01:37:25 +0900 Subject: [PATCH 0312/1002] =?UTF-8?q?feat=20:=20=EB=82=B4=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=EC=97=90=20=EB=8C=93=EA=B8=80,=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=EC=97=90=20=EB=8B=B5=EA=B8=80=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/NotificationService.java | 20 +++++++++++++++++++ .../comment/service/CommentService.java | 3 +++ .../reply/service/ReplyCommentService.java | 5 +++++ 3 files changed, 28 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 496361a4..9c267d12 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,13 +1,16 @@ package inu.codin.codin.domain.notification.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.service.FcmService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.Map; @@ -18,7 +21,12 @@ public class NotificationService { private final NotificationRepository notificationRepository; + private final UserRepository userRepository; + private final FcmService fcmService; + private final String NOTI_COMMENT_TITLE = "누군가가 내 게시글에 댓글을 달았어요!"; + private final String NOTI_REPLY_TITLE = "누군가가 내 댓글에 답글을 달았어요!"; + /** * 특정 유저의 읽지 않은 알림 개수를 반환 @@ -40,6 +48,7 @@ public void sendFcmMessageToUser(String title, String body, UserEntity user) { .user(user) .title(title) .body(body) +// .imageUrl() //codin 로고 url .build(); try { @@ -100,4 +109,15 @@ private void saveNotificationLog(FcmMessageTopicDto msgDto) { notificationRepository.save(notificationEntity); } + public void sendNotificationMessageByComment(ObjectId userId, String content) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + sendFcmMessageToUser(NOTI_COMMENT_TITLE, content, user); + } + + public void sendNotificationMessageByReply(ObjectId userId, String content) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + sendFcmMessageToUser(NOTI_REPLY_TITLE, content, user); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 3f32f390..991cff75 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; @@ -37,6 +38,7 @@ public class CommentService { private final UserRepository userRepository; private final LikeService likeService; private final ReplyCommentService replyCommentService; + private final NotificationService notificationService; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -57,6 +59,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { post.updateCommentCount(post.getCommentCount() + 1); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); + notificationService.sendNotificationMessageByComment(post.getUserId(), comment.getContent()); } // 댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index b677b671..514de6d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -34,7 +35,10 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; + private final LikeService likeService; + private final NotificationService notificationService; + // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -60,6 +64,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { post.updateCommentCount(post.getCommentCount() + 1); postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); + notificationService.sendNotificationMessageByReply(comment.getUserId(), reply.getContent()); } // 대댓글 삭제 (Soft Delete) From 727a2f443d881de01c0ce52bbad1b89b1c9d5613 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 2 Jan 2025 02:59:50 +0900 Subject: [PATCH 0313/1002] =?UTF-8?q?feat=20:=20=EC=B2=98=EC=9D=8C=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=EB=A5=BC=20=EB=B0=9B=EC=95=98?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/CodinApplication.java | 1 - .../common/security/service/JwtService.java | 1 + .../service/NotificationService.java | 68 +++++++++++++++++-- .../comment/service/CommentService.java | 2 +- .../post/domain/like/service/LikeService.java | 5 ++ .../reply/service/ReplyCommentService.java | 2 +- .../codin/infra/fcm/service/FcmService.java | 15 ++-- 7 files changed, 82 insertions(+), 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index 07567624..8e0a271f 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -3,7 +3,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; -import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 51acfd09..08e7891e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -5,6 +5,7 @@ import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.infra.redis.RedisStorageService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 9c267d12..ae6fbd30 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,16 +3,26 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.service.FcmService; +import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.Map; @Service @@ -22,10 +32,14 @@ public class NotificationService { private final NotificationRepository notificationRepository; private final UserRepository userRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ReplyCommentRepository replyCommentRepository; private final FcmService fcmService; private final String NOTI_COMMENT_TITLE = "누군가가 내 게시글에 댓글을 달았어요!"; private final String NOTI_REPLY_TITLE = "누군가가 내 댓글에 답글을 달았어요!"; + private final String NOTI_LIKE_TITLE = "나에게 첫 좋아요가 달렸어요!"; /** @@ -41,13 +55,15 @@ public long getUnreadNotificationCount(UserEntity user) { * FCM 메시지를 특정 사용자에게 전송하는 로직 * @param title 메시지 제목 * @param body 메시지 내용 + * @param data 알림 대상의 _id * @param user 메시지를 받을 사용자 */ - public void sendFcmMessageToUser(String title, String body, UserEntity user) { + public void sendFcmMessageToUser(String title, String body, Map data, UserEntity user) { FcmMessageUserDto msgDto = FcmMessageUserDto.builder() .user(user) .title(title) .body(body) + .data(data) // .imageUrl() //codin 로고 url .build(); @@ -109,15 +125,57 @@ private void saveNotificationLog(FcmMessageTopicDto msgDto) { notificationRepository.save(notificationEntity); } - public void sendNotificationMessageByComment(ObjectId userId, String content) { + public void sendNotificationMessageByComment(ObjectId userId, String postId, String content) { UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); - sendFcmMessageToUser(NOTI_COMMENT_TITLE, content, user); + Map post = new HashMap<>(); + post.put("postId", postId); + sendFcmMessageToUser(NOTI_COMMENT_TITLE, content, post, user); } - public void sendNotificationMessageByReply(ObjectId userId, String content) { + public void sendNotificationMessageByReply(ObjectId userId, String postId, String content) { UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); - sendFcmMessageToUser(NOTI_REPLY_TITLE, content, user); + Map post = new HashMap<>(); + post.put("postId", postId); + sendFcmMessageToUser(NOTI_REPLY_TITLE, content, post, user); + } + + public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { + switch(likeType){ + case POST -> { + PostEntity postEntity = postRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + UserEntity user = userRepository.findById(postEntity.getUserId()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + Map post = new HashMap<>(); + post.put("postId", postEntity.get_id().toString()); + sendFcmMessageToUser(NOTI_LIKE_TITLE, "내 게시글 보러 가기", post, user); + } + case REPLY -> { + ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(replyCommentEntity.getCommentId()) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + PostEntity postEntity = postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + UserEntity user = userRepository.findById(replyCommentEntity.getUserId()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + Map post = new HashMap<>(); + post.put("postId", postEntity.get_id().toString()); + sendFcmMessageToUser(NOTI_LIKE_TITLE, "내 답글 보러 가기", post, user); + } + case COMMENT -> { + CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + PostEntity postEntity = postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + UserEntity user = userRepository.findById(commentEntity.getUserId()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + Map post = new HashMap<>(); + post.put("postId", postEntity.get_id().toString()); + sendFcmMessageToUser(NOTI_LIKE_TITLE, "내 댓글 보러 가기", post, user); + } + } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 733199f9..fce1110c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -62,7 +62,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { redisService.applyBestScore(1, postId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); - notificationService.sendNotificationMessageByComment(post.getUserId(), comment.getContent()); + notificationService.sendNotificationMessageByComment(post.getUserId(), post.get_id().toString(), comment.getContent()); } // 댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index e345b0b6..09260f75 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; @@ -28,6 +29,7 @@ public class LikeService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; + private final NotificationService notificationService; public String toggleLike(LikeRequestDto likeRequestDto) { ObjectId likeId = new ObjectId(likeRequestDto.getId()); @@ -65,6 +67,9 @@ public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId) { .build()); if (likeType == LikeType.POST) redisService.applyBestScore(1, likeId); + if (redisService.getLikeCount(likeType.toString(), likeId) == 1){ + notificationService.sendNotificationMessageByLike(likeType, likeId); + } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 1b81eb14..85872079 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -66,7 +66,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { redisService.applyBestScore(1, post.get_id()); postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); - notificationService.sendNotificationMessageByReply(comment.getUserId(), reply.getContent()); + notificationService.sendNotificationMessageByReply(comment.getUserId(), post.get_id().toString(), reply.getContent()); } // 대댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 58827c47..0f471b68 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -1,9 +1,11 @@ package inu.codin.codin.infra.fcm.service; import com.google.firebase.messaging.*; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.notification.entity.NotificationPreference; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; @@ -26,7 +28,7 @@ public class FcmService { private final FcmTokenRepository fcmTokenRepository; - private final UserService userService; + private final UserRepository userRepository; /** * 클라이언트로부터 받은 FCM 토큰을 저장하는 로직 @@ -35,7 +37,7 @@ public class FcmService { public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { // 유저의 FCM 토큰이 존재하는지 확인 ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = userService.getUserEntityFromUserId(userId); + UserEntity user = getUserEntityFromUserId(userId); Optional fcmToken = fcmTokenRepository.findByUser(user); if (fcmToken.isPresent()) { // 이미 존재하는 유저라면 토큰 추가 @@ -164,7 +166,7 @@ private void removeFcmToken(FcmTokenEntity fcmTokenEntity, String fcmToken) { */ public void subscribeTopic(String topic) { ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = userService.getUserEntityFromUserId(userId); + UserEntity user = getUserEntityFromUserId(userId); FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); @@ -184,7 +186,7 @@ public void subscribeTopic(String topic) { */ public void unsubscribeTopic(String topic) { ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = userService.getUserEntityFromUserId(userId); + UserEntity user = getUserEntityFromUserId(userId); FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); @@ -198,4 +200,9 @@ public void unsubscribeTopic(String topic) { } } + public UserEntity getUserEntityFromUserId(ObjectId userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); + } + } \ No newline at end of file From fb4ce756f4d482a9a221401f95a2f30e3e172124 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 2 Jan 2025 02:59:54 +0900 Subject: [PATCH 0314/1002] =?UTF-8?q?feat=20:=20=EC=B2=98=EC=9D=8C=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=EB=A5=BC=20=EB=B0=9B=EC=95=98?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/user/repository/UserRepository.java | 2 -- .../inu/codin/codin/domain/user/service/UserService.java | 5 ----- 2 files changed, 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 2dbe37bf..8cea3e39 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -17,6 +17,4 @@ public interface UserRepository extends MongoRepository { @Query("{'studentId': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByStudentId(String studentId); - Optional findById(String id); - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index c520dda2..7a6e88d0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -176,11 +176,6 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } - public UserEntity getUserEntityFromUserId(ObjectId userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); - } - public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { ObjectId userId = SecurityUtils.getCurrentUserId(); UserEntity user = userRepository.findById(userId) From f99ceeaccc7cc3a0e75c94676eb65719dc2f4462 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 2 Jan 2025 18:11:45 +0900 Subject: [PATCH 0315/1002] =?UTF-8?q?Refactor=20::=201.=20Annotation=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=202.=20PostPollDetailResponseDTO=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=EC=A0=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20(=20poll=20=EC=83=9D=EC=84=B1=20->=20post?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20)=203.=20PostService=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=EC=A0=81=20=EC=88=98=EC=A0=95=204.=20=EB=93=B1?= =?UTF-8?q?=EB=93=B1=20PR=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../poll/controller/PollController.java | 9 +- .../domain/poll/dto/PollCreateRequestDTO.java | 8 +- .../domain/poll/dto/PollVotingRequestDTO.java | 3 +- .../post/domain/poll/entity/PollEntity.java | 13 +-- .../post/domain/poll/service/PollService.java | 4 +- .../response/PostPollDetailResponseDTO.java | 94 ++++++++----------- .../domain/post/service/PostService.java | 57 ++++++----- 7 files changed, 93 insertions(+), 95 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index 49768ef0..52507d85 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -6,6 +6,7 @@ import inu.codin.codin.domain.post.domain.poll.service.PollService; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -13,7 +14,7 @@ import org.springframework.validation.annotation.Validated; @RestController -@RequestMapping("/api/polls") +@RequestMapping("/polls") @RequiredArgsConstructor public class PollController { @@ -22,7 +23,7 @@ public class PollController { @Operation(summary = "투표 생성") @PostMapping public ResponseEntity createPoll( - @Validated @RequestBody PollCreateRequestDTO pollRequestDTO) { + @Valid @RequestBody PollCreateRequestDTO pollRequestDTO) { pollService.createPoll(pollRequestDTO); return ResponseEntity.status(HttpStatus.CREATED) @@ -33,10 +34,10 @@ public ResponseEntity createPoll( @PostMapping("/voting/{postId}") public ResponseEntity votingPoll( @PathVariable String postId, - @Validated @RequestBody PollVotingRequestDTO pollRequestDTO) { + @Valid @RequestBody PollVotingRequestDTO pollRequestDTO) { pollService.votingPoll(postId, pollRequestDTO); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "투표 실시 완료", null)); + .body(new SingleResponse<>(200, "투표 실시 완료", null)); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java index 80c9626b..0ef6eeda 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -7,13 +8,13 @@ import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; import java.util.List; @Getter -@NoArgsConstructor public class PollCreateRequestDTO { @Schema(description = "투표 제목", example = "투표 제목") @@ -30,11 +31,12 @@ public class PollCreateRequestDTO { @NotNull private List<@NotBlank String> pollOptions; - @Schema(description = "복수 선택 가능 여부", example = "false") + @Schema(description = "복수 선택 가능 여부", example = "true") private boolean multipleChoice; - @Schema(description = "설문조사 종료 시간 (ISO8601 format)", example = "2024-01-21T23:59:59") + @Schema(description = "설문조사 종료 시간 (yyyy/MM/dd HH:mm format)", example = "2024/01/21 23:59") @NotNull + @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "Asia/Seoul") // Jackson에서 LocalDateTime 변환을 위한 어노테이션 private LocalDateTime pollEndTime; @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java index 1cfb97fb..c06f44ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java @@ -4,10 +4,11 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; +import lombok.Getter; import java.util.List; -@Data +@Getter public class PollVotingRequestDTO { @Schema(description = "사용자가 선택한 옵션 인덱스 리스트 (복수 투표 가능)", example = "[1, 2]") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 01a74deb..3db9d6a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -1,12 +1,15 @@ package inu.codin.codin.domain.post.domain.poll.entity; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; import java.util.ArrayList; @@ -24,6 +27,7 @@ public class PollEntity extends BaseTimeEntity { private List pollOptions = new ArrayList<>(); // 설문조사 선택지 private List pollVotesCounts = new ArrayList<>(); // 선택지별 투표 수 + @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "Asia/Seoul") // JSON 직렬화/역직렬화 시 포맷 지정 private LocalDateTime pollEndTime; // 설문조사 종료 시간 private boolean multipleChoice; // 복수 선택 가능 여부 @@ -33,13 +37,10 @@ public PollEntity(ObjectId _id, ObjectId postId, List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice) { this._id = _id; this.postId = postId; - - // pollOptions가 null일 경우 빈 리스트로 초기화 - this.pollOptions = (pollOptions != null) ? pollOptions : new ArrayList<>(); + this.pollOptions = pollOptions; // pollVotesCounts가 null일 경우, pollOptions의 크기만큼 0으로 초기화 - this.pollVotesCounts = (pollVotesCounts != null && !pollVotesCounts.isEmpty()) ? - pollVotesCounts : new ArrayList<>(Collections.nCopies(this.pollOptions.size(), 0)); + this.pollVotesCounts = new ArrayList<>(Collections.nCopies(this.pollOptions.size(), 0)); this.pollEndTime = pollEndTime; this.multipleChoice = multipleChoice; @@ -49,7 +50,7 @@ public PollEntity(ObjectId _id, ObjectId postId, List pollOptions, //각 옵션의 투표 수 증가 public void vote(int optionIndex) { if (optionIndex < 0 || optionIndex >= pollOptions.size()) { - throw new IllegalArgumentException("잘못된 선택지입니다."); + throw new PollOptionChoiceException("잘못된 선택지입니다."); } pollVotesCounts.set(optionIndex, pollVotesCounts.get(optionIndex) + 1); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index 0d9905f0..8e7c1f3e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -41,7 +41,7 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { ObjectId userId = SecurityUtils.getCurrentUserId(); // 권한 확인 처리 로직 추가 if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - pollRequestDTO.getPostCategory().toString().split("_")[0].equals("POLL")){ + pollRequestDTO.getPostCategory().toString().equals("POLL")){ throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표에 대한 권한이 없습니다."); } @@ -118,7 +118,7 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { private void validateUserAndPost(PostEntity post) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("POLL")){ + post.getPostCategory().toString().equals("POLL")){ throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표 게시글에 대한 권한이 없습니다."); } SecurityUtils.validateUser(post.getUserId()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index f9387ae3..61a983ff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.dto.response; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostEntity; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Getter; import lombok.Setter; @@ -12,76 +11,57 @@ @Setter public class PostPollDetailResponseDTO extends PostDetailResponseDTO { - private PollInfo poll; - - public PostPollDetailResponseDTO( - String userId, - String postId, - String title, - String content, - String nickname, - boolean isAnonymous, - int likeCount, - int scrapCount, - int hitsCount, - LocalDateTime createdAt, - int commentCount, - UserInfo userInfo, - PollInfo poll - ) { - super(userId, postId, title, content, nickname, - PostCategory.POLL, null, isAnonymous, - likeCount, scrapCount, hitsCount, createdAt, commentCount, - userInfo); + private final PollInfo poll; + public PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfo poll) { + super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), + baseDTO.getPostCategory(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), + baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); this.poll = poll; } - public static PostPollDetailResponseDTO of(PostEntity post, String nickname, - int likeCount, int scrapCount, int hitsCount, - int commentCount, UserInfo userInfo, - List pollOptions, LocalDateTime pollEndTime, - boolean multipleChoice, List pollVotesCounts, - boolean isPollFinished, List userVotes, - Long totalVotes) { - - PollInfo pollInfo = new PollInfo(pollOptions, pollEndTime, multipleChoice, pollVotesCounts, userVotes, totalVotes, isPollFinished); - return new PostPollDetailResponseDTO( - post.getUserId().toString(), - post.get_id().toString(), - post.getTitle(), - post.getContent(), - nickname, - post.isAnonymous(), - likeCount, - scrapCount, - hitsCount, - post.getCreatedAt(), - commentCount, - userInfo, - pollInfo - ); + public static PostPollDetailResponseDTO of(PostDetailResponseDTO base, PollInfo poll) { + return new PostPollDetailResponseDTO(base, poll); } @Getter public static class PollInfo { - private List pollOptions; - private LocalDateTime pollEndTime; - private boolean multipleChoice; - private List pollVotesCounts; - private List userVotesOptions; - private Long totalVotes; - private boolean isPollFinished; + //투표 선택지 + private final List pollOptions; + + //투표 종료시간 + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + private final LocalDateTime pollEndTime; + + //복수 투표 여부 + private final boolean multipleChoice; + //투표 항목별 총 카운트 + private final List pollVotesCounts; + //유저가 선택한 항목 + private final List userVotesOptions; + //투표 참여자 수 + private final Long totalParticipants; + //유저 투표 실시 여부 + private final boolean hasUserVoted; + //투표 종료 여부 + private final boolean pollFinished; public PollInfo(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, - List pollVotesCounts, List userVotesOptions, Long totalVotes, boolean isPollFinished) { + List pollVotesCounts, List userVotesOptions, Long totalParticipants, boolean hasUserVoted ,boolean PollFinished) { this.pollOptions = pollOptions; this.pollEndTime = pollEndTime; this.multipleChoice = multipleChoice; this.pollVotesCounts = pollVotesCounts; this.userVotesOptions = userVotesOptions; - this.totalVotes = totalVotes; - this.isPollFinished = isPollFinished; + this.totalParticipants = totalParticipants; + this.hasUserVoted = hasUserVoted; + this.pollFinished = PollFinished; + } + public static PollInfo of(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, + List pollVotesCounts, List userVotesOptions, + Long totalParticipants, boolean hasUserVoted , boolean pollFinished) { + return new PollInfo(pollOptions, pollEndTime, multipleChoice, pollVotesCounts, userVotesOptions, totalParticipants,hasUserVoted ,pollFinished); } + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index bcb55f15..7abe47e2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; @@ -37,7 +38,8 @@ import org.springframework.web.multipart.MultipartFile; -import java.util.Comparator; +import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; @Service @@ -113,51 +115,60 @@ private void validateUserAndPost(PostEntity post) { SecurityUtils.validateUser(post.getUserId()); } - // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page; - if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) - page = postRepository.findByPostCategoryStartingWithOrderByCreatedAt(postCategory.toString(), pageRequest); - else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); - } // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { + //Post 관련 인자 처리 String nickname = post.isAnonymous() ? "익명" : getNicknameByUserId(post.getUserId()); - UserInfo userInfo = getUserInfoAboutPost(post.get_id()); + int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); int scrapCount = scrapService.getScrapCount(post.get_id()); int hitsCount = redisService.getHitsCount(post.get_id()); int commentCount = post.getCommentCount(); - // POLL 게시물 처리 - if (post.getPostCategory().equals(PostCategory.POLL)) { + UserInfo userInfo = getUserInfoAboutPost(post.get_id()); + + // 투표 게시물 처리 + if (post.getPostCategory() == PostCategory.POLL) { PollEntity poll = pollRepository.findByPostId(post.get_id()) .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); - long totalVotes = pollVoteRepository.countByPollId(poll.get_id()); + + long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), post.getUserId()) - .stream().flatMap(vote -> vote.getSelectedOptions().stream()).toList(); + .map(PollVoteEntity::getSelectedOptions) + .orElse(Collections.emptyList()); + boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), post.getUserId()); + //투표 DTO 생성 + PostPollDetailResponseDTO.PollInfo pollInfo = PostPollDetailResponseDTO.PollInfo.of(poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), + poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + + //게시물 + 투표 DTO 생성 return PostPollDetailResponseDTO.of( - post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo, - poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), - poll.getPollVotesCounts(), hasUserVoted, userVotes, totalVotes + PostDetailResponseDTO.of(post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo), + pollInfo ); } + // 일반 게시물 처리 - return PostDetailResponseDTO.of( - post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo - ); + return PostDetailResponseDTO.of(post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo); + } + + // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page; + if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) + page = postRepository.findByPostCategoryStartingWithOrderByCreatedAt(postCategory.toString(), pageRequest); + else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } // 게시물 리스트 가져오기 public List getPostListResponseDtos(List posts) { return posts.stream() - .sorted(Comparator.comparing(PostEntity::getCreatedAt).reversed()) .map(this::createPostDetailResponse) .toList(); } @@ -174,6 +185,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { return createPostDetailResponse(post); } + + public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); From 32302cc00f37a9b209deaf47456150f524091bd7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 3 Jan 2025 10:36:36 +0900 Subject: [PATCH 0316/1002] =?UTF-8?q?Refactor=20::=20x-frame-option=20head?= =?UTF-8?q?er=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/config/SecurityConfig.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 73f21f87..4efdff1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -16,6 +16,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -66,7 +67,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { UsernamePasswordAuthenticationFilter.class ) // 예외 처리 필터 추가 - .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class); + .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) + // Content-Security-Policy 및 Frame-Options 설정 + .headers(headers -> headers + .contentSecurityPolicy(csp -> csp + .policyDirectives("frame-ancestors 'self' http://localhost:8080 http://www.codin.co.kr https://www.codin.co.kr;") // 특정 도메인만 허용 + ) + .frameOptions(frame -> frame.disable()) // X-Frame-Options 비활성화 + ); + + http.setSharedObject(AuthenticationManager.class, authenticationManager(http)); http.setSharedObject(RoleHierarchy.class, roleHierarchy()); From 46bb81b89db73edf83c80fd1405408f39c13c31f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 3 Jan 2025 22:17:49 +0900 Subject: [PATCH 0317/1002] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatting/service/ChattingService.java | 13 +++++++--- .../notification/NotificationController.java | 24 +++++++++++++++++++ .../service/NotificationService.java | 16 +++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 382efc1e..d6791a90 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; @@ -9,6 +10,7 @@ import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.security.CustomUserDetails; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; @@ -32,16 +34,21 @@ public class ChattingService { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; private final S3Service s3Service; - - //todo 이미지 채팅에 따른 S3 처리 + private final NotificationService notificationService; public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto.getContent(), userId, chattingRequestDto.getContentType()); - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); chattingRepository.save(chatting); + log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); + //Receiver의 알림 체크 후, 메세지 전송 + for (Participants participant : chatRoom.getParticipants()){ + if (participant.getUserId() != userId && participant.isNotificationsEnabled()){ + notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom); + } + } return ChattingResponseDto.of(chatting); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java new file mode 100644 index 00000000..cf8e09d1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.notification; + +import inu.codin.codin.domain.notification.service.NotificationService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +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.RestController; + +@RestController +@RequestMapping("/notification") +@RequiredArgsConstructor +public class NotificationController { + + private final NotificationService notificationService; + + @Operation(summary = "알림 읽기") + @GetMapping("/{notificationId}") + public void readNotification(@PathVariable String notificationId){ + notificationService.readNotification(notificationId); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index ae6fbd30..38b2f940 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.notification.service; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -8,7 +10,6 @@ import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -16,7 +17,6 @@ import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; import inu.codin.codin.infra.fcm.service.FcmService; -import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -40,6 +40,7 @@ public class NotificationService { private final String NOTI_COMMENT_TITLE = "누군가가 내 게시글에 댓글을 달았어요!"; private final String NOTI_REPLY_TITLE = "누군가가 내 댓글에 답글을 달았어요!"; private final String NOTI_LIKE_TITLE = "나에게 첫 좋아요가 달렸어요!"; + private final String NOTI_CHAT_TITLE = "에서 연락이 왔어요!"; /** @@ -178,4 +179,15 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { } } } + + public void sendNotificationMessageByChat(ObjectId userId, ChattingRequestDto chattingRequestDto, ChatRoom chatRoom) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + Map chat = new HashMap<>(); + chat.put("chatRoomId", chatRoom.get_id().toString()); + sendFcmMessageToUser(chatRoom.getRoomName()+NOTI_CHAT_TITLE, chattingRequestDto.getContent(), chat, user); + } + + public void readNotification(String notificationId){ + } } From 7215c03b12baa39e0c25b3cef0464fa4ea659578 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 3 Jan 2025 22:47:20 +0900 Subject: [PATCH 0318/1002] =?UTF-8?q?docs=20:=20@Tag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/poll/controller/PollController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index 52507d85..0036a3aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -4,17 +4,17 @@ import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.service.PollService; -import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.validation.annotation.Validated; @RestController @RequestMapping("/polls") +@Tag(name = "Poll API", description = "투표 API") @RequiredArgsConstructor public class PollController { From ce6c5869bf17697cf19c81eff8bfaeb687a04e06 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 3 Jan 2025 22:50:11 +0900 Subject: [PATCH 0319/1002] =?UTF-8?q?fix=20:=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=EC=9D=B4=202=EA=B0=9C=20=EC=9D=B4=EC=83=81?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=EC=97=90=EB=A7=8C=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/poll/dto/PollCreateRequestDTO.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java index 0ef6eeda..2721bc45 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java @@ -5,10 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; +import jakarta.validation.constraints.Size; import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; import java.util.List; @@ -28,8 +26,8 @@ public class PollCreateRequestDTO { private String content; @Schema(description = "투표 옵션 리스트", example = "[\"a\", \"b\", \"c\"]") - @NotNull - private List<@NotBlank String> pollOptions; + @Size(min = 2) + private List pollOptions; @Schema(description = "복수 선택 가능 여부", example = "true") private boolean multipleChoice; From a7df99c15e250e30704337991d0b2fde48c47cc4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 3 Jan 2025 22:51:28 +0900 Subject: [PATCH 0320/1002] =?UTF-8?q?fix=20:=20USER=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=ED=88=AC=ED=91=9C=20=EA=B6=8C=ED=95=9C=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/poll/service/PollService.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index 8e7c1f3e..3de4d777 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -7,23 +7,21 @@ import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; - import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserRole; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; - import java.time.LocalDateTime; import java.util.List; @@ -39,11 +37,6 @@ public class PollService { public void createPoll(PollCreateRequestDTO pollRequestDTO) { ObjectId userId = SecurityUtils.getCurrentUserId(); - // 권한 확인 처리 로직 추가 - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - pollRequestDTO.getPostCategory().toString().equals("POLL")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표에 대한 권한이 없습니다."); - } // PostEntity 생성 및 저장 PostEntity postEntity = PostEntity.builder() From bd57550d168a6d5d66378cc338b99d0b8e16cb48 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 00:07:13 +0900 Subject: [PATCH 0321/1002] =?UTF-8?q?feat=20:=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../poll/controller/PollController.java | 12 ++++++-- .../post/domain/poll/entity/PollEntity.java | 10 +++++-- .../post/domain/poll/service/PollService.java | 29 ++++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index 0036a3aa..f1924f4a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -22,7 +22,7 @@ public class PollController { @Operation(summary = "투표 생성") @PostMapping - public ResponseEntity createPoll( + public ResponseEntity> createPoll( @Valid @RequestBody PollCreateRequestDTO pollRequestDTO) { pollService.createPoll(pollRequestDTO); @@ -32,7 +32,7 @@ public ResponseEntity createPoll( @Operation(summary = "투표 실시") @PostMapping("/voting/{postId}") - public ResponseEntity votingPoll( + public ResponseEntity> votingPoll( @PathVariable String postId, @Valid @RequestBody PollVotingRequestDTO pollRequestDTO) { @@ -40,4 +40,12 @@ public ResponseEntity votingPoll( return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(200, "투표 실시 완료", null)); } + + @Operation(summary = "투표 취소") + @DeleteMapping("/voting/{postId}") + public ResponseEntity> deleteVoting(@PathVariable String postId){ + pollService.deleteVoting(postId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "투표 취소 완료", null)); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 3db9d6a4..2e63c596 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -49,9 +49,15 @@ public PollEntity(ObjectId _id, ObjectId postId, List pollOptions, //각 옵션의 투표 수 증가 public void vote(int optionIndex) { - if (optionIndex < 0 || optionIndex >= pollOptions.size()) { + if (optionIndex < 0 || optionIndex >= this.pollOptions.size()) { throw new PollOptionChoiceException("잘못된 선택지입니다."); } - pollVotesCounts.set(optionIndex, pollVotesCounts.get(optionIndex) + 1); + this.pollVotesCounts.set(optionIndex, this.pollVotesCounts.get(optionIndex) + 1); + } + + public void deleteVote(int optionIndex){ + if (this.pollVotesCounts.get(optionIndex) - 1 < 0) + throw new PollOptionChoiceException("올바르지 않은 선택지입니다."); + this.pollVotesCounts.set(optionIndex, this.pollVotesCounts.get(optionIndex) - 1); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index 3de4d777..ceac9d4e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -60,11 +60,9 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { } public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { - // 게시글 조회 및 검증 PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("해당 게시물을 찾을 수 없습니다.")); - validateUserAndPost(post); // 투표 데이터(PollEntity) 조회 PollEntity poll = pollRepository.findByPostId(post.get_id()) @@ -101,7 +99,6 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { .build(); pollVoteRepository.save(vote); - // 투표 항목 DB 반영 for (Integer index : selectedOptions) { poll.vote(index); @@ -109,11 +106,27 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { pollRepository.save(poll); } - private void validateUserAndPost(PostEntity post) { - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().equals("POLL")){ - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "투표 게시글에 대한 권한이 없습니다."); + public void deleteVoting(String postId) { + // 게시글 조회 및 검증 + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("해당 게시물을 찾을 수 없습니다.")); + + // 투표 데이터(PollEntity) 조회 + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 데이터가 존재하지 않습니다.")); + + if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { + throw new PollTimeFailException("이미 종료된 투표입니다."); + } + + ObjectId userId = SecurityUtils.getCurrentUserId(); + PollVoteEntity pollVote = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) + .orElseThrow(() -> new NotFoundException("유저의 투표 내역이 존재하지 않습니다.")); + + for (Integer index : pollVote.getSelectedOptions()) { + poll.deleteVote(index); } - SecurityUtils.validateUser(post.getUserId()); + pollRepository.save(poll); + pollVoteRepository.delete(pollVote); } } \ No newline at end of file From 81baef2d8f6dda2c51d209602d1e5141ed7ac427 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 00:12:24 +0900 Subject: [PATCH 0322/1002] =?UTF-8?q?docs=20:=20pollEndTime=20=EC=B4=88?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=EB=B0=98=ED=99=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/dto/response/PostPollDetailResponseDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 61a983ff..2f564cee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -30,7 +30,7 @@ public static class PollInfo { private final List pollOptions; //투표 종료시간 - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") private final LocalDateTime pollEndTime; //복수 투표 여부 From a6e861bdef49de07927085b1d44c2ae259bc90d4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 00:30:05 +0900 Subject: [PATCH 0323/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 136b0f56..56ed44cb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 136b0f562fad2480e9194f2e7467a0dda1991178 +Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a From dd81fb2c16589382fe48790abef31e43c6d47d1d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 00:47:55 +0900 Subject: [PATCH 0324/1002] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/notification/NotificationController.java | 6 +++++- .../domain/notification/service/NotificationService.java | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java index cf8e09d1..f198b853 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java @@ -1,8 +1,10 @@ package inu.codin.codin.domain.notification; +import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.notification.service.NotificationService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,8 +19,10 @@ public class NotificationController { @Operation(summary = "알림 읽기") @GetMapping("/{notificationId}") - public void readNotification(@PathVariable String notificationId){ + public ResponseEntity> readNotification(@PathVariable String notificationId){ notificationService.readNotification(notificationId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "알림 읽기 완료", null)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 38b2f940..9268b27f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -189,5 +189,9 @@ public void sendNotificationMessageByChat(ObjectId userId, ChattingRequestDto ch } public void readNotification(String notificationId){ + NotificationEntity notificationEntity = notificationRepository.findById(new ObjectId(notificationId)) + .orElseThrow(() -> new NotFoundException("해당 알림을 찾을 수 없습니다.")); + notificationEntity.markAsRead(); + notificationRepository.save(notificationEntity); } } From 2d3db1e3860441e9a0da54a6efcdc3f41deadeee Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 01:53:21 +0900 Subject: [PATCH 0325/1002] =?UTF-8?q?fix=20:=20X-Frame-Options=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 49b2f083..43ffb358 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -71,9 +71,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // Content-Security-Policy 및 Frame-Options 설정 .headers(headers -> headers .contentSecurityPolicy(csp -> csp - .policyDirectives("frame-ancestors 'self' http://localhost:8080 http://www.codin.co.kr https://www.codin.co.kr;") // 특정 도메인만 허용 + .policyDirectives("frame-ancestors 'self' http://localhost:3000 http://localhost:8080 https://www.codin.co.kr https://www.codin.co.kr") // 특정 도메인만 허용 ) - .frameOptions(frame -> frame.disable()) // X-Frame-Options 비활성화 + .frameOptions(HeadersConfigurer.FrameOptionsConfig::deny) // X-Frame-Options 비활성화 ); From 77bb2c598349ba30fb605718012e9d32cabe3cef Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 02:03:16 +0900 Subject: [PATCH 0326/1002] =?UTF-8?q?fix=20:=20X-Frame-Options=20disable?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 43ffb358..be361ddb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -73,7 +73,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .contentSecurityPolicy(csp -> csp .policyDirectives("frame-ancestors 'self' http://localhost:3000 http://localhost:8080 https://www.codin.co.kr https://www.codin.co.kr") // 특정 도메인만 허용 ) - .frameOptions(HeadersConfigurer.FrameOptionsConfig::deny) // X-Frame-Options 비활성화 + .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable) // X-Frame-Options 비활성화 ); From 41bdd449ba70f18259dc39b7841fbf6b4b9678f1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 02:30:31 +0900 Subject: [PATCH 0327/1002] =?UTF-8?q?fix=20:=20setExposedHeaders=EC=97=90?= =?UTF-8?q?=20x-refresh-token=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index be361ddb..966b71b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -154,7 +154,7 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr")); config.setAllowedHeaders(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); - config.addExposedHeader("Authorization"); + config.setExposedHeaders(List.of("Authorization", "x-refresh-token")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; From 52c65d3be8ee3358f1502cdb85a669e7c55cb8c7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 4 Jan 2025 02:36:40 +0900 Subject: [PATCH 0328/1002] =?UTF-8?q?fix=20:=20Security=20frameOptions=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 966b71b1..5a2734d9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -70,10 +70,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) // Content-Security-Policy 및 Frame-Options 설정 .headers(headers -> headers - .contentSecurityPolicy(csp -> csp - .policyDirectives("frame-ancestors 'self' http://localhost:3000 http://localhost:8080 https://www.codin.co.kr https://www.codin.co.kr") // 특정 도메인만 허용 - ) - .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable) // X-Frame-Options 비활성화 + .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // X-Frame-Options 비활성화 ); From 748829eb0c7ca3cb76c154e0e8e7b0f0f54d5011 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 12 Jan 2025 23:42:49 +0900 Subject: [PATCH 0329/1002] =?UTF-8?q?docs=20:=20chat,=20info=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EA=B7=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/service/ChatRoomService.java | 7 +++++++ .../domain/chat/chatting/service/ChattingService.java | 6 +++--- .../codin/domain/info/domain/lab/service/LabService.java | 7 +++++++ .../domain/info/domain/office/service/OfficeService.java | 8 +++++++- .../info/domain/professor/service/ProfessorService.java | 7 +++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index a8c7e197..c8197125 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -38,6 +38,7 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat .orElseThrow(() -> new NotFoundException("Receive 유저를 찾을 수 없습니다.")); //Receive 유저에 대한 유효성 검사 ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); + log.info("[createChatRoom] {} 채팅방 생성, Maker : {}, Receiver : {}", chatRoom.get_id().toString(), senderId.toString(), chatRoomCreateRequestDto.getReceiverId()) Map response = new HashMap<>(); response.put("chatRoomId", chatRoom.get_id().toString()); return response; @@ -49,6 +50,7 @@ public List getAllChatRoomByUser(UserDetails userDetail return chatRooms.stream() .map(chatRoom -> { Chatting chat = customChattingRepository.findMostRecentByChatRoomId(chatRoom.get_id()); + log.info("[getAllChatRoomByUser] {}의 채팅방 반환", userId.toString()); return ChatRoomListResponseDto.of(chatRoom, chat); }).toList(); } @@ -66,6 +68,7 @@ public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { log.info("[LeaveChatRoom] 채팅방에 참여자가 없어 채팅방 삭제"); } chatRoomRepository.save(chatRoom); + log.info("[leaveChatRoom] 유저 {}가 {} 채팅방 떠나기", userId, chatRoomId); } public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) { @@ -76,5 +79,9 @@ public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) .filter(participants -> participants.getUserId().equals(userId)) .forEach(Participants::updateNotification); chatRoomRepository.save(chatRoom); + log.info("[setNotificationChatRoom] 유저 {} 의 채팅방 {}의 알림 설정 {}", userId.toString(), chatRoomId, + chatRoom.getParticipants().stream() + .filter(participants -> participants.getUserId().equals(userId)) + .map(Participants::isNotificationsEnabled)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 382efc1e..a8e19e4c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -33,14 +33,12 @@ public class ChattingService { private final ChattingRepository chattingRepository; private final S3Service s3Service; - //todo 이미지 채팅에 따른 S3 처리 - public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto.getContent(), userId, chattingRequestDto.getContentType()); - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); + log.info("[sendMessage] [{}] (유저 {} , 채팅방 {})", chattingRequestDto.getContent(), userId, id); chattingRepository.save(chatting); return ChattingResponseDto.of(chatting); } @@ -51,10 +49,12 @@ public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); List chattingResponseDto = chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) .stream().map(ChattingResponseDto::of).toList(); + log.info("[getAllMessage] 유저 {}, 채팅방 {}의 메세지 내역(page = {}) 반환", SecurityUtils.getCurrentUserId().toString(), id, page); return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); } public List sendImageMessage(List chatImages) { + log.info("[sendImageMessage] 이미지 업로드"); return s3Service.handleImageUpload(chatImages); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index f74b411d..d56a1700 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; @@ -14,6 +15,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class LabService { private final InfoRepository infoRepository; @@ -21,18 +23,21 @@ public class LabService { public LabThumbnailResponseDto getLabThumbnail(String id) { Lab lab = infoRepository.findLabById(new ObjectId(id)) .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + log.info("[getLabThumbnail] {}의 연구실 정보 열람", id); return LabThumbnailResponseDto.of(lab); } public List getAllLab() { List labs = infoRepository.findAllLabs(); List list = labs.stream().map(LabListResponseDto::of).toList(); + log.info("[getAllLab] 모든 연구실 정보 반환"); return list; } public void createLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto) { Lab lab = Lab.of(labCreateUpdateRequestDto); infoRepository.save(lab); + log.info("[createLab] '{}'의 연구실 정보 생성", lab.getTitle()); } public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, String id) { @@ -40,6 +45,7 @@ public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, Strin .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); lab.update(labCreateUpdateRequestDto); infoRepository.save(lab); + log.info("[updateLab] {}의 연구실 정보 업데이트", lab.get_id().toString()); } public void deleteLab(String id) { @@ -47,5 +53,6 @@ public void deleteLab(String id) { .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); lab.delete(); infoRepository.save(lab); + log.info("[deleteLab] {}의 연구실 정보 삭제", lab.get_id().toString()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index 06663476..bedb4139 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -8,16 +8,19 @@ import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Slf4j public class OfficeService { private final InfoRepository infoRepository; public OfficeDetailsResponseDto getOfficeByDepartment(Department department) { Office office = infoRepository.findOfficeByDepartment(department); + log.info("[getOfficeByDepartment] '{}' 사무실 정보 열람", office.getDepartment().getDescription()); return OfficeDetailsResponseDto.of(office); } @@ -26,13 +29,14 @@ public void createOfficeMember(Department department, OfficeMemberCreateUpdateRe OfficeMember officeMember = OfficeMember.of(officeMemberCreateUpdateRequestDto); office.addOfficeMember(officeMember); infoRepository.save(office); - + log.info("[createOfficeMember] '{}' 사무실의 멤버 추가", office.getDepartment().getDescription()); } public void updateOffice(Department department, OfficeUpdateRequestDto officeUpdateRequestDto) { Office office = infoRepository.findOfficeByDepartment(department); office.update(officeUpdateRequestDto); infoRepository.save(office); + log.info("[updateOffice] '{}' 사무실 정보 업데이트", office.getDepartment().getDescription()); } public void updateOfficeMember(Department department, int num, OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { @@ -40,11 +44,13 @@ public void updateOfficeMember(Department department, int num, OfficeMemberCreat OfficeMember officeMember = office.getMember().get(num); officeMember.update(officeMemberCreateUpdateRequestDto); infoRepository.save(office); + log.info("[updateOfficeMember] '{}' 사무실의 '{}' 멤버 정보 업데이트", office.getDepartment().getDescription(), officeMember.getName()); } public void deleteOfficeMember(Department department, int num) { Office office = infoRepository.findOfficeByDepartment(department); office.getMember().get(num).delete(); infoRepository.save(office); + log.info("[deleteOfficeMember] '{} 사무실의 '{}' 멤버 정보 삭제", department.getDescription(), office.getMember().get(num).getName()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index d2cdb51a..a96850ad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; @@ -16,18 +17,21 @@ @Service @RequiredArgsConstructor +@Slf4j public class ProfessorService { private final InfoRepository infoRepository; public List getProfessorByDepartment(Department department){ List professors = infoRepository.findAllProfessorsByDepartment(department); + log.info("[getProfessorByDepartment] '{}' 학과의 교수님 정보 모두 반환", department.getDescription()); return professors.stream().map(ProfessorListResponseDto::of).toList(); } public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + log.info("[getProfessorThumbnail] {} 교수님의 정보 열람", professor.get_id().toString()); return ProfessorThumbnailResponseDto.of(professor); } @@ -37,6 +41,7 @@ public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdat } Professor professor = Professor.of(professorCreateUpdateRequestDto); infoRepository.save(professor); + log.info("[createProfessor] {} 교수님의 정보 생성", professor.getName()); } public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { @@ -44,6 +49,7 @@ public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professor .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); professor.update(professorCreateUpdateRequestDto); infoRepository.save(professor); + log.info("[updateProfessor] {} 교수님의 정보 업데이트", professor.get_id().toString()); } @@ -53,5 +59,6 @@ public void deleteProfessor(String id) { .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); professor.delete(); infoRepository.save(professor); + log.info("[deleteProfessor] {} 교수님의 정보 삭제", professor.get_id().toString()); } } From c76c1683e40c435f91996eb9c23b95a20b9f39f2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 14 Jan 2025 01:48:18 +0900 Subject: [PATCH 0330/1002] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/controller/EmailController.java | 14 ++------ .../domain/email/entity/EmailAuthEntity.java | 4 +++ .../email/repository/EmailAuthRepository.java | 2 ++ .../email/service/EmailAuthService.java | 13 +------ .../email/service/EmailSendService.java | 2 +- .../user/controller/UserController.java | 7 ++-- .../dto/request/UserPasswordRequestDto.java | 6 +--- .../codin/domain/user/entity/UserEntity.java | 2 +- .../domain/user/service/UserService.java | 36 ++++++++++++++++--- 9 files changed, 47 insertions(+), 39 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 5c90cc80..356d41eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -43,7 +43,7 @@ public ResponseEntity> checkAuthNum( } @Operation( - summary = "비밀번호 찾기를 위한 이메일 인증" + summary = "비밀번호 재설정을 위한 이메일 전송" ) @PostMapping("/auth/password") public ResponseEntity> sendPasswordEmail( @@ -51,16 +51,6 @@ public ResponseEntity> sendPasswordEmail( ) { emailAuthService.sendPasswordEmail(emailAuthRequestDto); return ResponseEntity.ok() - .body(new SingleResponse<>(200, "이메일 인증 코드 전송 성공", null)); - } - - @Operation(summary = "비밀번호 재설정 인증 코드 확인 - 학교인증 X") - @PostMapping("/auth/password/check") - public ResponseEntity> checkPasswordAuthNum( - @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto - ) { - emailAuthService.checkPasswordAuthNum(joinEmailCheckRequestDto); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "이메일 인증 성공", null)); + .body(new SingleResponse<>(200, "이메일 비밀번호 재설정 링크 전송 성공", null)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index ab29ee6d..6d20f237 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -39,6 +39,10 @@ public void verifyEmail() { this.isVerified = true; } + public void unVerifyEmail(){ + this.isVerified=false; + } + /** * 10분 이상이면 만료됌 */ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java index a3b65844..4a1702b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java @@ -14,4 +14,6 @@ public interface EmailAuthRepository extends MongoRepository findByEmailAndAuthNum(String email, String authNum); + Optional findByAuthNum(String authNum); + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index c48cec16..3d678df0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,13 +1,10 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -22,7 +19,6 @@ public class EmailAuthService { private final EmailAuthRepository emailAuthRepository; private final EmailSendService emailSendService; - private final UserRepository userRepository; public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { @@ -78,6 +74,7 @@ public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { if (emailAuth.isPresent()) { emailAuthEntity = emailAuth.get(); emailAuthEntity.changeAuthNum(generateAuthNum()); + emailAuthEntity.unVerifyEmail(); } else { // 인증 생성 로직 @@ -92,14 +89,6 @@ public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { emailSendService.sendPasswordEmail(email, emailAuthEntity.getAuthNum()); } - public void checkPasswordAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { - checkEmailAndAuthNum(joinEmailCheckRequestDto); - UserEntity user = userRepository.findByEmail(joinEmailCheckRequestDto.getEmail()) - .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); - user.changePassword(); - userRepository.save(user); - } - private void checkEmailAndAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { String email = joinEmailCheckRequestDto.getEmail(); String authNum = joinEmailCheckRequestDto.getAuthNum(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java index faeb4673..f8691f5c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java @@ -59,7 +59,7 @@ public void sendPasswordEmail(String email, String authNum) { String htmlContent = templateEngine.process("password-email", context); helper.setTo(email); - helper.setSubject("[CODIN] 비밀번호 찾기 인증번호입니다."); + helper.setSubject("[CODIN] 비밀번호 재설정 링크입니다."); helper.setText(htmlContent, true); javaMailSender.send(message); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index f85d9889..1b45e039 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -79,9 +79,10 @@ public ResponseEntity> getUserComment(@RequestP @Operation( summary = "비밀번호 재설정" ) - @PutMapping("/password") - public ResponseEntity> setUserPassword(@RequestBody @Valid UserPasswordRequestDto userPasswordRequestDto){ - userService.setUserPassword(userPasswordRequestDto); + @PutMapping("/password/{code}") + public ResponseEntity> setUserPassword(@RequestBody @Valid UserPasswordRequestDto userPasswordRequestDto, + @PathVariable("code") String code){ + userService.setUserPassword(userPasswordRequestDto, code); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "비밀번호 재설정 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java index d89cf97a..06b1e4b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java @@ -8,12 +8,8 @@ @Getter public class UserPasswordRequestDto { - @Schema(description = "이메일 주소", example = "example@inu.ac.kr") - @Email - @NotBlank - private String email; - @Schema(description = "변경된 비밀번호", example = "password1234") + @NotBlank private String password; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index f7dea137..b75f4c99 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -54,7 +54,7 @@ public void updatePassword(String password){ this.password = password; } - public void changePassword(){ + public void canChangePassword(){ this.changePassword = !this.changePassword; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 7a6e88d0..9e38aeeb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -37,9 +38,9 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.services.s3.endpoints.internal.Not; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -148,19 +149,44 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } } - public void setUserPassword(@Valid UserPasswordRequestDto userPasswordRequestDto) { - UserEntity user = userRepository.findByEmail(userPasswordRequestDto.getEmail()) - .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); + public void setUserPassword(@Valid UserPasswordRequestDto userPasswordRequestDto, String code) { + UserEntity user = checkPasswordAuthNum(code); if (user.isChangePassword()){ String encodedPassword = passwordEncoder.encode(userPasswordRequestDto.getPassword()); user.updatePassword(encodedPassword); - user.changePassword(); + user.canChangePassword(); userRepository.save(user); } else { throw new UserPasswordChangeFailException("유저의 비밀번호를 변경할 수 없습니다. 이메일 인증을 먼저 진행해주세요."); } } + public UserEntity checkPasswordAuthNum(String authNum) { + EmailAuthEntity emailAuthEntity = checkEmailAndAuthNum(authNum); + log.info("[checkAuthNumForPW] email : {}, authNum : {}", emailAuthEntity.getEmail(), authNum); + + UserEntity user = userRepository.findByEmail(emailAuthEntity.getEmail()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + user.canChangePassword(); + userRepository.save(user); + return user; + } + + private EmailAuthEntity checkEmailAndAuthNum(String authNum) { + EmailAuthEntity emailAuthEntity = emailAuthRepository.findByAuthNum(authNum) + .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", authNum)); + + // 10분 이내에 인증하지 않을 시에 인증번호 만료 + if (emailAuthEntity.isExpired()) { + throw new EmailAuthFailException("인증번호가 만료되었습니다.", emailAuthEntity.getEmail()); + } + + emailAuthEntity.verifyEmail(); + emailAuthRepository.save(emailAuthEntity); + log.info("[checkAuthNumForPW] Can change password, email : {}", emailAuthEntity.getEmail()); + return emailAuthEntity; + } + public void deleteUser(UserDeleteRequestDto userDeleteRequestDto) { UserEntity user = userRepository.findByEmail(userDeleteRequestDto.getEmail()) .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); From e700b1b3590687b3331e11305d6dded0a4922228 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 14 Jan 2025 01:49:20 +0900 Subject: [PATCH 0331/1002] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 56ed44cb..309c9aec 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a +Subproject commit 309c9aec8dc95274176a48e14564aad99a34e4bc From 0721e9b2ca3ba55d53f5a895e66263e4c7fe680a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 14 Jan 2025 02:20:33 +0900 Subject: [PATCH 0332/1002] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EC=8B=9C,=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=ED=95=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=EC=9D=B8=EC=A7=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/email/service/EmailAuthService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 3d678df0..7ced4e55 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -19,6 +20,7 @@ public class EmailAuthService { private final EmailAuthRepository emailAuthRepository; private final EmailSendService emailSendService; + private final UserRepository userRepository; public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { @@ -64,6 +66,9 @@ public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { + userRepository.findByEmail(joinEmailSendRequestDto.getEmail()) + .orElseThrow(() -> new EmailAuthFailException("회원가입을 먼저 진행해주세요.")); + String email = joinEmailSendRequestDto.getEmail(); log.info("[sendAuthEmail] email : {}", email); From b3b807ea3871ea52308bb235d308ec4891c311e5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 14 Jan 2025 02:23:02 +0900 Subject: [PATCH 0333/1002] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EC=8B=9C,=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=ED=95=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=EC=9D=B8=EC=A7=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/email/service/EmailAuthService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 7ced4e55..3e5fe55d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.email.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; @@ -67,7 +68,7 @@ public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { userRepository.findByEmail(joinEmailSendRequestDto.getEmail()) - .orElseThrow(() -> new EmailAuthFailException("회원가입을 먼저 진행해주세요.")); + .orElseThrow(() -> new NotFoundException("회원가입을 먼저 진행해주세요.")); String email = joinEmailSendRequestDto.getEmail(); log.info("[sendAuthEmail] email : {}", email); From a485d44369465f4e2a33d17f23c4558d4ad570e0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 02:09:56 +0900 Subject: [PATCH 0334/1002] =?UTF-8?q?feat=20::=20PostService=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/service/PostService.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 6cc08d73..1c1f3610 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -32,6 +32,7 @@ import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -46,6 +47,7 @@ import java.util.Map; import java.util.stream.Collectors; +@Slf4j @Service @RequiredArgsConstructor public class PostService { @@ -61,11 +63,13 @@ public class PostService { private final PollVoteRepository pollVoteRepository; public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); List imageUrls = s3Service.handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + log.error("비교과 게시물에 대한 접근권한 없음. UserId: {}", userId); throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } @@ -81,10 +85,13 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + log.info("게시물 수정 시작. PostId: {}", postId); PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); @@ -94,6 +101,8 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); + log.info("게시물 수정 성공. PostId: {}", postId); + } public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { @@ -103,6 +112,8 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); + log.info("게시물 익명 수정 성공. PostId: {}", postId); + } public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) @@ -111,10 +122,13 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); + log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); + } private void validateUserAndPost(PostEntity post) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } SecurityUtils.validateUser(post.getUserId()); @@ -150,6 +164,8 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { PostPollDetailResponseDTO.PollInfo pollInfo = PostPollDetailResponseDTO.PollInfo.of(poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + log.info("게시글-투표 상세정보 생성 성공. PostId: {}", post.get_id()); + //게시물 + 투표 DTO 생성 return PostPollDetailResponseDTO.of( PostDetailResponseDTO.of(post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo), @@ -157,6 +173,7 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { ); } + log.info("일반 게시물 상세정보 생성 성공 PostId: {}", post.get_id()); // 일반 게시물 처리 return PostDetailResponseDTO.of(post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo); } @@ -168,6 +185,8 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) page = postRepository.findByPostCategoryStartingWithAndDeletedAtIsNullAndPostStatusInOrderByCreatedAt(postCategory.toString(), PostStatus.ACTIVE, pageRequest); else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); + + log.info("모든 글 반환 성공 Category: {}, Page: {}", postCategory, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } @@ -184,8 +203,10 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - if (redisService.validateHits(post.get_id(), userId)) + if (redisService.validateHits(post.get_id(), userId)) { redisService.addHits(post.get_id(), userId); + log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); + } return createPostDetailResponse(post); } @@ -198,6 +219,8 @@ public void softDeletePost(String postId) { validateUserAndPost(post); post.delete(); + + log.info("게시물 안전 삭제. PostId: {}", postId); postRepository.save(post); } @@ -207,16 +230,20 @@ public void deletePostImage(String postId, String imageUrl) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); validateUserAndPost(post); - if (!post.getPostImageUrls().contains(imageUrl)) + if (!post.getPostImageUrls().contains(imageUrl)) { + log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", postId, imageUrl); throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); - + } try { // S3에서 이미지 삭제 s3Service.deleteFile(imageUrl); // 게시물의 이미지 리스트에서 제거 post.removePostImage(imageUrl); postRepository.save(post); + log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", postId, imageUrl); + } catch (Exception e) { + log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", postId, imageUrl, e); throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); } } @@ -239,6 +266,7 @@ public String getNicknameByUserId(ObjectId userId) { public PostPageResponse searchPosts(String keyword, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, pageRequest); + log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } public List getTop3BestPosts() { @@ -258,6 +286,7 @@ public List getTop3BestPosts() { return postEntity; } ).toList(); + log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); } From f5d5db845ed99a19c8be9b7ae6750c72da9e0fd2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 02:17:52 +0900 Subject: [PATCH 0335/1002] =?UTF-8?q?feat=20::=20comment,=20reply=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/service/CommentService.java | 7 +++++++ .../domain/reply/service/ReplyCommentService.java | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 8abd0e18..2e97b608 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -42,6 +42,8 @@ public class CommentService { // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { + log.info("댓글 추가 요청. postId: {}, 사용자: {}, 내용: {}", id, SecurityUtils.getCurrentUserId(), requestDTO.getContent()); + ObjectId postId = new ObjectId(id); PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); @@ -64,6 +66,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 삭제 (Soft Delete) public void softDeleteComment(String id) { + log.info("댓글 삭제 요청. commentId: {}", id); ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); @@ -128,6 +131,7 @@ public List getCommentsByPostId(String id) { } public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { + log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) @@ -135,6 +139,9 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); + + log.info("댓글 업데이트 완료. commentId: {}", commentId); + } public UserInfo getUserInfoAboutPost(ObjectId commentId) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 1747bc05..502bd5ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -41,6 +41,9 @@ public class ReplyCommentService { // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { + log.info("대댓글 추가 요청 - commentId: {}, content: {}, anonymous: {}", + id, requestDTO.getContent(), requestDTO.isAnonymous()); + ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); @@ -64,10 +67,16 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { redisService.applyBestScore(1, post.get_id()); postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); + + log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", + reply.get_id(), post.get_id(), post.getCommentCount()); + } // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { + log.info("대댓글 삭제 요청 - replyId: {}", replyId); + ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); SecurityUtils.validateUser(reply.getUserId()); @@ -89,6 +98,7 @@ public void softDeleteReply(String replyId) { // 특정 댓글의 대댓글 조회 public List getRepliesByCommentId(ObjectId commentId) { + List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); Map userNicknameMap = userRepository.findAllById( @@ -125,6 +135,7 @@ public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { + log.info("대댓글 수정 요청 - replyId: {}, newContent: {}", id, requestDTO.getContent()); ObjectId replyId = new ObjectId(id); ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) @@ -132,5 +143,8 @@ public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { reply.updateReply(requestDTO.getContent()); replyCommentRepository.save(reply); + + log.info("대댓글 수정 완료 - replyId: {}", replyId); + } } From 6df8f096ccf291ad9e4cb97502e039d3ea390f44 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 02:27:10 +0900 Subject: [PATCH 0336/1002] =?UTF-8?q?feat=20::=20like,=20scrap=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/like/service/LikeService.java | 24 +++++++++++++++++-- .../domain/scrap/service/ScrapService.java | 24 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index e345b0b6..fdd1a03a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -30,6 +30,9 @@ public class LikeService { private final RedisHealthChecker redisHealthChecker; public String toggleLike(LikeRequestDto likeRequestDto) { + log.info("좋아요 토글 요청 - likeType: {}, id: {}", likeRequestDto.getLikeType(), likeRequestDto.getId()); + + ObjectId likeId = new ObjectId(likeRequestDto.getId()); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 @@ -39,41 +42,55 @@ public String toggleLike(LikeRequestDto likeRequestDto) { if (like != null && like.getDeletedAt() == null) { removeLike(like); + log.info("좋아요 취소 완료 - likeId: {}, userId: {}", like.get_id(), userId); return "좋아요가 삭제되었습니다."; } addLike(likeRequestDto.getLikeType(), likeId, userId); + log.info("좋아요 추가 완료 - likeType: {}, likeId: {}, userId: {}", likeRequestDto.getLikeType(), likeId, userId); return "좋아요가 추가되었습니다."; } public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId) { + log.info("좋아요 추가 요청 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); if (like != null){ if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 + log.info("삭제된 좋아요 복구 - likeId: {}, userId: {}", like.get_id(), userId); like.recreatedAt(); like.restore(); likeRepository.save(like); - } else throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); + log.info("좋아요 복구 완료 - likeId: {}, userId: {}", like.get_id(), userId); + } else { + log.warn("좋아요 추가 실패 - 이미 좋아요가 눌려 있음 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); + } } else { //좋아요 내역이 없으면 새로 생성 if (redisHealthChecker.isRedisAvailable()) { redisService.addLike(likeType.name(), likeId, userId); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); } likeRepository.save(LikeEntity.builder() .likeType(likeType) .likeTypeId(likeId) .userId(userId) .build()); - if (likeType == LikeType.POST) + if (likeType == LikeType.POST) { redisService.applyBestScore(1, likeId); + log.info("Redis에 Best Score 적용 - postId: {}", likeId); + } } } public void removeLike(LikeEntity like) { + log.info("좋아요 삭제 요청 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); if (redisHealthChecker.isRedisAvailable()) { redisService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); + log.info("Redis에서 좋아요 삭제 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); } like.delete(); likeRepository.save(like); + log.info("좋아요 삭제 완료 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); } public int getLikeCount(LikeType entityType, ObjectId entityId) { @@ -85,13 +102,16 @@ public int getLikeCount(LikeType entityType, ObjectId entityId) { } public void recoverRedisFromDB() { + log.info("Redis 복구 요청 - DB의 좋아요 데이터를 기반으로 복구 시작"); likeRepository.findAll().forEach(like -> { redisService.addLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); + log.info("Redis에 좋아요 복구 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); }); } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ ObjectId id = new ObjectId(likeRequestDto.getId()); + log.info("엔티티 삭제 상태 확인 - likeType: {}, id: {}", likeRequestDto.getLikeType(), id); switch(likeRequestDto.getLikeType()){ case POST -> postRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java index dfd8c2e4..cddff655 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java @@ -24,9 +24,14 @@ public class ScrapService { private final RedisHealthChecker redisHealthChecker; public String toggleScrap(String id) { + log.info("스크랩 토글 요청 - postId: {}", id); + ObjectId postId = new ObjectId(id); postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("스크랩 토글 실패 - 게시글을 찾을 수 없음 - postId: {}", postId); + return new NotFoundException("게시글을 찾을 수 없습니다."); + }); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -43,6 +48,8 @@ public String toggleScrap(String id) { } private void addScrap(ObjectId postId, ObjectId userId) { + log.info("스크랩 추가 요청 - postId: {}, userId: {}", postId, userId); + ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); if (scrap != null){ @@ -50,25 +57,33 @@ private void addScrap(ObjectId postId, ObjectId userId) { scrap.recreatedAt(); scrap.restore(); scrapRepository.save(scrap); - } else throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); + } else { + log.warn("스크랩 추가 실패 - 이미 스크랩된 상태 - postId: {}, userId: {}", postId, userId); + throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); + } } else { if (redisHealthChecker.isRedisAvailable()) { redisService.addScrap(postId, userId); + log.info("Redis에 스크랩 추가 - postId: {}, userId: {}", postId, userId); } scrapRepository.save(ScrapEntity.builder() .postId(postId) .userId(userId) .build()); redisService.applyBestScore(2, postId); + log.info("Redis에 Best Score 적용 - postId: {}", postId); } } private void removeScrap(ScrapEntity scrap) { + log.info("스크랩 삭제 요청 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); if (redisHealthChecker.isRedisAvailable()) { redisService.removeScrap(scrap.getPostId(), scrap.getUserId()); + log.info("Redis에서 스크랩 삭제 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); } scrap.delete(); scrapRepository.save(scrap); + log.info("스크랩 삭제 완료 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); } public int getScrapCount(ObjectId postId) { @@ -80,8 +95,13 @@ public int getScrapCount(ObjectId postId) { } public void recoverRedisFromDB() { + log.info("Redis 복구 요청 - DB의 스크랩 데이터를 기반으로 복구 시작"); + scrapRepository.findAll().forEach(scrap -> { redisService.addScrap(scrap.getPostId(), scrap.getUserId()); + log.info("Redis에 스크랩 복구 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); }); + + log.info("Redis 복구 완료"); } } \ No newline at end of file From 952a71a2ff81c4ba3b1a8e074cffebf0ef09d1ff Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 02:48:06 +0900 Subject: [PATCH 0337/1002] =?UTF-8?q?feat=20::=20poll=20-=ED=88=AC?= =?UTF-8?q?=ED=91=9C=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/poll/service/PollService.java | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index ceac9d4e..e8b35c99 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -18,15 +18,16 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserRole; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; - @Service @RequiredArgsConstructor +@Slf4j public class PollService { private final PostRepository postRepository; @@ -35,6 +36,7 @@ public class PollService { @Transactional public void createPoll(PollCreateRequestDTO pollRequestDTO) { + log.info("투표 생성 요청 - title: {}, userId: {}", pollRequestDTO.getTitle(), SecurityUtils.getCurrentUserId()); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -48,6 +50,7 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { .postStatus(PostStatus.ACTIVE) .build(); postEntity = postRepository.save(postEntity); + log.info("게시글 저장 완료 - postId: {}", postEntity.get_id()); // PollEntity 생성 및 저장 PollEntity pollEntity = PollEntity.builder() @@ -57,76 +60,98 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { .multipleChoice(pollRequestDTO.isMultipleChoice()) .build(); pollRepository.save(pollEntity); + log.info("투표 저장 완료 - pollId: {}", pollEntity.get_id()); } public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { - // 게시글 조회 및 검증 + log.info("투표 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("투표 실패 - 게시글 없음 - postId: {}", postId); + return new NotFoundException("해당 게시물을 찾을 수 없습니다."); + }); - // 투표 데이터(PollEntity) 조회 PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 데이터가 존재하지 않습니다.")); + .orElseThrow(() -> { + log.warn("투표 실패 - 투표 데이터 없음 - postId: {}", postId); + return new NotFoundException("투표 데이터가 존재하지 않습니다."); + }); - // 투표 종료 여부 확인 if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { + log.warn("투표 실패 - 투표 종료됨 - pollId: {}", poll.get_id()); throw new PollTimeFailException("이미 종료된 투표입니다."); } - // 사용자 투표 여부 검증 ObjectId userId = SecurityUtils.getCurrentUserId(); boolean hasAlreadyVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); if (hasAlreadyVoted) { + log.warn("투표 실패 - 중복 투표 - pollId: {}, userId: {}", poll.get_id(), userId); throw new PollDuplicateVoteException("이미 투표하셨습니다."); } - //투표 항목 검증 List selectedOptions = pollRequestDTO.getSelectedOptions(); if (!poll.isMultipleChoice() && selectedOptions.size() > 1) { + log.warn("투표 실패 - 복수 선택 허용 안됨 - pollId: {}, userId: {}", poll.get_id(), userId); throw new PollOptionChoiceException("복수 선택이 허용되지 않은 투표입니다."); } + for (Integer index : selectedOptions) { if (index < 0 || index >= poll.getPollOptions().size()) { + log.warn("투표 실패 - 잘못된 선택지 - pollId: {}, optionIndex: {}", poll.get_id(), index); throw new PollOptionChoiceException("잘못된 선택지입니다."); } } - //투표 기록 PollVoteEntity vote = PollVoteEntity.builder() .pollId(poll.get_id()) .userId(userId) - .selectedOptions(selectedOptions) // 다중 선택 지원 + .selectedOptions(selectedOptions) .build(); pollVoteRepository.save(vote); + log.info("투표 기록 저장 완료 - pollId: {}, userId: {}", poll.get_id(), userId); - // 투표 항목 DB 반영 for (Integer index : selectedOptions) { poll.vote(index); + log.info("투표 항목 반영 - pollId: {}, optionIndex: {}", poll.get_id(), index); } pollRepository.save(poll); + log.info("투표 완료 - pollId: {}, userId: {}", poll.get_id(), userId); } public void deleteVoting(String postId) { - // 게시글 조회 및 검증 + log.info("투표 취소 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("투표 취소 실패 - 게시글 없음 - postId: {}", postId); + return new NotFoundException("해당 게시물을 찾을 수 없습니다."); + }); - // 투표 데이터(PollEntity) 조회 PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 데이터가 존재하지 않습니다.")); + .orElseThrow(() -> { + log.warn("투표 취소 실패 - 투표 데이터 없음 - postId: {}", postId); + return new NotFoundException("투표 데이터가 존재하지 않습니다."); + }); if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { + log.warn("투표 취소 실패 - 투표 종료됨 - pollId: {}", poll.get_id()); throw new PollTimeFailException("이미 종료된 투표입니다."); } ObjectId userId = SecurityUtils.getCurrentUserId(); PollVoteEntity pollVote = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) - .orElseThrow(() -> new NotFoundException("유저의 투표 내역이 존재하지 않습니다.")); + .orElseThrow(() -> { + log.warn("투표 취소 실패 - 유저 투표 내역 없음 - pollId: {}, userId: {}", poll.get_id(), userId); + return new NotFoundException("유저의 투표 내역이 존재하지 않습니다."); + }); for (Integer index : pollVote.getSelectedOptions()) { poll.deleteVote(index); + log.info("투표 항목 취소 반영 - pollId: {}, optionIndex: {}", poll.get_id(), index); } pollRepository.save(poll); pollVoteRepository.delete(pollVote); + log.info("투표 취소 완료 - pollId: {}, userId: {}", poll.get_id(), userId); } } \ No newline at end of file From e7cfcbdc85d8b61f0f1f4ff68bf1fc08640f40d5 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 03:16:20 +0900 Subject: [PATCH 0338/1002] =?UTF-8?q?feat=20::=20User=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/service/UserService.java | 133 ++++++++++++++---- 1 file changed, 104 insertions(+), 29 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 9e38aeeb..1d0242a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -61,8 +61,14 @@ public class UserService { public void createUser(UserCreateRequestDto userCreateRequestDto, MultipartFile userImage) { + log.info("[회원가입] 요청 데이터: {}", userCreateRequestDto); + String imageUrl = null; - if (userImage != null) imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); + if (userImage != null) { + log.info("[회원가입] 프로필 이미지 업로드 중..."); + imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); + log.info("[회원가입] 프로필 이미지 업로드 완료: {}", imageUrl); + } String encodedPassword = passwordEncoder.encode(userCreateRequestDto.getPassword()); @@ -88,145 +94,214 @@ public void createUser(UserCreateRequestDto userCreateRequestDto, MultipartFile // todo : 정책적으로 보안 위반 사항 확인 -> 에러 메세지를 통해서 유추 금지 private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto) { - EmailAuthEntity emailAuth = emailAuthRepository.findByEmail(userCreateRequestDto.getEmail()).orElseThrow(() -> - new UserCreateFailException("이메일 인증을 먼저 진행해주세요.")); - if (!emailAuth.isVerified()) + log.info("[회원가입 검증] 이메일 인증 상태 확인: {}", userCreateRequestDto.getEmail()); + EmailAuthEntity emailAuth = emailAuthRepository.findByEmail(userCreateRequestDto.getEmail()) + .orElseThrow(() -> { + log.warn("[회원가입 검증 실패] 이메일 인증 기록 없음: {}", userCreateRequestDto.getEmail()); + return new UserCreateFailException("이메일 인증을 먼저 진행해주세요."); + }); + if (!emailAuth.isVerified()) { + log.warn("[회원가입 검증 실패] 이메일 인증 미완료: {}", userCreateRequestDto.getEmail()); throw new UserCreateFailException("이메일 인증을 먼저 진행해주세요."); - if (userRepository.findByEmail(userCreateRequestDto.getEmail()).isPresent()) + } + if (userRepository.findByEmail(userCreateRequestDto.getEmail()).isPresent()) { + log.warn("[회원가입 검증 실패] 이미 존재하는 이메일: {}", userCreateRequestDto.getEmail()); throw new UserCreateFailException("이미 존재하는 이메일입니다."); - if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) + } + if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) { + log.warn("[회원가입 검증 실패] 이미 존재하는 학번: {}", userCreateRequestDto.getStudentId()); throw new UserCreateFailException("이미 존재하는 학번입니다."); + } + log.info("[회원가입 검증] 검증 성공: {}", userCreateRequestDto.getEmail()); } + //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllUserPosts(int pageNumber) { ObjectId userId = SecurityUtils.getCurrentUserId(); + log.info("[게시글 조회] 유저 ID: {}, 페이지 번호: {}", userId, pageNumber); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); - return PostPageResponse.of(postService.getPostListResponseDtos(page.getContent()), page.getTotalPages()-1, page.hasNext()? page.getPageable().getPageNumber() + 1 : -1); + + log.info("[게시글 조회 성공] 조회된 게시글 수: {}, 총 페이지 수: {}", page.getContent().size(), page.getTotalPages()); + return PostPageResponse.of( + postService.getPostListResponseDtos(page.getContent()), + page.getTotalPages() - 1, + page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1 + ); } public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType interactionType) { ObjectId userId = SecurityUtils.getCurrentUserId(); + log.info("[유저 상호작용 조회] 유저 ID: {}, 타입: {}, 페이지 번호: {}", userId, interactionType, pageNumber); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); switch (interactionType) { case LIKE: + log.info("[좋아요 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); List postUserLike = likePage.getContent().stream() .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) .toList(); + log.info("[좋아요 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", likePage.getTotalPages(), likePage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages()-1, likePage.hasNext()? likePage.getPageable().getPageNumber() + 1 : -1); case SCRAP: + log.info("[스크랩 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page scrapPage = scrapRepository.findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(userId, pageRequest); List postUserScrap = scrapPage.getContent().stream() .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) .toList(); + log.info("[스크랩 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", scrapPage.getTotalPages(), scrapPage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages()-1, scrapPage.hasNext()? scrapPage.getPageable().getPageNumber() + 1 : -1); case COMMENT: + log.info("[댓글 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page commentPage = commentRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); List postUserComment = commentPage.getContent().stream() .map(commentEntity -> postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) .orElseThrow(() -> new NotFoundException("유저가 작성한 댓글의 게시글을 찾을 수 없습니다."))) - //중복 필터링 로직 - //Map(ObjectId, PostEntity) 로 변환 + // 중복 필터링 로직 .collect(Collectors.toMap( PostEntity::get_id, // Key: postId postEntity -> postEntity, // Value: PostEntity - (existing, replacement) -> existing // 중복 발생 시 기존 값 유지 - 같은 키 존재 발생시 기존 값 유지 + (existing, replacement) -> existing // 중복 발생 시 기존 값 유지 )) - // 중복 제거된 후 Map 에서 PostEntity 추출 + // 중복 제거된 후 Map에서 PostEntity 추출 .values() .stream() .toList(); + log.info("[댓글 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", commentPage.getTotalPages(), commentPage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages()-1, commentPage.hasNext()? commentPage.getPageable().getPageNumber() + 1 : -1); - default: + default: + log.warn("[유효하지 않은 상호작용 타입] 유저 ID: {}, 상호작용 타입: {}", userId, interactionType); throw new IllegalArgumentException("지원하지 않는 타입입니다."); } } public void setUserPassword(@Valid UserPasswordRequestDto userPasswordRequestDto, String code) { + log.info("[비밀번호 변경] 인증번호 확인 시작: {}", code); + UserEntity user = checkPasswordAuthNum(code); - if (user.isChangePassword()){ + if (user.isChangePassword()) { + log.info("[비밀번호 변경] 비밀번호 변경 가능 확인 완료. 비밀번호 변경 진행 중..."); + String encodedPassword = passwordEncoder.encode(userPasswordRequestDto.getPassword()); user.updatePassword(encodedPassword); user.canChangePassword(); userRepository.save(user); + + log.info("[비밀번호 변경 성공] 이메일: {}", user.getEmail()); } else { + log.warn("[비밀번호 변경 실패] 비밀번호 변경 불가능. 이메일 인증 필요. 이메일: {}", user.getEmail()); throw new UserPasswordChangeFailException("유저의 비밀번호를 변경할 수 없습니다. 이메일 인증을 먼저 진행해주세요."); } } public UserEntity checkPasswordAuthNum(String authNum) { + log.info("[비밀번호 인증번호 확인] 인증번호: {}", authNum); + EmailAuthEntity emailAuthEntity = checkEmailAndAuthNum(authNum); log.info("[checkAuthNumForPW] email : {}, authNum : {}", emailAuthEntity.getEmail(), authNum); UserEntity user = userRepository.findByEmail(emailAuthEntity.getEmail()) - .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[비밀번호 인증번호 확인 실패] 유저 정보를 찾을 수 없음. 이메일: {}", emailAuthEntity.getEmail()); + return new NotFoundException("유저 정보를 찾을 수 없습니다."); + }); user.canChangePassword(); userRepository.save(user); + log.info("[비밀번호 변경 가능 상태 업데이트] 이메일: {}", user.getEmail()); return user; } + private EmailAuthEntity checkEmailAndAuthNum(String authNum) { + log.info("[인증번호 확인] 인증번호: {}", authNum); + EmailAuthEntity emailAuthEntity = emailAuthRepository.findByAuthNum(authNum) - .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", authNum)); + .orElseThrow(() -> { + log.warn("[인증번호 확인 실패] 인증번호가 일치하지 않음: {}", authNum); + return new EmailAuthFailException("인증번호가 일치하지 않습니다.", authNum); + }); - // 10분 이내에 인증하지 않을 시에 인증번호 만료 + // 인증번호 만료 확인 if (emailAuthEntity.isExpired()) { + log.warn("[인증번호 만료] 인증번호 만료됨. 이메일: {}", emailAuthEntity.getEmail()); throw new EmailAuthFailException("인증번호가 만료되었습니다.", emailAuthEntity.getEmail()); } emailAuthEntity.verifyEmail(); emailAuthRepository.save(emailAuthEntity); - log.info("[checkAuthNumForPW] Can change password, email : {}", emailAuthEntity.getEmail()); + log.info("[인증번호 확인 성공] 인증 완료. 이메일: {}", emailAuthEntity.getEmail()); return emailAuthEntity; } public void deleteUser(UserDeleteRequestDto userDeleteRequestDto) { + log.info("[회원 탈퇴 요청] 이메일: {}", userDeleteRequestDto.getEmail()); + UserEntity user = userRepository.findByEmail(userDeleteRequestDto.getEmail()) - .orElseThrow(() -> new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[회원 탈퇴 실패] 유저 정보 없음: {}", userDeleteRequestDto.getEmail()); + return new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다."); + }); + SecurityUtils.validateUser(user.get_id()); user.delete(); userRepository.save(user); + + log.info("[회원 탈퇴 성공] 이메일: {}", userDeleteRequestDto.getEmail()); } public UserInfoResponseDto getUserInfo() { ObjectId userId = SecurityUtils.getCurrentUserId(); + log.info("[유저 정보 조회] 유저 ID: {}", userId); + UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[유저 정보 조회 실패] 유저 정보 없음: {}", userId); + return new NotFoundException("유저 정보를 찾을 수 없습니다."); + }); + + log.info("[유저 정보 조회 성공] 닉네임: {}", user.getNickname()); return UserInfoResponseDto.of(user); } - public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { ObjectId userId = SecurityUtils.getCurrentUserId(); + log.info("[유저 정보 업데이트] 현재 사용자 ID: {}", userId); + UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[유저 정보 찾기 실패] 유저 정보를 찾을 수 없음. 사용자 ID: {}", userId); + return new NotFoundException("유저 정보를 찾을 수 없습니다."); + }); + user.updateUserInfo(userUpdateRequestDto); userRepository.save(user); + log.info("[유저 정보 업데이트 성공] 사용자 ID: {}, 업데이트된 정보: {}", userId, userUpdateRequestDto); } public void updateUserProfile(MultipartFile profileImage) { ObjectId userId = SecurityUtils.getCurrentUserId(); + log.info("[프로필 이미지 업데이트] 현재 사용자 ID: {}", userId); + UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[유저 정보 찾기 실패] 유저 정보를 찾을 수 없음. 사용자 ID: {}", userId); + return new NotFoundException("유저 정보를 찾을 수 없습니다."); + }); + String profileImageUrl = s3Service.handleImageUpload(List.of(profileImage)).get(0); user.updateProfileImageUrl(profileImageUrl); userRepository.save(user); + log.info("[프로필 이미지 업데이트 성공] 사용자 ID: {}, 프로필 이미지 URL: {}", userId, profileImageUrl); } public enum InteractionType { LIKE, SCRAP, COMMENT } - - //user id 기반 nickname 반환 - public String getNicknameByUserId(ObjectId userId) { - UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); - return user.getNickname(); - } } From e5be04a2bfffef7bfb467cb24959f30367eb0fa0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 03:42:26 +0900 Subject: [PATCH 0339/1002] =?UTF-8?q?feat=20::=20Chat=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/service/ChatRoomService.java | 50 ++++++++++++++++--- .../chatting/service/ChattingService.java | 32 ++++++++++-- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index a8c7e197..635a60bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -34,47 +34,85 @@ public class ChatRoomService { public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { ObjectId senderId = ((CustomUserDetails) userDetails).getId(); + log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); + userRepository.findById(new ObjectId(chatRoomCreateRequestDto.getReceiverId())) - .orElseThrow(() -> new NotFoundException("Receive 유저를 찾을 수 없습니다.")); //Receive 유저에 대한 유효성 검사 + .orElseThrow(() -> { + log.error("[Receive 유저 확인 실패] 수신자 ID: {}를 찾을 수 없습니다.", chatRoomCreateRequestDto.getReceiverId()); + return new NotFoundException("Receive 유저를 찾을 수 없습니다."); + }); + log.info("[Receive 유저 확인 완료] 수신자 ID: {}", chatRoomCreateRequestDto.getReceiverId()); + ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); + log.info("[채팅방 생성 완료] 채팅방 ID: {}, 송신자 ID: {}, 수신자 ID: {}", chatRoom.get_id(), senderId, chatRoomCreateRequestDto.getReceiverId()); + Map response = new HashMap<>(); response.put("chatRoomId", chatRoom.get_id().toString()); + log.info("[채팅방 생성 응답] 생성된 채팅방 ID: {}", chatRoom.get_id()); return response; } public List getAllChatRoomByUser(UserDetails userDetails) { ObjectId userId = ((CustomUserDetails) userDetails).getId(); + log.info("[유저의 채팅방 조회] 유저 ID: {}", userId); + List chatRooms = chatRoomRepository.findByParticipant(userId); + log.info("[채팅방 조회 결과] 유저 ID: {}가 참여 중인 채팅방 개수: {}", userId, chatRooms.size()); return chatRooms.stream() .map(chatRoom -> { Chatting chat = customChattingRepository.findMostRecentByChatRoomId(chatRoom.get_id()); + log.info("[최근 채팅 조회] 채팅방 ID: {}, 최근 채팅 내용: {}", chatRoom.get_id(), chat != null ? chat.getContent() : "없음"); return ChatRoomListResponseDto.of(chatRoom, chat); }).toList(); } public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { ObjectId userId = ((CustomUserDetails) userDetails).getId(); + log.info("[채팅방 탈퇴 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) - .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.error("[채팅방 확인 실패] 채팅방 ID: {}를 찾을 수 없습니다.", chatRoomId); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); + log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, chatRoom.getParticipants().size()); + boolean isRemoved = chatRoom.getParticipants() .removeIf(participant -> participant.getUserId().equals(userId)); - if (!isRemoved) throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); + if (!isRemoved) { + log.warn("[채팅방 탈퇴 실패] 유저 ID: {}는 채팅방에 참여하지 않았습니다.", userId); + throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); + } + log.info("[채팅방 탈퇴 성공] 유저 ID: {}가 채팅방에서 탈퇴", userId); if (chatRoom.getParticipants().isEmpty()) { chatRoom.delete(); - log.info("[LeaveChatRoom] 채팅방에 참여자가 없어 채팅방 삭제"); + log.info("[채팅방 삭제] 채팅방 ID: {}에 더 이상 참여자가 없어 채팅방을 삭제합니다.", chatRoomId); } chatRoomRepository.save(chatRoom); + log.info("[채팅방 삭제 후 저장] 채팅방 ID: {} 저장 완료", chatRoomId); } public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) { ObjectId userId = ((CustomUserDetails) userDetails).getId(); + log.info("[알림 설정 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) - .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.error("[알림 설정 실패] 채팅방을 찾을 수 없습니다. 채팅방 ID: {}", chatRoomId); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); + log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, chatRoom.getParticipants().size()); + chatRoom.getParticipants().stream() .filter(participants -> participants.getUserId().equals(userId)) - .forEach(Participants::updateNotification); + .forEach(participants -> { + participants.updateNotification(); + log.info("[알림 설정] 유저 ID: {}의 알림 설정 완료", userId); + }); + chatRoomRepository.save(chatRoom); + log.info("[알림 설정 완료] 채팅방 ID: {}에 알림 설정 완료", chatRoomId); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 382efc1e..a3c59e09 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -36,25 +36,49 @@ public class ChattingService { //todo 이미지 채팅에 따른 S3 처리 public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { + log.info("[메시지 전송] 채팅방 ID: {}, 내용: {}", id, chattingRequestDto.getContent()); + ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) - .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); + ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto.getContent(), userId, chattingRequestDto.getContentType()); - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); + + log.info("[메시지 전송 성공] 메시지: [{}], 송신자 ID: {}, 채팅방 ID: {}", chattingRequestDto.getContent(), userId, id); + chattingRepository.save(chatting); + return ChattingResponseDto.of(chatting); } public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { + log.info("[메시지 조회] 채팅방 ID: {}, 페이지: {}", id, page); + Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); chatRoomRepository.findById(new ObjectId(id)) - .orElseThrow(() -> new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); + List chattingResponseDto = chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) .stream().map(ChattingResponseDto::of).toList(); + + log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); + return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); } public List sendImageMessage(List chatImages) { - return s3Service.handleImageUpload(chatImages); + log.info("[이미지 메시지 전송] 이미지 개수: {}", chatImages.size()); + + List imageUrls = s3Service.handleImageUpload(chatImages); + + log.info("[이미지 메시지 전송 성공] 업로드된 이미지 URL 개수: {}", imageUrls.size()); + + return imageUrls; } } From c71d0e1c287661ec4a42c79574a6355cecfc1144 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 04:28:02 +0900 Subject: [PATCH 0340/1002] feat :: default profile image URL --- .../inu/codin/codin/domain/user/service/UserService.java | 6 ++++++ .../src/main/java/inu/codin/codin/infra/s3/S3Service.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 9e38aeeb..0cc34de0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -64,6 +64,12 @@ public void createUser(UserCreateRequestDto userCreateRequestDto, MultipartFile String imageUrl = null; if (userImage != null) imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); + // imageUrl이 null이면 기본 이미지로 설정 + if (imageUrl == null) { + imageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 + + } + String encodedPassword = passwordEncoder.encode(userCreateRequestDto.getPassword()); validateUserCreateRequest(userCreateRequestDto); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java index 6f278e1a..d50d969b 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/s3/S3Service.java @@ -2,6 +2,7 @@ import com.amazonaws.services.s3.AmazonS3Client; import inu.codin.codin.infra.s3.exception.*; +import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -22,6 +23,10 @@ public S3Service(AmazonS3Client amazonS3Client) { @Value("codin-s3-bucket") public String bucket; + @Getter + @Value("${cloud.aws.s3.defaultProfileImageUrl}") + private String defaultProfileImageUrl; + private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB private static final int MAX_FILE_COUNT = 10; // 최대 파일 개수 @@ -120,4 +125,5 @@ public void deleteFiles(List fileUrls) { deleteFile(fileUrl); } } + } From 087a663e27ef44ef6722869cf5101bdc0ae66947 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 15:53:35 +0900 Subject: [PATCH 0341/1002] =?UTF-8?q?fix=20:=20=EC=9C=A0=EC=A0=80=EB=93=A4?= =?UTF-8?q?=EC=9D=98=20imageUrl=20=EB=B0=98=ED=99=98,=20=EC=9D=B5=EB=AA=85?= =?UTF-8?q?=20=EC=8B=9C=EC=97=90=20=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/CommentResponseDTO.java | 6 ++++- .../comment/service/CommentService.java | 27 ++++++++++++++----- .../reply/service/ReplyCommentService.java | 20 ++++++++++---- .../dto/response/PostDetailResponseDTO.java | 11 +++++--- .../response/PostPollDetailResponseDTO.java | 2 +- .../domain/post/dto/response/UserDto.java | 4 +++ .../domain/post/service/PostService.java | 19 +++++++++---- 7 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index fa1ba085..72344466 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -27,6 +27,9 @@ public class CommentResponseDTO { @Schema(description = "유저 nickname 익명시 익명으로 표시됨") private final String nickname; + @Schema(description = "유저 이미지 url") + private final String userImageUrl; + @Schema(description = "익명 여부", example = "true") @NotNull private final boolean anonymous; @@ -48,13 +51,14 @@ public class CommentResponseDTO { private final UserInfo userInfo; public CommentResponseDTO(String _id, String userId, String content, - String nickname, Boolean anonymous , + String nickname, String userImageUrl, Boolean anonymous , List replies, int likeCount, boolean isDeleted, LocalDateTime createdAt, UserInfo userInfo) { this._id = _id; this.userId = userId; this.content = content; this.nickname = nickname; + this.userImageUrl = userImageUrl; this.anonymous = anonymous; this.replies = replies; this.likeCount = likeCount; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 8abd0e18..66892b56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.like.service.LikeService; @@ -17,6 +18,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -39,6 +41,7 @@ public class CommentService { private final LikeService likeService; private final ReplyCommentService replyCommentService; private final RedisService redisService; + private final S3Service s3Service; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -101,19 +104,31 @@ public List getCommentsByPostId(String id) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); List comments = commentRepository.findByPostId(postId); - Map userNicknameMap = userRepository.findAllById( - comments.stream().map(CommentEntity::getUserId).distinct().toList() - ).stream().collect(Collectors.toMap(UserEntity::get_id, UserEntity::getNickname)); + Map userMap = userRepository.findAllById( + comments.stream() + .filter(commentEntity -> !commentEntity.isAnonymous()) + .map(CommentEntity::getUserId) + .distinct() + .toList() + ).stream() + .collect(Collectors.toMap( + UserEntity::get_id, + user -> new UserDto(user.getNickname(), user.getProfileImageUrl()) // DTO 생성 + )); + + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); return comments.stream() .map(comment -> { - String nickname = comment.isAnonymous() ? "익명" : userNicknameMap.get(comment.getUserId()); + String nickname = comment.isAnonymous() ? "익명" : userMap.get(comment.getUserId()).nickname(); + String userImageUrl = comment.isAnonymous() ? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); boolean isDeleted = comment.getDeletedAt() != null; return new CommentResponseDTO( comment.get_id().toString(), comment.getUserId().toString(), comment.getContent(), nickname, + userImageUrl, comment.isAnonymous(), replyCommentService.getRepliesByCommentId(comment.get_id()), // 대댓글 조회 likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), // 댓글 좋아요 수 @@ -121,9 +136,7 @@ public List getCommentsByPostId(String id) { comment.getCreatedAt(), getUserInfoAboutPost(comment.get_id()) ); - - - }) + }) .toList(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 1747bc05..46f67285 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -11,11 +11,13 @@ import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -38,6 +40,7 @@ public class ReplyCommentService { private final LikeService likeService; private final RedisService redisService; + private final S3Service s3Service; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -91,21 +94,28 @@ public void softDeleteReply(String replyId) { public List getRepliesByCommentId(ObjectId commentId) { List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); - Map userNicknameMap = userRepository.findAllById( - replies.stream().map(ReplyCommentEntity::getUserId).distinct().toList() - ).stream().collect(Collectors.toMap(UserEntity::get_id, UserEntity::getNickname)); + Map userMap = userRepository.findAllById( + replies.stream() + .filter(replyCommentEntity -> !replyCommentEntity.isAnonymous()) + .map(ReplyCommentEntity::getUserId).distinct().toList() + ).stream() + .collect(Collectors.toMap( + UserEntity::get_id, + user -> new UserDto(user.getNickname(), user.getProfileImageUrl()))); + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); return replies.stream() .map(reply -> { - String nickname = reply.isAnonymous() ? "익명" : userNicknameMap.get(reply.getUserId()); - + String nickname = reply.isAnonymous() ? "익명" : userMap.get(reply.getUserId()).nickname(); + String userImageUrl = reply.isAnonymous() ? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); boolean isDeleted = reply.getDeletedAt() != null; return new CommentResponseDTO( reply.get_id().toString(), reply.getUserId().toString(), reply.getContent(), nickname, + userImageUrl, reply.isAnonymous(), List.of(), //대댓글은 대댓글이 없음 likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.get_id()), // 대댓글 좋아요 수 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index daa75041..64d87bb1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -37,6 +37,9 @@ public class PostDetailResponseDTO { @Schema(description = "유저 nickname 익명시 익명으로 표시됨") private final String nickname; + @Schema(description = "유저 이미지 url", example = "https://~") + private final String userImageUrl; + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") private final List postImageUrl; @@ -63,14 +66,15 @@ public class PostDetailResponseDTO { @Schema(description = "해당 게시글에 대한 유저 반응 여부") private final UserInfo userInfo; - public PostDetailResponseDTO(String userId, String _id, String title, String content,String nickname ,PostCategory postCategory, List < String > postImageUrls, - boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfo userInfo){ + public PostDetailResponseDTO(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, + boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfo userInfo){ this.userId = userId; this._id = _id; this.title = title; this.content = content; this.nickname = nickname; this.postCategory = postCategory; + this.userImageUrl = userImageUrl; this.postImageUrl = postImageUrls; this.isAnonymous = isAnonymous; this.likeCount = likeCount; @@ -93,7 +97,7 @@ public UserInfo(boolean isLike, boolean isScrap) { } } - public static PostDetailResponseDTO of(PostEntity post, String nickname, int likeCount, int scrapCount, int hitsCount, int commentCount, UserInfo userInfo) { + public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount, UserInfo userInfo) { return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), @@ -101,6 +105,7 @@ public static PostDetailResponseDTO of(PostEntity post, String nickname, int lik post.getContent(), nickname, post.getPostCategory(), + userImageUrl, post.getPostImageUrls(), post.isAnonymous(), likeCount, diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 2f564cee..e62c2450 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -15,7 +15,7 @@ public class PostPollDetailResponseDTO extends PostDetailResponseDTO { public PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfo poll) { super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), + baseDTO.getPostCategory(), userImageUrl, baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); this.poll = poll; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java new file mode 100644 index 00000000..e94a2894 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java @@ -0,0 +1,4 @@ +package inu.codin.codin.domain.post.dto.response; + +public record UserDto(String nickname, String imageUrl) { +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 6cc08d73..70ffd9cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -123,9 +123,19 @@ private void validateUserAndPost(PostEntity post) { // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { + String nickname; + String userImageUrl; //Post 관련 인자 처리 - String nickname = post.isAnonymous() ? "익명" : getNicknameByUserId(post.getUserId()); + if (post.isAnonymous()){ + nickname = "익명"; + userImageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 + } else { + UserEntity user = userRepository.findById(post.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + nickname = user.getNickname(); + userImageUrl = user.getProfileImageUrl(); + } int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); int scrapCount = scrapService.getScrapCount(post.get_id()); @@ -152,13 +162,13 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { //게시물 + 투표 DTO 생성 return PostPollDetailResponseDTO.of( - PostDetailResponseDTO.of(post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo), + PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), pollInfo ); } // 일반 게시물 처리 - return PostDetailResponseDTO.of(post, nickname, likeCount, scrapCount, hitsCount, commentCount, userInfo); + return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); } // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 @@ -231,8 +241,7 @@ public UserInfo getUserInfoAboutPost(ObjectId postId){ //user id 기반 nickname 반환 public String getNicknameByUserId(ObjectId userId) { - UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + return user.getNickname(); } From 8770a772cfdf4e30dd9d97b239f8fe578a6d8dd7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 15:56:07 +0900 Subject: [PATCH 0342/1002] =?UTF-8?q?fix=20:=20=EC=9C=A0=EC=A0=80=EB=93=A4?= =?UTF-8?q?=EC=9D=98=20imageUrl=20=EB=B0=98=ED=99=98,=20=EC=9D=B5=EB=AA=85?= =?UTF-8?q?=20=EC=8B=9C=EC=97=90=20=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/dto/response/PostPollDetailResponseDTO.java | 2 +- .../inu/codin/codin/domain/post/service/PostService.java | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index e62c2450..568829c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -15,7 +15,7 @@ public class PostPollDetailResponseDTO extends PostDetailResponseDTO { public PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfo poll) { super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), userImageUrl, baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), + baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); this.poll = poll; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 70ffd9cb..673cb2c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -239,12 +239,6 @@ public UserInfo getUserInfoAboutPost(ObjectId postId){ .build(); } - //user id 기반 nickname 반환 - public String getNicknameByUserId(ObjectId userId) { - - return user.getNickname(); - } - public PostPageResponse searchPosts(String keyword, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, pageRequest); From 9dfe287f2fd686d6138add6d06af66e7d5c18156 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 15 Jan 2025 16:15:31 +0900 Subject: [PATCH 0343/1002] =?UTF-8?q?fix=20::=20log.error=20->=20warn=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatting/service/ChattingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index a3c59e09..4726e064 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -40,7 +40,7 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> { - log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); + log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); }); From ef5ee4612c9a93cb5b23b3a122f554728373e993 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 16:18:17 +0900 Subject: [PATCH 0344/1002] =?UTF-8?q?fix=20:=20s3=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 309c9aec..abb065eb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 309c9aec8dc95274176a48e14564aad99a34e4bc +Subproject commit abb065eb9977e1627e3ab8317bde7c92f931f722 From b3b25dc15e2849c854fa6a4847c04cf02f9c89c0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 16:39:16 +0900 Subject: [PATCH 0345/1002] =?UTF-8?q?fix=20:=20=EB=8C=93=EA=B8=80=EC=9D=B4?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=96=B4=EB=8F=84=20count?= =?UTF-8?q?=EB=8A=94=20=EA=B0=90=EC=86=8C=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/comment/service/CommentService.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 8abd0e18..1e4c0969 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -83,14 +83,13 @@ public void softDeleteComment(String id) { comment.delete(); commentRepository.save(comment); - // 댓글 수 감소 (댓글 + 대댓글 수만큼 감소) - PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - post.updateCommentCount(post.getCommentCount() - (1 + replies.size())); - postRepository.save(post); +// // 댓글 수 감소 (댓글 + 대댓글 수만큼 감소) +// PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) +// .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); +// post.updateCommentCount(post.getCommentCount() - (1 + replies.size())); +// postRepository.save(post); - log.info("삭제된 commentId: {} , 대댓글 {} . 총 삭제 수: {} postId: {}", - commentId, replies.size(), (1 + replies.size()), post.get_id()); + log.info("삭제된 commentId: {}", commentId); } From f70f49a096160335a861470226fb3dd5dd2e12f4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 16:44:53 +0900 Subject: [PATCH 0346/1002] =?UTF-8?q?fix=20:=20=EC=82=AD=EC=A0=9C=EB=90=9C?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80,=20=EB=8C=80=EB=8C=93=EA=B8=80=EB=8F=84?= =?UTF-8?q?=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ReplyCommentRepository.java | 3 +-- .../reply/service/ReplyCommentService.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java index c7a3d598..7be2aeb6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java @@ -14,6 +14,5 @@ public interface ReplyCommentRepository extends MongoRepository findByIdAndNotDeleted(ObjectId id); - @Query("{'commentId': ?0, 'deletedAt': null}") - List findByCommentIdAndNotDeleted(ObjectId commentId); + List findByCommentId(ObjectId commentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 1747bc05..7a014d45 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -75,21 +75,21 @@ public void softDeleteReply(String replyId) { reply.delete(); replyCommentRepository.save(reply); - // 댓글 수 감소 (대댓글도 댓글 수에서 감소) - CommentEntity comment = commentRepository.findByIdAndNotDeleted(reply.getCommentId()) - .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); - - PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - post.updateCommentCount(post.getCommentCount() - 1); - postRepository.save(post); - - log.info("대댓글 성공적 삭제 replyId: {} , postId: {}.", replyId, post.get_id()); +// // 댓글 수 감소 (대댓글도 댓글 수에서 감소) +// CommentEntity comment = commentRepository.findByIdAndNotDeleted(reply.getCommentId()) +// .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); +// +// PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) +// .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); +// post.updateCommentCount(post.getCommentCount() - 1); +// postRepository.save(post); + + log.info("대댓글 성공적 삭제 replyId: {}", replyId); } // 특정 댓글의 대댓글 조회 public List getRepliesByCommentId(ObjectId commentId) { - List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); + List replies = replyCommentRepository.findByCommentId(commentId); Map userNicknameMap = userRepository.findAllById( replies.stream().map(ReplyCommentEntity::getUserId).distinct().toList() From bf76405cfbce70476382f8e0e30f3e15ad904c91 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 16:45:18 +0900 Subject: [PATCH 0347/1002] =?UTF-8?q?fix=20:=20=EC=82=AD=EC=A0=9C=EB=90=9C?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=8C=80?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/service/CommentService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 1e4c0969..cb1a5040 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -69,15 +69,15 @@ public void softDeleteComment(String id) { .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); SecurityUtils.validateUser(comment.getUserId()); - // 댓글의 대댓글 조회 - List replies = replyCommentRepository.findByCommentIdAndNotDeleted(commentId); - // 대댓글 Soft Delete 처리 - replies.forEach(reply -> { - if (reply.getDeletedAt()!=null) { - reply.delete(); - replyCommentRepository.save(reply); - } - }); +// // 댓글의 대댓글 조회 +// List replies = replyCommentRepository.findByCommentId(commentId); +// // 대댓글 Soft Delete 처리 +// replies.forEach(reply -> { +// if (reply.getDeletedAt()!=null) { +// reply.delete(); +// replyCommentRepository.save(reply); +// } +// }); // 댓글 Soft Delete 처리 comment.delete(); From 1410b5b520bfa74629b1de7dfb0430ea574b4cad Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 23:34:48 +0900 Subject: [PATCH 0348/1002] =?UTF-8?q?pref=20:=20=EC=A4=91=EA=B3=A0?= =?UTF-8?q?=EC=B1=85(=ED=8C=90=EB=A7=A4=EC=A4=91,=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=EC=A4=91)=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/entity/PostCategory.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 19118be5..0b66a8e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -21,7 +21,11 @@ public enum PostCategory { EXTRACURRICULAR_OUTER("비교과_교외"), EXTRACURRICULAR_INNER("비교과_교내"), - POLL("설문조사"); + POLL("설문조사"), + + BOOKS_SELL("중고책_판매중"), + BOOKS_BUY("중고책_구매중"); + private final String description; From dc16270349b1e459af2308245855a68e23e5c67b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 23:39:34 +0900 Subject: [PATCH 0349/1002] =?UTF-8?q?fix=20:=20=EC=9E=90=EC=8B=A0=EA=B3=BC?= =?UTF-8?q?=EC=9D=98=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/exception/ChatRoomCreateFailException.java | 7 +++++++ .../domain/chat/chatroom/service/ChatRoomService.java | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java new file mode 100644 index 00000000..06ed9a88 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.chat.chatroom.exception; + +public class ChatRoomCreateFailException extends RuntimeException{ + public ChatRoomCreateFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 635a60bf..078a2e67 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -4,12 +4,11 @@ import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; @@ -17,7 +16,6 @@ import org.bson.types.ObjectId; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; -import reactor.core.publisher.Flux; import java.util.HashMap; import java.util.List; @@ -34,6 +32,9 @@ public class ChatRoomService { public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { ObjectId senderId = ((CustomUserDetails) userDetails).getId(); + if (senderId.toString().equals(chatRoomCreateRequestDto.getReceiverId())){ + throw new ChatRoomCreateFailException("자기 자신과는 채팅방을 생성할 수 없습니다."); + } log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); userRepository.findById(new ObjectId(chatRoomCreateRequestDto.getReceiverId())) From a95d0a212dde35956e929713e78fcfa5fa399797 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 15 Jan 2025 23:39:34 +0900 Subject: [PATCH 0350/1002] =?UTF-8?q?fix=20:=20=EC=9E=90=EC=8B=A0=EA=B3=BC?= =?UTF-8?q?=EC=9D=98=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/exception/ChatRoomCreateFailException.java | 7 +++++++ .../domain/chat/chatroom/service/ChatRoomService.java | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java new file mode 100644 index 00000000..06ed9a88 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomCreateFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.chat.chatroom.exception; + +public class ChatRoomCreateFailException extends RuntimeException{ + public ChatRoomCreateFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 635a60bf..078a2e67 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -4,12 +4,11 @@ import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; @@ -17,7 +16,6 @@ import org.bson.types.ObjectId; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; -import reactor.core.publisher.Flux; import java.util.HashMap; import java.util.List; @@ -34,6 +32,9 @@ public class ChatRoomService { public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { ObjectId senderId = ((CustomUserDetails) userDetails).getId(); + if (senderId.toString().equals(chatRoomCreateRequestDto.getReceiverId())){ + throw new ChatRoomCreateFailException("자기 자신과는 채팅방을 생성할 수 없습니다."); + } log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); userRepository.findById(new ObjectId(chatRoomCreateRequestDto.getReceiverId())) From 3ea115acea57f0ea8d49014176fd0bc4a71f56f9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 16 Jan 2025 02:51:25 +0900 Subject: [PATCH 0351/1002] =?UTF-8?q?perf=20:=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EC=8B=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EA=B0=80=20=EB=B0=98=ED=99=98=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EA=B2=BD=EC=9A=B0=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/user/service/UserService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 10482eaa..462f90bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -190,6 +190,9 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } public void setUserPassword(@Valid UserPasswordRequestDto userPasswordRequestDto, String code) { + if (code==null){ + throw new UserPasswordChangeFailException("코드가 비어있거나 유효하지 않습니다."); + } log.info("[비밀번호 변경] 인증번호 확인 시작: {}", code); UserEntity user = checkPasswordAuthNum(code); From 49401040275de43410592100ca5c2dbfeae2b9a4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 16 Jan 2025 02:56:57 +0900 Subject: [PATCH 0352/1002] =?UTF-8?q?perf=20:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=5Fid=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/controller/PostController.java | 4 +--- .../inu/codin/codin/domain/post/service/PostService.java | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index e4c2ca16..e665840e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -47,10 +47,8 @@ public ResponseEntity> createPost( // postImages가 null이면 빈 리스트로 처리 if (postImages == null) postImages = List.of(); - - postService.createPost(postCreateRequestDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", null)); + .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", postService.createPost(postCreateRequestDTO, postImages))); } @Operation( diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index a5154f7d..73425ce9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -40,12 +40,11 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; - import java.time.LocalDateTime; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; @Slf4j @Service @@ -62,7 +61,7 @@ public class PostService { private final PollRepository pollRepository; private final PollVoteRepository pollVoteRepository; - public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); List imageUrls = s3Service.handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -86,7 +85,9 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List response = new HashMap<>(); + response.put("postId", postEntity.get_id().toString()); + return response; } From 22a25161e8609b7911dc9079b0ba34921bbf94db Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 16 Jan 2025 03:11:10 +0900 Subject: [PATCH 0353/1002] =?UTF-8?q?fix=20:=20=ED=98=84=EC=9E=AC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=ED=95=9C=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=A0=95=EB=B3=B4=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/service/PostService.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index a5154f7d..1a9d7c3d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -156,7 +156,9 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { int hitsCount = redisService.getHitsCount(post.get_id()); int commentCount = post.getCommentCount(); - UserInfo userInfo = getUserInfoAboutPost(post.get_id()); + ObjectId userId = SecurityUtils.getCurrentUserId(); + + UserInfo userInfo = getUserInfoAboutPost(userId, post.get_id()); // 투표 게시물 처리 if (post.getPostCategory() == PostCategory.POLL) { @@ -164,11 +166,11 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); - List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), post.getUserId()) + List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) .map(PollVoteEntity::getSelectedOptions) .orElse(Collections.emptyList()); boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); - boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), post.getUserId()); + boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); //투표 DTO 생성 PostPollDetailResponseDTO.PollInfo pollInfo = PostPollDetailResponseDTO.PollInfo.of(poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), @@ -258,8 +260,7 @@ public void deletePostImage(String postId, String imageUrl) { } } - public UserInfo getUserInfoAboutPost(ObjectId postId){ - ObjectId userId = SecurityUtils.getCurrentUserId(); + public UserInfo getUserInfoAboutPost(ObjectId userId, ObjectId postId){ return UserInfo.builder() .isLike(redisService.isPostLiked(postId, userId)) .isScrap(redisService.isPostScraped(postId, userId)) From 4d252596badb6c57524ed8494e9f51491cbb9628 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 16 Jan 2025 03:13:37 +0900 Subject: [PATCH 0354/1002] =?UTF-8?q?pref=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98=20=EC=8B=9C=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84,=20=ED=95=99=EA=B3=BC=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/dto/response/UserInfoResponseDto.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java index 910bb07b..e7b0de71 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.user.dto.response; +import inu.codin.codin.common.Department; import inu.codin.codin.domain.user.entity.UserEntity; import lombok.Builder; import lombok.Getter; @@ -12,13 +13,17 @@ public class UserInfoResponseDto { private String email; private String name; private String profileImageUrl; + private String nickname; + private Department department; @Builder - public UserInfoResponseDto(String _id, String email, String name, String profileImageUrl) { + public UserInfoResponseDto(String _id, String email, String name, String profileImageUrl, String nickname, Department department) { this._id = _id; this.email = email; this.name = name; this.profileImageUrl = profileImageUrl; + this.nickname = nickname; + this.department = department; } public static UserInfoResponseDto of(UserEntity user) { @@ -27,6 +32,8 @@ public static UserInfoResponseDto of(UserEntity user) { .email(user.getEmail()) .name(user.getName()) .profileImageUrl(user.getProfileImageUrl()) + .nickname(user.getNickname()) + .department(user.getDepartment()) .build(); } } From b8145f8945ac30f2b227dac14a131b6d8a3f5d6d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Jan 2025 03:59:41 +0900 Subject: [PATCH 0355/1002] docs :: lab logging --- .../domain/info/domain/lab/service/LabService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java index d56a1700..723711d3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java @@ -22,7 +22,10 @@ public class LabService { public LabThumbnailResponseDto getLabThumbnail(String id) { Lab lab = infoRepository.findLabById(new ObjectId(id)) - .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[getLabThumbnail] 연구실 정보 조회 실패, 연구실 ID: {}", id); + return new LabNotFoundException("연구실 정보를 찾을 수 없습니다."); + }); log.info("[getLabThumbnail] {}의 연구실 정보 열람", id); return LabThumbnailResponseDto.of(lab); } @@ -42,7 +45,10 @@ public void createLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto) { public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, String id) { Lab lab = infoRepository.findLabById(new ObjectId(id)) - .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[updateLab] 연구실 정보 업데이트 실패, 연구실 ID: {}", id); + return new LabNotFoundException("연구실 정보를 찾을 수 없습니다."); + }); lab.update(labCreateUpdateRequestDto); infoRepository.save(lab); log.info("[updateLab] {}의 연구실 정보 업데이트", lab.get_id().toString()); From b2446cd98de4d2215f76a6fa0c5d13452dbab9b3 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Jan 2025 04:03:17 +0900 Subject: [PATCH 0356/1002] docs :: Office logging --- .../codin/domain/info/domain/office/service/OfficeService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index bedb4139..823b98bd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -19,12 +19,14 @@ public class OfficeService { private final InfoRepository infoRepository; public OfficeDetailsResponseDto getOfficeByDepartment(Department department) { + log.info("[getOfficeByDepartment] '{}' 사무실 정보 조회 시작", department.getDescription()); Office office = infoRepository.findOfficeByDepartment(department); log.info("[getOfficeByDepartment] '{}' 사무실 정보 열람", office.getDepartment().getDescription()); return OfficeDetailsResponseDto.of(office); } public void createOfficeMember(Department department, OfficeMemberCreateUpdateRequestDto officeMemberCreateUpdateRequestDto) { + log.info("[createOfficeMember] '{}' 사무실에 멤버 추가 시도", department.getDescription()); Office office = infoRepository.findOfficeByDepartment(department); OfficeMember officeMember = OfficeMember.of(officeMemberCreateUpdateRequestDto); office.addOfficeMember(officeMember); From 271bdc43bf930bd103899c85478b0ce954ba1794 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Jan 2025 04:03:24 +0900 Subject: [PATCH 0357/1002] docs :: Professor logging --- .../professor/service/ProfessorService.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index a96850ad..d2246b72 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -23,12 +23,14 @@ public class ProfessorService { private final InfoRepository infoRepository; public List getProfessorByDepartment(Department department){ + log.info("[getProfessorByDepartment] '{}' 학과의 교수님 정보 조회 시작", department.getDescription()); List professors = infoRepository.findAllProfessorsByDepartment(department); log.info("[getProfessorByDepartment] '{}' 학과의 교수님 정보 모두 반환", department.getDescription()); return professors.stream().map(ProfessorListResponseDto::of).toList(); } public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { + log.info("[getProfessorThumbnail] 교수 ID '{}'로 정보 조회 시도", id); Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); log.info("[getProfessorThumbnail] {} 교수님의 정보 열람", professor.get_id().toString()); @@ -36,7 +38,9 @@ public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { } public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { + log.info("[createProfessor] 교수 이메일 '{}'로 정보 생성 시도", professorCreateUpdateRequestDto.getEmail()); if (infoRepository.findProfessorByEmail(professorCreateUpdateRequestDto.getEmail()).isPresent()){ + log.warn("[createProfessor] 교수 이메일 '{}' 이미 존재", professorCreateUpdateRequestDto.getEmail()); throw new ProfessorDuplicatedException("이미 존재하는 Professor 정보 입니다."); } Professor professor = Professor.of(professorCreateUpdateRequestDto); @@ -46,7 +50,10 @@ public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdat public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto) { Professor professor = infoRepository.findProfessorById(new ObjectId(id)) - .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.warn("[updateProfessor] 교수 ID '{}' 정보가 존재하지 않음", id); + return new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다."); + }); professor.update(professorCreateUpdateRequestDto); infoRepository.save(professor); log.info("[updateProfessor] {} 교수님의 정보 업데이트", professor.get_id().toString()); @@ -55,9 +62,12 @@ public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professor public void deleteProfessor(String id) { + log.info("[deleteProfessor] 교수 ID '{}'로 정보 삭제 시도", id); Professor professor = infoRepository.findProfessorById(new ObjectId(id)) - .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); - professor.delete(); + .orElseThrow(() -> { + log.warn("[deleteProfessor] 교수 ID '{}' 정보가 존재하지 않음", id); + return new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다."); + });professor.delete(); infoRepository.save(professor); log.info("[deleteProfessor] {} 교수님의 정보 삭제", professor.get_id().toString()); } From 611657e3f80ddf937cfff1e4d87faa9f664ffb28 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Jan 2025 04:05:25 +0900 Subject: [PATCH 0358/1002] =?UTF-8?q?fic=20::=20=EC=84=B8=EB=AF=B8?= =?UTF-8?q?=EC=BD=9C=EB=A1=A0=20=EB=B9=A0=EC=A7=84=EA=B2=83=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index c8197125..f9388e86 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -38,7 +38,7 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat .orElseThrow(() -> new NotFoundException("Receive 유저를 찾을 수 없습니다.")); //Receive 유저에 대한 유효성 검사 ChatRoom chatRoom = ChatRoom.of(chatRoomCreateRequestDto, senderId); chatRoomRepository.save(chatRoom); - log.info("[createChatRoom] {} 채팅방 생성, Maker : {}, Receiver : {}", chatRoom.get_id().toString(), senderId.toString(), chatRoomCreateRequestDto.getReceiverId()) + log.info("[createChatRoom] {} 채팅방 생성, Maker : {}, Receiver : {}", chatRoom.get_id().toString(), senderId.toString(), chatRoomCreateRequestDto.getReceiverId()); Map response = new HashMap<>(); response.put("chatRoomId", chatRoom.get_id().toString()); return response; From a343c6df49c2dddc8b1e47535c362d00a17def37 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 16 Jan 2025 05:23:59 +0900 Subject: [PATCH 0359/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 56ed44cb..abb065eb 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 56ed44cbf846d2ab5927532e5cface327e65a31a +Subproject commit abb065eb9977e1627e3ab8317bde7c92f931f722 From 0ab4ac98cc5973ecff84afe9b0c9a642fc1ccc2a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 01:00:48 +0900 Subject: [PATCH 0360/1002] =?UTF-8?q?feat=20:=20feign-client=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=ED=8F=AC=ED=83=88=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/CodinApplication.java | 3 +- .../codin/common/config/FeignConfig.java | 14 +++ .../codin/domain/user/feign/PortalClient.java | 13 +++ .../domain/user/feign/PortalController.java | 29 ++++++ .../domain/user/feign/PortalService.java | 98 +++++++++++++++++++ .../feign/dto/UserPortalSignUpRequestDto.java | 21 ++++ .../dto/UserPortalSignUpResponseDto.java | 17 ++++ 7 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index 07567624..21619914 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -2,8 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.mongodb.config.EnableMongoAuditing; -import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -13,6 +13,7 @@ @EnableMongoAuditing @EnableMethodSecurity @EnableScheduling +@EnableFeignClients public class CodinApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java new file mode 100644 index 00000000..38d348e8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java @@ -0,0 +1,14 @@ +package inu.codin.codin.common.config; + +import feign.Client; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FeignConfig { + + @Bean + public Client feignClient(){ + return new Client.Default(null, null); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java new file mode 100644 index 00000000..3bb22966 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.user.feign; + +import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpRequestDto; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = "portal", url = "${feign.client.config.portal.url}") +public interface PortalClient { + + @PostMapping + String signUp(@RequestBody UserPortalSignUpRequestDto userSignUpRequestDto); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java new file mode 100644 index 00000000..3f0c80fb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java @@ -0,0 +1,29 @@ +package inu.codin.codin.domain.user.feign; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.user.dto.request.UserSignUpRequestDto; +import io.swagger.v3.oas.annotations.Operation; +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.RestController; + +@RestController +@RequiredArgsConstructor +public class PortalController { + + private final PortalService portalService; + + @Operation( + summary = "포탈 로그인 / 회원가입", + description = "포탈 아이디, 비밀번호를 통해 회원가입 진행" + ) + @PostMapping("/portal") + public ResponseEntity> portalSignUp(@RequestBody @Valid UserSignUpRequestDto userSignUpRequestDto) throws Exception { + portalService.signUp(userSignUpRequestDto); + return null; + + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java new file mode 100644 index 00000000..50a2e254 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java @@ -0,0 +1,98 @@ +package inu.codin.codin.domain.user.feign; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import inu.codin.codin.common.Department; +import inu.codin.codin.common.util.AESUtil; +import inu.codin.codin.domain.user.dto.request.UserSignUpRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpRequestDto; +import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpResponseDto; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class PortalService { + + private final PortalClient client; + private final UserRepository userRepository; + + public void signUp(UserSignUpRequestDto userSignUpRequestDto) throws Exception { + String password = AESUtil.encrypt(userSignUpRequestDto.getPassword()); + String html = client.signUp(UserPortalSignUpRequestDto.builder() + ._enpass_login_("submit") + .username(AESUtil.encrypt(userSignUpRequestDto.getStudentId())) + .password(password) + .build()); + Document doc = Jsoup.parse(html); + + // todo 가져오는 tag 맞는지 확인 + String value = doc.select(".main").select("input[type=hidden]").attr("value"); + System.out.println(value); + + UserPortalSignUpResponseDto userPortalSignUpResponseDto = readJson(value); + userPortalSignUpResponseDto.setPassword(password); + UserEntity userEntity = UserEntity.of(userPortalSignUpResponseDto); + userRepository.save(userEntity); + } + + private static UserPortalSignUpResponseDto readJson(String value) throws IOException { + JsonFactory jsonFactory = JsonFactory.builder().build(); + JsonParser parser = jsonFactory.createParser(value); + UserPortalSignUpResponseDto userPortalSignUpResponseDto = new UserPortalSignUpResponseDto(); + try { + // 시작 데이터가 Object인지 확인 + if (parser.currentToken() == JsonToken.START_OBJECT) { + + // Object 데이터 끝이 될때까지 반복합니다. + while (parser.nextToken() != JsonToken.END_OBJECT) { + + // JSON의 키 값을 가져옵니다. + String fieldName = parser.getCurrentName(); + parser.nextToken(); + + // JSON의 키 값을 기반으로 값을 추출합니다. + if ("studId".equals(fieldName)) { + String studId = parser.getValueAsString(); + userPortalSignUpResponseDto.setStudentId(studId); + } else if ("userNm".equals(fieldName)) { + String userNm = parser.getValueAsString(); + userPortalSignUpResponseDto.setName(userNm); + } else if ("userEml".equals(fieldName)) { + String userEml = parser.getValueAsString(); + userPortalSignUpResponseDto.setEmail(userEml); + +// }else if ("userMptel".equals(fieldName)) {//전화번호 +// String userMptel = parser.getValueAsString(); +// }else if ("userScregStaNm".equals(fieldName)) { //재학 +// String userScregStaNm = parser.getValueAsString(); +// }else if ("schgrNm".equals(fieldName)) { //학년 +// String schgrNm = parser.getValueAsString(); + + } else if ("userDpmtNm".equals(fieldName)) { + String userDpmtNm = parser.getValueAsString(); + Department department = Department.fromDescription(userDpmtNm); + userPortalSignUpResponseDto.setDepartment(department); + +// }else if ("userCollNm".equals(fieldName)) { +// String userCollNm = parser.getValueAsString(); +// } + + parser.close(); + + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return userPortalSignUpResponseDto; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java new file mode 100644 index 00000000..132f1a2b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java @@ -0,0 +1,21 @@ +package inu.codin.codin.domain.user.feign.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class UserPortalSignUpRequestDto { + + private String _enpass_login_; + + private String username; + + private String password; + + @Builder + public UserPortalSignUpRequestDto(String _enpass_login_, String username, String password) { + this._enpass_login_ = _enpass_login_; + this.username = username; + this.password = password; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java new file mode 100644 index 00000000..1f908425 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.user.feign.dto; + +import inu.codin.codin.common.Department; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Setter +@Getter +public class UserPortalSignUpResponseDto { + private String email; + private String password; + private String studentId; + private String name; + private Department department; +} From b974de74110963fffddc56cd20830ac978829125 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 01:02:20 +0900 Subject: [PATCH 0361/1002] =?UTF-8?q?feat=20:=20=ED=8F=AC=ED=83=88=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?Department=20=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/Department.java | 14 ++++++++++++-- .../user/dto/request/UserSignUpRequestDto.java | 16 ++++++++++++++++ .../codin/domain/user/entity/UserEntity.java | 11 +++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/Department.java b/codin-core/src/main/java/inu/codin/codin/common/Department.java index eaee241d..0dacd844 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/Department.java @@ -8,11 +8,21 @@ public enum Department { IT_COLLEGE("정보기술대학"), - COMPUTER_SCI("컴퓨터공학과"), + COMPUTER_SCI("컴퓨터공학부"), INFO_COMM("정보통신공학과"), EMBEDDED("임베디드시스템공학과"), - STAFF("교직원"); + STAFF("교직원"), + OTHERS("타과대"); private final String description; + public static Department fromDescription(String description) { + for (Department department : Department.values()) { + if (department.getDescription().equals(description)) { + return department; + } + } + return OTHERS; + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java new file mode 100644 index 00000000..830a01cb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.user.dto.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserSignUpRequestDto { + + @NotBlank + private String studentId; + + @NotBlank + private String password; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index b75f4c99..6d98e289 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; +import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpResponseDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -67,4 +68,14 @@ public void updateUserInfo(UserUpdateRequestDto userUpdateRequestDto) { public void updateProfileImageUrl(String profileImageUrl) { this.profileImageUrl = profileImageUrl; } + + public static UserEntity of(UserPortalSignUpResponseDto userPortalSignUpResponseDto){ + return UserEntity.builder() + .studentId(userPortalSignUpResponseDto.getStudentId()) + .email(userPortalSignUpResponseDto.getEmail()) + .name(userPortalSignUpResponseDto.getName()) + .password(userPortalSignUpResponseDto.getPassword()) + .department(userPortalSignUpResponseDto.getDepartment()) + .build(); + } } From a83e3c837106a95f867724221ee9f404ec2870b4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 01:02:31 +0900 Subject: [PATCH 0362/1002] =?UTF-8?q?feat=20:=20AES=20=EC=95=94=ED=98=B8?= =?UTF-8?q?=ED=99=94=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/util/AESUtil.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/AESUtil.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/AESUtil.java b/codin-core/src/main/java/inu/codin/codin/common/util/AESUtil.java new file mode 100644 index 00000000..77871cce --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/AESUtil.java @@ -0,0 +1,39 @@ +package inu.codin.codin.common.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.util.Base64; + +@Component +public class AESUtil { + + private static final String ALGORITHM = "AES"; + + private static String SECRET_KEY; // 반드시 16, 24, 또는 32바이트여야 합니다. + + @Autowired + public AESUtil(Environment environment) { + SECRET_KEY = environment.getProperty("user.sign_up.commSessionKEY"); + } + + public static String encrypt(String input) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM); + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + byte[] encryptedBytes = cipher.doFinal(input.getBytes()); + return Base64.getEncoder().encodeToString(encryptedBytes); + } + + public static String decrypt(String encryptedInput) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM); + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, keySpec); + byte[] encryptedBytes = Base64.getDecoder().decode(encryptedInput); + byte[] decryptedBytes = cipher.doFinal(encryptedBytes); + return new String(decryptedBytes); + } +} \ No newline at end of file From 4022ca2aeddf4f81c12463fe60ffbf98634191af Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 01:02:43 +0900 Subject: [PATCH 0363/1002] =?UTF-8?q?docs=20:=20import=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 --- .../java/inu/codin/codin/domain/user/service/UserService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 462f90bb..eb010cf2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -38,7 +38,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import software.amazon.awssdk.services.s3.endpoints.internal.Not; import java.util.List; import java.util.stream.Collectors; From 26fc3306de6c76551a477f7a8bd370a29828063f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:06:45 +0900 Subject: [PATCH 0364/1002] =?UTF-8?q?perf=20:=20=ED=8F=AC=ED=83=88=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20Auth=EB=A1=9C=20=EB=B3=91?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 20 ++++- .../common/security/service/AuthService.java | 89 +++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 2376d69e..5ca00a8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,12 +1,14 @@ package inu.codin.codin.common.security.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.common.security.dto.LoginRequestDto; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.common.security.service.AuthService; import inu.codin.codin.common.security.service.JwtService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; @@ -26,13 +28,14 @@ public class AuthController { private final AuthenticationManager authenticationManager; private final JwtService jwtService; + private final AuthService authService; @Operation(summary = "로그인") @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) { + public ResponseEntity login(@RequestBody SignUpAndLoginRequestDto loginRequestDto, HttpServletResponse response) { UsernamePasswordAuthenticationToken authenticationToken - = new UsernamePasswordAuthenticationToken(loginRequestDto.getEmail(), loginRequestDto.getPassword()); + = new UsernamePasswordAuthenticationToken(loginRequestDto.getStudentId(), loginRequestDto.getPassword()); Authentication authentication = authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); @@ -55,4 +58,15 @@ public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse jwtService.reissueToken(request, response); return ResponseEntity.ok().body(new SingleResponse<>(200, "토큰 재발급 성공", null)); } + + @Operation( + summary = "포탈 로그인", + description = "포탈 아이디, 비밀번호를 통해 로그인 진행 및 학적 정보 반환" + ) + @PostMapping("/portal") + public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { + authService.signUp(signUpAndLoginRequestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "포탈 로그인을 통한 학적 정보 반환 완료", null)); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java new file mode 100644 index 00000000..08019d7a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -0,0 +1,89 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.Department; +import inu.codin.codin.common.security.feign.inu.InuClient; +import inu.codin.codin.common.security.feign.portal.PortalClient; +import inu.codin.codin.common.security.dto.PortalLoginResponseDto; +import inu.codin.codin.common.util.AESUtil; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.repository.UserRepository; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.springframework.stereotype.Service; + +import java.util.Base64; +import java.util.Map; +import java.util.Optional; + +import static feign.Util.ISO_8859_1; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final PortalClient portalClient; + private final InuClient inuClient; + private final UserRepository userRepository; + + public void signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { + //학번으로 회원가입 유무 판단 + Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); + if (user.isPresent()) throw new UserCreateFailException("이미 회원가입된 유저입니다."); + + PortalLoginResponseDto userPortalLoginResponseDto + = returnPortalInfo(signUpAndLoginRequestDto); + UserEntity userEntity = UserEntity.of(userPortalLoginResponseDto); + userRepository.save(userEntity); + } + + private PortalLoginResponseDto returnPortalInfo(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { + String password = AESUtil.encrypt(signUpAndLoginRequestDto.getPassword()); + String html = portalClient.signUp( + "submit", + AESUtil.encrypt(signUpAndLoginRequestDto.getStudentId()), + password + ); + Document doc = Jsoup.parse(html); + String value = doc.select(".main").select("input[type=hidden]").attr("value"); + PortalLoginResponseDto userPortalLoginResponseDto = readJson(value); + userPortalLoginResponseDto.setPassword(password); + userPortalLoginResponseDto.setUndergraduate(isUnderGraduate(signUpAndLoginRequestDto)); + return userPortalLoginResponseDto; + } + + private PortalLoginResponseDto readJson(String value) { + String[] arrayList = value.substring(1).split(", "); + PortalLoginResponseDto userPortalLoginResponseDto = new PortalLoginResponseDto(); + + for (String user : arrayList) { + String[] info = user.split("="); + String fieldName = info[0]; + + if ("studId".equals(fieldName)) { + String studId = info[1]; + userPortalLoginResponseDto.setStudentId(studId); + } else if ("userNm".equals(fieldName)) { + String userNm = info[1]; + userPortalLoginResponseDto.setName(userNm); + } else if ("userEml".equals(fieldName)) { + String userEml = info[1]; + userPortalLoginResponseDto.setEmail(userEml); + } else if ("userDpmtNm".equals(fieldName)) { + String userDpmtNm = info[1]; + Department department = Department.fromDescription(userDpmtNm); + userPortalLoginResponseDto.setDepartment(department); + } + } + return userPortalLoginResponseDto; + } + + public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto){ + String basic = "Basic " + Base64.getEncoder().encodeToString((signUpAndLoginRequestDto.getStudentId() + ":" + signUpAndLoginRequestDto.getPassword()).getBytes(ISO_8859_1)); + Map graduate = inuClient.status(basic); + return graduate.get("undergraduate").equals("true"); + } +} From 30c211d3731be66209df49910a6f4d9516c2ded1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:07:09 +0900 Subject: [PATCH 0365/1002] =?UTF-8?q?fix=20:=20Security=EC=9D=98=20usernam?= =?UTF-8?q?e=EC=9D=84=20studentId=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/user/security/CustomUserDetails.java | 4 ++-- .../domain/user/security/CustomUserDetailsService.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index c34cda32..5bdffc10 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -73,11 +73,11 @@ public String getPassword() { } /** - * Spring Security에서 사용하는 유저네임은 email로 사용 + * Spring Security에서 사용하는 유저네임은 studentId로 사용 */ @Override public String getUsername() { - return email; + return studentId; } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java index e4a5d995..8e5c4a3e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java @@ -17,10 +17,10 @@ public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + public UserDetails loadUserByUsername(String studentId) throws UsernameNotFoundException { - UserEntity user = userRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음, email :" + email)); + UserEntity user = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음, studentId :" + studentId)); if (!UserStatus.ACTIVE.equals(user.getStatus())) { throw new UserDisabledException("유저가 활성화되지 않았습니다"); From 4a3c8559adfee528553499f80e4ef18fffe304cd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:07:27 +0900 Subject: [PATCH 0366/1002] =?UTF-8?q?perf=20:=20=EC=95=BC=EA=B0=84?= =?UTF-8?q?=ED=95=99=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/java/inu/codin/codin/common/Department.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/Department.java b/codin-core/src/main/java/inu/codin/codin/common/Department.java index 0dacd844..625752bc 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/Department.java @@ -9,6 +9,7 @@ public enum Department { IT_COLLEGE("정보기술대학"), COMPUTER_SCI("컴퓨터공학부"), + COMPUTER_SCI_NIGHT("컴퓨터공학부(야)"), INFO_COMM("정보통신공학과"), EMBEDDED("임베디드시스템공학과"), STAFF("교직원"), From f38fd6339df08eb0087a3ce553ff8f163126db4d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:08:58 +0900 Subject: [PATCH 0367/1002] =?UTF-8?q?feat=20:=20=EC=9E=AC=ED=95=99,?= =?UTF-8?q?=EC=A1=B8=EC=97=85=20=EC=97=AC=EB=B6=80=20=EB=B0=8F=20=EB=8B=A8?= =?UTF-8?q?=EA=B3=BC=EB=8C=80=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/dto/PortalLoginResponseDto.java} | 6 +++-- .../common/security/feign/inu/InuClient.java | 15 ++++++++++++ .../security/feign/portal/PortalClient.java | 20 ++++++++++++++++ .../codin/domain/user/entity/UserEntity.java | 24 ++++++++++++------- 4 files changed, 55 insertions(+), 10 deletions(-) rename codin-core/src/main/java/inu/codin/codin/{domain/user/feign/dto/UserPortalSignUpResponseDto.java => common/security/dto/PortalLoginResponseDto.java} (67%) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java similarity index 67% rename from codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java index 1f908425..55848f7b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.feign.dto; +package inu.codin.codin.common.security.dto; import inu.codin.codin.common.Department; import lombok.Getter; @@ -8,10 +8,12 @@ @NoArgsConstructor @Setter @Getter -public class UserPortalSignUpResponseDto { +public class PortalLoginResponseDto { private String email; private String password; private String studentId; private String name; private Department department; + private String college; + private Boolean undergraduate; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java new file mode 100644 index 00000000..2ffad8be --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java @@ -0,0 +1,15 @@ +package inu.codin.codin.common.security.feign.inu; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.Map; + +@FeignClient(name = "inu", url = "${feign.client.config.inu.url}") +public interface InuClient { + + @GetMapping + Map status(@RequestHeader("Authorization") String basic); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java new file mode 100644 index 00000000..509bc440 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java @@ -0,0 +1,20 @@ +package inu.codin.codin.common.security.feign.portal; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "portal", url = "${feign.client.config.portal.url}") +public interface PortalClient { + + @PostMapping( + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE + ) + String signUp( + @RequestParam("_enpass_login_") String enpassLogin, + @RequestParam("username") String username, + @RequestParam("password") String password + ); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 6d98e289..eb95a9ee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; -import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpResponseDto; +import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -32,6 +32,10 @@ public class UserEntity extends BaseTimeEntity { private Department department; + private String college; + + private Boolean undergraduate; + private UserRole role; private UserStatus status; @@ -39,7 +43,7 @@ public class UserEntity extends BaseTimeEntity { private boolean changePassword = false; @Builder - public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, UserRole role, UserStatus status) { + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status) { this.email = email; this.password = password; this.studentId = studentId; @@ -47,6 +51,8 @@ public UserEntity(String email, String password, String studentId, String name, this.nickname = nickname; this.profileImageUrl = profileImageUrl; this.department = department; + this.college = college; + this.undergraduate = undergraduate; this.role = role; this.status = status; } @@ -69,13 +75,15 @@ public void updateProfileImageUrl(String profileImageUrl) { this.profileImageUrl = profileImageUrl; } - public static UserEntity of(UserPortalSignUpResponseDto userPortalSignUpResponseDto){ + public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ return UserEntity.builder() - .studentId(userPortalSignUpResponseDto.getStudentId()) - .email(userPortalSignUpResponseDto.getEmail()) - .name(userPortalSignUpResponseDto.getName()) - .password(userPortalSignUpResponseDto.getPassword()) - .department(userPortalSignUpResponseDto.getDepartment()) + .studentId(userPortalLoginResponseDto.getStudentId()) + .email(userPortalLoginResponseDto.getEmail()) + .name(userPortalLoginResponseDto.getName()) + .password(userPortalLoginResponseDto.getPassword()) + .department(userPortalLoginResponseDto.getDepartment()) + .college(userPortalLoginResponseDto.getCollege()) + .undergraduate(userPortalLoginResponseDto.getUndergraduate()) .build(); } } From 339f623b1a6b1b90417950ce26786b8bb5c37121 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:09:47 +0900 Subject: [PATCH 0368/1002] =?UTF-8?q?fix=20:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=20=ED=95=99=EB=B2=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/dto/LoginRequestDto.java | 18 ------------------ .../dto/SignUpAndLoginRequestDto.java} | 4 ++-- 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java rename codin-core/src/main/java/inu/codin/codin/{domain/user/dto/request/UserSignUpRequestDto.java => common/security/dto/SignUpAndLoginRequestDto.java} (70%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java deleted file mode 100644 index 0766b432..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/LoginRequestDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package inu.codin.codin.common.security.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -@Data -public class LoginRequestDto { - - @Schema(description = "이메일 주소", example = "codin@gmail.com") - @NotBlank - private String email; - - @Schema(description = "비밀번호", example = "1234") - @NotBlank - private String password; - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java index 830a01cb..4d531c7a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserSignUpRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.dto.request; +package inu.codin.codin.common.security.dto; import jakarta.validation.constraints.NotBlank; import lombok.Getter; @@ -6,7 +6,7 @@ @Getter @Setter -public class UserSignUpRequestDto { +public class SignUpAndLoginRequestDto { @NotBlank private String studentId; From b67c8a0bc4bdcc044954bc3d2eeac6b630945cfb Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:10:15 +0900 Subject: [PATCH 0369/1002] =?UTF-8?q?refactor=20:=20Auth=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/user/feign/PortalClient.java | 13 --- .../domain/user/feign/PortalController.java | 29 ------ .../domain/user/feign/PortalService.java | 98 ------------------- .../feign/dto/UserPortalSignUpRequestDto.java | 21 ---- 4 files changed, 161 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java deleted file mode 100644 index 3bb22966..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalClient.java +++ /dev/null @@ -1,13 +0,0 @@ -package inu.codin.codin.domain.user.feign; - -import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpRequestDto; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -@FeignClient(name = "portal", url = "${feign.client.config.portal.url}") -public interface PortalClient { - - @PostMapping - String signUp(@RequestBody UserPortalSignUpRequestDto userSignUpRequestDto); -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java deleted file mode 100644 index 3f0c80fb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalController.java +++ /dev/null @@ -1,29 +0,0 @@ -package inu.codin.codin.domain.user.feign; - -import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.user.dto.request.UserSignUpRequestDto; -import io.swagger.v3.oas.annotations.Operation; -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.RestController; - -@RestController -@RequiredArgsConstructor -public class PortalController { - - private final PortalService portalService; - - @Operation( - summary = "포탈 로그인 / 회원가입", - description = "포탈 아이디, 비밀번호를 통해 회원가입 진행" - ) - @PostMapping("/portal") - public ResponseEntity> portalSignUp(@RequestBody @Valid UserSignUpRequestDto userSignUpRequestDto) throws Exception { - portalService.signUp(userSignUpRequestDto); - return null; - - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java deleted file mode 100644 index 50a2e254..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/PortalService.java +++ /dev/null @@ -1,98 +0,0 @@ -package inu.codin.codin.domain.user.feign; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import inu.codin.codin.common.Department; -import inu.codin.codin.common.util.AESUtil; -import inu.codin.codin.domain.user.dto.request.UserSignUpRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpRequestDto; -import inu.codin.codin.domain.user.feign.dto.UserPortalSignUpResponseDto; -import inu.codin.codin.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.springframework.stereotype.Service; - -import java.io.IOException; - -@Service -@RequiredArgsConstructor -public class PortalService { - - private final PortalClient client; - private final UserRepository userRepository; - - public void signUp(UserSignUpRequestDto userSignUpRequestDto) throws Exception { - String password = AESUtil.encrypt(userSignUpRequestDto.getPassword()); - String html = client.signUp(UserPortalSignUpRequestDto.builder() - ._enpass_login_("submit") - .username(AESUtil.encrypt(userSignUpRequestDto.getStudentId())) - .password(password) - .build()); - Document doc = Jsoup.parse(html); - - // todo 가져오는 tag 맞는지 확인 - String value = doc.select(".main").select("input[type=hidden]").attr("value"); - System.out.println(value); - - UserPortalSignUpResponseDto userPortalSignUpResponseDto = readJson(value); - userPortalSignUpResponseDto.setPassword(password); - UserEntity userEntity = UserEntity.of(userPortalSignUpResponseDto); - userRepository.save(userEntity); - } - - private static UserPortalSignUpResponseDto readJson(String value) throws IOException { - JsonFactory jsonFactory = JsonFactory.builder().build(); - JsonParser parser = jsonFactory.createParser(value); - UserPortalSignUpResponseDto userPortalSignUpResponseDto = new UserPortalSignUpResponseDto(); - try { - // 시작 데이터가 Object인지 확인 - if (parser.currentToken() == JsonToken.START_OBJECT) { - - // Object 데이터 끝이 될때까지 반복합니다. - while (parser.nextToken() != JsonToken.END_OBJECT) { - - // JSON의 키 값을 가져옵니다. - String fieldName = parser.getCurrentName(); - parser.nextToken(); - - // JSON의 키 값을 기반으로 값을 추출합니다. - if ("studId".equals(fieldName)) { - String studId = parser.getValueAsString(); - userPortalSignUpResponseDto.setStudentId(studId); - } else if ("userNm".equals(fieldName)) { - String userNm = parser.getValueAsString(); - userPortalSignUpResponseDto.setName(userNm); - } else if ("userEml".equals(fieldName)) { - String userEml = parser.getValueAsString(); - userPortalSignUpResponseDto.setEmail(userEml); - -// }else if ("userMptel".equals(fieldName)) {//전화번호 -// String userMptel = parser.getValueAsString(); -// }else if ("userScregStaNm".equals(fieldName)) { //재학 -// String userScregStaNm = parser.getValueAsString(); -// }else if ("schgrNm".equals(fieldName)) { //학년 -// String schgrNm = parser.getValueAsString(); - - } else if ("userDpmtNm".equals(fieldName)) { - String userDpmtNm = parser.getValueAsString(); - Department department = Department.fromDescription(userDpmtNm); - userPortalSignUpResponseDto.setDepartment(department); - -// }else if ("userCollNm".equals(fieldName)) { -// String userCollNm = parser.getValueAsString(); -// } - - parser.close(); - - } - } - } - } catch (IOException e) { - e.printStackTrace(); - } - return userPortalSignUpResponseDto; - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java deleted file mode 100644 index 132f1a2b..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/feign/dto/UserPortalSignUpRequestDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package inu.codin.codin.domain.user.feign.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class UserPortalSignUpRequestDto { - - private String _enpass_login_; - - private String username; - - private String password; - - @Builder - public UserPortalSignUpRequestDto(String _enpass_login_, String username, String password) { - this._enpass_login_ = _enpass_login_; - this.username = username; - this.password = password; - } -} From a6f3a80643b8fd5d27af0e92c941cb881d43ad3a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:53:20 +0900 Subject: [PATCH 0370/1002] =?UTF-8?q?perf=20:=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EC=9C=A0=EC=A0=80=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 30 +--- .../dto/request/UserCreateRequestDto.java | 36 ----- ...stDto.java => UserNicknameRequestDto.java} | 18 +-- .../dto/request/UserPasswordRequestDto.java | 15 -- .../codin/domain/user/entity/UserEntity.java | 22 +-- .../domain/user/service/UserService.java | 139 +----------------- 6 files changed, 23 insertions(+), 237 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java rename codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/{UserUpdateRequestDto.java => UserNicknameRequestDto.java} (51%) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 1b45e039..ae61b3c8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -2,17 +2,14 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; -import inu.codin.codin.domain.user.dto.request.UserPasswordRequestDto; -import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; +import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -26,16 +23,6 @@ public class UserController { private final UserService userService; - @Operation(summary = "회원가입") - @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> signUpUser( - @RequestPart @Valid UserCreateRequestDto userCreateRequestDto, - @RequestPart(value = "userImage", required = false) MultipartFile userImage) { - userService.createUser(userCreateRequestDto, userImage); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "회원가입 성공", null)); - } - @Operation( summary = "해당 사용자 게시물 전체 조회" ) @@ -76,17 +63,6 @@ public ResponseEntity> getUserComment(@RequestP .body(new SingleResponse<>(200, "사용자가 작성한 댓글의 게시물 조회 성공", posts)); } - @Operation( - summary = "비밀번호 재설정" - ) - @PutMapping("/password/{code}") - public ResponseEntity> setUserPassword(@RequestBody @Valid UserPasswordRequestDto userPasswordRequestDto, - @PathVariable("code") String code){ - userService.setUserPassword(userPasswordRequestDto, code); - return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "비밀번호 재설정 완료", null)); - } - @Operation( summary = "회원 탈퇴" ) @@ -107,10 +83,10 @@ public ResponseEntity> getUserInfo(){ } @Operation( - summary = "유저 정보 수정" + summary = "유저 닉네임 수정" ) @PutMapping - public ResponseEntity> updateUserInfo(@RequestBody @Valid UserUpdateRequestDto userUpdateRequestDto){ + public ResponseEntity> updateUserInfo(@RequestBody @Valid UserNicknameRequestDto userUpdateRequestDto){ userService.updateUserInfo(userUpdateRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "유저 정보 수정 완료", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java deleted file mode 100644 index 2ca598d8..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserCreateRequestDto.java +++ /dev/null @@ -1,36 +0,0 @@ -package inu.codin.codin.domain.user.dto.request; - -import inu.codin.codin.common.Department; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; - -@Getter -public class UserCreateRequestDto { - - // todo : 길이 관련 validation 추가 - - @Schema(description = "이메일 주소", example = "codin@inu.ac.kr") - @NotBlank - private String email; - - @Schema(description = "비밀번호", example = "password") - @NotBlank - private String password; - - @Schema(description = "학번", example = "20210000") - @NotBlank - private String studentId; - - @Schema(description = "이름", example = "홍길동") - @NotBlank - private String name; - - @Schema(description = "닉네임", example = "코딩") - @NotBlank - private String nickname; - - @Schema(description = "소속", example = "IT_COLLEGE") - private Department department; - -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java similarity index 51% rename from codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java index 6a4655c9..d6114a90 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java @@ -1,21 +1,21 @@ package inu.codin.codin.domain.user.dto.request; -import inu.codin.codin.common.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import lombok.Setter; -@Getter -public class UserUpdateRequestDto { +import java.beans.ConstructorProperties; - @Schema(description = "이름", example = "홍길동") - @NotBlank - private String name; +@Getter +public class UserNicknameRequestDto { @Schema(description = "닉네임", example = "코딩") @NotBlank private String nickname; - @Schema(description = "소속", example = "IT_COLLEGE") - private Department department; -} + @ConstructorProperties({"nickname"}) + public UserNicknameRequestDto(String nickname) { + this.nickname = nickname; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java deleted file mode 100644 index 06b1e4b1..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserPasswordRequestDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package inu.codin.codin.domain.user.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; - -@Getter -public class UserPasswordRequestDto { - - @Schema(description = "변경된 비밀번호", example = "password1234") - @NotBlank - private String password; - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index eb95a9ee..73107c16 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; -import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; +import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; @@ -40,8 +40,6 @@ public class UserEntity extends BaseTimeEntity { private UserStatus status; - private boolean changePassword = false; - @Builder public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status) { this.email = email; @@ -57,18 +55,8 @@ public UserEntity(String email, String password, String studentId, String name, this.status = status; } - public void updatePassword(String password){ - this.password = password; - } - - public void canChangePassword(){ - this.changePassword = !this.changePassword; - } - - public void updateUserInfo(UserUpdateRequestDto userUpdateRequestDto) { - this.name = userUpdateRequestDto.getName(); - this.nickname = userUpdateRequestDto.getNickname(); - this.department = userUpdateRequestDto.getDepartment(); + public void updateNickname(UserNicknameRequestDto userNicknameRequestDto) { + this.nickname = userNicknameRequestDto.getNickname(); } public void updateProfileImageUrl(String profileImageUrl) { @@ -84,6 +72,10 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .department(userPortalLoginResponseDto.getDepartment()) .college(userPortalLoginResponseDto.getCollege()) .undergraduate(userPortalLoginResponseDto.getUndergraduate()) + .nickname("") + .profileImageUrl("") + .role(UserRole.USER) + .status(UserStatus.ACTIVE) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index eb010cf2..8559d443 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -2,8 +2,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.email.entity.EmailAuthEntity; -import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -16,16 +14,10 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; -import inu.codin.codin.domain.user.dto.request.UserCreateRequestDto; import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; -import inu.codin.codin.domain.user.dto.request.UserPasswordRequestDto; -import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; +import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.entity.UserRole; -import inu.codin.codin.domain.user.entity.UserStatus; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserPasswordChangeFailException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; @@ -58,68 +50,6 @@ public class UserService { private final PostService postService; private final S3Service s3Service; - public void createUser(UserCreateRequestDto userCreateRequestDto, MultipartFile userImage) { - - log.info("[회원가입] 요청 데이터: {}", userCreateRequestDto); - - String imageUrl = null; - if (userImage != null) { - log.info("[회원가입] 프로필 이미지 업로드 중..."); - imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); - log.info("[회원가입] 프로필 이미지 업로드 완료: {}", imageUrl); - } - - // imageUrl이 null이면 기본 이미지로 설정 - if (imageUrl == null) { - imageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 - - } - - String encodedPassword = passwordEncoder.encode(userCreateRequestDto.getPassword()); - - validateUserCreateRequest(userCreateRequestDto); - log.info("[signUpUser] UserCreateRequestDto : {}", userCreateRequestDto); - - // todo : 중복 이메일, 닉네임 체크, 유저 상태, 유저 역할 변경 기능 추가 - UserEntity user = UserEntity.builder() - .email(userCreateRequestDto.getEmail()) - .password(encodedPassword) - .studentId(userCreateRequestDto.getStudentId()) - .name(userCreateRequestDto.getName()) - .nickname(userCreateRequestDto.getNickname()) - .profileImageUrl(imageUrl) - .department(userCreateRequestDto.getDepartment()) - .status(UserStatus.ACTIVE) - .role(UserRole.USER) - .build(); - - userRepository.save(user); - log.info("[signUpUser] SIGN UP SUCCESS!! : {}", user.getEmail()); - } - - // todo : 정책적으로 보안 위반 사항 확인 -> 에러 메세지를 통해서 유추 금지 - private void validateUserCreateRequest(UserCreateRequestDto userCreateRequestDto) { - log.info("[회원가입 검증] 이메일 인증 상태 확인: {}", userCreateRequestDto.getEmail()); - EmailAuthEntity emailAuth = emailAuthRepository.findByEmail(userCreateRequestDto.getEmail()) - .orElseThrow(() -> { - log.warn("[회원가입 검증 실패] 이메일 인증 기록 없음: {}", userCreateRequestDto.getEmail()); - return new UserCreateFailException("이메일 인증을 먼저 진행해주세요."); - }); - if (!emailAuth.isVerified()) { - log.warn("[회원가입 검증 실패] 이메일 인증 미완료: {}", userCreateRequestDto.getEmail()); - throw new UserCreateFailException("이메일 인증을 먼저 진행해주세요."); - } - if (userRepository.findByEmail(userCreateRequestDto.getEmail()).isPresent()) { - log.warn("[회원가입 검증 실패] 이미 존재하는 이메일: {}", userCreateRequestDto.getEmail()); - throw new UserCreateFailException("이미 존재하는 이메일입니다."); - } - if (userRepository.findByStudentId(userCreateRequestDto.getStudentId()).isPresent()) { - log.warn("[회원가입 검증 실패] 이미 존재하는 학번: {}", userCreateRequestDto.getStudentId()); - throw new UserCreateFailException("이미 존재하는 학번입니다."); - } - log.info("[회원가입 검증] 검증 성공: {}", userCreateRequestDto.getEmail()); - } - //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllUserPosts(int pageNumber) { @@ -188,67 +118,6 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } } - public void setUserPassword(@Valid UserPasswordRequestDto userPasswordRequestDto, String code) { - if (code==null){ - throw new UserPasswordChangeFailException("코드가 비어있거나 유효하지 않습니다."); - } - log.info("[비밀번호 변경] 인증번호 확인 시작: {}", code); - - UserEntity user = checkPasswordAuthNum(code); - if (user.isChangePassword()) { - log.info("[비밀번호 변경] 비밀번호 변경 가능 확인 완료. 비밀번호 변경 진행 중..."); - - String encodedPassword = passwordEncoder.encode(userPasswordRequestDto.getPassword()); - user.updatePassword(encodedPassword); - user.canChangePassword(); - userRepository.save(user); - - log.info("[비밀번호 변경 성공] 이메일: {}", user.getEmail()); - } else { - log.warn("[비밀번호 변경 실패] 비밀번호 변경 불가능. 이메일 인증 필요. 이메일: {}", user.getEmail()); - throw new UserPasswordChangeFailException("유저의 비밀번호를 변경할 수 없습니다. 이메일 인증을 먼저 진행해주세요."); - } - } - - public UserEntity checkPasswordAuthNum(String authNum) { - log.info("[비밀번호 인증번호 확인] 인증번호: {}", authNum); - - EmailAuthEntity emailAuthEntity = checkEmailAndAuthNum(authNum); - log.info("[checkAuthNumForPW] email : {}, authNum : {}", emailAuthEntity.getEmail(), authNum); - - UserEntity user = userRepository.findByEmail(emailAuthEntity.getEmail()) - .orElseThrow(() -> { - log.warn("[비밀번호 인증번호 확인 실패] 유저 정보를 찾을 수 없음. 이메일: {}", emailAuthEntity.getEmail()); - return new NotFoundException("유저 정보를 찾을 수 없습니다."); - }); - user.canChangePassword(); - userRepository.save(user); - log.info("[비밀번호 변경 가능 상태 업데이트] 이메일: {}", user.getEmail()); - return user; - } - - - private EmailAuthEntity checkEmailAndAuthNum(String authNum) { - log.info("[인증번호 확인] 인증번호: {}", authNum); - - EmailAuthEntity emailAuthEntity = emailAuthRepository.findByAuthNum(authNum) - .orElseThrow(() -> { - log.warn("[인증번호 확인 실패] 인증번호가 일치하지 않음: {}", authNum); - return new EmailAuthFailException("인증번호가 일치하지 않습니다.", authNum); - }); - - // 인증번호 만료 확인 - if (emailAuthEntity.isExpired()) { - log.warn("[인증번호 만료] 인증번호 만료됨. 이메일: {}", emailAuthEntity.getEmail()); - throw new EmailAuthFailException("인증번호가 만료되었습니다.", emailAuthEntity.getEmail()); - } - - emailAuthEntity.verifyEmail(); - emailAuthRepository.save(emailAuthEntity); - log.info("[인증번호 확인 성공] 인증 완료. 이메일: {}", emailAuthEntity.getEmail()); - return emailAuthEntity; - } - public void deleteUser(UserDeleteRequestDto userDeleteRequestDto) { log.info("[회원 탈퇴 요청] 이메일: {}", userDeleteRequestDto.getEmail()); @@ -278,7 +147,7 @@ public UserInfoResponseDto getUserInfo() { log.info("[유저 정보 조회 성공] 닉네임: {}", user.getNickname()); return UserInfoResponseDto.of(user); } - public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { + public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) { ObjectId userId = SecurityUtils.getCurrentUserId(); log.info("[유저 정보 업데이트] 현재 사용자 ID: {}", userId); @@ -288,9 +157,9 @@ public void updateUserInfo(@Valid UserUpdateRequestDto userUpdateRequestDto) { return new NotFoundException("유저 정보를 찾을 수 없습니다."); }); - user.updateUserInfo(userUpdateRequestDto); + user.updateNickname(userNicknameRequestDto); userRepository.save(user); - log.info("[유저 정보 업데이트 성공] 사용자 ID: {}, 업데이트된 정보: {}", userId, userUpdateRequestDto); + log.info("[유저 정보 업데이트 성공] 사용자 ID: {}, 업데이트된 정보: {}", userId, userNicknameRequestDto); } public void updateUserProfile(MultipartFile profileImage) { From 51d0f559d479645227b8674e9adca047791e1ac3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:54:04 +0900 Subject: [PATCH 0371/1002] =?UTF-8?q?perf=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=95=94?= =?UTF-8?q?=ED=98=B8=ED=99=94=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 19 ++++++-- .../common/security/service/AuthService.java | 43 +++++++++++++++++-- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 5ca00a8c..50bc6f37 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -4,21 +4,21 @@ import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthService; import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -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; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping(value = "/auth") @@ -69,4 +69,15 @@ public ResponseEntity> portalSignUp(@RequestBody @Valid SignUp return ResponseEntity.ok() .body(new SingleResponse<>(200, "포탈 로그인을 통한 학적 정보 반환 완료", null)); } + + @Operation(summary = "회원가입") + @PostMapping(value = "/signup/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> signUpUser( + @PathVariable("studentId") String studentId, + @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, + @RequestPart(value = "userImage", required = false) MultipartFile userImage) { + authService.createUser(studentId, userNicknameRequestDto, userImage); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "회원가입 성공", null)); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 08019d7a..afdf83cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -1,21 +1,27 @@ package inu.codin.codin.common.security.service; import inu.codin.codin.common.Department; +import inu.codin.codin.common.security.dto.PortalLoginResponseDto; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.feign.inu.InuClient; import inu.codin.codin.common.security.feign.portal.PortalClient; -import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.common.util.AESUtil; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.util.Base64; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -23,11 +29,14 @@ @Service @RequiredArgsConstructor +@Slf4j public class AuthService { private final PortalClient portalClient; private final InuClient inuClient; private final UserRepository userRepository; + private final S3Service s3Service; + private final PasswordEncoder passwordEncoder; public void signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { //학번으로 회원가입 유무 판단 @@ -41,15 +50,15 @@ public void signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exc } private PortalLoginResponseDto returnPortalInfo(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { - String password = AESUtil.encrypt(signUpAndLoginRequestDto.getPassword()); String html = portalClient.signUp( "submit", AESUtil.encrypt(signUpAndLoginRequestDto.getStudentId()), - password + AESUtil.encrypt(signUpAndLoginRequestDto.getPassword()) ); Document doc = Jsoup.parse(html); String value = doc.select(".main").select("input[type=hidden]").attr("value"); PortalLoginResponseDto userPortalLoginResponseDto = readJson(value); + String password = passwordEncoder.encode(signUpAndLoginRequestDto.getPassword()); userPortalLoginResponseDto.setPassword(password); userPortalLoginResponseDto.setUndergraduate(isUnderGraduate(signUpAndLoginRequestDto)); return userPortalLoginResponseDto; @@ -86,4 +95,30 @@ public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginReq Map graduate = inuClient.status(basic); return graduate.get("undergraduate").equals("true"); } + + public void createUser(String studentId, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage) { + + UserEntity user = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new UserCreateFailException("존재하지 않는 학번입니다. 포탈 로그인부터 진행해주세요.")); + log.info("[createUser] 요청 데이터: {}", studentId); + + String imageUrl = null; + if (userImage != null) { + log.info("[회원가입] 프로필 이미지 업로드 중..."); + imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); + log.info("[회원가입] 프로필 이미지 업로드 완료: {}", imageUrl); + } + + // imageUrl이 null이면 기본 이미지로 설정 + if (imageUrl == null) { + imageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 + + } + + user.updateNickname(new UserNicknameRequestDto(userNicknameRequestDto.getNickname())); + user.updateProfileImageUrl(imageUrl); + userRepository.save(user); + + log.info("[signUpUser] SIGN UP SUCCESS!! : {}", user.getStudentId()); + } } From ded9f60995df823a11d304bccb3e8ae6e0caf2f6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 21 Jan 2025 22:59:22 +0900 Subject: [PATCH 0372/1002] =?UTF-8?q?feat=20:=20login=EC=97=90=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20url,=20key=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index abb065eb..58c0fba0 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit abb065eb9977e1627e3ab8317bde7c92f931f722 +Subproject commit 58c0fba0af8634b9fc150196b6a331bf507df747 From 163c7fc644bb7433b428cea144e8550ba5e264a0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 22 Jan 2025 14:46:56 +0900 Subject: [PATCH 0373/1002] =?UTF-8?q?fix=20:=20=EC=A2=8B=EC=95=84=EC=9A=94?= =?UTF-8?q?=20=ED=86=A0=EA=B8=80=20Redis=EC=97=90=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=EC=9D=B4=20=EC=95=88=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/like/service/LikeService.java | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java index fdd1a03a..5d2f6e3b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java @@ -30,9 +30,6 @@ public class LikeService { private final RedisHealthChecker redisHealthChecker; public String toggleLike(LikeRequestDto likeRequestDto) { - log.info("좋아요 토글 요청 - likeType: {}, id: {}", likeRequestDto.getLikeType(), likeRequestDto.getId()); - - ObjectId likeId = new ObjectId(likeRequestDto.getId()); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 @@ -40,50 +37,57 @@ public String toggleLike(LikeRequestDto likeRequestDto) { // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - if (like != null && like.getDeletedAt() == null) { + if (like != null && like.getDeletedAt() == null) { //좋아요가 존재 removeLike(like); - log.info("좋아요 취소 완료 - likeId: {}, userId: {}", like.get_id(), userId); return "좋아요가 삭제되었습니다."; + } else if (like == null){ + createLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 추가되었습니다."; + } else { + addLike(like); //좋아요가 존재하는데 삭제된 상태 + return "좋아요가 복구되었습니다"; + } + } + + public void createLike(LikeType likeType, ObjectId likeId, ObjectId userId){ + if (redisHealthChecker.isRedisAvailable()) { + redisService.addLike(likeType.name(), likeId, userId); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + } + + likeRepository.save(LikeEntity.builder() + .likeType(likeType) + .likeTypeId(likeId) + .userId(userId) + .build()); + if (likeType == LikeType.POST) { + redisService.applyBestScore(1, likeId); + log.info("Redis에 Best Score 적용 - postId: {}", likeId); } - addLike(likeRequestDto.getLikeType(), likeId, userId); - log.info("좋아요 추가 완료 - likeType: {}, likeId: {}, userId: {}", likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 추가되었습니다."; } - public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId) { - log.info("좋아요 추가 요청 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); - LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeType, likeId, userId); - - if (like != null){ - if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 - log.info("삭제된 좋아요 복구 - likeId: {}, userId: {}", like.get_id(), userId); - like.recreatedAt(); - like.restore(); - likeRepository.save(like); - log.info("좋아요 복구 완료 - likeId: {}, userId: {}", like.get_id(), userId); - } else { - log.warn("좋아요 추가 실패 - 이미 좋아요가 눌려 있음 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); - throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); - } - } else { //좋아요 내역이 없으면 새로 생성 - if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeType.name(), likeId, userId); - log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); - } - likeRepository.save(LikeEntity.builder() - .likeType(likeType) - .likeTypeId(likeId) - .userId(userId) - .build()); - if (likeType == LikeType.POST) { - redisService.applyBestScore(1, likeId); - log.info("Redis에 Best Score 적용 - postId: {}", likeId); - } + public void addLike(LikeEntity like) { + LikeType likeType = like.getLikeType(); + ObjectId likeId = like.getLikeTypeId(); + ObjectId userId = like.getUserId(); + + if (redisHealthChecker.isRedisAvailable()) { + redisService.addLike(likeType.name(), likeId, userId); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + } + + if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 + like.recreatedAt(); + like.restore(); + likeRepository.save(like); + log.info("좋아요 복구 완료 - likeId: {}, userId: {}", like.get_id(), userId); + } else { + log.warn("좋아요 추가 실패 - 이미 좋아요가 눌려 있음 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); } } public void removeLike(LikeEntity like) { - log.info("좋아요 삭제 요청 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); if (redisHealthChecker.isRedisAvailable()) { redisService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); log.info("Redis에서 좋아요 삭제 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); @@ -111,7 +115,6 @@ public void recoverRedisFromDB() { private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ ObjectId id = new ObjectId(likeRequestDto.getId()); - log.info("엔티티 삭제 상태 확인 - likeType: {}, id: {}", likeRequestDto.getLikeType(), id); switch(likeRequestDto.getLikeType()){ case POST -> postRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); From aa7a8f6ac79cb7a7a92efef014eab7faab894871 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 22 Jan 2025 14:47:32 +0900 Subject: [PATCH 0374/1002] =?UTF-8?q?perf=20:=20=EB=8B=A8=EA=B3=BC?= =?UTF-8?q?=EB=8C=80=20=EB=AA=85=EC=B9=AD=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/AuthService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index afdf83cd..3bfe9688 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -85,6 +85,9 @@ private PortalLoginResponseDto readJson(String value) { String userDpmtNm = info[1]; Department department = Department.fromDescription(userDpmtNm); userPortalLoginResponseDto.setDepartment(department); + } else if ("userCollNm".equals(fieldName)){ + String userCollNm = info[1]; + userPortalLoginResponseDto.setCollege(userCollNm); } } return userPortalLoginResponseDto; From 5c67c4444252db8e5788ad9144c7d143c84ceb77 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 22 Jan 2025 21:09:15 +0900 Subject: [PATCH 0375/1002] =?UTF-8?q?CI=20:=20=EB=8B=A8=EC=9D=BC=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/.DS_Store | Bin 0 -> 6148 bytes .../src/main/java/inu/codin/codin/.DS_Store | Bin 0 -> 6148 bytes .../main/java/inu/codin/codin/common/.DS_Store | Bin 0 -> 6148 bytes codin-core/src/main/resources | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 codin-core/src/main/.DS_Store create mode 100644 codin-core/src/main/java/inu/codin/codin/.DS_Store create mode 100644 codin-core/src/main/java/inu/codin/codin/common/.DS_Store diff --git a/codin-core/src/main/.DS_Store b/codin-core/src/main/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..420d4c847e135cd6d5b1175540c11f92a7a9f9aa GIT binary patch literal 6148 zcmeHKO;5r=5S;}S8e+mh6OK*15=BrG;-w-Uyjr6NHP`~tYV8tQ;6O-v)*s{5U*g}< zncc-i!FV&o%p|jK_Tz24ug&fb0Ep(Wy8}=HfC4&U#=>HOQ9t>dWlT#BQP3PGq3rmf z^s!D8vMs*?^zCwR4<3xb`}X|~v2W{LnOcYi1L#2v5{4LcDE`KH`JLg9{5l|a=jM64J*J3 zEP(>FKiKGmj=@x;+B&dMDF7l}Bb8ySuKtKNH6S_$Q;pbyCNvdMQ-w(}gvlI)renWj z#-|!J9fW!^&SRb|Ook#%;vsA`9E78hOICmt_^LowS4(vMAOD>He#Ey za$8MYk~v%F7Ds2TjedhpM&nYApA-z#QH*2hC@!Nb!*-hjM8{yN5nIssBA{g8f))5v G1wH^~gJ&ZE literal 0 HcmV?d00001 diff --git a/codin-core/src/main/java/inu/codin/codin/.DS_Store b/codin-core/src/main/java/inu/codin/codin/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..45f4ec022dc0c7d6f522afa6bf4f089ef967ceae GIT binary patch literal 6148 zcmeHKI|{-;5S>vG!N$^Zuiy<9(G%nXf`W}k5VTI^xjdS0K23;r+Q^$Q`*vsNZOAKj zG9seu+hHZL5Rn<&P#!i6&GyX)Hp++s;W*cPe`@&v^QL+oMqd zDnJFO02QDD-&G*%>umnrV|f}CpaOrOfZY!TZden$K)*ULcnbiWAnb;@_Y%Nj0bot+ z0ug~}P=P_!Y%w(Gh?mT(iCtjOMYH+Pe6wbUqW*TAUp!s326ChVRG_cGIF<{m|6BNn z{=ZM+iV9GHzfwRan`X1dCuMCNe4N$V0^h+c=Lt8%+$k8m90R=^V`1fZ=}D1SY>xe! V*abQrai;_MGhn*VsKBokcme016|DdO literal 0 HcmV?d00001 diff --git a/codin-core/src/main/java/inu/codin/codin/common/.DS_Store b/codin-core/src/main/java/inu/codin/codin/common/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..15b9adb34041665f76f7ea98a636dbfcd4578e27 GIT binary patch literal 6148 zcmeHK%}N9@4E~~v40u`a;zj5aEcga>*gbgG2XJ@%L0KKpUBTPDk&mcfk}A$PM-h<( znlIBPY5T#n4FIXPm&d>qK*gpgYK(|>j}Gk==Ut*$j&t1N9#>f4VI(QhZ36`J;Fdo1OWSxkIvJ=wKii z=rf?TFJ-Ft|AN2FXpwI&p@M;6;J-2;llj@a=I+Yf`fZ Date: Wed, 22 Jan 2025 21:15:34 +0900 Subject: [PATCH 0376/1002] =?UTF-8?q?DB=20Docker=20name=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index c2041c0b..1466fb80 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit c2041c0be50bfb50a904330d48db81e8172f7d3f +Subproject commit 1466fb8081cc886d661576a067327a5a421868d7 From 88fd01036a6de7a8f6684ea2b77c783cd4a27767 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 23 Jan 2025 01:35:03 +0900 Subject: [PATCH 0377/1002] =?UTF-8?q?refactor=20:=20Mapping=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=A4=91=EB=B3=B5=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/AuthService.java | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 3bfe9688..bfc306af 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import static feign.Util.ISO_8859_1; @@ -68,31 +69,25 @@ private PortalLoginResponseDto readJson(String value) { String[] arrayList = value.substring(1).split(", "); PortalLoginResponseDto userPortalLoginResponseDto = new PortalLoginResponseDto(); + Map> fieldSetters = Map.of( + "studId", userPortalLoginResponseDto::setStudentId, + "userNm", userPortalLoginResponseDto::setName, + "userEml", userPortalLoginResponseDto::setEmail, + "userDpmtNm", v -> userPortalLoginResponseDto.setDepartment(Department.fromDescription(v)), + "userCollNm", userPortalLoginResponseDto::setCollege + ); + for (String user : arrayList) { String[] info = user.split("="); - String fieldName = info[0]; - - if ("studId".equals(fieldName)) { - String studId = info[1]; - userPortalLoginResponseDto.setStudentId(studId); - } else if ("userNm".equals(fieldName)) { - String userNm = info[1]; - userPortalLoginResponseDto.setName(userNm); - } else if ("userEml".equals(fieldName)) { - String userEml = info[1]; - userPortalLoginResponseDto.setEmail(userEml); - } else if ("userDpmtNm".equals(fieldName)) { - String userDpmtNm = info[1]; - Department department = Department.fromDescription(userDpmtNm); - userPortalLoginResponseDto.setDepartment(department); - } else if ("userCollNm".equals(fieldName)){ - String userCollNm = info[1]; - userPortalLoginResponseDto.setCollege(userCollNm); + if (info.length == 2 && fieldSetters.containsKey(info[0])) { // Only process known fields + fieldSetters.get(info[0]).accept(info[1]); } } + return userPortalLoginResponseDto; } + public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto){ String basic = "Basic " + Base64.getEncoder().encodeToString((signUpAndLoginRequestDto.getStudentId() + ":" + signUpAndLoginRequestDto.getPassword()).getBytes(ISO_8859_1)); Map graduate = inuClient.status(basic); From 17b7d310a021592ea0150db1f42970e9a38b2a6e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 23 Jan 2025 20:25:05 +0900 Subject: [PATCH 0378/1002] =?UTF-8?q?feat=20:=20=EC=B8=B5(floor)=EC=9D=98?= =?UTF-8?q?=20=EA=B0=95=EC=9D=98=EC=8B=A4=20=ED=98=84=ED=99=A9=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/controller/LectureController.java | 37 ++++++++++ .../lecture/dto/EmptyRoomResponseDto.java | 33 +++++++++ .../domain/lecture/entity/LectureEntity.java | 32 +++++++++ .../lecture/repository/LectureRepository.java | 11 +++ .../LectureRepositoryCustomImpl.java | 23 ++++++ .../lecture/service/LectureService.java | 71 +++++++++++++++++++ 6 files changed, 207 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepositoryCustomImpl.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java new file mode 100644 index 00000000..9c0bb853 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -0,0 +1,37 @@ +package inu.codin.codin.domain.lecture.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.lecture.service.LectureService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/lectures") +@RequiredArgsConstructor +@Tag(name = "Lecture API", description = "강의실 정보 API") +public class LectureController { + + private final LectureService lectureService; + + @Operation(summary = "강의실 정보 반환") + @GetMapping("/{lectureId}") + public ResponseEntity getLectureDetails(@PathVariable("lectureId") String lectureId){ + lectureService.getLectureDetails(lectureId); + return null; + } + + @Operation( + summary = "오늘의 강의 현황", + description = "당일의 요일에 따라 층마다 호실에서의 수업 내용 반환" + ) + @GetMapping("/rooms/empty") + public ResponseEntity statusOfEmptyRoom(@RequestParam("floor") int floor){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, floor + "층의 강의실 현황 반환", lectureService.statusOfEmptyRoom(floor))); + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java new file mode 100644 index 00000000..9285b3c0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java @@ -0,0 +1,33 @@ +package inu.codin.codin.domain.lecture.dto; + +import inu.codin.codin.domain.lecture.entity.LectureEntity; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class EmptyRoomResponseDto { + private String lectureNm; + private int roomNum; + private String startTime; + private String endTime; + + @Builder + public EmptyRoomResponseDto(String lectureNm, int roomNum, String startTime, String endTime) { + this.lectureNm = lectureNm; + this.roomNum = roomNum; + this.startTime = startTime; + this.endTime = endTime; + } + + public static EmptyRoomResponseDto of(LectureEntity lectureEntity, String time) { + return EmptyRoomResponseDto.builder() + .lectureNm(lectureEntity.getLectureNm()) + .roomNum(lectureEntity.getRoomNum()) + .startTime(time.split("/")[0]) + .endTime(time.split("/")[1]) + .build(); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java new file mode 100644 index 00000000..fe258cb2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.lecture.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.Department; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.DayOfWeek; +import java.util.List; +import java.util.Map; + +@Document(collection = "lectures") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class LectureEntity { + + private ObjectId _id; + private String lectureNm; + private String lectureCode; + private String professor; + private Department department; //OTHERS : 교양 + private int grade; //0 : 전학년 + private int roomNum; + + @Field("dayTime") + private Map> dayTime; // The dayTime map structure + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java new file mode 100644 index 00000000..a6bb862d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.lecture.repository; + +import inu.codin.codin.domain.lecture.entity.LectureEntity; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LectureRepository extends MongoRepository { + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepositoryCustomImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepositoryCustomImpl.java new file mode 100644 index 00000000..9acb5640 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepositoryCustomImpl.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.lecture.repository; + +import lombok.RequiredArgsConstructor; +import org.bson.Document; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class LectureRepositoryCustomImpl implements AggregationOperation{ + + private String jsonOperation; + + public LectureRepositoryCustomImpl(String jsonOperation) { + this.jsonOperation = jsonOperation; + } + + @Override + public Document toDocument(AggregationOperationContext context) { + return context.getMappedObject(Document.parse(jsonOperation)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java new file mode 100644 index 00000000..e07eb80b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -0,0 +1,71 @@ +package inu.codin.codin.domain.lecture.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.lecture.dto.EmptyRoomResponseDto; +import inu.codin.codin.domain.lecture.entity.LectureEntity; +import inu.codin.codin.domain.lecture.repository.LectureRepository; +import inu.codin.codin.domain.lecture.repository.LectureRepositoryCustomImpl; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.stereotype.Service; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class LectureService { + + private final LectureRepository lectureRepository; + private final MongoTemplate mongoTemplate; + + public List getLecturesByFloor(int floor) { + + AggregationOperation match = Aggregation.match( + Criteria.where("roomNum").ne("") + ); + String query = + "{ $match: {" + + "$expr: {"+ + "$and: ["+ + "{ $ne: [ '$roomNum', ''] },"+ + "{ $eq: [{ $floor: { $divide: [{ $toInt: '$roomNum' }, 100] } }, "+ floor+"] }]}}}"; + + TypedAggregation aggregation = Aggregation.newAggregation( + LectureEntity.class, + match, + new LectureRepositoryCustomImpl(query) + ); + // Execute aggregation + AggregationResults results = mongoTemplate.aggregate(aggregation, LectureEntity.class); + return results.getMappedResults(); + } + + public void getLectureDetails(String lectureId) { + LectureEntity lectureEntity = lectureRepository.findById(new ObjectId(lectureId)) + .orElseThrow(() -> new NotFoundException("강의 정보를 찾을 수 없습니다.")); + + } + + public Map> statusOfEmptyRoom(int floor) { + LocalDateTime now = LocalDateTime.now(); + DayOfWeek today = now.getDayOfWeek(); + List lectureEntity = getLecturesByFloor(floor); + return lectureEntity.stream() + .filter(lecture -> lecture.getDayTime().containsKey(today)) + .flatMap(lecture -> lecture.getDayTime().get(today).stream() + .map(time -> EmptyRoomResponseDto.of(lecture, time))) + .collect(Collectors.groupingBy( + EmptyRoomResponseDto::getRoomNum + )); + } +} From 6a97ecbf5012ed2ef978aba031b1bf60a3ea2b3f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 23 Jan 2025 23:42:02 +0900 Subject: [PATCH 0379/1002] =?UTF-8?q?feat=20:=20=ED=95=99=EA=B3=BC=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=90=EC=88=98/=EA=B3=BC=EB=AA=A9=EB=AA=85=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/controller/LectureController.java | 24 ++++++++++- .../lecture/dto/LectureListResponseDto.java | 41 +++++++++++++++++++ .../lecture/dto/LecturePageResponse.java | 31 ++++++++++++++ .../codin/domain/lecture/dto/Option.java | 12 ++++++ .../exception/WrongInputException.java | 7 ++++ .../lecture/repository/LectureRepository.java | 4 ++ .../lecture/service/LectureService.java | 20 +++++++++ .../post/dto/response/PostPageResponse.java | 8 ++-- 8 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Option.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/WrongInputException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index 9c0bb853..a5c57a77 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -1,9 +1,14 @@ package inu.codin.codin.domain.lecture.controller; +import inu.codin.codin.common.Department; +import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.lecture.dto.Option; import inu.codin.codin.domain.lecture.service.LectureService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,6 +21,23 @@ public class LectureController { private final LectureService lectureService; + @Operation( + summary = "학과명 및 과목/교수 정렬 페이지", + description = "학과명과 과목/교수 라디오 토클을 통해 정렬한 리스트 반환
"+ + "department : COMPUTER_SCI, INFO_COMM, EMBEDDED, OTHER(교양 및 프로젝트 실습 강의)
"+ + "option : LEC(과목명) , PROF(교수명)" + ) + @GetMapping("/list") + public ResponseEntity> sortListOfLectures(@RequestParam("department") Department department, + @RequestParam("option") Option option, + @RequestParam("page") int page){ + + return ResponseEntity.ok() + .body(new SingleResponse<>(200, + department.getDescription()+" 강의들 "+option.getDescription()+"순으로 정렬 반환", + lectureService.sortListOfLectures(department, option, page))); + } + @Operation(summary = "강의실 정보 반환") @GetMapping("/{lectureId}") public ResponseEntity getLectureDetails(@PathVariable("lectureId") String lectureId){ @@ -28,7 +50,7 @@ public ResponseEntity getLectureDetails(@PathVariable("lectureId") String lec description = "당일의 요일에 따라 층마다 호실에서의 수업 내용 반환" ) @GetMapping("/rooms/empty") - public ResponseEntity statusOfEmptyRoom(@RequestParam("floor") int floor){ + public ResponseEntity statusOfEmptyRoom(@RequestParam("floor") @Max(5) @Min(1) int floor){ return ResponseEntity.ok() .body(new SingleResponse<>(200, floor + "층의 강의실 현황 반환", lectureService.statusOfEmptyRoom(floor))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java new file mode 100644 index 00000000..76681963 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.lecture.dto; + +import inu.codin.codin.domain.lecture.entity.LectureEntity; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; + +@Setter +@Getter +public class LectureListResponseDto { + + private String _id; + private String lectureNm; + private String lectureCode; + private String professor; + + private int starRating; + private int participants; + + @Builder + public LectureListResponseDto(String _id, String lectureNm, String lectureCode, String professor, int starRating, int participants) { + this._id = _id; + this.lectureNm = lectureNm; + this.lectureCode = lectureCode; + this.professor = professor; + this.starRating = starRating; + this.participants = participants; + } + + public static LectureListResponseDto of(LectureEntity lectureEntity){ + return LectureListResponseDto.builder() + ._id(lectureEntity.get_id().toString()) + .lectureNm(lectureEntity.getLectureNm()) + .lectureCode(lectureEntity.getLectureCode()) + .professor(lectureEntity.getProfessor()) +// .starRating() +// .participants() + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java new file mode 100644 index 00000000..bfb8a75b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.lecture.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class LecturePageResponse { + + private List contents = new ArrayList<>(); + private long lastPage; + private long nextPage; + + public LecturePageResponse(List contents, long lastPage, long nextPage) { + this.contents = contents; + this.lastPage = lastPage; + this.nextPage = nextPage; + } + + public static LecturePageResponse of(List lecturePaging, long totalElements, long nextPage){ + return LecturePageResponse.nextPagingHasNext(lecturePaging, totalElements, nextPage); + } + + public static LecturePageResponse nextPagingHasNext(List lectures, long totalElements, long nextPage){ + return new LecturePageResponse(lectures, totalElements, nextPage); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Option.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Option.java new file mode 100644 index 00000000..49bd1f15 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Option.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.lecture.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum Option { + LEC("과목명"), PROF("교수명"); + + private final String description; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/WrongInputException.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/WrongInputException.java new file mode 100644 index 00000000..391b1546 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/WrongInputException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.lecture.exception; + +public class WrongInputException extends RuntimeException{ + public WrongInputException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java index a6bb862d..d9469235 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -1,11 +1,15 @@ package inu.codin.codin.domain.lecture.repository; +import inu.codin.codin.common.Department; import inu.codin.codin.domain.lecture.entity.LectureEntity; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @Repository public interface LectureRepository extends MongoRepository { + Page findAllByDepartment(Pageable pageable, Department department); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index e07eb80b..a1375542 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -1,22 +1,32 @@ package inu.codin.codin.domain.lecture.service; +import inu.codin.codin.common.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.dto.EmptyRoomResponseDto; +import inu.codin.codin.domain.lecture.dto.LectureListResponseDto; +import inu.codin.codin.domain.lecture.dto.LecturePageResponse; +import inu.codin.codin.domain.lecture.dto.Option; import inu.codin.codin.domain.lecture.entity.LectureEntity; +import inu.codin.codin.domain.lecture.exception.WrongInputException; import inu.codin.codin.domain.lecture.repository.LectureRepository; import inu.codin.codin.domain.lecture.repository.LectureRepositoryCustomImpl; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.aggregation.TypedAggregation; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.time.DayOfWeek; import java.time.LocalDateTime; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -68,4 +78,14 @@ public Map> statusOfEmptyRoom(int floor) { EmptyRoomResponseDto::getRoomNum )); } + + public LecturePageResponse sortListOfLectures(Department department, Option option, int page) { + if (department.equals(Department.EMBEDDED) || department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.OTHERS)) { + PageRequest pageRequest = PageRequest.of(page, 20, option==Option.LEC? Sort.by("lectureNm"):Sort.by("professor")); + Page lecturePage = lectureRepository.findAllByDepartment(pageRequest, department); + return LecturePageResponse.of(lecturePage.stream().map(LectureListResponseDto::of).toList(), + lecturePage.getTotalPages() -1, + lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1: -1); + } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java index e033c02c..26bda696 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java @@ -21,12 +21,12 @@ private PostPageResponse(List contents, long lastPage, lo this.nextPage = nextPage; } - public static PostPageResponse of(List postPaging, long totalElements, long nextCursor) { - return PostPageResponse.newPagingHasNext(postPaging, totalElements, nextCursor); + public static PostPageResponse of(List postPaging, long totalElements, long nextPage) { + return PostPageResponse.newPagingHasNext(postPaging, totalElements, nextPage); } - private static PostPageResponse newPagingHasNext(List posts, long totalElements, long nextCursor) { - return new PostPageResponse(posts, totalElements, nextCursor); + private static PostPageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { + return new PostPageResponse(posts, totalElements, nextPage); } } \ No newline at end of file From 392d4d6d7b1cb08dd2e12c3e94e94c68dfafa212 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 23 Jan 2025 23:43:38 +0900 Subject: [PATCH 0380/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 1466fb80..0a32654e 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 1466fb8081cc886d661576a067327a5a421868d7 +Subproject commit 0a32654e3f70e284db1d9545751af3f4b934375e From 1ae852a0b5510de5f291046826a7640151495811 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 24 Jan 2025 00:04:45 +0900 Subject: [PATCH 0381/1002] =?UTF-8?q?feat=20:=20=EA=B3=BC=EB=AA=A9?= =?UTF-8?q?=EB=AA=85,=20=EA=B5=90=EC=88=98=EB=AA=85,=20=EA=B3=BC=EB=AA=A9?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B2=80=EC=83=89=20=EC=97=94=EC=A7=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/lecture/controller/LectureController.java | 12 +++++++++++- .../domain/lecture/repository/LectureRepository.java | 6 ++++++ .../codin/domain/lecture/service/LectureService.java | 12 ++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index a5c57a77..f319c35c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.lecture.controller; import inu.codin.codin.common.Department; -import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.dto.Option; import inu.codin.codin.domain.lecture.service.LectureService; @@ -38,6 +37,17 @@ public ResponseEntity> sortListOfLectures(@RequestParam("depar lectureService.sortListOfLectures(department, option, page))); } + @Operation( + summary = "교수명, 과목명, 과목코드 검색", + description = "keyword 입력을 통해 (교수명, 과목명, 과목코드) 중 일치하는 결과 반환" + ) + @GetMapping("/search") + public ResponseEntity> searchLectures(@RequestParam("keyword") String keyword, + @RequestParam("page") int page){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, keyword+" 의 검색 결과 반환", lectureService.searchLectures(keyword, page))); + } + @Operation(summary = "강의실 정보 반환") @GetMapping("/{lectureId}") public ResponseEntity getLectureDetails(@PathVariable("lectureId") String lectureId){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java index d9469235..3908eeb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -6,10 +6,16 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @Repository public interface LectureRepository extends MongoRepository { Page findAllByDepartment(Pageable pageable, Department department); + @Query("{ '$or': [ { 'lectureNm': { $regex: ?0, $options: 'i' } }, " + + "{ 'professor': { $regex: ?0, $options: 'i' } }, " + + "{ 'lectureCode': { $regex: ?0, $options: 'i' } } ] }") + Page findAllByKeyword(String keyword, Pageable pageable); + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index a1375542..04434eca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -21,12 +21,10 @@ import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.aggregation.TypedAggregation; import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.time.DayOfWeek; import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -88,4 +86,14 @@ public LecturePageResponse sortListOfLectures(Department department, Option opti lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1: -1); } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); } + + public Object searchLectures(String keyword, int page) { + PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); + Page lecturePage = lectureRepository.findAllByKeyword(keyword, pageRequest); + return LecturePageResponse.of( + lecturePage.stream().map(LectureListResponseDto::of).toList(), + lecturePage.getTotalPages() - 1, + lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1 : -1 + ); + } } From 89d1073ef0734a28c1470546f7099cd3fcb0ff36 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 24 Jan 2025 12:40:44 +0900 Subject: [PATCH 0382/1002] =?UTF-8?q?refactor=20:=20like,=20scrap=EC=9D=84?= =?UTF-8?q?=20post=20=ED=8C=A8=ED=82=A4=EC=A7=80=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/controller/LikeController.java | 6 +++--- .../domain => }/like/dto/LikeRequestDto.java | 6 +++--- .../{post/domain => }/like/entity/LikeEntity.java | 2 +- .../{post/domain => }/like/entity/LikeType.java | 2 +- .../like/exception/LikeCreateFailException.java | 2 +- .../like/exception/LikeRemoveFailException.java | 2 +- .../like/repository/LikeRepository.java | 6 +++--- .../domain => }/like/service/LikeService.java | 12 ++++++------ .../domain/comment/service/CommentService.java | 5 ++--- .../domain/reply/service/ReplyCommentService.java | 4 ++-- .../codin/domain/post/service/PostService.java | 6 +++--- .../scrap/controller/ScrapController.java | 4 ++-- .../domain => }/scrap/entity/ScrapEntity.java | 2 +- .../scrap/exception/ScrapCreateFailException.java | 2 +- .../scrap/exception/ScrapRemoveFailException.java | 2 +- .../scrap/repository/ScrapRepository.java | 4 ++-- .../domain => }/scrap/service/ScrapService.java | 8 ++++---- .../codin/domain/user/service/UserService.java | 10 +++++----- .../infra/redis/RedisRecoverSyncScheduler.java | 4 ++-- .../inu/codin/codin/infra/redis/RedisService.java | 5 +---- .../codin/codin/infra/redis/SyncScheduler.java | 15 +++++---------- 21 files changed, 50 insertions(+), 59 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/controller/LikeController.java (83%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/dto/LikeRequestDto.java (73%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/entity/LikeEntity.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/entity/LikeType.java (81%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/exception/LikeCreateFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/exception/LikeRemoveFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/repository/LikeRepository.java (84%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/like/service/LikeService.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/scrap/controller/ScrapController.java (89%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/scrap/entity/ScrapEntity.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/scrap/exception/ScrapCreateFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/scrap/exception/ScrapRemoveFailException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/scrap/repository/ScrapRepository.java (84%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/scrap/service/ScrapService.java (93%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java similarity index 83% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 90ceb084..d177f1aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -1,8 +1,8 @@ -package inu.codin.codin.domain.post.domain.like.controller; +package inu.codin.codin.domain.like.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; -import inu.codin.codin.domain.post.domain.like.service.LikeService; +import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.service.LikeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java similarity index 73% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java index 1938cb40..b84b3186 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/dto/LikeRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.post.domain.like.dto; +package inu.codin.codin.domain.like.dto; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.entity.LikeType; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -10,7 +10,7 @@ public class LikeRequestDto { @NotNull - @Schema(description = "좋아요를 반영할 entity 타입(POST, COMMENT, REPLY)", example = "POST") + @Schema(description = "좋아요를 반영할 entity 타입(POST, COMMENT, REPLY, REVIEW)", example = "POST") private LikeType likeType; @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java index aaa3827c..c2615fb2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.like.entity; +package inu.codin.codin.domain.like.entity; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java similarity index 81% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java index 6dff73ee..f9683989 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/entity/LikeType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.like.entity; +package inu.codin.codin.domain.like.entity; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeCreateFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java index fa878d92..a4ed09bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeCreateFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.like.exception; +package inu.codin.codin.domain.like.exception; public class LikeCreateFailException extends RuntimeException{ public LikeCreateFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeRemoveFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java index b6613545..26130191 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/exception/LikeRemoveFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.like.exception; +package inu.codin.codin.domain.like.exception; public class LikeRemoveFailException extends RuntimeException { public LikeRemoveFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java index a67f3873..64ba53da 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.domain.like.repository; +package inu.codin.codin.domain.like.repository; -import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.like.entity.LikeType; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index fdd1a03a..6c08f9c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -1,13 +1,13 @@ -package inu.codin.codin.domain.post.domain.like.service; +package inu.codin.codin.domain.like.service; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.exception.LikeCreateFailException; +import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.like.dto.LikeRequestDto; -import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.like.exception.LikeCreateFailException; -import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; +import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 23823f7c..7d46c19b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -9,9 +9,8 @@ import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index cf17b725..f0aff45b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -5,8 +5,8 @@ import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.like.service.LikeService; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 93070245..e19bfb75 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -6,13 +6,13 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.like.service.LikeService; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; -import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java similarity index 89% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java rename to codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java index 5b45bee4..f64a06fc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.domain.scrap.controller; +package inu.codin.codin.domain.scrap.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java index 9ccfea5a..0e3626e6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/entity/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.scrap.entity; +package inu.codin.codin.domain.scrap.entity; import inu.codin.codin.common.BaseTimeEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/exception/ScrapCreateFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapCreateFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/scrap/exception/ScrapCreateFailException.java index aa942d5e..dda756d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapCreateFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/exception/ScrapCreateFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.scrap.exception; +package inu.codin.codin.domain.scrap.exception; public class ScrapCreateFailException extends RuntimeException { public ScrapCreateFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/exception/ScrapRemoveFailException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapRemoveFailException.java rename to codin-core/src/main/java/inu/codin/codin/domain/scrap/exception/ScrapRemoveFailException.java index c7b280dc..6dab3f59 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/exception/ScrapRemoveFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/exception/ScrapRemoveFailException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.scrap.exception; +package inu.codin.codin.domain.scrap.exception; public class ScrapRemoveFailException extends RuntimeException { public ScrapRemoveFailException(String message) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java index 75fe507f..24bd8660 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.post.domain.scrap.repository; +package inu.codin.codin.domain.scrap.repository; -import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.scrap.entity.ScrapEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java rename to codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index cddff655..95080376 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -1,10 +1,10 @@ -package inu.codin.codin.domain.post.domain.scrap.service; +package inu.codin.codin.domain.scrap.service; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.post.domain.scrap.exception.ScrapCreateFailException; -import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.scrap.exception.ScrapCreateFailException; +import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.RedisHealthChecker; import inu.codin.codin.infra.redis.RedisService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 8559d443..bb60393b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -5,11 +5,11 @@ import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; -import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.repository.LikeRepository; +import inu.codin.codin.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java index 26833f86..81a54f7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java @@ -1,7 +1,7 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.domain.post.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java index 4f9f3d13..a5719da1 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java @@ -1,10 +1,7 @@ package inu.codin.codin.infra.redis; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.like.entity.LikeType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java index 71905579..7653f279 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java @@ -9,25 +9,20 @@ import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.post.domain.like.repository.LikeRepository; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.post.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.scrap.repository.ScrapRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.redis.core.ZSetOperations; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; From 41f8c6917596c2b0d99c2e6203f55027129712e7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 24 Jan 2025 13:38:58 +0900 Subject: [PATCH 0383/1002] =?UTF-8?q?refactor=20:=20Redis=20like,=20scrap,?= =?UTF-8?q?=20hits=20Service=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20domain/red?= =?UTF-8?q?is=20Service=20=EA=B5=AC=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/entity/LikeType.java | 3 +- .../domain/like/service/LikeService.java | 26 +-- .../comment/service/CommentService.java | 4 +- .../domain/hits/{ => entity}/HitsEntity.java | 2 +- .../hits/{ => repository}/HitsRepository.java | 3 +- .../post/domain/hits/service/HitsService.java | 26 +++ .../reply/service/ReplyCommentService.java | 6 +- .../domain/post/service/PostService.java | 20 +- .../domain/scrap/service/ScrapService.java | 23 +-- .../codin/codin/infra/redis/RedisService.java | 178 ------------------ .../infra/redis/{ => config}/RedisConfig.java | 3 +- .../{ => config}/RedisHealthChecker.java | 2 +- .../redis/{ => dto}/RedisProperties.java | 2 +- .../RedisRecoverSyncScheduler.java | 15 +- .../redis/{ => scheduler}/SyncScheduler.java | 22 ++- .../infra/redis/service/RedisHitsService.java | 44 +++++ .../infra/redis/service/RedisLikeService.java | 72 +++++++ .../redis/service/RedisScrapService.java | 55 ++++++ .../infra/redis/service/RedisService.java | 98 ++++++++++ .../codin/infra/redis/RedisConfigTest.java | 1 + 20 files changed, 367 insertions(+), 238 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/{ => entity}/HitsEntity.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/{ => repository}/HitsRepository.java (74%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java rename codin-core/src/main/java/inu/codin/codin/infra/redis/{ => config}/RedisConfig.java (96%) rename codin-core/src/main/java/inu/codin/codin/infra/redis/{ => config}/RedisHealthChecker.java (97%) rename codin-core/src/main/java/inu/codin/codin/infra/redis/{ => dto}/RedisProperties.java (88%) rename codin-core/src/main/java/inu/codin/codin/infra/redis/{ => scheduler}/RedisRecoverSyncScheduler.java (84%) rename codin-core/src/main/java/inu/codin/codin/infra/redis/{ => scheduler}/SyncScheduler.java (91%) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java index f9683989..3c404894 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java @@ -6,7 +6,8 @@ public enum LikeType { POST("게시물"), COMMENT("댓글"), - REPLY("대댓글"); + REPLY("대댓글"), + REVIEW("수강 후기"); private final String description; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 0716cf2e..370e8386 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -10,8 +10,9 @@ import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.infra.redis.RedisHealthChecker; -import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -26,6 +27,7 @@ public class LikeService { private final CommentRepository commentRepository; private final ReplyCommentRepository replyCommentRepository; + private final RedisLikeService redisLikeService; private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; @@ -51,7 +53,7 @@ public String toggleLike(LikeRequestDto likeRequestDto) { public void createLike(LikeType likeType, ObjectId likeId, ObjectId userId){ if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeType.name(), likeId, userId); + redisLikeService.addLike(likeType.name(), likeId, userId); log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); } @@ -72,7 +74,7 @@ public void addLike(LikeEntity like) { ObjectId userId = like.getUserId(); if (redisHealthChecker.isRedisAvailable()) { - redisService.addLike(likeType.name(), likeId, userId); + redisLikeService.addLike(likeType.name(), likeId, userId); log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); } @@ -89,7 +91,7 @@ public void addLike(LikeEntity like) { public void removeLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { - redisService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); + redisLikeService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); log.info("Redis에서 좋아요 삭제 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); } like.delete(); @@ -99,18 +101,18 @@ public void removeLike(LikeEntity like) { public int getLikeCount(LikeType entityType, ObjectId entityId) { if (redisHealthChecker.isRedisAvailable()) { - return redisService.getLikeCount(entityType.name(), entityId); + return redisLikeService.getLikeCount(entityType.name(), entityId); } long count = likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); return (int) Math.max(0, count); } - public void recoverRedisFromDB() { - log.info("Redis 복구 요청 - DB의 좋아요 데이터를 기반으로 복구 시작"); - likeRepository.findAll().forEach(like -> { - redisService.addLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); - log.info("Redis에 좋아요 복구 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); - }); + public boolean isPostLiked(ObjectId postId, ObjectId userId){ + return redisLikeService.isPostLiked(postId, userId); + } + + public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ + return redisLikeService.isCommentLiked(commentId, userId); } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 7d46c19b..32ff5447 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -16,7 +16,7 @@ import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -158,7 +158,7 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { public UserInfo getUserInfoAboutPost(ObjectId commentId) { ObjectId userId = SecurityUtils.getCurrentUserId(); return UserInfo.builder() - .isLike(redisService.isCommentLiked(commentId, userId)) + .isLike(likeService.isCommentLiked(commentId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java index 47e323c1..6f00726f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.hits; +package inu.codin.codin.domain.post.domain.hits.entity; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java similarity index 74% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java index 9cc775f8..cc5efb3f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/HitsRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java @@ -1,5 +1,6 @@ -package inu.codin.codin.domain.post.domain.hits; +package inu.codin.codin.domain.post.domain.hits.repository; +import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java new file mode 100644 index 00000000..e9b406b1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.post.domain.hits.service; + +import inu.codin.codin.infra.redis.service.RedisHitsService; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class HitsService { + + private final RedisHitsService redisHitsService; + + public void addHits(ObjectId postId, ObjectId userId){ + redisHitsService.addHits(postId,userId); + } + + public boolean validateHits(ObjectId postId, ObjectId userId) { + return redisHitsService.validateHits(postId,userId); + } + + public int getHitsCount(ObjectId postId) { + return redisHitsService.getHitsCount(postId); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index f0aff45b..430b3dd7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -16,7 +16,8 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -40,6 +41,7 @@ public class ReplyCommentService { private final LikeService likeService; private final RedisService redisService; + private final RedisLikeService redisLikeService; private final S3Service s3Service; // 대댓글 추가 @@ -139,7 +141,7 @@ public List getRepliesByCommentId(ObjectId commentId) { public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); return CommentResponseDTO.UserInfo.builder() - .isLike(redisService.isReplyLiked(replyId, userId)) + .isLike(redisLikeService.isReplyLiked(replyId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index e19bfb75..79fce4db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; @@ -28,7 +29,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; @@ -52,14 +53,15 @@ public class PostService { private final PostRepository postRepository; private final BestRepository bestRepository; + private final UserRepository userRepository; + private final PollRepository pollRepository; + private final PollVoteRepository pollVoteRepository; private final S3Service s3Service; private final LikeService likeService; private final ScrapService scrapService; + private final HitsService hitsService; private final RedisService redisService; - private final UserRepository userRepository; - private final PollRepository pollRepository; - private final PollVoteRepository pollVoteRepository; public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); @@ -154,7 +156,7 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); int scrapCount = scrapService.getScrapCount(post.get_id()); - int hitsCount = redisService.getHitsCount(post.get_id()); + int hitsCount = hitsService.getHitsCount(post.get_id()); int commentCount = post.getCommentCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -216,8 +218,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - if (redisService.validateHits(post.get_id(), userId)) { - redisService.addHits(post.get_id(), userId); + if (hitsService.validateHits(post.get_id(), userId)) { + hitsService.addHits(post.get_id(), userId); log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); } @@ -263,8 +265,8 @@ public void deletePostImage(String postId, String imageUrl) { public UserInfo getUserInfoAboutPost(ObjectId userId, ObjectId postId){ return UserInfo.builder() - .isLike(redisService.isPostLiked(postId, userId)) - .isScrap(redisService.isPostScraped(postId, userId)) + .isLike(likeService.isPostLiked(postId, userId)) + .isScrap(scrapService.isPostScraped(postId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 95080376..75a4770f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -6,8 +6,9 @@ import inu.codin.codin.domain.scrap.exception.ScrapCreateFailException; import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.infra.redis.RedisHealthChecker; -import inu.codin.codin.infra.redis.RedisService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisScrapService; +import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -21,6 +22,7 @@ public class ScrapService { private final PostRepository postRepository; private final RedisService redisService; + private final RedisScrapService redisScrapService; private final RedisHealthChecker redisHealthChecker; public String toggleScrap(String id) { @@ -63,7 +65,7 @@ private void addScrap(ObjectId postId, ObjectId userId) { } } else { if (redisHealthChecker.isRedisAvailable()) { - redisService.addScrap(postId, userId); + redisScrapService.addScrap(postId, userId); log.info("Redis에 스크랩 추가 - postId: {}, userId: {}", postId, userId); } scrapRepository.save(ScrapEntity.builder() @@ -78,7 +80,7 @@ private void addScrap(ObjectId postId, ObjectId userId) { private void removeScrap(ScrapEntity scrap) { log.info("스크랩 삭제 요청 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); if (redisHealthChecker.isRedisAvailable()) { - redisService.removeScrap(scrap.getPostId(), scrap.getUserId()); + redisScrapService.removeScrap(scrap.getPostId(), scrap.getUserId()); log.info("Redis에서 스크랩 삭제 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); } scrap.delete(); @@ -88,20 +90,13 @@ private void removeScrap(ScrapEntity scrap) { public int getScrapCount(ObjectId postId) { if (redisHealthChecker.isRedisAvailable()) { - return redisService.getScrapCount(postId); + return redisScrapService.getScrapCount(postId); } long count = scrapRepository.countByPostIdAndDeletedAtIsNull(postId); return (int) Math.max(0, count); } - public void recoverRedisFromDB() { - log.info("Redis 복구 요청 - DB의 스크랩 데이터를 기반으로 복구 시작"); - - scrapRepository.findAll().forEach(scrap -> { - redisService.addScrap(scrap.getPostId(), scrap.getUserId()); - log.info("Redis에 스크랩 복구 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); - }); - - log.info("Redis 복구 완료"); + public boolean isPostScraped(ObjectId postId, ObjectId userId){ + return redisScrapService.isPostScraped(postId, userId); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java deleted file mode 100644 index a5719da1..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisService.java +++ /dev/null @@ -1,178 +0,0 @@ -package inu.codin.codin.infra.redis; - - -import inu.codin.codin.domain.like.entity.LikeType; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ZSetOperations; -import org.springframework.stereotype.Service; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Slf4j -public class RedisService { - /** - * Redis 기반 Like/Scrap 관리 Service - * Redis 작업 실패시 DB 기반으로 직접 처리 - * 장애 복구를 대비한 보완 로직 추가 - */ - private final RedisTemplate redisTemplate; - - private static final String LIKE_KEY=":likes:"; - private static final String SCRAP_KEY = "post:scraps:"; - private static final String HITS_KEY = "post:hits:"; - - - //post, comment, reply 구분 - public Set getKeys(String pattern) { - try { - Set keys = redisTemplate.keys(pattern); - if (keys == null || keys.isEmpty()) { - return Set.of(); // keys가 null이거나 빈 경우 빈 Set 반환 - } - return keys.stream() - .filter(key -> key != null && !key.isEmpty()) // key가 null 또는 빈 문자열이 아닌 경우 필터링 - .collect(Collectors.toSet()); - } catch (Exception e) { - log.warn("Redis 연결 중 오류 발생: {}", e.getMessage()); - return Set.of(); // Redis 예외 발생 시 빈 Set 반환 - } - } - - //Like - public void addLike(String entityType, ObjectId entityId, ObjectId userId) { - String redisKey = entityType + LIKE_KEY + entityId; - redisTemplate.opsForSet().add(redisKey, String.valueOf(userId)); - } - - public void removeLike(String entityType, ObjectId entityId, ObjectId userId) { - String redisKey = entityType + LIKE_KEY + entityId; - redisTemplate.opsForSet().remove(redisKey, String.valueOf(userId)); - } - - public int getLikeCount(String entityType, ObjectId entityId) { - String redisKey = entityType + LIKE_KEY + entityId; - Long count = redisTemplate.opsForSet().size(redisKey); - return count != null ? count.intValue() : 0; - } - - public Set getLikedUsers(String entityType, String entityId) { - String redisKey = entityType + LIKE_KEY + entityId; - return redisTemplate.opsForSet().members(redisKey); - } - - public boolean isPostLiked(ObjectId postId, ObjectId userId){ - String redisKey = LikeType.POST + LIKE_KEY + postId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ - String redisKey = LikeType.COMMENT + LIKE_KEY + commentId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - public boolean isReplyLiked(ObjectId replyId, ObjectId userId){ - String redisKey = LikeType.REPLY + LIKE_KEY + replyId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - - //Scrap - public void addScrap(ObjectId postId, ObjectId userId) { - String redisKey = SCRAP_KEY + postId.toString(); - redisTemplate.opsForSet().add(redisKey, userId.toString()); - } - - public void removeScrap(ObjectId postId, ObjectId userId) { - String redisKey = SCRAP_KEY + postId.toString(); - redisTemplate.opsForSet().remove(redisKey, userId.toString()); - } - - public int getScrapCount(ObjectId postId) { - String redisKey = SCRAP_KEY + postId.toString(); - Long scrapCount = redisTemplate.opsForSet().size(redisKey); - return scrapCount != null ? scrapCount.intValue() : 0; - } - - public boolean isPostScraped(ObjectId postId, ObjectId userId){ - String redisKey = SCRAP_KEY + postId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - - //Hits - public void addHits(ObjectId postId, ObjectId userId){ - String redisKey = HITS_KEY + postId.toString(); - redisTemplate.opsForSet().add(redisKey, userId.toString()); - } - - public boolean validateHits(ObjectId postId, ObjectId userId){ - String redisKey = HITS_KEY + postId.toString(); - return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); //없어야 유효성 검증 통과 - } - - public int getHitsCount(ObjectId postId){ - String redisKey = HITS_KEY + postId.toString(); - Long hitsCount = redisTemplate.opsForSet().size(redisKey); - return hitsCount != null ? hitsCount.intValue() : 0; - } - - public Set getHitsUser(ObjectId postId) { - String redisKey = HITS_KEY + postId.toString(); - return redisTemplate.opsForSet().members(redisKey); - } - - // Top N 게시물 조회 - public Map getTopNPosts(int N) { - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - - Map result = new HashMap<>(); - for (int i = 0; i < 24; i++) { - String redisKey = now.minusHours(i).format(formatter); - Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); - if (members != null) { - for (ZSetOperations.TypedTuple member :members){ - String postId = member.getValue(); - Double score = member.getScore(); - result.put(postId, score); - } - } - } - - return result.entrySet().stream() - .sorted((e1, e2) -> { - int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); - if (scoreComparison != 0) { - return scoreComparison; - } - return Integer.compare(getHitsCount(new ObjectId(e2.getKey())), getHitsCount(new ObjectId(e1.getKey()))); - }) - .limit(N).collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue, - (existing, replacement) -> existing, // Merge function (not needed here) - LinkedHashMap::new // Use LinkedHashMap to preserve the order - )); - } - - public void applyBestScore(int score, ObjectId id){ - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - - String redisKey; - for (int i=0; i<24; i++){ - redisKey = now.minusHours(i).format(formatter); - Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, id.toString()); - if (scoreOfBest != null){ - redisTemplate.opsForZSet().incrementScore(redisKey, id.toString(), score); - return; - } - } - redisKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); - redisTemplate.opsForZSet().add(redisKey, id.toString(), score); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java rename to codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java index 5bf8564d..9aee12d0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java @@ -1,5 +1,6 @@ -package inu.codin.codin.infra.redis; +package inu.codin.codin.infra.redis.config; +import inu.codin.codin.infra.redis.dto.RedisProperties; import lombok.RequiredArgsConstructor; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisHealthChecker.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java rename to codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisHealthChecker.java index 30b5ba0a..ae6ab10b 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisHealthChecker.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisHealthChecker.java @@ -1,4 +1,4 @@ -package inu.codin.codin.infra.redis; +package inu.codin.codin.infra.redis.config; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/dto/RedisProperties.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java rename to codin-core/src/main/java/inu/codin/codin/infra/redis/dto/RedisProperties.java index e1fe77ef..b0d5bbb9 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisProperties.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/dto/RedisProperties.java @@ -1,4 +1,4 @@ -package inu.codin.codin.infra.redis; +package inu.codin.codin.infra.redis.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java rename to codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java index 81a54f7f..45509790 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java @@ -1,8 +1,9 @@ -package inu.codin.codin.infra.redis; +package inu.codin.codin.infra.redis.scheduler; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; +import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisScrapService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -16,8 +17,8 @@ public class RedisRecoverSyncScheduler { private final RedisHealthChecker redisHealthChecker; - private final LikeService likeService; - private final ScrapService scrapService; + private final RedisLikeService redisLikeService; + private final RedisScrapService redisScrapService; private Instant lastRecoveryTime = Instant.MIN; // 마지막 복구 시간 @@ -55,8 +56,8 @@ private void recoverRedisData() { } try { log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 복구 작업 시작..."); - likeService.recoverRedisFromDB(); - scrapService.recoverRedisFromDB(); + redisLikeService.recoverRedisFromDB(); + redisScrapService.recoverRedisFromDB(); lastRecoveryTime = Instant.now(); log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 데이터 복구 완료."); } catch (Exception e) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java rename to codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 7653f279..c253e518 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -1,11 +1,11 @@ -package inu.codin.codin.infra.redis; +package inu.codin.codin.infra.redis.scheduler; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.hits.HitsEntity; -import inu.codin.codin.domain.post.domain.hits.HitsRepository; +import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; +import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; @@ -16,6 +16,10 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisHitsService; +import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -42,6 +46,8 @@ public class SyncScheduler { private final BestRepository bestRepository; private final RedisService redisService; + private final RedisLikeService redisLikeService; + private final RedisHitsService redisHitsService; private final RedisHealthChecker redisHealthChecker; @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 @@ -68,7 +74,7 @@ private void syncEntityLikes(String entityType, MongoRepository for (String redisKey : redisKeys) { String likeTypeId = redisKey.replace(entityType + ":likes:", ""); - Set likedUsers = redisService.getLikedUsers(entityType, likeTypeId); + Set likedUsers = redisLikeService.getLikedUsers(entityType, likeTypeId); ObjectId likeId = new ObjectId(likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 @@ -130,7 +136,7 @@ public void syncPostScraps() { for (String redisKey : redisKeys) { String postId = redisKey.replace("post:scraps:", ""); - Set redisScrappedUsers = redisService.getLikedUsers("post", postId); + Set redisScrappedUsers = redisLikeService.getLikedUsers("post", postId); ObjectId id = new ObjectId(postId); // MongoDB의 스크랩 데이터 가져오기 @@ -193,8 +199,8 @@ public void synPostHits(){ List dbHits = hitsRepository.findAllByPostId(postId); dbHits.forEach(hitsEntity -> { - if (redisService.validateHits(postId, hitsEntity.getUserId())) { - redisService.addHits(postId, hitsEntity.getUserId()); + if (redisHitsService.validateHits(postId, hitsEntity.getUserId())) { + redisHitsService.addHits(postId, hitsEntity.getUserId()); } }); }); @@ -210,7 +216,7 @@ public Map> fetchAllPostHits(){ }, key -> { String postId = key.replace("post:hits:", ""); - return redisService.getHitsUser(new ObjectId(postId)); + return redisHitsService.getHitsUser(new ObjectId(postId)); } ) ); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java new file mode 100644 index 00000000..67b9e5f8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java @@ -0,0 +1,44 @@ +package inu.codin.codin.infra.redis.service; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RedisHitsService { + /** + * Redis 기반 Hits 관리 Service + */ + private final RedisTemplate redisTemplate; + + private static final String HITS_KEY = "post:hits:"; + + //Hits + public void addHits(ObjectId postId, ObjectId userId){ + String redisKey = HITS_KEY + postId.toString(); + redisTemplate.opsForSet().add(redisKey, userId.toString()); + } + + public boolean validateHits(ObjectId postId, ObjectId userId){ + String redisKey = HITS_KEY + postId.toString(); + return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); //없어야 유효성 검증 통과 + } + + public int getHitsCount(ObjectId postId){ + String redisKey = HITS_KEY + postId.toString(); + Long hitsCount = redisTemplate.opsForSet().size(redisKey); + return hitsCount != null ? hitsCount.intValue() : 0; + } + + public Set getHitsUser(ObjectId postId) { + String redisKey = HITS_KEY + postId.toString(); + return redisTemplate.opsForSet().members(redisKey); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java new file mode 100644 index 00000000..401989a9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java @@ -0,0 +1,72 @@ +package inu.codin.codin.infra.redis.service; + + +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.repository.LikeRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RedisLikeService { + /** + * Redis 기반 Like 관리 Service + */ + private final RedisTemplate redisTemplate; + private final LikeRepository likeRepository; + + private static final String LIKE_KEY=":likes:"; + + //Like + public void addLike(String entityType, ObjectId entityId, ObjectId userId) { + String redisKey = entityType + LIKE_KEY + entityId; + redisTemplate.opsForSet().add(redisKey, String.valueOf(userId)); + } + + public void removeLike(String entityType, ObjectId entityId, ObjectId userId) { + String redisKey = entityType + LIKE_KEY + entityId; + redisTemplate.opsForSet().remove(redisKey, String.valueOf(userId)); + } + + public int getLikeCount(String entityType, ObjectId entityId) { + String redisKey = entityType + LIKE_KEY + entityId; + Long count = redisTemplate.opsForSet().size(redisKey); + return count != null ? count.intValue() : 0; + } + + public Set getLikedUsers(String entityType, String entityId) { + String redisKey = entityType + LIKE_KEY + entityId; + return redisTemplate.opsForSet().members(redisKey); + } + + public boolean isPostLiked(ObjectId postId, ObjectId userId){ + String redisKey = LikeType.POST + LIKE_KEY + postId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ + String redisKey = LikeType.COMMENT + LIKE_KEY + commentId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + public boolean isReplyLiked(ObjectId replyId, ObjectId userId){ + String redisKey = LikeType.REPLY + LIKE_KEY + replyId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + + public void recoverRedisFromDB() { + log.info("Redis 복구 요청 - DB의 좋아요 데이터를 기반으로 복구 시작"); + likeRepository.findAll().forEach(like -> { + addLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); + log.info("Redis에 좋아요 복구 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); + }); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java new file mode 100644 index 00000000..5254541c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java @@ -0,0 +1,55 @@ +package inu.codin.codin.infra.redis.service; + + +import inu.codin.codin.domain.scrap.repository.ScrapRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RedisScrapService { + /** + * Redis 기반 Scrap 관리 Service + **/ + private final RedisTemplate redisTemplate; + private final ScrapRepository scrapRepository; + + private static final String SCRAP_KEY = "post:scraps:"; + + //Scrap + public void addScrap(ObjectId postId, ObjectId userId) { + String redisKey = SCRAP_KEY + postId.toString(); + redisTemplate.opsForSet().add(redisKey, userId.toString()); + } + + public void removeScrap(ObjectId postId, ObjectId userId) { + String redisKey = SCRAP_KEY + postId.toString(); + redisTemplate.opsForSet().remove(redisKey, userId.toString()); + } + + public int getScrapCount(ObjectId postId) { + String redisKey = SCRAP_KEY + postId.toString(); + Long scrapCount = redisTemplate.opsForSet().size(redisKey); + return scrapCount != null ? scrapCount.intValue() : 0; + } + + public boolean isPostScraped(ObjectId postId, ObjectId userId){ + String redisKey = SCRAP_KEY + postId.toString(); + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); + } + + public void recoverRedisFromDB() { + log.info("Redis 복구 요청 - DB의 스크랩 데이터를 기반으로 복구 시작"); + + scrapRepository.findAll().forEach(scrap -> { + addScrap(scrap.getPostId(), scrap.getUserId()); + log.info("Redis에 스크랩 복구 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); + }); + + log.info("Redis 복구 완료"); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java new file mode 100644 index 00000000..036d1bd6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java @@ -0,0 +1,98 @@ +package inu.codin.codin.infra.redis.service; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RedisService { + /** + * Redis 기반 관리 Service + * Redis 작업 실패시 DB 기반으로 직접 처리 + * 장애 복구를 대비한 보완 로직 추가 + */ + private final RedisTemplate redisTemplate; + private final RedisHitsService redisHitsService; + + + //post, comment, reply 구분 + public Set getKeys(String pattern) { + try { + Set keys = redisTemplate.keys(pattern); + if (keys == null || keys.isEmpty()) { + return Set.of(); // keys가 null이거나 빈 경우 빈 Set 반환 + } + return keys.stream() + .filter(key -> key != null && !key.isEmpty()) // key가 null 또는 빈 문자열이 아닌 경우 필터링 + .collect(Collectors.toSet()); + } catch (Exception e) { + log.warn("Redis 연결 중 오류 발생: {}", e.getMessage()); + return Set.of(); // Redis 예외 발생 시 빈 Set 반환 + } + } + + // Top N 게시물 조회 + public Map getTopNPosts(int N) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + Map result = new HashMap<>(); + for (int i = 0; i < 24; i++) { + String redisKey = now.minusHours(i).format(formatter); + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); + if (members != null) { + for (ZSetOperations.TypedTuple member :members){ + String postId = member.getValue(); + Double score = member.getScore(); + result.put(postId, score); + } + } + } + + return result.entrySet().stream() + .sorted((e1, e2) -> { + int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); + if (scoreComparison != 0) { + return scoreComparison; + } + return Integer.compare(redisHitsService.getHitsCount(new ObjectId(e2.getKey())), redisHitsService.getHitsCount(new ObjectId(e1.getKey()))); + }) + .limit(N).collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (existing, replacement) -> existing, // Merge function (not needed here) + LinkedHashMap::new // Use LinkedHashMap to preserve the order + )); + } + + public void applyBestScore(int score, ObjectId id){ + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + String redisKey; + for (int i=0; i<24; i++){ + redisKey = now.minusHours(i).format(formatter); + Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, id.toString()); + if (scoreOfBest != null){ + redisTemplate.opsForZSet().incrementScore(redisKey, id.toString(), score); + return; + } + } + redisKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); + redisTemplate.opsForZSet().add(redisKey, id.toString(), score); + } +} diff --git a/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java b/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java index 6fff996e..dc3d00bb 100644 --- a/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java +++ b/codin-core/src/test/java/inu/codin/codin/infra/redis/RedisConfigTest.java @@ -1,5 +1,6 @@ package inu.codin.codin.infra.redis; +import inu.codin.codin.infra.redis.dto.RedisProperties; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; From 281e7997eeb51b6f9cae72430056d4a52bf14622 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 24 Jan 2025 13:40:24 +0900 Subject: [PATCH 0384/1002] =?UTF-8?q?docs=20:=20Unused=20import=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/service/RedisLikeService.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java index 401989a9..725d5672 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java @@ -7,13 +7,9 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Set; @Service @RequiredArgsConstructor From 43f859298fbe6475be8a1432cb276f0fb3c43f85 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 00:24:57 +0900 Subject: [PATCH 0385/1002] =?UTF-8?q?refactor=20:=20=EA=B0=95=EC=9D=98?= =?UTF-8?q?=EC=8B=A4=20=ED=98=84=ED=99=A9=20=EA=B8=B0=EB=8A=A5=20=EB=94=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20LectureRoomEntity?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/controller/LectureController.java | 22 ++----- .../controller/LectureRoomController.java | 31 ++++++++++ .../room}/dto/EmptyRoomResponseDto.java | 15 +++-- .../domain/room/entity/LectureRoomEntity.java | 24 +++++++ .../room/service/LectureRoomService.java | 62 +++++++++++++++++++ .../domain/lecture/entity/LectureEntity.java | 10 --- 6 files changed, 132 insertions(+), 32 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java rename codin-core/src/main/java/inu/codin/codin/domain/lecture/{ => domain/room}/dto/EmptyRoomResponseDto.java (50%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/entity/LectureRoomEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index f319c35c..d3289c66 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -6,8 +6,6 @@ import inu.codin.codin.domain.lecture.service.LectureService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -23,14 +21,13 @@ public class LectureController { @Operation( summary = "학과명 및 과목/교수 정렬 페이지", description = "학과명과 과목/교수 라디오 토클을 통해 정렬한 리스트 반환
"+ - "department : COMPUTER_SCI, INFO_COMM, EMBEDDED, OTHER(교양 및 프로젝트 실습 강의)
"+ + "department : COMPUTER_SCI, INFO_COMM, EMBEDDED, OTHER(공통)
"+ "option : LEC(과목명) , PROF(교수명)" ) @GetMapping("/list") public ResponseEntity> sortListOfLectures(@RequestParam("department") Department department, @RequestParam("option") Option option, @RequestParam("page") int page){ - return ResponseEntity.ok() .body(new SingleResponse<>(200, department.getDescription()+" 강의들 "+option.getDescription()+"순으로 정렬 반환", @@ -48,21 +45,14 @@ public ResponseEntity> searchLectures(@RequestParam("keyword") .body(new SingleResponse<>(200, keyword+" 의 검색 결과 반환", lectureService.searchLectures(keyword, page))); } - @Operation(summary = "강의실 정보 반환") - @GetMapping("/{lectureId}") - public ResponseEntity getLectureDetails(@PathVariable("lectureId") String lectureId){ - lectureService.getLectureDetails(lectureId); - return null; - } - @Operation( - summary = "오늘의 강의 현황", - description = "당일의 요일에 따라 층마다 호실에서의 수업 내용 반환" + summary = "강의 별점 정보 반환", + description = "강의후기 > 상세보기 눌렀을 때 뜨는 강의 정보 반환" ) - @GetMapping("/rooms/empty") - public ResponseEntity statusOfEmptyRoom(@RequestParam("floor") @Max(5) @Min(1) int floor){ + @GetMapping("/{lectureId}") + public ResponseEntity> getLectureDetails(@PathVariable("lectureId") String lectureId){ return ResponseEntity.ok() - .body(new SingleResponse<>(200, floor + "층의 강의실 현황 반환", lectureService.statusOfEmptyRoom(floor))); + .body(new SingleResponse<>(200, "강의 별점 정보 반환", lectureService.getLectureDetails(lectureId))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java new file mode 100644 index 00000000..057414dc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.lecture.domain.room.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.lecture.domain.room.service.LectureRoomService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/rooms") +@RestController +@RequiredArgsConstructor +public class LectureRoomController { + + private final LectureRoomService lectureRoomService; + + @Operation( + summary = "오늘의 강의 현황", + description = "당일의 요일에 따라 층마다 호실에서의 수업 내용 반환" + ) + @GetMapping("/empty") + public ResponseEntity statusOfEmptyRoom(@RequestParam("floor") @Max(5) @Min(1) int floor){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, floor + "층의 강의실 현황 반환", lectureRoomService.statusOfEmptyRoom(floor))); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java similarity index 50% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java index 9285b3c0..12fa2e04 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/EmptyRoomResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.domain.room.dto; -import inu.codin.codin.domain.lecture.entity.LectureEntity; +import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -9,22 +9,25 @@ @Setter public class EmptyRoomResponseDto { private String lectureNm; + private String professor; private int roomNum; private String startTime; private String endTime; @Builder - public EmptyRoomResponseDto(String lectureNm, int roomNum, String startTime, String endTime) { + public EmptyRoomResponseDto(String lectureNm, String professor, int roomNum, String startTime, String endTime) { this.lectureNm = lectureNm; + this.professor = professor; this.roomNum = roomNum; this.startTime = startTime; this.endTime = endTime; } - public static EmptyRoomResponseDto of(LectureEntity lectureEntity, String time) { + public static EmptyRoomResponseDto of(LectureRoomEntity roomEntity, String time) { return EmptyRoomResponseDto.builder() - .lectureNm(lectureEntity.getLectureNm()) - .roomNum(lectureEntity.getRoomNum()) + .lectureNm(roomEntity.getLectureNm()) + .professor(roomEntity.getProfessor()) + .roomNum(roomEntity.getRoomNum()) .startTime(time.split("/")[0]) .endTime(time.split("/")[1]) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/entity/LectureRoomEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/entity/LectureRoomEntity.java new file mode 100644 index 00000000..07af5566 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/entity/LectureRoomEntity.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.lecture.domain.room.entity; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.DayOfWeek; +import java.util.List; +import java.util.Map; + +@Document(collection = "lecture_rooms") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class LectureRoomEntity { + private ObjectId _id; + private String lectureNm; + private String professor; + private int roomNum; + @Field("dayTime") + private Map> dayTime; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java new file mode 100644 index 00000000..741ba3e7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java @@ -0,0 +1,62 @@ +package inu.codin.codin.domain.lecture.domain.room.service; + +import inu.codin.codin.domain.lecture.domain.room.dto.EmptyRoomResponseDto; +import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; +import inu.codin.codin.domain.lecture.repository.LectureRepositoryCustomImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.stereotype.Service; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class LectureRoomService { + + private final MongoTemplate mongoTemplate; + + public List getLecturesByFloor(int floor) { + + AggregationOperation match = Aggregation.match( + Criteria.where("roomNum").ne("") + ); + String query = + "{ $match: {" + + "$expr: {"+ + "$and: ["+ + "{ $ne: [ '$roomNum', ''] },"+ + "{ $eq: [{ $floor: { $divide: [{ $toInt: '$roomNum' }, 100] } }, "+ floor+"] }]}}}"; + + TypedAggregation aggregation = Aggregation.newAggregation( + LectureRoomEntity.class, + match, + new LectureRepositoryCustomImpl(query) + ); + // Execute aggregation + AggregationResults results = mongoTemplate.aggregate(aggregation, LectureRoomEntity.class); + return results.getMappedResults(); + } + + + public Map> statusOfEmptyRoom(int floor) { + LocalDateTime now = LocalDateTime.now(); + DayOfWeek today = now.getDayOfWeek(); + List roomEntities = getLecturesByFloor(floor); + return roomEntities.stream() + .filter(lecture -> lecture.getDayTime().containsKey(today)) + .flatMap(lecture -> lecture.getDayTime().get(today).stream() + .map(time -> EmptyRoomResponseDto.of(lecture, time))) + .collect(Collectors.groupingBy( + EmptyRoomResponseDto::getRoomNum + )); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index fe258cb2..f18885db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -1,17 +1,11 @@ package inu.codin.codin.domain.lecture.entity; -import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.common.Department; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.data.mongodb.core.mapping.Field; - -import java.time.DayOfWeek; -import java.util.List; -import java.util.Map; @Document(collection = "lectures") @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -20,13 +14,9 @@ public class LectureEntity { private ObjectId _id; private String lectureNm; - private String lectureCode; private String professor; private Department department; //OTHERS : 교양 private int grade; //0 : 전학년 - private int roomNum; - @Field("dayTime") - private Map> dayTime; // The dayTime map structure } From 937ca6848319978fd66fdda8a51cad622724dfac Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 22:17:20 +0900 Subject: [PATCH 0386/1002] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EA=B0=95?= =?UTF-8?q?=EC=9D=98=EC=9D=98=20=EC=9E=91=EC=84=B1=EB=90=9C=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=EB=93=A4=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 42 +++++++++++ .../review/dto/CreateReviewRequestDto.java | 20 ++++++ .../review/dto/ReviewListResposneDto.java | 44 ++++++++++++ .../domain/review/dto/ReviewPageResponse.java | 31 +++++++++ .../domain/review/entity/ReviewEntity.java | 48 +++++++++++++ .../review/repository/ReviewRepository.java | 17 +++++ .../domain/review/service/ReviewService.java | 69 +++++++++++++++++++ .../lecture/dto/LectureDetailResponseDto.java | 19 +++++ .../redis/service/RedisReviewService.java | 20 ++++++ 9 files changed, 310 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java new file mode 100644 index 00000000..abba54b8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -0,0 +1,42 @@ +package inu.codin.codin.domain.lecture.domain.review.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; +import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/reviews") +@RequiredArgsConstructor +@Tag(name = "Review API", description = "수강 후기 API") +public class ReviewController { + + private final ReviewService reviewService; + + @Operation( + summary = "수강 후기 작성" + ) + @PostMapping("/{lectureId}") + public ResponseEntity> createReview(@PathVariable("lectureId") String lectureId, + @RequestBody @Valid CreateReviewRequestDto createReviewRequestDto){ + reviewService.createReview(lectureId, createReviewRequestDto); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "수강 후기 작성 완료", null)); + } + + @Operation( + summary = "해당 강의의 수강 후기 반환" + ) + @GetMapping("/{lectureId}") + public ResponseEntity> getListOfReviews(@PathVariable("lectureId") String lectureId, + @RequestParam("page") int page){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "수강 후기 리스트 반환", reviewService.getListOfReviews(lectureId, page))); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java new file mode 100644 index 00000000..d27a5313 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.lecture.domain.review.dto; + +import jakarta.validation.constraints.*; +import lombok.Getter; + +@Getter +public class CreateReviewRequestDto { + + @NotBlank + private String title; + + @NotBlank + private String content; + + @NotNull + @Digits(integer = 1, fraction = 2) + private double starRating; + + private String semester; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java new file mode 100644 index 00000000..c41e24c4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java @@ -0,0 +1,44 @@ +package inu.codin.codin.domain.lecture.domain.review.dto; + + +import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ReviewListResposneDto { + private String _id; + private String lectureId; + private String userId; + private String title; + private String content; + private double starRating; + private int likes; + + private boolean isLiked; + + @Builder + public ReviewListResposneDto(String _id, String lectureId, String userId, String title, String content, double starRating, int likes, boolean isLiked) { + this._id = _id; + this.lectureId = lectureId; + this.userId = userId; + this.title = title; + this.content = content; + this.starRating = starRating; + this.likes = likes; + this.isLiked = isLiked; + } + + public static ReviewListResposneDto of(ReviewEntity reviewEntity, boolean isLiked, int likes){ + return ReviewListResposneDto.builder() + ._id(reviewEntity.get_id().toString()) + .lectureId(reviewEntity.getLectureId().toString()) + .userId(reviewEntity.getUserId().toString()) + .title(reviewEntity.getTitle()) + .content(reviewEntity.getContent()) + .starRating(reviewEntity.getStarRating()) + .likes(likes) + .isLiked(isLiked) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java new file mode 100644 index 00000000..95671318 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.lecture.domain.review.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReviewPageResponse { + + private List contents = new ArrayList<>(); + private long lastPage; + private long nextPage; + + public ReviewPageResponse(List contents, long lastPage, long nextPage) { + this.contents = contents; + this.lastPage = lastPage; + this.nextPage = nextPage; + } + + public static ReviewPageResponse of(List reviewPaging, long totalElements, long nextPage){ + return ReviewPageResponse.nextPagingHasNext(reviewPaging, totalElements, nextPage); + } + + public static ReviewPageResponse nextPagingHasNext(List reviews, long totalElements, long nextPage){ + return new ReviewPageResponse(reviews, totalElements, nextPage); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java new file mode 100644 index 00000000..32fa30a2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -0,0 +1,48 @@ +package inu.codin.codin.domain.lecture.domain.review.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "reviews") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class ReviewEntity extends BaseTimeEntity { + private ObjectId _id; + private ObjectId lectureId; + private ObjectId userId; + private String title; + private String content; + private double starRating; + private int likes; + private String semester; + + @Builder + public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String title, String content, double starRating, int likes, String semester) { + this._id = _id; + this.lectureId = lectureId; + this.userId = userId; + this.title = title; + this.content = content; + this.starRating = starRating; + this.likes = likes; + this.semester = semester; + } + + public static ReviewEntity of(CreateReviewRequestDto createReviewRequestDto, ObjectId lectureId, ObjectId userId){ + return ReviewEntity.builder() + .title(createReviewRequestDto.getTitle()) + .content(createReviewRequestDto.getContent()) + .starRating(createReviewRequestDto.getStarRating()) + .lectureId(lectureId) + .userId(userId) + .semester(createReviewRequestDto.getSemester()) + .likes(0) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java new file mode 100644 index 00000000..66d90c8a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.lecture.domain.review.repository; + +import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ReviewRepository extends MongoRepository { + Page findAllByLectureId(ObjectId lectureId, PageRequest pageRequest); + + Optional findByLectureIdAndUserId(ObjectId lectureId, ObjectId userId); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java new file mode 100644 index 00000000..ec5553e1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -0,0 +1,69 @@ +package inu.codin.codin.domain.lecture.domain.review.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.ReviewListResposneDto; +import inu.codin.codin.domain.lecture.domain.review.dto.ReviewPageResponse; +import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import inu.codin.codin.domain.lecture.domain.review.exception.ReviewExistenceException; +import inu.codin.codin.domain.lecture.domain.review.exception.WrongRatingException; +import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisReviewService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ReviewService { + + private final ReviewRepository reviewRepository; + + private final LikeService likeService; + private final RedisReviewService redisReviewService; + private final RedisHealthChecker redisHealthChecker; + + public void createReview(String lectureId, CreateReviewRequestDto createReviewRequestDto) { + if (createReviewRequestDto.getStarRating() > 5.0 || createReviewRequestDto.getStarRating() < 0.25){ + log.warn("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요"); + throw new WrongRatingException("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요."); + } + ObjectId userId = SecurityUtils.getCurrentUserId(); + Optional review = reviewRepository.findByLectureIdAndUserId(new ObjectId(lectureId), userId); + if (review.isPresent()) { + log.error("이미 유저가 작성한 후기가 존재합니다. userId: {}, lectureId: {}", userId, lectureId); + throw new ReviewExistenceException("이미 유저가 작성한 후기가 존재합니다."); + } + + ReviewEntity newReview = ReviewEntity.of(createReviewRequestDto, new ObjectId(lectureId), userId); + reviewRepository.save(newReview); + if (redisHealthChecker.isRedisAvailable()) { + redisReviewService.addReview(lectureId, createReviewRequestDto.getStarRating(), userId); + } + } + + public Object getListOfReviews(String lectureId, int page) { + PageRequest pageRequest = PageRequest.of(page, 10, Sort.by("created_at").descending()); + Page reviewPage = reviewRepository.findAllByLectureId(new ObjectId(lectureId), pageRequest); + + ObjectId userId = SecurityUtils.getCurrentUserId(); + return ReviewPageResponse.of(reviewPage.stream() + .map(review -> ReviewListResposneDto.of(review, + likeService.isReviewLiked(review.get_id(), userId), + likeService.getLikeCount(LikeType.REVIEW, review.get_id()))).toList(), + reviewPage.getTotalPages() -1, + reviewPage.hasNext()? reviewPage.getPageable().getPageNumber() + 1: -1); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java new file mode 100644 index 00000000..f398eb85 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.lecture.dto; + +import inu.codin.codin.domain.lecture.entity.LectureEntity; + +public class LectureDetailResponseDto extends LectureListResponseDto{ + + public LectureDetailResponseDto(String _id, String lectureNm, String professor, int starRating, int participants) { + super(_id, lectureNm, professor, starRating, participants); + } + + public static LectureDetailResponseDto of(LectureEntity lectureEntity){ + return new LectureDetailResponseDto( + lectureEntity.get_id().toString(), + lectureEntity.getLectureNm(), + lectureEntity.getProfessor(), + 0,0) + ; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java new file mode 100644 index 00000000..32371f7a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java @@ -0,0 +1,20 @@ +package inu.codin.codin.infra.redis.service; + +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RedisReviewService { + + private static final String REVIEW_KEY = "review:lectures:"; + private final RedisTemplate redisTemplate; + + public void addReview(String lectureCode, double starRating, ObjectId userId){ + String redisKey = REVIEW_KEY + lectureCode; + redisTemplate.opsForZSet().add(redisKey, String.valueOf(userId), starRating); + } + +} From f867bbbd4b94267356c5235fbdf574ecedee8f05 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 22:17:32 +0900 Subject: [PATCH 0387/1002] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EC=A4=91=EB=B3=B5=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8F=89=EC=A0=90=20=EB=B2=94=EC=9C=84=EC=97=90=20?= =?UTF-8?q?=EA=B4=80=ED=95=9C=20=EC=98=A4=EB=A5=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/review/exception/ReviewExistenceException.java | 7 +++++++ .../domain/review/exception/WrongRatingException.java | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/ReviewExistenceException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/WrongRatingException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/ReviewExistenceException.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/ReviewExistenceException.java new file mode 100644 index 00000000..b2f98ccc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/ReviewExistenceException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.lecture.domain.review.exception; + +public class ReviewExistenceException extends RuntimeException{ + public ReviewExistenceException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/WrongRatingException.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/WrongRatingException.java new file mode 100644 index 00000000..01fdb6ac --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/exception/WrongRatingException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.lecture.domain.review.exception; + +public class WrongRatingException extends RuntimeException{ + public WrongRatingException(String message){ + super(message); + } +} From 46ed4c0b1d19353b6ea2ed78cc0363ba74aae345 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 22:17:59 +0900 Subject: [PATCH 0388/1002] =?UTF-8?q?feat=20:=20=EC=88=98=EA=B0=95=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/service/LikeService.java | 14 ++++++++++++-- .../reply/service/ReplyCommentService.java | 3 +-- .../infra/redis/service/RedisLikeService.java | 18 ++++++++---------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 370e8386..e2ea7a7e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -108,11 +108,20 @@ public int getLikeCount(LikeType entityType, ObjectId entityId) { } public boolean isPostLiked(ObjectId postId, ObjectId userId){ - return redisLikeService.isPostLiked(postId, userId); + return redisLikeService.isLiked(LikeType.POST, postId, userId); } public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ - return redisLikeService.isCommentLiked(commentId, userId); + return redisLikeService.isLiked(LikeType.COMMENT, commentId, userId); + } + + public boolean isReplyLiked(ObjectId replyId, ObjectId userId) { + return redisLikeService.isLiked(LikeType.COMMENT, replyId, userId); + + } + + public boolean isReviewLiked(ObjectId reviewId, ObjectId userId){ + return redisLikeService.isLiked(LikeType.REVIEW, reviewId, userId); } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ @@ -128,4 +137,5 @@ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ } } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 430b3dd7..0ceed080 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -41,7 +41,6 @@ public class ReplyCommentService { private final LikeService likeService; private final RedisService redisService; - private final RedisLikeService redisLikeService; private final S3Service s3Service; // 대댓글 추가 @@ -141,7 +140,7 @@ public List getRepliesByCommentId(ObjectId commentId) { public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); return CommentResponseDTO.UserInfo.builder() - .isLike(redisLikeService.isReplyLiked(replyId, userId)) + .isLike(likeService.isReplyLiked(replyId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java index 725d5672..a3514e12 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java @@ -45,16 +45,14 @@ public Set getLikedUsers(String entityType, String entityId) { return redisTemplate.opsForSet().members(redisKey); } - public boolean isPostLiked(ObjectId postId, ObjectId userId){ - String redisKey = LikeType.POST + LIKE_KEY + postId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ - String redisKey = LikeType.COMMENT + LIKE_KEY + commentId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - public boolean isReplyLiked(ObjectId replyId, ObjectId userId){ - String redisKey = LikeType.REPLY + LIKE_KEY + replyId.toString(); + public boolean isLiked(LikeType likeType, ObjectId id, ObjectId userId){ + String redisKey = ""; + switch(likeType){ + case POST -> redisKey = LikeType.POST + LIKE_KEY + id.toString(); + case COMMENT -> redisKey = LikeType.COMMENT + LIKE_KEY + id.toString(); + case REPLY -> redisKey = LikeType.REPLY + LIKE_KEY + id.toString(); + case REVIEW -> redisKey = LikeType.REVIEW + LIKE_KEY + id.toString(); + } return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); } From fb4ad8b969e11c5194ae620e1f69fa4d414360b7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 23:37:19 +0900 Subject: [PATCH 0389/1002] =?UTF-8?q?perf=20:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=EB=93=A4=EC=9D=98=20=ED=8F=89=EC=A0=90=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8=ED=95=98=EC=97=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/review/service/ReviewService.java | 2 - .../codin/domain/lecture/dto/Emotion.java | 21 +++++ .../lecture/dto/LectureDetailResponseDto.java | 15 +++- .../lecture/dto/LectureListResponseDto.java | 16 ++-- .../lecture/service/LectureService.java | 76 +++++-------------- .../redis/service/RedisReviewService.java | 39 +++++++++- 6 files changed, 93 insertions(+), 76 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index ec5553e1..974e722a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -12,7 +12,6 @@ import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisReviewService; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -20,7 +19,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; import java.util.Optional; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java new file mode 100644 index 00000000..c73fad82 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java @@ -0,0 +1,21 @@ +package inu.codin.codin.domain.lecture.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class Emotion { + + private double hard; + private double ok; + private double best; + + @Builder + public Emotion(double hard, double ok, double best) { + this.hard = hard; + this.ok = ok; + this.best = best; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java index f398eb85..7b0b59a1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java @@ -1,19 +1,26 @@ package inu.codin.codin.domain.lecture.dto; import inu.codin.codin.domain.lecture.entity.LectureEntity; +import lombok.Getter; +@Getter public class LectureDetailResponseDto extends LectureListResponseDto{ - public LectureDetailResponseDto(String _id, String lectureNm, String professor, int starRating, int participants) { + private Emotion emotion; + + public LectureDetailResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, Emotion emotion) { super(_id, lectureNm, professor, starRating, participants); + this.emotion = emotion; } - public static LectureDetailResponseDto of(LectureEntity lectureEntity){ + public static LectureDetailResponseDto of(LectureEntity lectureEntity, double ave, Emotion emotion, long participants){ return new LectureDetailResponseDto( lectureEntity.get_id().toString(), lectureEntity.getLectureNm(), lectureEntity.getProfessor(), - 0,0) - ; + ave, //평균 평점 + participants, //참여 인원 수 + emotion //평점 당 인원 평균 + ); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java index 76681963..646fff05 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java @@ -4,7 +4,6 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; -import org.bson.types.ObjectId; @Setter @Getter @@ -12,30 +11,27 @@ public class LectureListResponseDto { private String _id; private String lectureNm; - private String lectureCode; private String professor; - private int starRating; - private int participants; + private double starRating; + private long participants; @Builder - public LectureListResponseDto(String _id, String lectureNm, String lectureCode, String professor, int starRating, int participants) { + public LectureListResponseDto(String _id, String lectureNm, String professor, double starRating, long participants) { this._id = _id; this.lectureNm = lectureNm; - this.lectureCode = lectureCode; this.professor = professor; this.starRating = starRating; this.participants = participants; } - public static LectureListResponseDto of(LectureEntity lectureEntity){ + public static LectureListResponseDto of(LectureEntity lectureEntity, double starRating, long participants){ return LectureListResponseDto.builder() ._id(lectureEntity.get_id().toString()) .lectureNm(lectureEntity.getLectureNm()) - .lectureCode(lectureEntity.getLectureCode()) .professor(lectureEntity.getProfessor()) -// .starRating() -// .participants() + .starRating(starRating) + .participants(participants) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 04434eca..d370c471 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -2,86 +2,43 @@ import inu.codin.codin.common.Department; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.lecture.dto.EmptyRoomResponseDto; -import inu.codin.codin.domain.lecture.dto.LectureListResponseDto; -import inu.codin.codin.domain.lecture.dto.LecturePageResponse; -import inu.codin.codin.domain.lecture.dto.Option; +import inu.codin.codin.domain.lecture.dto.*; import inu.codin.codin.domain.lecture.entity.LectureEntity; import inu.codin.codin.domain.lecture.exception.WrongInputException; import inu.codin.codin.domain.lecture.repository.LectureRepository; -import inu.codin.codin.domain.lecture.repository.LectureRepositoryCustomImpl; +import inu.codin.codin.infra.redis.service.RedisReviewService; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.aggregation.Aggregation; -import org.springframework.data.mongodb.core.aggregation.AggregationOperation; -import org.springframework.data.mongodb.core.aggregation.AggregationResults; -import org.springframework.data.mongodb.core.aggregation.TypedAggregation; -import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Service; -import java.time.DayOfWeek; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor public class LectureService { private final LectureRepository lectureRepository; - private final MongoTemplate mongoTemplate; - - public List getLecturesByFloor(int floor) { - - AggregationOperation match = Aggregation.match( - Criteria.where("roomNum").ne("") - ); - String query = - "{ $match: {" + - "$expr: {"+ - "$and: ["+ - "{ $ne: [ '$roomNum', ''] },"+ - "{ $eq: [{ $floor: { $divide: [{ $toInt: '$roomNum' }, 100] } }, "+ floor+"] }]}}}"; - - TypedAggregation aggregation = Aggregation.newAggregation( - LectureEntity.class, - match, - new LectureRepositoryCustomImpl(query) - ); - // Execute aggregation - AggregationResults results = mongoTemplate.aggregate(aggregation, LectureEntity.class); - return results.getMappedResults(); - } + private final RedisReviewService redisReviewService; - public void getLectureDetails(String lectureId) { + public LectureDetailResponseDto getLectureDetails(String lectureId) { LectureEntity lectureEntity = lectureRepository.findById(new ObjectId(lectureId)) .orElseThrow(() -> new NotFoundException("강의 정보를 찾을 수 없습니다.")); - - } - - public Map> statusOfEmptyRoom(int floor) { - LocalDateTime now = LocalDateTime.now(); - DayOfWeek today = now.getDayOfWeek(); - List lectureEntity = getLecturesByFloor(floor); - return lectureEntity.stream() - .filter(lecture -> lecture.getDayTime().containsKey(today)) - .flatMap(lecture -> lecture.getDayTime().get(today).stream() - .map(time -> EmptyRoomResponseDto.of(lecture, time))) - .collect(Collectors.groupingBy( - EmptyRoomResponseDto::getRoomNum - )); + double ave = redisReviewService.getAveOfRating(lectureId); + Emotion emotion = redisReviewService.getEmotionRating(lectureId); + long participants = redisReviewService.getParticipants(lectureId); + return LectureDetailResponseDto.of(lectureEntity, ave, emotion, participants); } public LecturePageResponse sortListOfLectures(Department department, Option option, int page) { if (department.equals(Department.EMBEDDED) || department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.OTHERS)) { PageRequest pageRequest = PageRequest.of(page, 20, option==Option.LEC? Sort.by("lectureNm"):Sort.by("professor")); Page lecturePage = lectureRepository.findAllByDepartment(pageRequest, department); - return LecturePageResponse.of(lecturePage.stream().map(LectureListResponseDto::of).toList(), + return LecturePageResponse.of(lecturePage.stream() + .map(lecture -> LectureListResponseDto.of(lecture, + redisReviewService.getAveOfRating(lecture.get_id().toString()), + redisReviewService.getParticipants(lecture.get_id().toString()) )) + .toList(), lecturePage.getTotalPages() -1, lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1: -1); } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); @@ -90,8 +47,11 @@ public LecturePageResponse sortListOfLectures(Department department, Option opti public Object searchLectures(String keyword, int page) { PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); Page lecturePage = lectureRepository.findAllByKeyword(keyword, pageRequest); - return LecturePageResponse.of( - lecturePage.stream().map(LectureListResponseDto::of).toList(), + return LecturePageResponse.of(lecturePage.stream() + .map(lecture -> LectureListResponseDto.of(lecture, + redisReviewService.getAveOfRating(lecture.get_id().toString()), + redisReviewService.getParticipants(lecture.get_id().toString()) )) + .toList(), lecturePage.getTotalPages() - 1, lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1 : -1 ); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java index 32371f7a..616b7dad 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java @@ -1,10 +1,15 @@ package inu.codin.codin.infra.redis.service; +import inu.codin.codin.domain.lecture.dto.Emotion; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; +import java.util.Objects; +import java.util.Set; + @Service @RequiredArgsConstructor public class RedisReviewService { @@ -12,9 +17,39 @@ public class RedisReviewService { private static final String REVIEW_KEY = "review:lectures:"; private final RedisTemplate redisTemplate; - public void addReview(String lectureCode, double starRating, ObjectId userId){ - String redisKey = REVIEW_KEY + lectureCode; + public void addReview(String lectureId, double starRating, ObjectId userId){ + String redisKey = REVIEW_KEY + lectureId; redisTemplate.opsForZSet().add(redisKey, String.valueOf(userId), starRating); } + public double getAveOfRating(String lectureId) { + String redisKey = REVIEW_KEY + lectureId; + Set> ratingWithScores = + redisTemplate.opsForZSet().rangeWithScores(redisKey, 0, -1); + if (ratingWithScores == null || ratingWithScores.isEmpty()) return 0.0; + + double totalScore = ratingWithScores.stream() + .mapToDouble(rating -> rating.getScore() != null? rating.getScore():0.0).sum(); + long count = ratingWithScores.size(); + return count > 0 ? totalScore / (double) count : 0.0; + } + + public Emotion getEmotionRating(String lectureId){ + String redisKey = REVIEW_KEY + lectureId; + double total = getParticipants(lectureId); + return Emotion.builder() + .hard(getPercentOfRating(redisKey, 0.25, 2.0, total)) + .ok(getPercentOfRating(redisKey, 2.25, 4.0, total)) + .best(getPercentOfRating(redisKey, 4.25, 5.0, total)) + .build(); + } + + private double getPercentOfRating(String redisKey, double min, double max, double total){ + return (Objects.requireNonNull(redisTemplate.opsForZSet().rangeByScore(redisKey, min, max)).size() / total) * 100; + } + + public long getParticipants(String lectureId){ + String redisKey = REVIEW_KEY + lectureId; + return Objects.requireNonNull(redisTemplate.opsForZSet().rangeWithScores(redisKey, 0, -1)).size(); + } } From f4325afd289f0df5a75ce955e423fc581ccbd7b7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 23:37:46 +0900 Subject: [PATCH 0390/1002] =?UTF-8?q?perf=20:=20=EC=88=98=EA=B0=95=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=EC=97=90=20=ED=95=99=EA=B8=B0(semester)=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/review/dto/ReviewListResposneDto.java | 6 ++++-- .../codin/codin/domain/lecture/entity/LectureEntity.java | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java index c41e24c4..6bf2ccc2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java @@ -14,11 +14,11 @@ public class ReviewListResposneDto { private String content; private double starRating; private int likes; - private boolean isLiked; + private String semester; @Builder - public ReviewListResposneDto(String _id, String lectureId, String userId, String title, String content, double starRating, int likes, boolean isLiked) { + public ReviewListResposneDto(String _id, String lectureId, String userId, String title, String content, double starRating, int likes, boolean isLiked, String semester) { this._id = _id; this.lectureId = lectureId; this.userId = userId; @@ -27,6 +27,7 @@ public ReviewListResposneDto(String _id, String lectureId, String userId, String this.starRating = starRating; this.likes = likes; this.isLiked = isLiked; + this.semester = semester; } public static ReviewListResposneDto of(ReviewEntity reviewEntity, boolean isLiked, int likes){ @@ -39,6 +40,7 @@ public static ReviewListResposneDto of(ReviewEntity reviewEntity, boolean isLike .starRating(reviewEntity.getStarRating()) .likes(likes) .isLiked(isLiked) + .semester(reviewEntity.getSemester()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index f18885db..35efd2b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -7,6 +7,8 @@ import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.List; + @Document(collection = "lectures") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -17,6 +19,5 @@ public class LectureEntity { private String professor; private Department department; //OTHERS : 교양 private int grade; //0 : 전학년 - - + private List semester; } From a24b78af9c251ffaefb33392e797d7a2c96292af Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 23:38:13 +0900 Subject: [PATCH 0391/1002] =?UTF-8?q?perf=20:=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B3=BC=EB=AA=A9=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/lecture/controller/LectureController.java | 2 +- .../codin/domain/lecture/repository/LectureRepository.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index d3289c66..3de2b728 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -35,7 +35,7 @@ public ResponseEntity> sortListOfLectures(@RequestParam("depar } @Operation( - summary = "교수명, 과목명, 과목코드 검색", + summary = "교수명, 과목명 검색", description = "keyword 입력을 통해 (교수명, 과목명, 과목코드) 중 일치하는 결과 반환" ) @GetMapping("/search") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java index 3908eeb0..ad03191a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -14,8 +14,7 @@ public interface LectureRepository extends MongoRepository findAllByDepartment(Pageable pageable, Department department); @Query("{ '$or': [ { 'lectureNm': { $regex: ?0, $options: 'i' } }, " + - "{ 'professor': { $regex: ?0, $options: 'i' } }, " + - "{ 'lectureCode': { $regex: ?0, $options: 'i' } } ] }") + "{ 'professor': { $regex: ?0, $options: 'i' } } ] }") Page findAllByKeyword(String keyword, Pageable pageable); } From c36934fab9bcae1e456f21bed732210ce5c36de7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 25 Jan 2025 23:38:56 +0900 Subject: [PATCH 0392/1002] =?UTF-8?q?docs=20:=20Unused=20import=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/lecture/dto/Emotion.java | 1 - .../post/domain/reply/service/ReplyCommentService.java | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java index c73fad82..4d253c53 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.lecture.dto; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 0ceed080..bfcc107e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -2,11 +2,11 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; @@ -16,7 +16,6 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisLikeService; import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; From 31545dee9dbde71b506af3e062f40a9070d6d6db Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 27 Jan 2025 00:21:14 +0900 Subject: [PATCH 0393/1002] =?UTF-8?q?docs=20:=20@Tag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/room/controller/LectureRoomController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java index 057414dc..fef040d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.domain.room.service.LectureRoomService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ @RequestMapping("/rooms") @RestController @RequiredArgsConstructor +@Tag(name = "Lecture Room API", description = "강의실 현황 API") public class LectureRoomController { private final LectureRoomService lectureRoomService; From c3407ef042aef51a51d21b35c4561b4d24796388 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 27 Jan 2025 00:21:53 +0900 Subject: [PATCH 0394/1002] =?UTF-8?q?fix=20:=20total=EC=9D=B4=200=EC=9D=B8?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0=20Nan=20=EB=B0=98=ED=99=98=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/infra/redis/service/RedisReviewService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java index 616b7dad..eff3f87d 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java @@ -37,6 +37,7 @@ public double getAveOfRating(String lectureId) { public Emotion getEmotionRating(String lectureId){ String redisKey = REVIEW_KEY + lectureId; double total = getParticipants(lectureId); + if (total == 0) return Emotion.builder().hard(0).ok(0).best(0).build(); return Emotion.builder() .hard(getPercentOfRating(redisKey, 0.25, 2.0, total)) .ok(getPercentOfRating(redisKey, 2.25, 4.0, total)) From d9fc8fa9c1f647280fca0e2a8623d351050e133c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 27 Jan 2025 00:26:20 +0900 Subject: [PATCH 0395/1002] =?UTF-8?q?fix=20:=20title=20=EC=B9=BC=EB=9F=BC?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/review/dto/CreateReviewRequestDto.java | 4 +--- .../lecture/domain/review/dto/ReviewListResposneDto.java | 5 +---- .../domain/lecture/domain/review/entity/ReviewEntity.java | 5 +---- .../lecture/domain/review/repository/ReviewRepository.java | 4 ++-- .../domain/lecture/domain/review/service/ReviewService.java | 4 ++-- 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java index d27a5313..683a0954 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java @@ -6,9 +6,6 @@ @Getter public class CreateReviewRequestDto { - @NotBlank - private String title; - @NotBlank private String content; @@ -16,5 +13,6 @@ public class CreateReviewRequestDto { @Digits(integer = 1, fraction = 2) private double starRating; + @NotBlank private String semester; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java index 6bf2ccc2..81f7896f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java @@ -10,7 +10,6 @@ public class ReviewListResposneDto { private String _id; private String lectureId; private String userId; - private String title; private String content; private double starRating; private int likes; @@ -18,11 +17,10 @@ public class ReviewListResposneDto { private String semester; @Builder - public ReviewListResposneDto(String _id, String lectureId, String userId, String title, String content, double starRating, int likes, boolean isLiked, String semester) { + public ReviewListResposneDto(String _id, String lectureId, String userId, String content, double starRating, int likes, boolean isLiked, String semester) { this._id = _id; this.lectureId = lectureId; this.userId = userId; - this.title = title; this.content = content; this.starRating = starRating; this.likes = likes; @@ -35,7 +33,6 @@ public static ReviewListResposneDto of(ReviewEntity reviewEntity, boolean isLike ._id(reviewEntity.get_id().toString()) .lectureId(reviewEntity.getLectureId().toString()) .userId(reviewEntity.getUserId().toString()) - .title(reviewEntity.getTitle()) .content(reviewEntity.getContent()) .starRating(reviewEntity.getStarRating()) .likes(likes) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index 32fa30a2..21bbd65e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -16,18 +16,16 @@ public class ReviewEntity extends BaseTimeEntity { private ObjectId _id; private ObjectId lectureId; private ObjectId userId; - private String title; private String content; private double starRating; private int likes; private String semester; @Builder - public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String title, String content, double starRating, int likes, String semester) { + public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String content, double starRating, int likes, String semester) { this._id = _id; this.lectureId = lectureId; this.userId = userId; - this.title = title; this.content = content; this.starRating = starRating; this.likes = likes; @@ -36,7 +34,6 @@ public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String ti public static ReviewEntity of(CreateReviewRequestDto createReviewRequestDto, ObjectId lectureId, ObjectId userId){ return ReviewEntity.builder() - .title(createReviewRequestDto.getTitle()) .content(createReviewRequestDto.getContent()) .starRating(createReviewRequestDto.getStarRating()) .lectureId(lectureId) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java index 66d90c8a..79799217 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -11,7 +11,7 @@ @Repository public interface ReviewRepository extends MongoRepository { - Page findAllByLectureId(ObjectId lectureId, PageRequest pageRequest); + Page findAllByLectureIdAndDeletedAtIsNull(ObjectId lectureId, PageRequest pageRequest); - Optional findByLectureIdAndUserId(ObjectId lectureId, ObjectId userId); + Optional findByLectureIdAndUserIdAndDeletedAtIsNull(ObjectId lectureId, ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index 974e722a..0ddf62e6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -39,7 +39,7 @@ public void createReview(String lectureId, CreateReviewRequestDto createReviewRe throw new WrongRatingException("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요."); } ObjectId userId = SecurityUtils.getCurrentUserId(); - Optional review = reviewRepository.findByLectureIdAndUserId(new ObjectId(lectureId), userId); + Optional review = reviewRepository.findByLectureIdAndUserIdAndDeletedAtIsNull(new ObjectId(lectureId), userId); if (review.isPresent()) { log.error("이미 유저가 작성한 후기가 존재합니다. userId: {}, lectureId: {}", userId, lectureId); throw new ReviewExistenceException("이미 유저가 작성한 후기가 존재합니다."); @@ -54,7 +54,7 @@ public void createReview(String lectureId, CreateReviewRequestDto createReviewRe public Object getListOfReviews(String lectureId, int page) { PageRequest pageRequest = PageRequest.of(page, 10, Sort.by("created_at").descending()); - Page reviewPage = reviewRepository.findAllByLectureId(new ObjectId(lectureId), pageRequest); + Page reviewPage = reviewRepository.findAllByLectureIdAndDeletedAtIsNull(new ObjectId(lectureId), pageRequest); ObjectId userId = SecurityUtils.getCurrentUserId(); return ReviewPageResponse.of(reviewPage.stream() From 33c7d64b990f695df6de553e5691083ce6482d11 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 27 Jan 2025 00:47:57 +0900 Subject: [PATCH 0396/1002] =?UTF-8?q?docs=20:=20@Schema=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/dto/CreateReviewRequestDto.java | 4 ++++ .../review/dto/ReviewListResposneDto.java | 17 +++++++++++++++++ .../domain/room/dto/EmptyRoomResponseDto.java | 11 +++++++++++ .../lecture/dto/LectureDetailResponseDto.java | 2 ++ .../lecture/dto/LectureListResponseDto.java | 9 +++++++++ 5 files changed, 43 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java index 683a0954..7044b113 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.lecture.domain.review.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.Getter; @@ -7,12 +8,15 @@ public class CreateReviewRequestDto { @NotBlank + @Schema(description = "수강 후기 내용", example = "완전 강추합니다!") private String content; @NotNull @Digits(integer = 1, fraction = 2) + @Schema(description = "수강 평점 (0.25 ~ 5.0 사이의 값, 0.25 단위)") private double starRating; @NotBlank + @Schema(description = "수강 학기 (년도-학기)", example = "24-2") private String semester; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java index 81f7896f..ec8d8e00 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java @@ -2,18 +2,35 @@ import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter public class ReviewListResposneDto { + + @Schema(description = "ReviewEntity _id", example = "1111111") private String _id; + + @Schema(description = "수강 후기를 단 강의 _id", example = "2222222") private String lectureId; + + @Schema(description = "수강 후기 작성한 유저 _id", example = "3333333") private String userId; + + @Schema(description = "수강 후기 내용", example = "완전 강추") private String content; + + @Schema(description = "수강 후기 평점") private double starRating; + + @Schema(description = "좋아요 수", example = "3") private int likes; + + @Schema(description = "유저의 좋아요 반영 여부", example = "true") private boolean isLiked; + + @Schema(description = "수강 학기", example = "24-2") private String semester; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java index 12fa2e04..7c294194 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.lecture.domain.room.dto; import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -8,10 +9,20 @@ @Getter @Setter public class EmptyRoomResponseDto { + + @Schema(description = "강의명", example = "Java") private String lectureNm; + + @Schema(description = "교수명", example = "홍길동") private String professor; + + @Schema(description = "강의실 호수", example = "419") private int roomNum; + + @Schema(description = "시작 시간", example = "09:00") private String startTime; + + @Schema(description = "종료 시간", example = "18:00") private String endTime; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java index 7b0b59a1..541527d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java @@ -1,11 +1,13 @@ package inu.codin.codin.domain.lecture.dto; import inu.codin.codin.domain.lecture.entity.LectureEntity; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter public class LectureDetailResponseDto extends LectureListResponseDto{ + @Schema(description = "후기 평점들의 범위마다 100분율 계산", example = "hard : 30, ok : 20, best : 50") private Emotion emotion; public LectureDetailResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, Emotion emotion) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java index 646fff05..a80069d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.lecture.dto; import inu.codin.codin.domain.lecture.entity.LectureEntity; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -9,11 +10,19 @@ @Getter public class LectureListResponseDto { + @Schema(description = "LectureEntity _id", example = "1111111") private String _id; + + @Schema(description = "강의명", example = "Java") private String lectureNm; + + @Schema(description = "교수명", example = "홍길동") private String professor; + @Schema(description = "강의 평점 평균", example = "2.5") private double starRating; + + @Schema(description = "수강 후기 작성자 수", example = "10") private long participants; @Builder From 06ac7ad1f242ff122d15d99026145b0ef4c9de36 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 27 Jan 2025 00:54:52 +0900 Subject: [PATCH 0397/1002] =?UTF-8?q?docs=20:=20like=EC=97=90=20=EC=88=98?= =?UTF-8?q?=EA=B0=95=20=ED=9B=84=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/like/controller/LikeController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index d177f1aa..3d3cfe25 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -14,12 +14,12 @@ @RestController @RequestMapping("/likes") @RequiredArgsConstructor -@Tag(name = "Like API", description = "게시물, 댓글, 대댓글 좋아요 API") +@Tag(name = "Like API", description = "게시물, 댓글, 대댓글, 수강후기 좋아요 API") public class LikeController { private final LikeService likeService; - @Operation(summary = "게시물, 댓글, 대댓글 좋아요 토글") + @Operation(summary = "게시물, 댓글, 대댓글, 수강후기 좋아요 토글") @PostMapping public ResponseEntity> toggleLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { String message = likeService.toggleLike(likeRequestDto); From 15f7b5d05dc60e2df286fbd1d5f8b42cda81d8b5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 28 Jan 2025 16:49:33 +0900 Subject: [PATCH 0398/1002] =?UTF-8?q?feat=20:=20=ED=95=99=EA=B3=BC,=20?= =?UTF-8?q?=ED=95=99=EB=85=84,=20=EC=88=98=EA=B0=95=ED=95=99=EA=B8=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=99=EC=A0=81=20=EA=B2=80=EC=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/controller/LectureController.java | 28 +++++++--- .../lecture/service/LectureService.java | 54 +++++++++++++++---- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index 3de2b728..6154ac12 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -34,9 +34,19 @@ public ResponseEntity> sortListOfLectures(@RequestParam("depar lectureService.sortListOfLectures(department, option, page))); } + @Operation( + summary = "강의 별점 정보 반환", + description = "강의후기 > 상세보기 눌렀을 때 뜨는 강의 정보 반환" + ) + @GetMapping("/{lectureId}") + public ResponseEntity> getLectureDetails(@PathVariable("lectureId") String lectureId){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "강의 별점 정보 반환", lectureService.getLectureDetails(lectureId))); + } + @Operation( summary = "교수명, 과목명 검색", - description = "keyword 입력을 통해 (교수명, 과목명, 과목코드) 중 일치하는 결과 반환" + description = "keyword 입력을 통해 (교수명, 과목명) 중 일치하는 결과 반환" ) @GetMapping("/search") public ResponseEntity> searchLectures(@RequestParam("keyword") String keyword, @@ -46,14 +56,20 @@ public ResponseEntity> searchLectures(@RequestParam("keyword") } @Operation( - summary = "강의 별점 정보 반환", - description = "강의후기 > 상세보기 눌렀을 때 뜨는 강의 정보 반환" + summary = "학과, 학년, 수강학기 로 강의 검색", + description = "수강 후기 작성 시 필요한 검색엔진
" + + "학과, 학년, 수강학기 중 하나만으로도 검색 가능" ) - @GetMapping("/{lectureId}") - public ResponseEntity> getLectureDetails(@PathVariable("lectureId") String lectureId){ + @GetMapping("/search-review") + public ResponseEntity> searchLecturesToReview(@RequestParam(required = false) Department department, + @RequestParam(required = false) Integer grade, + @RequestParam(required = false) String semester, + @RequestParam int page){ return ResponseEntity.ok() - .body(new SingleResponse<>(200, "강의 별점 정보 반환", lectureService.getLectureDetails(lectureId))); + .body(new SingleResponse<>(200, "필터링 된 강의들 반환 완료", + lectureService.searchLecturesToReview(department, grade, semester, page))); } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index d370c471..84a7d076 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -10,16 +10,23 @@ import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class LectureService { private final LectureRepository lectureRepository; private final RedisReviewService redisReviewService; + private final MongoTemplate mongoTemplate; public LectureDetailResponseDto getLectureDetails(String lectureId) { LectureEntity lectureEntity = lectureRepository.findById(new ObjectId(lectureId)) @@ -34,26 +41,51 @@ public LecturePageResponse sortListOfLectures(Department department, Option opti if (department.equals(Department.EMBEDDED) || department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.OTHERS)) { PageRequest pageRequest = PageRequest.of(page, 20, option==Option.LEC? Sort.by("lectureNm"):Sort.by("professor")); Page lecturePage = lectureRepository.findAllByDepartment(pageRequest, department); - return LecturePageResponse.of(lecturePage.stream() - .map(lecture -> LectureListResponseDto.of(lecture, - redisReviewService.getAveOfRating(lecture.get_id().toString()), - redisReviewService.getParticipants(lecture.get_id().toString()) )) - .toList(), - lecturePage.getTotalPages() -1, - lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1: -1); + return getLecturePageResponse(lecturePage); } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); } - public Object searchLectures(String keyword, int page) { + public LecturePageResponse searchLectures(String keyword, int page) { PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); Page lecturePage = lectureRepository.findAllByKeyword(keyword, pageRequest); + return getLecturePageResponse(lecturePage); + } + + public LecturePageResponse searchLecturesToReview(Department department, Integer grade, String semester, int page) { + PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); + Page lecturePage = findLectures(department, grade, semester, pageRequest); + return getLecturePageResponse(lecturePage); + } + + private LecturePageResponse getLecturePageResponse(Page lecturePage) { return LecturePageResponse.of(lecturePage.stream() .map(lecture -> LectureListResponseDto.of(lecture, redisReviewService.getAveOfRating(lecture.get_id().toString()), - redisReviewService.getParticipants(lecture.get_id().toString()) )) + redisReviewService.getParticipants(lecture.get_id().toString()))) .toList(), lecturePage.getTotalPages() - 1, - lecturePage.hasNext()? lecturePage.getPageable().getPageNumber() + 1 : -1 - ); + lecturePage.hasNext() ? lecturePage.getPageable().getPageNumber() + 1 : -1); + } + + public Page findLectures(Department department, Integer grade, String semester, PageRequest pageRequest) { + Query query = new Query(); + + if (department != null) { + query.addCriteria(Criteria.where("department").is(department)); + } + + if (grade != null && grade > 0) { + query.addCriteria(Criteria.where("grade").is(grade)); + } + + if (semester != null) { + query.addCriteria(Criteria.where("semester").in(List.of(semester))); + } + + long total = mongoTemplate.count(query, LectureEntity.class); + query.with(pageRequest); + + List lectures = mongoTemplate.find(query, LectureEntity.class); + return new PageImpl<>(lectures, pageRequest, total); } } From 7f2f682e95a7e13d9ab46e739940314ede007616 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 28 Jan 2025 16:57:32 +0900 Subject: [PATCH 0399/1002] =?UTF-8?q?pref=20:=20grade=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20min,=20max=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/lecture/controller/LectureController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index 6154ac12..29e9d735 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -6,6 +6,8 @@ import inu.codin.codin.domain.lecture.service.LectureService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -62,7 +64,7 @@ public ResponseEntity> searchLectures(@RequestParam("keyword") ) @GetMapping("/search-review") public ResponseEntity> searchLecturesToReview(@RequestParam(required = false) Department department, - @RequestParam(required = false) Integer grade, + @RequestParam(required = false) @Min(1) @Max(4) Integer grade, @RequestParam(required = false) String semester, @RequestParam int page){ return ResponseEntity.ok() From 9c80b160f9b4a68bbf7421b042b260e7f20a0e7b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 29 Jan 2025 00:00:01 +0900 Subject: [PATCH 0400/1002] =?UTF-8?q?pref=20:=20=EC=B8=B5=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=EA=B0=80=20=EC=95=84=EB=8B=8C=20=ED=95=9C=20=EB=B2=88?= =?UTF-8?q?=EC=97=90=20=EA=B0=95=EC=9D=98=EC=8B=A4=20=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LectureRoomController.java | 7 ++---- .../room/service/LectureRoomService.java | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java index fef040d4..6ad57578 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java @@ -4,13 +4,10 @@ import inu.codin.codin.domain.lecture.domain.room.service.LectureRoomService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/rooms") @@ -26,8 +23,8 @@ public class LectureRoomController { description = "당일의 요일에 따라 층마다 호실에서의 수업 내용 반환" ) @GetMapping("/empty") - public ResponseEntity statusOfEmptyRoom(@RequestParam("floor") @Max(5) @Min(1) int floor){ + public ResponseEntity statusOfEmptyRoom(){ return ResponseEntity.ok() - .body(new SingleResponse<>(200, floor + "층의 강의실 현황 반환", lectureRoomService.statusOfEmptyRoom(floor))); + .body(new SingleResponse<>(200, "오늘의 강의실 현황 반환", lectureRoomService.statusOfEmptyRoom())); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java index 741ba3e7..ef8a95f4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java @@ -14,6 +14,7 @@ import java.time.DayOfWeek; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -47,16 +48,20 @@ public List getLecturesByFloor(int floor) { } - public Map> statusOfEmptyRoom(int floor) { + public List>> statusOfEmptyRoom() { LocalDateTime now = LocalDateTime.now(); DayOfWeek today = now.getDayOfWeek(); - List roomEntities = getLecturesByFloor(floor); - return roomEntities.stream() - .filter(lecture -> lecture.getDayTime().containsKey(today)) - .flatMap(lecture -> lecture.getDayTime().get(today).stream() - .map(time -> EmptyRoomResponseDto.of(lecture, time))) - .collect(Collectors.groupingBy( - EmptyRoomResponseDto::getRoomNum - )); + ArrayList>> lectureRoom = new ArrayList<>(); + for (int floor=1; floor<=5; floor++) { + List roomEntities = getLecturesByFloor(floor); + lectureRoom.add(roomEntities.stream() + .filter(lecture -> lecture.getDayTime().containsKey(today)) + .flatMap(lecture -> lecture.getDayTime().get(today).stream() + .map(time -> EmptyRoomResponseDto.of(lecture, time))) + .collect(Collectors.groupingBy( + EmptyRoomResponseDto::getRoomNum + ))); + } + return lectureRoom; } } From 88e5393a4bf3a5dfde1b608ba536e60b4bda3710 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 17:32:22 +0900 Subject: [PATCH 0401/1002] =?UTF-8?q?feat=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20->=20=EC=9C=A0=EC=A0=80,=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC,=20=EB=8C=93=EA=B8=80,=20=EB=8C=80=EB=8C=93=EA=B8=80)?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=EC=9E=90=20->=20=EC=8A=B9=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=95=EC=A7=80=20=EA=B8=B0=EA=B0=84=20=EB=B6=80?= =?UTF-8?q?=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/controller/ReportController.java | 94 ++++++++ .../dto/request/ReportCreateRequestDto.java | 25 ++ .../dto/request/ReportExecuteRequestDto.java | 24 ++ .../dto/response/ReportResponseDto.java | 90 +++++++ .../domain/report/entity/ReportEntity.java | 97 ++++++++ .../domain/report/entity/ReportStatus.java | 15 ++ .../report/entity/ReportTargetType.java | 18 ++ .../domain/report/entity/ReportType.java | 21 ++ .../report/entity/SuspensionPeriod.java | 19 ++ .../domain/report/service/ReportService.java | 225 ++++++++++++++++++ 10 files changed, 628 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java new file mode 100644 index 00000000..113bd796 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -0,0 +1,94 @@ +package inu.codin.codin.domain.report.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; +import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; +import inu.codin.codin.domain.report.dto.response.ReportResponseDto; +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.entity.ReportStatus; +import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.entity.ReportType; +import inu.codin.codin.domain.report.service.ReportService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/reports") +public class ReportController { + private final ReportService reportService; + + //신고 작성 + /** + User -> User , Post, Comment, Reply + **/ + @Operation(summary = "신고 하기 - 게시물, 댓글, 대댓글") + @PostMapping + public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto reportCreateRequestDto) { + reportService.createReport(reportCreateRequestDto); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "신고 생성 완료", null)); + } + + //신고 목록 조회 (관리자) + /*** + 전체조회 + ReportType 별 조회 + + 특정 신고횟수 이상 조회 + */ + + //특정 유저 신고 상세 조회 (관리자) + + //신고 처리 (관리자) + + // 특정 신고 타입 목록 조회 (관리자) + @Operation(summary = "특정 신고 타입 목록 조회 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping + public ResponseEntity getReports( + @RequestParam(required = false) ReportTargetType reportTargetType, + @RequestParam(required = false) Integer minReportCount) { + + List reports = reportService.getAllReports(reportTargetType, minReportCount); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "특정 신고 타입 목록 조회", reports)); + } + + + // 특정 유저 신고 내역 조회 (관리자) + @Operation(summary = "특정 유저 신고 내역 조회 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/user") + public ResponseEntity getReportsByUserId( + @RequestParam("userId") @NotNull ObjectId userId) { + + List userReports = reportService.getReportsByUserId(userId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "특정 유저가 신고한 내역 조회", userReports)); + } + + + // 신고 처리 (관리자) + @Operation(summary = "신고 처리 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @PutMapping("/{reportId}") + public ResponseEntity handleReport( + @RequestBody ReportExecuteRequestDto reportExecuteRequestDto) { + + reportService.executeReport(reportExecuteRequestDto); + + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(200, "(관리자) 신고 처리",null)); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java new file mode 100644 index 00000000..18536cc6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.report.dto.request; + +import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.entity.ReportType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import org.bson.types.ObjectId; + +@Getter +public class ReportCreateRequestDto { + + @Schema(description = "신고할 대상 타입", example = "USER, POST, COMMENT, REPLY") + @NotNull + private ReportTargetType reportTargetType; + + @Schema(description = "신고 대상 타입의 ID ( 유저, 게시물, 댓글, 대댓글)", example = "2") + @NotBlank + private String reportTargetId; + + @Schema(description = " 신고 유형 ( 게시글 부적절, 스팸 ,,.)", example = "INAPPROPRIATE_CONTENT, COMMERCIAL_AD , ABUSE , OBSCENE, POLITICAL,FRAUD , SPAM ,,") + @NotNull + private ReportType reportType; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java new file mode 100644 index 00000000..e4fc6caa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.report.dto.request; + +import inu.codin.codin.domain.report.entity.ReportStatus; +import inu.codin.codin.domain.report.entity.SuspensionPeriod; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +public class ReportExecuteRequestDto { + private String reportId; // 신고 ID + private String comment; // 신고 처리에 대한 코멘트 + private SuspensionPeriod suspensionPeriod; // 정지 기간 + + public LocalDateTime getSuspensionEndDate() { + if (suspensionPeriod == null) { + return null; + } + return LocalDateTime.now().plusDays(suspensionPeriod.getDays()); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java new file mode 100644 index 00000000..dbcc29cf --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java @@ -0,0 +1,90 @@ +package inu.codin.codin.domain.report.dto.response; + +import inu.codin.codin.domain.report.entity.*; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import org.bson.types.ObjectId; + +import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.entity.ReportType; +import inu.codin.codin.domain.report.entity.ReportStatus; +import inu.codin.codin.domain.report.entity.ReportEntity.ReportActionEntity; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ReportResponseDto { + + @Schema(description = "report id", example = "1") + @NotNull + private String _id; + + @Schema(description = "신고한 유저", example = "2") + @NotNull + private String reportingUserId; + + @Schema(description = "신고당한 유저", example = "2") + @NotNull + private String reportedUserId; + + @Schema(description = "신고할 대상 타입", example = "USER, POST, COMMENT, REPLY") + @NotNull + private ReportTargetType reportTargetType; + + @Schema(description = "신고 대상 타입의 ID (유저, 게시물, 댓글, 대댓글)", example = "2") + @NotNull + private String reportTargetId; + + @Schema(description = "신고 유형 (게시글 부적절, 스팸, ...)", example = "INAPPROPRIATE_CONTENT, COMMERCIAL_AD , ABUSE , OBSCENE, POLITICAL, FRAUD , SPAM") + @NotNull + private ReportType reportType; + + + @Schema(description = "신고 처리 상태 Pending <-> Reloved", example = "Pending, Resolved") + @NotNull + private ReportStatus reportStatus; + + + @Schema(description = "처리 정보 (ReportActionEntity)", example = "") + @NotNull + private ReportActionDto action; + + @Getter + public static class ReportActionDto { + // 신고 처리 관리자 id + private String actionTakenById; + + // 신고에 대한 코멘트 + private String comment; + + // 정지 기간 Enum + private String suspensionPeriod; + + // 정지 종료일 + private String suspensionEndDate; + + // DTO 생성자 (ReportActionEntity로부터 데이터를 변환) + public ReportActionDto(ReportActionEntity actionEntity) { + if (actionEntity != null) { + this.actionTakenById = actionEntity.getActionTakenById().toString(); + this.comment = actionEntity.getComment(); + this.suspensionPeriod = actionEntity.getSuspensionPeriod() != null ? actionEntity.getSuspensionPeriod().name() : null; + this.suspensionEndDate = actionEntity.getSuspensionEndDate() != null ? actionEntity.getSuspensionEndDate().toString() : null; + } + } + } + + // ReportEntity를 ReportResponseDto로 변환 + public static ReportResponseDto from(ReportEntity reportEntity) { + return ReportResponseDto.builder() + ._id(reportEntity.get_id().toString()) + .reportingUserId(reportEntity.getReportingUserId().toString()) + .reportTargetType(reportEntity.getReportTargetType()) + .reportTargetId(reportEntity.getReportTargetId().toString()) + .reportType(reportEntity.getReportType()) + .reportStatus(reportEntity.getReportStatus()) + .action(reportEntity.getAction() != null ? new ReportActionDto(reportEntity.getAction()) : null) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java new file mode 100644 index 00000000..8bc1175d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -0,0 +1,97 @@ +package inu.codin.codin.domain.report.entity; + + +import inu.codin.codin.common.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDateTime; + +@Document(collection = "reports") +@Getter +public class ReportEntity extends BaseTimeEntity { + @Id + @NotBlank + private ObjectId _id; + + //신고한 유저 + private ObjectId reportingUserId; + + //신고당 유저 + private ObjectId reportedUserId; + + //신고 대상 타입 ( 유저, 게시물, 댓글, 대댓글) + private ReportTargetType reportTargetType; + + //신고 대상 ID ( 유저, 게시물, 댓글, 대댓글) + private ObjectId reportTargetId; + + //신고 유형 ( 게시글 부적절, 스팸 ,,.) + private ReportType reportType; + + //신고 처리 상태 Pending <-> Reloved + private ReportStatus reportStatus; + + private ReportActionEntity action; // 처리 정보 + + @Builder + public ReportEntity(ObjectId reportingUserId, ObjectId reportedUserId, ReportTargetType reportTargetType, + ObjectId reportTargetId, ReportType reportType, ReportStatus reportStatus, + ReportActionEntity action) { + this.reportingUserId = reportingUserId; + this.reportedUserId = reportedUserId; + this.reportTargetType = reportTargetType; + this.reportTargetId = reportTargetId; + this.reportType = reportType; + this.reportStatus = ReportStatus.PENDING; + this.action = action; + } + + // 신고 상태 업데이트 (빌더 패턴 활용) + public ReportEntity updateReport(ReportActionEntity action) { + return ReportEntity.builder() + .reportingUserId(this.reportingUserId) + .reportedUserId(this.reportedUserId) + .reportTargetType(this.reportTargetType) + .reportTargetId(this.reportTargetId) + .reportType(this.reportType) + .reportStatus(ReportStatus.RESOLVED) // 상태 변경 + .action(action) // 신고 처리 정보 업데이트 + .build(); + } + + public void updateReportResolved(ReportActionEntity action) { + this.reportStatus = ReportStatus.RESOLVED; // 상태 변경 + this.action = action; // 신고 처리 정보 업데이트 + } + + + + @Getter + public static class ReportActionEntity extends BaseTimeEntity { + //신고 처리 관리자 id + private ObjectId actionTakenById; + + //신고에 대한 코멘트 + private String comment; + + // 정지 기간 Enum + private SuspensionPeriod suspensionPeriod; + + // 정지 종료일 + private LocalDateTime suspensionEndDate; + + @Builder + public ReportActionEntity(ObjectId actionTakenById, String comment, + SuspensionPeriod suspensionPeriod, LocalDateTime suspensionEndDate) { + this.actionTakenById = actionTakenById; + this.comment = comment; + this.suspensionPeriod = suspensionPeriod; + this.suspensionEndDate = suspensionEndDate; + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java new file mode 100644 index 00000000..85a8bdd7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.report.entity; + +import lombok.Getter; + +@Getter +public enum ReportStatus { + PENDING("대기"), + RESOLVED("처리 완료"); + + private final String description; + + ReportStatus(String description) { + this.description = description; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java new file mode 100644 index 00000000..301be054 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java @@ -0,0 +1,18 @@ +package inu.codin.codin.domain.report.entity; + +import lombok.Getter; + +@Getter +public enum ReportTargetType { + USER("사용자"), + POST("게시물"), + COMMENT("댓글"), + REPLY("대댓글"), + REVIEW("수강 후기"); + + private final String description; + + ReportTargetType(String description) { + this.description = description; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java new file mode 100644 index 00000000..5a64c0e2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java @@ -0,0 +1,21 @@ +package inu.codin.codin.domain.report.entity; + +import lombok.Getter; + +@Getter +public enum ReportType { + INAPPROPRIATE_CONTENT("게시판 성격에 부적절"), + POLITICAL("부적절한 정치적 의견"), + FRAUD("사칭/사기"), + SPAM("도배/낚시"), + COMMERCIAL_AD("상업적 광고"), + ABUSE("욕설"), + OBSCENE("음란물 및 불건전함"); + + + private final String description; + + ReportType(String description) { + this.description = description; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java new file mode 100644 index 00000000..1acf3896 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.report.entity; + +import lombok.Getter; +import software.amazon.awssdk.services.s3.endpoints.internal.Value; + +@Getter +public enum SuspensionPeriod { + ONE_DAY(1), + THREE_DAYS(3), + SEVEN_DAYS(7), + THIRTY_DAYS(30), + PERMANENT(Integer.MAX_VALUE); // 영구 정지 + + private final int days; + + SuspensionPeriod(Integer days) { + this.days = days; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java new file mode 100644 index 00000000..4638a5d1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -0,0 +1,225 @@ +package inu.codin.codin.domain.report.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; +import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; +import inu.codin.codin.domain.report.dto.response.ReportResponseDto; +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.entity.ReportStatus; +import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.entity.SuspensionPeriod; +import inu.codin.codin.domain.report.exception.ReportAlreadyExistsException; +import inu.codin.codin.domain.report.repository.ReportRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.repository.UserRepository; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ReportService { + private final ReportRepository reportRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ReplyCommentRepository replyRepository; + + + public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { + /*** + * User 검증 + * 중복신고 방지 + * 신고 대상 유효성 검증 (reportTargetId가 유효한 대상을 참조) + */ + log.info("신고 생성 요청 시작: {} ", reportCreateRequestDto); + + // 신고한 유저 검증 + ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId reportTargetId = new ObjectId(reportCreateRequestDto.getReportTargetId()); + + log.info("신고 유저 검증 완료: userId={}", userId); + + // 중복 신고 방지 + log.info("중복 신고 검증: reportingUserId={}, reportTargetId={}, reportTargetType={}", + userId, + reportCreateRequestDto.getReportTargetId(), + reportCreateRequestDto.getReportTargetType()); + + Boolean reportExists = reportRepository.existsByReportingUserIdAndReportTargetIdAndReportTargetType( + userId, + reportTargetId, + reportCreateRequestDto.getReportTargetType() + ); + + // null 방지: reportExists가 null이면 false로 처리 + if (Boolean.TRUE.equals(reportExists)) { + log.warn("중복 신고 발견: reportingUserId={}, reportTargetId={}, reportTargetType={}", + userId, + reportCreateRequestDto.getReportTargetId(), + reportCreateRequestDto.getReportTargetType()); + throw new ReportAlreadyExistsException("중복신고 : 이미 해당 대상에 대한 신고를 시행했습니다."); + } + + // 신고 대상 타입 별 유효성 검증 + log.info("신고 대상 유효성 검증: reportTargetType={}, reportTargetId={}", + reportCreateRequestDto.getReportTargetType(), + reportCreateRequestDto.getReportTargetId()); + + // 신고 대상 검증 및 userId 가져오기 + ObjectId reportedUserId = validateAndGetReportedUserId(reportCreateRequestDto.getReportTargetType(), reportTargetId); + + log.info("신고 대상 유효성 검증 완료: reportTargetType={}, reportTargetId={}", + reportCreateRequestDto.getReportTargetType(), + reportCreateRequestDto.getReportTargetId()); + + // 신고 엔티티 생성 + ReportEntity report = ReportEntity.builder() + .reportingUserId(userId) + .reportedUserId(reportedUserId) + .reportTargetId(reportTargetId) + .reportTargetType(reportCreateRequestDto.getReportTargetType()) + .reportType(reportCreateRequestDto.getReportType()) + .build(); + + log.info("신고 엔티티 생성 완료: {}", report); + + // 신고 저장 + reportRepository.save(report); + log.info("신고 저장 완료: reportId={}, reportingUserId={}, reportTargetId={}", + report.get_id(), + userId, + reportCreateRequestDto.getReportTargetId()); + } + + /** + * 신고 대상 유효성 검증 및 신고 대상 UserId 추출 + */ + private ObjectId validateAndGetReportedUserId(ReportTargetType reportTargetType, ObjectId reportTargetId) { + // 타입별 유효성 검증 로직을 Map으로 관리 + Map>> validators = Map.of( + ReportTargetType.USER, Optional::of, // User의 경우, ID 자체가 신고 대상 + ReportTargetType.POST, id -> postRepository.findById(id).map(PostEntity::getUserId), + ReportTargetType.COMMENT, id -> commentRepository.findById(id).map(CommentEntity::getUserId), + ReportTargetType.REPLY, id -> replyRepository.findById(id).map(ReplyCommentEntity::getUserId) + ); + + log.info("신고 대상 검증 및 userId 조회: reportTargetType={}, reportTargetId={}", reportTargetType, reportTargetId); + + // 검증 및 userId 조회 + return Optional.ofNullable(validators.get(reportTargetType)) + .flatMap(validator -> validator.apply(reportTargetId)) + .orElseThrow(() -> { + log.error("유효하지 않은 신고 대상: reportTargetId={}, reportTargetType={}", reportTargetId, reportTargetType); + return new NotFoundException("신고 대상(ID: " + reportTargetId + ", Type: " + reportTargetType + ")이 존재하지 않습니다."); + }); + } + + + + + // 신고 목록 조회 (관리자) + public List getAllReports(ReportTargetType reportTargetType, Integer minReportCount) { + log.info("신고 목록 조회: reportType={}, minReportCount={}", reportTargetType, minReportCount); + + List reports; + if (reportTargetType != null) { + reports = reportRepository.findByReportTargetType(reportTargetType); + } else if (minReportCount != null) { + reports = reportRepository.findReportsByMinReportCount(minReportCount); + } else { + reports = reportRepository.findAll(); + } + + // ReportEntity를 ReportResponseDto로 변환 + return reports.stream() + .map(ReportResponseDto::from) + .collect(Collectors.toList()); + } + + // 특정 유저가 신고 내역 조회 (관리자) + public List getReportsByUserId(ObjectId userId) { + log.info("특정 유저 신고 내역 조회: userId={}", userId); + + List userReports = reportRepository.findByReportingUserId(userId); + return userReports.stream() + .map(ReportResponseDto::from) + .collect(Collectors.toList()); + } + + + + public void executeReport(ReportExecuteRequestDto requestDto) { + log.info("신고 처리 요청: {}", requestDto.getReportId()); + ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId ReportId = new ObjectId(requestDto.getReportId()); + + + // 신고 존재 확인 + ReportEntity report = reportRepository.findById(ReportId) + .orElseThrow(() -> new IllegalArgumentException("해당 신고가 존재하지 않습니다. ID: " + requestDto.getReportId())); + + // 이미 처리된 신고인지 확인 + if (report.getReportStatus() == ReportStatus.RESOLVED) { + throw new IllegalStateException("이미 처리된 신고입니다."); + } + + // 신고 처리 정보 생성 + ReportEntity.ReportActionEntity action = ReportEntity.ReportActionEntity.builder() + .actionTakenById(userId) + .comment(requestDto.getComment()) + .suspensionPeriod(requestDto.getSuspensionPeriod()) + .suspensionEndDate(requestDto.getSuspensionEndDate()) + .build(); + + // 엔티티 내부에서 업데이트 메서드 호출 + //ReportEntity updatedReport = report.updateReport(action); + + // 기존 객체의 필드를 직접 수정 (새 객체 생성 X) + report.updateReportResolved(action); + + + //유저 Suspended - 정지 상태로 변경 + Optional user = userRepository.findById(report.getReportedUserId()); + if (user.isEmpty()) throw new NotFoundException("존재하지 않는 회원입니다."); + //영구 정지 + if (requestDto.getSuspensionPeriod() == SuspensionPeriod.PERMANENT){ + user.get().disabledUser(); + } else { + user.get().suspendUser(); + } + + + // 업데이트된 신고 저장 + reportRepository.save(report); + userRepository.save(user.get()); + //userRepository.save(user); + log.info("신고가 처리되었습니다. ID: {}, 처리자: {}", report.get_id(), userId); + + ReportResponseDto.from(report); + } + + + + + + +} + From 4a15ef630567e3dcc552f3200cebd9641c0cb5e4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 17:32:44 +0900 Subject: [PATCH 0402/1002] =?UTF-8?q?feat=20::=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=A0=95=EC=A7=80=20=EC=8A=A4=EC=BC=80=EC=A5=B4?= =?UTF-8?q?=EB=9F=AC=20->=20=EB=A7=A4=EC=8B=9C=EA=B0=84=20=EC=A0=95?= =?UTF-8?q?=EC=A7=80=20=EC=9C=A0=EC=A0=80=EC=9D=98=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=ED=99=95=EC=9D=B8=ED=9B=84=20=EC=A0=95?= =?UTF-8?q?=EC=83=81=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/repository/ReportRepository.java | 36 ++++++++++++++++++ .../report/scheduler/SuspensionScheduler.java | 21 ++++++++++ .../report/service/SuspensionService.java | 38 +++++++++++++++++++ .../codin/domain/user/entity/UserEntity.java | 10 +++++ 4 files changed, 105 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java new file mode 100644 index 00000000..79390241 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.report.repository; + +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.entity.ReportTargetType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.Aggregation; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface ReportRepository extends MongoRepository { + + @Query("{'reportingUserId': ?0, 'reportTargetId': ?1, 'reportTargetType': ?2}") + Boolean existsByReportingUserIdAndReportTargetIdAndReportTargetType(ObjectId reportingUserId, ObjectId reportTargetId, ReportTargetType reportTargetType); + + List findByReportTargetType(ReportTargetType reportTargetType); + + @Aggregation(pipeline = { + "{ $group: { _id: '$reportTargetId', count: { $sum: 1 } } }", // reportTargetId별로 그룹화하고 개수 세기 + "{ $match: { count: { $gte: ?0 } } }" // minReportCount 이상인 count만 필터링 + }) + List findReportsByMinReportCount(Integer minReportCount); + + List findByReportingUserId(ObjectId userId); + + // 현재 정지 상태이며, 정지 종료일이 아직 남아있는 유저 조회 + @Query("{'action.suspensionEndDate': { $gt: ?0 }}") + List findSuspendedUsers(LocalDateTime now); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java new file mode 100644 index 00000000..4dea4c9f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java @@ -0,0 +1,21 @@ +package inu.codin.codin.domain.report.scheduler; + +import inu.codin.codin.domain.report.service.SuspensionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class SuspensionScheduler { + + private final SuspensionService suspensionService; + + @Scheduled(cron = "0 0 * * * ?") // 매 정시마다 실행 + public void checkAndReleaseSuspendedUsers() { + log.info("🚀 정지 해제 스케줄러 실행..."); + suspensionService.releaseSuspendedUsers(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java new file mode 100644 index 00000000..bcd1744b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java @@ -0,0 +1,38 @@ +package inu.codin.codin.domain.report.service; + +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.repository.ReportRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class SuspensionService { + + private final ReportRepository reportRepository; + private final UserRepository userRepository; + + public void releaseSuspendedUsers() { + LocalDateTime now = LocalDateTime.now(); + List suspendedUsers = reportRepository.findSuspendedUsers(now); + + for (ReportEntity report : suspendedUsers) { + Optional userOpt = userRepository.findById(report.getReportedUserId()); + if (userOpt.isEmpty()) continue; + + UserEntity user = userOpt.get(); + user.activateUser(); // 정지 해제 + userRepository.save(user); // DB 반영 + + log.info("유저 {} 정지 해제 완료", user.get_id()); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 73107c16..dd942b8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -78,4 +78,14 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .status(UserStatus.ACTIVE) .build(); } + + public void suspendUser() { + this.status = UserStatus.SUSPENDED; + } + public void disabledUser() { + this.status = UserStatus.DISABLED; + } + public void activateUser() { + this.status = UserStatus.ACTIVE; + } } From d48919149d3c76bdcfedb0ec5ae0ebe72cb2f368 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 17:32:53 +0900 Subject: [PATCH 0403/1002] =?UTF-8?q?feat=20::=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=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 --- .../report/exception/ReportAlreadyExistsException.java | 8 ++++++++ .../report/exception/ReportUnsupportedTypeException.java | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportAlreadyExistsException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportUnsupportedTypeException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportAlreadyExistsException.java b/codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportAlreadyExistsException.java new file mode 100644 index 00000000..67d56da6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportAlreadyExistsException.java @@ -0,0 +1,8 @@ +package inu.codin.codin.domain.report.exception; + +public class ReportAlreadyExistsException extends RuntimeException { + public ReportAlreadyExistsException(String message) { + super(message); + } +} + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportUnsupportedTypeException.java b/codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportUnsupportedTypeException.java new file mode 100644 index 00000000..fe8ec0c4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/exception/ReportUnsupportedTypeException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.report.exception; + +public class ReportUnsupportedTypeException extends RuntimeException { + public ReportUnsupportedTypeException(String message) { + super(message); + } +} From 94ee1dba42e87e6f74281d1aba118440cd5735b7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 17:40:52 +0900 Subject: [PATCH 0404/1002] refactor :: userId Object -> String --- .../codin/domain/report/controller/ReportController.java | 2 +- .../codin/codin/domain/report/service/ReportService.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 113bd796..6e017c14 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -70,7 +70,7 @@ public ResponseEntity getReports( @PreAuthorize("hasRole('ADMIN')") @GetMapping("/user") public ResponseEntity getReportsByUserId( - @RequestParam("userId") @NotNull ObjectId userId) { + @RequestParam("userId") @NotNull String userId) { List userReports = reportService.getReportsByUserId(userId); return ResponseEntity.ok() diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 4638a5d1..4479b4b0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -155,10 +155,13 @@ public List getAllReports(ReportTargetType reportTargetType, } // 특정 유저가 신고 내역 조회 (관리자) - public List getReportsByUserId(ObjectId userId) { + public List getReportsByUserId(String userId) { log.info("특정 유저 신고 내역 조회: userId={}", userId); - List userReports = reportRepository.findByReportingUserId(userId); + ObjectId ObjUserId = new ObjectId(userId); + + + List userReports = reportRepository.findByReportingUserId(ObjUserId); return userReports.stream() .map(ReportResponseDto::from) .collect(Collectors.toList()); From 0fa8fc1bdd36b001e73f027fb66d4aa36d01c2d0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 18:26:23 +0900 Subject: [PATCH 0405/1002] fix :: reportStatus Response Error --- .../codin/codin/domain/report/entity/ReportEntity.java | 3 ++- .../codin/codin/domain/report/entity/ReportStatus.java | 2 +- .../codin/codin/domain/report/service/ReportService.java | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index 8bc1175d..1f12933b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -47,7 +47,8 @@ public ReportEntity(ObjectId reportingUserId, ObjectId reportedUserId, ReportTar this.reportTargetType = reportTargetType; this.reportTargetId = reportTargetId; this.reportType = reportType; - this.reportStatus = ReportStatus.PENDING; + // Null이면 PENDING, 아니면 그대로 유지 + this.reportStatus = reportStatus != null ? reportStatus : ReportStatus.PENDING; this.action = action; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java index 85a8bdd7..d1fa6c5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java @@ -7,7 +7,7 @@ public enum ReportStatus { PENDING("대기"), RESOLVED("처리 완료"); - private final String description; + private String description; ReportStatus(String description) { this.description = description; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 4479b4b0..27e44fcd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -161,8 +161,12 @@ public List getReportsByUserId(String userId) { ObjectId ObjUserId = new ObjectId(userId); - List userReports = reportRepository.findByReportingUserId(ObjUserId); - return userReports.stream() + List reports = reportRepository.findByReportingUserId(ObjUserId); + + log.info("DB에서 가져온 ReportEntity 리스트:"); + reports.forEach(report -> log.info("ID: {}, ReportStatus: {}", report.get_id(), report.getReportStatus())); + + return reports.stream() .map(ReportResponseDto::from) .collect(Collectors.toList()); } From a72ac6465c4faa34698db6b75f6af0e6cf1af336 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 18:50:47 +0900 Subject: [PATCH 0406/1002] =?UTF-8?q?refactor=20::=20Boolean=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/report/service/ReportService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 27e44fcd..0578d40d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -18,13 +18,13 @@ import inu.codin.codin.domain.report.exception.ReportAlreadyExistsException; import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; @@ -70,7 +70,7 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { ); // null 방지: reportExists가 null이면 false로 처리 - if (Boolean.TRUE.equals(reportExists)) { + if (reportExists) { log.warn("중복 신고 발견: reportingUserId={}, reportTargetId={}, reportTargetType={}", userId, reportCreateRequestDto.getReportTargetId(), @@ -173,7 +173,8 @@ public List getReportsByUserId(String userId) { - public void executeReport(ReportExecuteRequestDto requestDto) { + @Transactional + public void resolveReport(ReportExecuteRequestDto requestDto) { log.info("신고 처리 요청: {}", requestDto.getReportId()); ObjectId userId = SecurityUtils.getCurrentUserId(); ObjectId ReportId = new ObjectId(requestDto.getReportId()); @@ -206,6 +207,7 @@ public void executeReport(ReportExecuteRequestDto requestDto) { //유저 Suspended - 정지 상태로 변경 Optional user = userRepository.findById(report.getReportedUserId()); if (user.isEmpty()) throw new NotFoundException("존재하지 않는 회원입니다."); + //영구 정지 if (requestDto.getSuspensionPeriod() == SuspensionPeriod.PERMANENT){ user.get().disabledUser(); From b651f0ce2615a92fc47f43a22dd596329874b947 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 18:51:02 +0900 Subject: [PATCH 0407/1002] =?UTF-8?q?refactor=20::=20method=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/controller/ReportController.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 6e017c14..70b7e76a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,20 +1,15 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportResponseDto; -import inu.codin.codin.domain.report.entity.ReportEntity; -import inu.codin.codin.domain.report.entity.ReportStatus; import inu.codin.codin.domain.report.entity.ReportTargetType; -import inu.codin.codin.domain.report.entity.ReportType; import inu.codin.codin.domain.report.service.ReportService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -85,7 +80,7 @@ public ResponseEntity getReportsByUserId( public ResponseEntity handleReport( @RequestBody ReportExecuteRequestDto reportExecuteRequestDto) { - reportService.executeReport(reportExecuteRequestDto); + reportService.resolveReport(reportExecuteRequestDto); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(200, "(관리자) 신고 처리",null)); From 91a55e02adeadd7fb9bcc4d8d88be89fbd454a84 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 18:51:32 +0900 Subject: [PATCH 0408/1002] =?UTF-8?q?fix=20::=20Suspended=20=EC=9D=B8=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=EB=A7=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/user/entity/UserEntity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index dd942b8c..cd4bca11 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -10,6 +10,7 @@ import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.security.core.parameters.P; @Document(collection = "users") @Getter @@ -86,6 +87,8 @@ public void disabledUser() { this.status = UserStatus.DISABLED; } public void activateUser() { - this.status = UserStatus.ACTIVE; + if ( this.status == UserStatus.SUSPENDED) { + this.status = UserStatus.ACTIVE; + } } } From 30bcb149ec33824714fa98b9622a36d693e6f5af Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 18:59:39 +0900 Subject: [PATCH 0409/1002] =?UTF-8?q?fix=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EB=8C=80=EC=83=81=20Review=20-=20=EC=88=98=EA=B0=95=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/report/entity/ReportTargetType.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java index 301be054..1da26249 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportTargetType.java @@ -7,8 +7,7 @@ public enum ReportTargetType { USER("사용자"), POST("게시물"), COMMENT("댓글"), - REPLY("대댓글"), - REVIEW("수강 후기"); + REPLY("대댓글"); private final String description; From 26463e926b1ddefe428f80cf0256b2967954248d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 19:27:37 +0900 Subject: [PATCH 0410/1002] =?UTF-8?q?refactor=20::=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C=20=EC=8A=A4=EC=BC=80=EC=A5=B4=EB=9F=AC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20(=EB=A7=A4=201=EB=B6=84=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=EC=8B=A4=ED=96=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/scheduler/SuspensionScheduler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java index 4dea4c9f..d725a362 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java @@ -13,9 +13,10 @@ public class SuspensionScheduler { private final SuspensionService suspensionService; - @Scheduled(cron = "0 0 * * * ?") // 매 정시마다 실행 + //@Scheduled(cron = "0 0 * * * ?") // 매 정시마다 실행 + @Scheduled(cron = "0 * * * * ?") // 매 1분마다 실행 public void checkAndReleaseSuspendedUsers() { - log.info("🚀 정지 해제 스케줄러 실행..."); + log.info("정지 해제 스케줄러 실행..."); suspensionService.releaseSuspendedUsers(); } } From b41bc0fa702d19383cdf11ebfdf13ac42f5b2cee Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 19:28:15 +0900 Subject: [PATCH 0411/1002] =?UTF-8?q?fix=20::=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=EA=B8=B0=ED=95=9C=20=EC=8B=9C=EA=B0=84=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/dto/request/ReportExecuteRequestDto.java | 6 ------ .../codin/codin/domain/report/service/ReportService.java | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java index e4fc6caa..0d43f8d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java @@ -15,10 +15,4 @@ public class ReportExecuteRequestDto { private String comment; // 신고 처리에 대한 코멘트 private SuspensionPeriod suspensionPeriod; // 정지 기간 - public LocalDateTime getSuspensionEndDate() { - if (suspensionPeriod == null) { - return null; - } - return LocalDateTime.now().plusDays(suspensionPeriod.getDays()); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 0578d40d..bbbea8d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -189,12 +190,13 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { throw new IllegalStateException("이미 처리된 신고입니다."); } + // 신고 처리 정보 생성 ReportEntity.ReportActionEntity action = ReportEntity.ReportActionEntity.builder() .actionTakenById(userId) .comment(requestDto.getComment()) .suspensionPeriod(requestDto.getSuspensionPeriod()) - .suspensionEndDate(requestDto.getSuspensionEndDate()) + .suspensionEndDate(LocalDateTime.now().plusDays(requestDto.getSuspensionPeriod().getDays())) .build(); // 엔티티 내부에서 업데이트 메서드 호출 From 37d8eaa2a77b7f57e4f8d6521faf1bde02cc50fd Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 19:28:30 +0900 Subject: [PATCH 0412/1002] =?UTF-8?q?fix=20::=20reporteduser=20=EB=AF=B8?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/dto/response/ReportResponseDto.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java index dbcc29cf..6f81fa67 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java @@ -80,6 +80,7 @@ public static ReportResponseDto from(ReportEntity reportEntity) { return ReportResponseDto.builder() ._id(reportEntity.get_id().toString()) .reportingUserId(reportEntity.getReportingUserId().toString()) + .reportedUserId(reportEntity.getReportedUserId().toString()) .reportTargetType(reportEntity.getReportTargetType()) .reportTargetId(reportEntity.getReportTargetId().toString()) .reportType(reportEntity.getReportType()) From 79b11ed732d249a24f5e62d4921be454749746f7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 29 Jan 2025 19:45:52 +0900 Subject: [PATCH 0413/1002] =?UTF-8?q?fix=20::=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(=20=EC=A0=95=EC=A7=80=20->=20Suspended,=20=EC=A0=95=EC=A7=80?= =?UTF-8?q?=20=ED=95=B4=EC=A0=9C=20->=20resolved)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/entity/ReportEntity.java | 12 +++++++++--- .../codin/domain/report/entity/ReportStatus.java | 1 + .../domain/report/repository/ReportRepository.java | 3 ++- .../codin/domain/report/service/ReportService.java | 5 ++--- .../domain/report/service/SuspensionService.java | 4 ++++ 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index 1f12933b..449e28ac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -60,17 +60,23 @@ public ReportEntity updateReport(ReportActionEntity action) { .reportTargetType(this.reportTargetType) .reportTargetId(this.reportTargetId) .reportType(this.reportType) - .reportStatus(ReportStatus.RESOLVED) // 상태 변경 + .reportStatus(ReportStatus.SUSPENDED) // 상태 변경 .action(action) // 신고 처리 정보 업데이트 .build(); } - public void updateReportResolved(ReportActionEntity action) { - this.reportStatus = ReportStatus.RESOLVED; // 상태 변경 + public void updateReportSuspended(ReportActionEntity action) { + this.reportStatus = ReportStatus.SUSPENDED; // 상태 변경 this.action = action; // 신고 처리 정보 업데이트 } + public void updateReportResolved() { + this.reportStatus = ReportStatus.RESOLVED; // 상태 변경 + } + + + @Getter public static class ReportActionEntity extends BaseTimeEntity { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java index d1fa6c5d..4ff23a5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportStatus.java @@ -5,6 +5,7 @@ @Getter public enum ReportStatus { PENDING("대기"), + SUSPENDED("정지 상태"), RESOLVED("처리 완료"); private String description; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java index 79390241..13b17749 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -31,6 +31,7 @@ public interface ReportRepository extends MongoRepository findByReportingUserId(ObjectId userId); // 현재 정지 상태이며, 정지 종료일이 아직 남아있는 유저 조회 - @Query("{'action.suspensionEndDate': { $gt: ?0 }}") + //정지 종료일(suspensionEndDate)이 현재 날짜보다 이전($lt) + @Query("{'reportStatus': 'SUSPENDED', 'action.suspensionEndDate': { $lt: ?0 }}") List findSuspendedUsers(LocalDateTime now); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index bbbea8d2..d5a75973 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -186,11 +186,10 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { .orElseThrow(() -> new IllegalArgumentException("해당 신고가 존재하지 않습니다. ID: " + requestDto.getReportId())); // 이미 처리된 신고인지 확인 - if (report.getReportStatus() == ReportStatus.RESOLVED) { + if (report.getReportStatus() == ReportStatus.SUSPENDED || report.getReportStatus() == ReportStatus.RESOLVED) { throw new IllegalStateException("이미 처리된 신고입니다."); } - // 신고 처리 정보 생성 ReportEntity.ReportActionEntity action = ReportEntity.ReportActionEntity.builder() .actionTakenById(userId) @@ -203,7 +202,7 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { //ReportEntity updatedReport = report.updateReport(action); // 기존 객체의 필드를 직접 수정 (새 객체 생성 X) - report.updateReportResolved(action); + report.updateReportSuspended(action); //유저 Suspended - 정지 상태로 변경 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java index bcd1744b..7461643b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java @@ -30,7 +30,11 @@ public void releaseSuspendedUsers() { UserEntity user = userOpt.get(); user.activateUser(); // 정지 해제 + report.updateReportResolved(); + userRepository.save(user); // DB 반영 + reportRepository.save(report); + log.info("유저 {} 정지 해제 완료", user.get_id()); } From a3bd3cafdcf2ef1a3e433d6e766c7fb99ed7ae6a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 30 Jan 2025 19:18:58 +0900 Subject: [PATCH 0414/1002] =?UTF-8?q?fix=20::=20Reply=20UserInfo=20-=20isL?= =?UTF-8?q?iked=20False=20=EB=9C=A8=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/like/service/LikeService.java | 2 +- .../domain/post/domain/reply/service/ReplyCommentService.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index e2ea7a7e..bf44ce60 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -116,7 +116,7 @@ public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ } public boolean isReplyLiked(ObjectId replyId, ObjectId userId) { - return redisLikeService.isLiked(LikeType.COMMENT, replyId, userId); + return redisLikeService.isLiked(LikeType.REPLY, replyId, userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index bfcc107e..2e53b65d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -138,6 +138,7 @@ public List getRepliesByCommentId(ObjectId commentId) { } public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); + //log.info("대댓글 userInfo - replyId: {}, userId: {}", replyId, userId); return CommentResponseDTO.UserInfo.builder() .isLike(likeService.isReplyLiked(replyId, userId)) .build(); From b36da865a2844fb693733b1711951002f41037d9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 1 Feb 2025 01:07:45 +0900 Subject: [PATCH 0415/1002] =?UTF-8?q?fix=20:=20=ED=98=84=EC=9E=AC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EB=90=9C=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserController.java | 5 ++--- .../user/dto/request/UserDeleteRequestDto.java | 12 ------------ .../domain/user/repository/UserRepository.java | 2 ++ .../codin/domain/user/service/UserService.java | 14 +++++--------- 4 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index ae61b3c8..9223fb31 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.service.UserService; @@ -67,8 +66,8 @@ public ResponseEntity> getUserComment(@RequestP summary = "회원 탈퇴" ) @DeleteMapping - public ResponseEntity> deleteUser(@RequestBody @Valid UserDeleteRequestDto userDeleteRequestDto){ - userService.deleteUser(userDeleteRequestDto); + public ResponseEntity> deleteUser(){ + userService.deleteUser(); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 탈퇴 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java deleted file mode 100644 index 56d10322..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserDeleteRequestDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package inu.codin.codin.domain.user.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; - -@Getter -public class UserDeleteRequestDto { - @Schema(description = "이메일 주소", example = "codin@inu.ac.kr") - @NotBlank - private String email; -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 8cea3e39..182da915 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -10,6 +10,8 @@ @Repository public interface UserRepository extends MongoRepository { + @Query("{'_id': ?0, 'deletedAt': null}") + Optional findByUserId(ObjectId userId); @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByEmail(String email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index bb60393b..e6acb88d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -14,7 +14,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; -import inu.codin.codin.domain.user.dto.request.UserDeleteRequestDto; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.entity.UserEntity; @@ -118,20 +117,17 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } } - public void deleteUser(UserDeleteRequestDto userDeleteRequestDto) { - log.info("[회원 탈퇴 요청] 이메일: {}", userDeleteRequestDto.getEmail()); + public void deleteUser() { + ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = userRepository.findByEmail(userDeleteRequestDto.getEmail()) + UserEntity user = userRepository.findByUserId(userId) .orElseThrow(() -> { - log.warn("[회원 탈퇴 실패] 유저 정보 없음: {}", userDeleteRequestDto.getEmail()); + log.warn("[회원 탈퇴 실패] 유저 정보 없음: {}", userId); return new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다."); }); - - SecurityUtils.validateUser(user.get_id()); user.delete(); userRepository.save(user); - - log.info("[회원 탈퇴 성공] 이메일: {}", userDeleteRequestDto.getEmail()); + log.info("[회원 탈퇴 성공] 이메일: {}", userId); } public UserInfoResponseDto getUserInfo() { From 3fd08d680a301cf80cc49a97c994ee1caca5760f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 1 Feb 2025 02:18:59 +0900 Subject: [PATCH 0416/1002] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/service/ChatRoomService.java | 10 +++ .../chatting/service/ChattingService.java | 12 ++-- .../notification/NotificationController.java | 11 ++++ .../dto/NotificationListResponseDto.java | 41 ++++++++++++ .../entity/NotificationEntity.java | 10 ++- .../repository/NotificationRepository.java | 4 ++ .../service/NotificationService.java | 62 ++++++++++++------- .../comment/service/CommentService.java | 2 +- .../reply/service/ReplyCommentService.java | 4 +- .../codin/domain/user/entity/UserEntity.java | 1 - 10 files changed, 124 insertions(+), 33 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 32ad9f30..721d8fdd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -4,11 +4,13 @@ import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; +import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; @@ -26,6 +28,8 @@ @Slf4j public class ChatRoomService { + private final NotificationService notificationService; + private final ChatRoomRepository chatRoomRepository; private final CustomChattingRepository customChattingRepository; private final UserRepository userRepository; @@ -48,6 +52,12 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat chatRoomRepository.save(chatRoom); log.info("[채팅방 생성 완료] 채팅방 ID: {}, 송신자 ID: {}, 수신자 ID: {}", chatRoom.get_id(), senderId, chatRoomCreateRequestDto.getReceiverId()); + for (Participants participant : chatRoom.getParticipants()){ + if (participant.getUserId() != senderId && participant.isNotificationsEnabled()){ + notificationService.sendNotificationMessageByChat(participant.getUserId(), chatRoom); + } + } + Map response = new HashMap<>(); response.put("chatRoomId", chatRoom.get_id().toString()); log.info("[채팅방 생성 응답] 생성된 채팅방 ID: {}", chatRoom.get_id()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index b9d53c9c..5bd0c210 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -52,12 +52,12 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq chattingRepository.save(chatting); log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); - //Receiver의 알림 체크 후, 메세지 전송 - for (Participants participant : chatRoom.getParticipants()){ - if (participant.getUserId() != userId && participant.isNotificationsEnabled()){ - notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom); - } - } +// //Receiver의 알림 체크 후, 메세지 전송 +// for (Participants participant : chatRoom.getParticipants()){ +// if (participant.getUserId() != userId && participant.isNotificationsEnabled()){ +// notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom); +// } +// } return ChattingResponseDto.of(chatting); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java index f198b853..b0490c88 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.notification; +import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.notification.service.NotificationService; import io.swagger.v3.oas.annotations.Operation; @@ -17,6 +18,16 @@ public class NotificationController { private final NotificationService notificationService; + @Operation( + summary = "알림 내역 반환" + ) + @GetMapping() + public ResponseEntity> getNotifications(){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "알림 내역 반환 완료", + notificationService.getNotification())); + } + @Operation(summary = "알림 읽기") @GetMapping("/{notificationId}") public ResponseEntity> readNotification(@PathVariable String notificationId){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java new file mode 100644 index 00000000..ea8ac8fb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.notification.dto; + +import inu.codin.codin.domain.notification.entity.NotificationEntity; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; + +@Getter +public class NotificationListResponseDto { + @Id + @NotBlank + private String id; + + private String targetId; + + private String title; + + private String message; + + private boolean isRead = false; + + @Builder + public NotificationListResponseDto(String id, String targetId, String title, String message, boolean isRead) { + this.id = id; + this.targetId = targetId; + this.title = title; + this.message = message; + this.isRead = isRead; + } + + public static NotificationListResponseDto of(NotificationEntity notificationEntity){ + return NotificationListResponseDto.builder() + .id(notificationEntity.getId().toString()) + .title(notificationEntity.getTitle()) + .message(notificationEntity.getMessage()) + .targetId(notificationEntity.getTargetId().toString()) + .isRead(notificationEntity.isRead()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index b1b013c6..a6f98f05 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -3,7 +3,10 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.user.entity.UserEntity; import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.DBRef; @@ -13,6 +16,8 @@ @Document(collection = "notification") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter public class NotificationEntity extends BaseTimeEntity { @Id @NotBlank @@ -21,6 +26,8 @@ public class NotificationEntity extends BaseTimeEntity { @DBRef(lazy = true) private UserEntity user; + private ObjectId targetId; + private String title; private String message; @@ -36,8 +43,9 @@ public class NotificationEntity extends BaseTimeEntity { private String priority; @Builder - public NotificationEntity(UserEntity user, String title, String message, String type, String priority) { + public NotificationEntity(UserEntity user, ObjectId targetId, String title, String message, String type, String priority) { this.user = user; + this.targetId = targetId; this.title = title; this.message = message; this.type = type; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java index f033952b..8256a6d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java @@ -7,8 +7,12 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface NotificationRepository extends MongoRepository { @Query("{ 'user': ?0, 'isRead': false, deletedAt: null }") long countUnreadNotificationsByUser(UserEntity user); + + List findAllByUser(UserEntity user); } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 9268b27f..ac0cddb9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,15 +1,18 @@ package inu.codin.codin.domain.notification.service; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.notification.dto.NotificationListResponseDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.like.entity.LikeType; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -23,6 +26,7 @@ import org.springframework.stereotype.Service; import java.util.HashMap; +import java.util.List; import java.util.Map; @Service @@ -37,10 +41,10 @@ public class NotificationService { private final ReplyCommentRepository replyCommentRepository; private final FcmService fcmService; - private final String NOTI_COMMENT_TITLE = "누군가가 내 게시글에 댓글을 달았어요!"; - private final String NOTI_REPLY_TITLE = "누군가가 내 댓글에 답글을 달았어요!"; - private final String NOTI_LIKE_TITLE = "나에게 첫 좋아요가 달렸어요!"; - private final String NOTI_CHAT_TITLE = "에서 연락이 왔어요!"; + private final String NOTI_COMMENT = "댓글이 달렸습니다: "; + private final String NOTI_REPLY = "대댓글이 달렸습니다: "; + private final String NOTI_LIKE = ""; + private final String NOTI_CHAT = "새로운 채팅이 있습니다."; /** @@ -71,10 +75,10 @@ public void sendFcmMessageToUser(String title, String body, Map try { fcmService.sendFcmMessage(msgDto); log.info("[sendFcmMessage] 알림 전송 성공"); - saveNotificationLog(msgDto); } catch (Exception e) { log.error("[sendFcmMessage] 알림 전송 실패 : {}", e.getMessage()); } + saveNotificationLog(msgDto, data); } /** @@ -104,11 +108,12 @@ public void sendFcmMessageToTopic(String title, String body, Map } // 알림 로그를 저장하는 로직 (특정 사용자 대상) - private void saveNotificationLog(FcmMessageUserDto msgDto) { + private void saveNotificationLog(FcmMessageUserDto msgDto, Map data) { NotificationEntity notificationEntity = NotificationEntity.builder() .user(msgDto.getUser()) .title(msgDto.getTitle()) .message(msgDto.getBody()) + .targetId(new ObjectId(data.get("id"))) .type("push") .priority("high") .build(); @@ -126,20 +131,22 @@ private void saveNotificationLog(FcmMessageTopicDto msgDto) { notificationRepository.save(notificationEntity); } - public void sendNotificationMessageByComment(ObjectId userId, String postId, String content) { + public void sendNotificationMessageByComment(PostCategory postCategory, ObjectId userId, String postId, String content) { UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); Map post = new HashMap<>(); - post.put("postId", postId); - sendFcmMessageToUser(NOTI_COMMENT_TITLE, content, post, user); + post.put("id", postId); + String title = postCategory.getDescription().split("_")[0]; + sendFcmMessageToUser(title, NOTI_COMMENT+content, post, user); } - public void sendNotificationMessageByReply(ObjectId userId, String postId, String content) { + public void sendNotificationMessageByReply(PostCategory postCategory, ObjectId userId, String postId, String content) { UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); Map post = new HashMap<>(); - post.put("postId", postId); - sendFcmMessageToUser(NOTI_REPLY_TITLE, content, post, user); + post.put("id", postId); + String title = postCategory.getDescription().split("_")[0]; + sendFcmMessageToUser(title, NOTI_REPLY+content, post, user); } public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { @@ -150,8 +157,8 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { UserEntity user = userRepository.findById(postEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map post = new HashMap<>(); - post.put("postId", postEntity.get_id().toString()); - sendFcmMessageToUser(NOTI_LIKE_TITLE, "내 게시글 보러 가기", post, user); + post.put("id", postEntity.get_id().toString()); + sendFcmMessageToUser(NOTI_LIKE, "내 게시글 보러 가기", post, user); } case REPLY -> { ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(id) @@ -163,8 +170,8 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { UserEntity user = userRepository.findById(replyCommentEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map post = new HashMap<>(); - post.put("postId", postEntity.get_id().toString()); - sendFcmMessageToUser(NOTI_LIKE_TITLE, "내 답글 보러 가기", post, user); + post.put("id", postEntity.get_id().toString()); + sendFcmMessageToUser(NOTI_LIKE, "내 답글 보러 가기", post, user); } case COMMENT -> { CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(id) @@ -174,18 +181,18 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { UserEntity user = userRepository.findById(commentEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map post = new HashMap<>(); - post.put("postId", postEntity.get_id().toString()); - sendFcmMessageToUser(NOTI_LIKE_TITLE, "내 댓글 보러 가기", post, user); + post.put("id", postEntity.get_id().toString()); + sendFcmMessageToUser(NOTI_LIKE, "내 댓글 보러 가기", post, user); } } } - public void sendNotificationMessageByChat(ObjectId userId, ChattingRequestDto chattingRequestDto, ChatRoom chatRoom) { + public void sendNotificationMessageByChat(ObjectId userId, ChatRoom chatRoom) { UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map chat = new HashMap<>(); - chat.put("chatRoomId", chatRoom.get_id().toString()); - sendFcmMessageToUser(chatRoom.getRoomName()+NOTI_CHAT_TITLE, chattingRequestDto.getContent(), chat, user); + chat.put("id", chatRoom.get_id().toString()); + sendFcmMessageToUser("익명 채팅방", NOTI_CHAT, chat, user); } public void readNotification(String notificationId){ @@ -194,4 +201,15 @@ public void readNotification(String notificationId){ notificationEntity.markAsRead(); notificationRepository.save(notificationEntity); } + + public List getNotification() { + //todo 유저에게 맞는 토픽 알림들도 반환 + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다")); + List notifications = notificationRepository.findAllByUser(user); + return notifications.stream() + .map(NotificationListResponseDto::of) + .toList(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 0d016d31..ee7c8a69 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -66,7 +66,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { redisService.applyBestScore(1, postId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); - notificationService.sendNotificationMessageByComment(post.getUserId(), post.get_id().toString(), comment.getContent()); + if (userId != post.getUserId()) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); } // 댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 15b48ce9..eb4a7975 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -13,6 +13,7 @@ import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.response.UserDto; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -72,8 +73,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - - notificationService.sendNotificationMessageByReply(comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (userId != comment.getUserId()) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } // 대댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 00ac275a..dfccd2d9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -5,7 +5,6 @@ import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.domain.notification.entity.NotificationPreference; -import inu.codin.codin.domain.user.dto.request.UserUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; From 5b4d8fcfe4c507079555efb96794ad3384e79287 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 1 Feb 2025 02:29:16 +0900 Subject: [PATCH 0417/1002] =?UTF-8?q?fix=20:=20=ED=95=AD=EB=93=B1=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20equals=20=ED=95=A8=EC=88=98=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 2 +- .../domain/post/domain/comment/service/CommentService.java | 3 ++- .../domain/post/domain/reply/service/ReplyCommentService.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 721d8fdd..8bd44ccb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -53,7 +53,7 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat log.info("[채팅방 생성 완료] 채팅방 ID: {}, 송신자 ID: {}, 수신자 ID: {}", chatRoom.get_id(), senderId, chatRoomCreateRequestDto.getReceiverId()); for (Participants participant : chatRoom.getParticipants()){ - if (participant.getUserId() != senderId && participant.isNotificationsEnabled()){ + if (!participant.getUserId().equals(senderId) && participant.isNotificationsEnabled()){ notificationService.sendNotificationMessageByChat(participant.getUserId(), chatRoom); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index ee7c8a69..734aa803 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -66,7 +66,8 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { redisService.applyBestScore(1, postId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); - if (userId != post.getUserId()) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); + } // 댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index eb4a7975..c33a4375 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -73,7 +73,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - if (userId != comment.getUserId()) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } // 대댓글 삭제 (Soft Delete) From d5c5582d69d6ba6a399afc1787c7c51563cc907f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 1 Feb 2025 02:30:35 +0900 Subject: [PATCH 0418/1002] =?UTF-8?q?docs=20:=20Unused=20import=20?= =?UTF-8?q?=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatting/service/ChattingService.java | 2 -- .../codin/domain/notification/service/NotificationService.java | 1 - .../domain/post/domain/reply/service/ReplyCommentService.java | 1 - 3 files changed, 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 5bd0c210..e454eb6c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; @@ -24,7 +23,6 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index ac0cddb9..07996bac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,7 +3,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.notification.dto.NotificationListResponseDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index c33a4375..cd89ad47 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -13,7 +13,6 @@ import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.response.UserDto; -import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; From 9601afd206744a54063cda733c31884644bc7514 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 1 Feb 2025 21:48:15 +0900 Subject: [PATCH 0419/1002] =?UTF-8?q?feat=20:=20=ED=83=88=ED=87=B4?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=EC=A0=80=EC=97=90=20=EB=8C=80=ED=95=98?= =?UTF-8?q?=EC=97=AC=20nickname=EC=9D=84=20"=ED=83=88=ED=87=B4=ED=95=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90"=EB=A1=9C=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/CommentResponseDTO.java | 35 ++++++++++++++++ .../comment/service/CommentService.java | 38 ++++++++--------- .../reply/service/ReplyCommentService.java | 42 +++++++++---------- .../domain/post/dto/response/UserDto.java | 2 +- .../domain/post/service/PostService.java | 24 ++++++----- .../domain/user/service/UserService.java | 4 +- 6 files changed, 90 insertions(+), 55 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 72344466..81fa537d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.post.domain.comment.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -50,6 +52,7 @@ public class CommentResponseDTO { @Schema(description = "해당 댓글 대한 유저 반응 여부") private final UserInfo userInfo; + @Builder public CommentResponseDTO(String _id, String userId, String content, String nickname, String userImageUrl, Boolean anonymous , List replies, int likeCount, @@ -67,6 +70,38 @@ public CommentResponseDTO(String _id, String userId, String content, this.userInfo = userInfo; } + public static CommentResponseDTO commentOf(CommentEntity commentEntity, String nickname, String userImageUrl, List repliesByCommentId, int like, UserInfo userInfoAboutPost){ + return CommentResponseDTO.builder() + ._id(commentEntity.get_id().toString()) + .userId(commentEntity.getUserId().toString()) + .content(commentEntity.getContent()) + .nickname(nickname) + .userImageUrl(userImageUrl) + .anonymous(commentEntity.isAnonymous()) + .replies(repliesByCommentId) + .likeCount(like) + .isDeleted(commentEntity.getDeletedAt() != null) + .createdAt(commentEntity.getCreatedAt()) + .userInfo(userInfoAboutPost) + .build(); + } + + public static CommentResponseDTO replyOf(ReplyCommentEntity replyCommentEntity, String nickname, String userImageUrl, List repliesByCommentId, int like, UserInfo userInfoAboutPost){ + return CommentResponseDTO.builder() + ._id(replyCommentEntity.get_id().toString()) + .userId(replyCommentEntity.getUserId().toString()) + .content(replyCommentEntity.getContent()) + .nickname(nickname) + .userImageUrl(userImageUrl) + .anonymous(replyCommentEntity.isAnonymous()) + .replies(repliesByCommentId) + .likeCount(like) + .isDeleted(replyCommentEntity.getDeletedAt() != null) + .createdAt(replyCommentEntity.getCreatedAt()) + .userInfo(userInfoAboutPost) + .build(); + } + @Getter public static class UserInfo { private final boolean isLike; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 32ff5447..19301bcb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -105,38 +105,38 @@ public List getCommentsByPostId(String id) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); List comments = commentRepository.findByPostId(postId); + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); + Map userMap = userRepository.findAllById( comments.stream() - .filter(commentEntity -> !commentEntity.isAnonymous()) .map(CommentEntity::getUserId) .distinct() .toList() ).stream() .collect(Collectors.toMap( UserEntity::get_id, - user -> new UserDto(user.getNickname(), user.getProfileImageUrl()) // DTO 생성 + user -> new UserDto(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) )); - String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); return comments.stream() .map(comment -> { - String nickname = comment.isAnonymous() ? "익명" : userMap.get(comment.getUserId()).nickname(); - String userImageUrl = comment.isAnonymous() ? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); - boolean isDeleted = comment.getDeletedAt() != null; - return new CommentResponseDTO( - comment.get_id().toString(), - comment.getUserId().toString(), - comment.getContent(), - nickname, - userImageUrl, - comment.isAnonymous(), - replyCommentService.getRepliesByCommentId(comment.get_id()), // 대댓글 조회 - likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), // 댓글 좋아요 수 - isDeleted, - comment.getCreatedAt(), - getUserInfoAboutPost(comment.get_id()) - ); + UserDto userDto = userMap.get(comment.getUserId()); + + String nickname; + String userImageUrl; + + if (userDto.isDeleted()){ + nickname = "탈퇴한 사용자"; + userImageUrl = defaultImageUrl; + } else { + nickname = comment.isAnonymous()? "익명" : userMap.get(comment.getUserId()).nickname(); + userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); + } + return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, + replyCommentService.getRepliesByCommentId(comment.get_id()), + likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), + getUserInfoAboutPost(comment.get_id())); }) .toList(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 2e53b65d..6edb2fd1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -101,39 +101,35 @@ public void softDeleteReply(String replyId) { // 특정 댓글의 대댓글 조회 public List getRepliesByCommentId(ObjectId commentId) { - List replies = replyCommentRepository.findByCommentId(commentId); + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); Map userMap = userRepository.findAllById( replies.stream() - .filter(replyCommentEntity -> !replyCommentEntity.isAnonymous()) .map(ReplyCommentEntity::getUserId).distinct().toList() ).stream() - .collect(Collectors.toMap( - UserEntity::get_id, - user -> new UserDto(user.getNickname(), user.getProfileImageUrl()))); - - String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); + .collect(Collectors.toMap( + UserEntity::get_id, + user -> new UserDto(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) + )); return replies.stream() .map(reply -> { - String nickname = reply.isAnonymous() ? "익명" : userMap.get(reply.getUserId()).nickname(); - String userImageUrl = reply.isAnonymous() ? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); - boolean isDeleted = reply.getDeletedAt() != null; - return new CommentResponseDTO( - reply.get_id().toString(), - reply.getUserId().toString(), - reply.getContent(), - nickname, - userImageUrl, - reply.isAnonymous(), - List.of(), //대댓글은 대댓글이 없음 + UserDto userDto = userMap.get(reply.getUserId()); + + String nickname; + String userImageUrl; + + if (userDto.isDeleted()){ + nickname = "탈퇴한 사용자"; + userImageUrl = defaultImageUrl; + } else { + nickname = reply.isAnonymous()? "익명" : userMap.get(reply.getUserId()).nickname(); + userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); + } + return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.get_id()), // 대댓글 좋아요 수 - isDeleted, - reply.getCreatedAt(), - getUserInfoAboutPost(reply.get_id()) - - ); + getUserInfoAboutPost(reply.get_id())); }).toList(); } public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java index e94a2894..022b8ad8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java @@ -1,4 +1,4 @@ package inu.codin.codin.domain.post.dto.response; -public record UserDto(String nickname, String imageUrl) { +public record UserDto(String nickname, String imageUrl, boolean isDeleted) { } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 79fce4db..db4a6f8e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -143,17 +143,23 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { String nickname; String userImageUrl; - //Post 관련 인자 처리 - if (post.isAnonymous()){ - nickname = "익명"; - userImageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 + UserEntity user = userRepository.findById(post.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + if (user.getDeletedAt() == null){ + if (post.isAnonymous()){ + nickname = "익명"; + userImageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 + } else { + nickname = user.getNickname(); + userImageUrl = user.getProfileImageUrl(); + } } else { - UserEntity user = userRepository.findById(post.getUserId()) - .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); - nickname = user.getNickname(); - userImageUrl = user.getProfileImageUrl(); + nickname = "탈퇴한 사용자"; + userImageUrl = s3Service.getDefaultProfileImageUrl(); } + //Post 관련 인자 처리 + int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); int scrapCount = scrapService.getScrapCount(post.get_id()); int hitsCount = hitsService.getHitsCount(post.get_id()); @@ -187,8 +193,6 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { pollInfo ); } - - log.info("일반 게시물 상세정보 생성 성공 PostId: {}", post.get_id()); // 일반 게시물 처리 return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index e6acb88d..9f4d0edb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -123,11 +123,11 @@ public void deleteUser() { UserEntity user = userRepository.findByUserId(userId) .orElseThrow(() -> { log.warn("[회원 탈퇴 실패] 유저 정보 없음: {}", userId); - return new NotFoundException("해당 이메일에 대한 유저 정보를 찾을 수 없습니다."); + return new NotFoundException("해당 id에 대한 유저 정보를 찾을 수 없습니다."); }); user.delete(); userRepository.save(user); - log.info("[회원 탈퇴 성공] 이메일: {}", userId); + log.info("[회원 탈퇴 성공] _id: {}", userId); } public UserInfoResponseDto getUserInfo() { From 7124650ddf03ba4f6ac410a722cdffeba745e8fc Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 4 Feb 2025 16:46:42 +0900 Subject: [PATCH 0420/1002] =?UTF-8?q?feat=20::=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=EC=97=90=20=EB=88=84=EC=A0=81=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=ED=9A=9F=EC=88=98=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EB=AC=BC=20=EC=8B=A0=EA=B3=A0=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PostDetailResponseDTO.java | 13 ++++-- .../response/PostPollDetailResponseDTO.java | 2 +- .../codin/domain/post/entity/PostEntity.java | 11 ++++- .../domain/post/service/PostService.java | 5 ++- .../report/controller/ReportController.java | 20 ++++----- .../response/ReportSummaryResponseDTO.java | 18 ++++++++ .../report/repository/ReportRepository.java | 10 ++++- .../domain/report/service/ReportService.java | 44 ++++++++++++++++--- 8 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 64d87bb1..929c824f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -59,6 +59,10 @@ public class PostDetailResponseDTO { @Schema(description = "조회수", example = "0") private final int hits; + private final int reportCount; + + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") private final LocalDateTime createdAt; @@ -67,7 +71,7 @@ public class PostDetailResponseDTO { private final UserInfo userInfo; public PostDetailResponseDTO(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, - boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfo userInfo){ + boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, int reportCount, UserInfo userInfo){ this.userId = userId; this._id = _id; this.title = title; @@ -82,6 +86,7 @@ public PostDetailResponseDTO(String userId, String _id, String title, String con this.commentCount = commentCount; this.hits = hits; this.createdAt = createdAt; + this.reportCount = reportCount; this.userInfo = userInfo; } @@ -97,7 +102,7 @@ public UserInfo(boolean isLike, boolean isScrap) { } } - public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount, UserInfo userInfo) { + public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount, int reportCount ,UserInfo userInfo) { return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), @@ -113,8 +118,8 @@ public static PostDetailResponseDTO of(PostEntity post, String nickname, String hitsCount, post.getCreatedAt(), commentCount, - userInfo - ); + reportCount, + userInfo); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 568829c5..0fe70c2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -16,7 +16,7 @@ public class PostPollDetailResponseDTO extends PostDetailResponseDTO { public PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfo poll) { super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), - baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); + baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getReportCount(), baseDTO.getUserInfo()); this.poll = poll; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index c45035e1..937e2f78 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -32,8 +32,11 @@ public class PostEntity extends BaseTimeEntity { private int likeCount = 0; // 좋아요 카운트 (redis) private int scrapCount = 0; // 스크랩 카운트 (redis) + private Integer reportCount = 0; // 신고 카운트 + @Builder - public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus, int commentCount, int likeCount, int scrapCount) { + public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus, + int commentCount, int likeCount, int scrapCount, Integer reportCount) { this._id = _id; this.userId = userId; this.title = title; @@ -45,6 +48,7 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri this.commentCount = commentCount; this.likeCount = likeCount; this.scrapCount = scrapCount; + this.reportCount = (reportCount == null) ? 0 : reportCount; } public void updatePostContent(String content, List postImageUrls) { @@ -82,5 +86,10 @@ public void updateScrapCount(int scrapCount) { this.scrapCount=scrapCount; } + //신고 수 업데이트 + public void updateReportCount(int reportCount) { + this.reportCount=reportCount; + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 79fce4db..89bafd6d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -158,6 +158,7 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { int scrapCount = scrapService.getScrapCount(post.get_id()); int hitsCount = hitsService.getHitsCount(post.get_id()); int commentCount = post.getCommentCount(); + int reportCount = post.getReportCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -183,14 +184,14 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { //게시물 + 투표 DTO 생성 return PostPollDetailResponseDTO.of( - PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), + PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, reportCount, userInfo), pollInfo ); } log.info("일반 게시물 상세정보 생성 성공 PostId: {}", post.get_id()); // 일반 게시물 처리 - return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, reportCount ,userInfo); } // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 70b7e76a..32026631 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportResponseDto; +import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.service.ReportService; import io.swagger.v3.oas.annotations.Operation; @@ -23,7 +24,7 @@ public class ReportController { private final ReportService reportService; - //신고 작성 + //(User)신고 작성 /** User -> User , Post, Comment, Reply **/ @@ -35,16 +36,15 @@ public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto .body(new SingleResponse<>(201, "신고 생성 완료", null)); } - //신고 목록 조회 (관리자) - /*** - 전체조회 - ReportType 별 조회 - + 특정 신고횟수 이상 조회 - */ - - //특정 유저 신고 상세 조회 (관리자) + //(User) 특정 게시물의 신고 정보 조회 API + @Operation(summary = "특정 게시물의 신고 내역 조회") + @GetMapping("/summary/{reportTargetId}") + public ResponseEntity getReportSummary(@PathVariable String reportTargetId) { + ReportSummaryResponseDTO reportSummary = reportService.getReportSummary(reportTargetId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "특정 게시물의 신고 내역 조회 완료", reportSummary)); + } - //신고 처리 (관리자) // 특정 신고 타입 목록 조회 (관리자) @Operation(summary = "특정 신고 타입 목록 조회 (관리자)") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java new file mode 100644 index 00000000..34957f3c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java @@ -0,0 +1,18 @@ +package inu.codin.codin.domain.report.dto.response; + +import inu.codin.codin.domain.report.entity.ReportType; +import lombok.Getter; +import org.bson.types.ObjectId; + +import java.util.Map; + +@Getter +public class ReportSummaryResponseDTO { + private final Map reportTypeCounts; + + public ReportSummaryResponseDTO(Map reportTypeCounts) { + this.reportTypeCounts = reportTypeCounts; + } +} + + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java index 13b17749..81bd245d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.entity.ReportType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import org.bson.types.ObjectId; @@ -17,8 +18,7 @@ @Repository public interface ReportRepository extends MongoRepository { - @Query("{'reportingUserId': ?0, 'reportTargetId': ?1, 'reportTargetType': ?2}") - Boolean existsByReportingUserIdAndReportTargetIdAndReportTargetType(ObjectId reportingUserId, ObjectId reportTargetId, ReportTargetType reportTargetType); + boolean existsByReportingUserIdAndReportTargetIdAndReportTargetType(ObjectId reportingUserId, ObjectId reportTargetId, ReportTargetType reportTargetType); List findByReportTargetType(ReportTargetType reportTargetType); @@ -34,4 +34,10 @@ public interface ReportRepository extends MongoRepository findSuspendedUsers(LocalDateTime now); + + // 특정 게시물의 전체 신고 개수 + int countByReportTargetId(ObjectId reportTargetId); + + // 특정 게시물의 특정 신고 유형 개수 + int countByReportTargetIdAndReportType(ObjectId reportTargetId, ReportType reportType); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index d5a75973..d49587cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -11,10 +11,8 @@ import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportResponseDto; -import inu.codin.codin.domain.report.entity.ReportEntity; -import inu.codin.codin.domain.report.entity.ReportStatus; -import inu.codin.codin.domain.report.entity.ReportTargetType; -import inu.codin.codin.domain.report.entity.SuspensionPeriod; +import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; +import inu.codin.codin.domain.report.entity.*; import inu.codin.codin.domain.report.exception.ReportAlreadyExistsException; import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -27,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -64,13 +63,13 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { reportCreateRequestDto.getReportTargetId(), reportCreateRequestDto.getReportTargetType()); - Boolean reportExists = reportRepository.existsByReportingUserIdAndReportTargetIdAndReportTargetType( + boolean reportExists = reportRepository.existsByReportingUserIdAndReportTargetIdAndReportTargetType( userId, reportTargetId, reportCreateRequestDto.getReportTargetType() ); + log.info("reportExists: {}", reportExists); - // null 방지: reportExists가 null이면 false로 처리 if (reportExists) { log.warn("중복 신고 발견: reportingUserId={}, reportTargetId={}, reportTargetType={}", userId, @@ -108,6 +107,25 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { report.get_id(), userId, reportCreateRequestDto.getReportTargetId()); + + // 신고 대상 타입 == POST(게시물) reportCount 증가 + if (reportCreateRequestDto.getReportTargetType() == ReportTargetType.POST) { + updatePostReportCount(reportTargetId); + } + } + + //post 총 신고수 증가 + private void updatePostReportCount(ObjectId postId) { + // 게시물 조회 + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + + // 신고 수 업데이트 + post.updateReportCount(post.getReportCount() + 1); + + // 게시물 저장 + postRepository.save(post); + log.info("게시물 신고 수 업데이트 완료: postId={}, reportCount={}", postId, post.getReportCount()); } /** @@ -136,6 +154,7 @@ private ObjectId validateAndGetReportedUserId(ReportTargetType reportTargetType, + // 신고 목록 조회 (관리자) public List getAllReports(ReportTargetType reportTargetType, Integer minReportCount) { log.info("신고 목록 조회: reportType={}, minReportCount={}", reportTargetType, minReportCount); @@ -228,8 +247,21 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { + public ReportSummaryResponseDTO getReportSummary(String reportTargetId) { + ObjectId targetId = new ObjectId(reportTargetId); + //int totalReports = reportRepository.countByReportTargetId(targetId); + // 모든 ReportType에 대해 개수 조회 + Map reportTypeCounts = new HashMap<>(); + for (ReportType reportType : ReportType.values()) { + int count = reportRepository.countByReportTargetIdAndReportType(targetId, reportType); + if (count > 0) { // 개수가 0이면 굳이 넣을 필요 없음 + reportTypeCounts.put(reportType, count); + } + } + return new ReportSummaryResponseDTO(reportTypeCounts); + } } From 50944972957fcde989d0f4b715908fa0c39dc898 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Feb 2025 18:39:44 +0900 Subject: [PATCH 0421/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/controller/ReportController.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 32026631..3900bf9e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.service.ReportService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/reports") +@Tag(name = "Reoprt API", description = "사용자 신고 기능") public class ReportController { private final ReportService reportService; @@ -37,10 +39,10 @@ public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto } //(User) 특정 게시물의 신고 정보 조회 API - @Operation(summary = "특정 게시물의 신고 내역 조회") - @GetMapping("/summary/{reportTargetId}") - public ResponseEntity getReportSummary(@PathVariable String reportTargetId) { - ReportSummaryResponseDTO reportSummary = reportService.getReportSummary(reportTargetId); + @Operation(summary = "특정 게시물의 신고 내역 조회(사용자)") + @GetMapping("/summary/{postId}") + public ResponseEntity getReportSummary(@PathVariable String postId) { + ReportSummaryResponseDTO reportSummary = reportService.getReportSummary(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "특정 게시물의 신고 내역 조회 완료", reportSummary)); } From b52f76eb7a27caa740f6e5e746db2d9e5a332fcb Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Feb 2025 18:40:34 +0900 Subject: [PATCH 0422/1002] =?UTF-8?q?feat=20::=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=ED=95=98=EA=B8=B0/=ED=95=B4=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/controller/BlockController.java | 39 ++++++++++ .../domain/block/entity/BlockEntity.java | 25 +++++++ .../exception/AlreadyBlockedException.java | 7 ++ .../block/exception/NotBlockedException.java | 7 ++ .../block/repository/BlockRepository.java | 19 +++++ .../domain/block/service/BlockService.java | 71 +++++++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java new file mode 100644 index 00000000..34c5bdb7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java @@ -0,0 +1,39 @@ +package inu.codin.codin.domain.block.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.block.service.BlockService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/block") +@RequiredArgsConstructor + +@Tag(name = "Block API", description = "사용자 차단 기능") +public class BlockController { + private final BlockService blockService; + + @Operation( + summary = "사용자 차단하기" + ) + @PostMapping("/{blockedUserId}") + public ResponseEntity blockUser(@PathVariable String blockedUserId) { + blockService.blockUser(blockedUserId); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "사용자 차단 완료", null)); + } + + @Operation( + summary = "사용자 차단 해제" + ) + @DeleteMapping("/{blockedUserId}") + public ResponseEntity unblockUser(@PathVariable String blockedUserId) { + blockService.unblockUser(blockedUserId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "사용자 차단 해제 완료", null)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java new file mode 100644 index 00000000..fe73e754 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.block.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import lombok.Builder; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + + +@Getter +@Document(collection = "blocks") +public class BlockEntity extends BaseTimeEntity { + @Id + private ObjectId id; // MongoDB의 기본 ID + + private ObjectId blockingUserId; // 차단한 사용자 + private ObjectId blockedUserId; // 차단된 사용자 + + @Builder + public BlockEntity(ObjectId blockingUserId, ObjectId blockedUserId) { + this.blockingUserId = blockingUserId; + this.blockedUserId = blockedUserId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java new file mode 100644 index 00000000..223c9844 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.block.exception; + +public class AlreadyBlockedException extends RuntimeException{ + public AlreadyBlockedException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java new file mode 100644 index 00000000..cb91fafb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.block.exception; + +public class NotBlockedException extends RuntimeException{ + public NotBlockedException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java new file mode 100644 index 00000000..9b01adac --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.block.repository; + +import inu.codin.codin.domain.block.entity.BlockEntity; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + + +@Repository +public interface BlockRepository extends MongoRepository { + + boolean existsByBlockingUserIdAndBlockedUserId(ObjectId blockingUserId, ObjectId blockedUserId); + Optional findByBlockingUserIdAndBlockedUserId(ObjectId blockingUserId, ObjectId blockedUserId); + +} + + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java new file mode 100644 index 00000000..f44ab22d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -0,0 +1,71 @@ +package inu.codin.codin.domain.block.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.entity.BlockEntity; +import inu.codin.codin.domain.block.exception.AlreadyBlockedException; +import inu.codin.codin.domain.block.exception.NotBlockedException; +import inu.codin.codin.domain.block.repository.BlockRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class BlockService { + private final BlockRepository blockRepository; + private final UserRepository userRepository; + + public void blockUser(String strBlockedUserId) { + ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + ObjectId blockedUserId = new ObjectId(strBlockedUserId); + + // 중복 차단 방지 + checkUserBlocked(blockingUserId,blockedUserId); + + if (blockRepository.existsByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId)) { + throw new AlreadyBlockedException("상대방을 차단했거나 차단당했습니다."); + } + + // 차단 정보 저장 + BlockEntity block = BlockEntity.builder() + .blockingUserId(blockingUserId) + .blockedUserId(blockedUserId) + .build(); + + blockRepository.save(block); + + // 유저 엔티티 조회 후 수정 + UserEntity user = userRepository.findById(blockingUserId) + .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + + user.getBlockedUsers().add(blockedUserId); // 차단 유저 추가 + userRepository.save(user); + } + + public void unblockUser(String strBlockedUserId) { + ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + ObjectId blockedUserId = new ObjectId(strBlockedUserId); + // 차단 정보 조회 + BlockEntity block = blockRepository.findByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId) + .orElseThrow(() -> new NotBlockedException("차단되지 않은 사용자입니다.")); + + // 차단 해제 + blockRepository.delete(block); + + // 유저 엔티티 조회 후 수정 + UserEntity user = userRepository.findById(blockingUserId) + .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + + user.getBlockedUsers().remove(blockedUserId); // 차단 유저 추가 + userRepository.save(user); + } + + public void checkUserBlocked(ObjectId blockingUserId, ObjectId blockedUserId) { + if (blockRepository.existsByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId)) { + throw new AlreadyBlockedException("상대방을 차단했거나 차단당했습니다."); + } + } +} From e94865d711985e8b09e41355b7165c26ad7977ad Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Feb 2025 21:04:51 +0900 Subject: [PATCH 0423/1002] =?UTF-8?q?feat=20::=20=EC=B0=A8=EB=8B=A8=20/=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20->=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=A1=B0=ED=9A=8C=20=EC=95=88=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/block/service/BlockService.java | 31 +++++++++++++------ .../post/repository/PostRepository.java | 12 ++++--- .../domain/post/service/PostService.java | 22 ++++++++----- .../codin/domain/user/entity/UserEntity.java | 9 +++++- .../user/repository/UserRepository.java | 2 +- 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index f44ab22d..4a0fcc42 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -12,6 +12,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class BlockService { @@ -20,6 +22,11 @@ public class BlockService { public void blockUser(String strBlockedUserId) { ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + // 유저 엔티티 조회 + UserEntity user = userRepository.findById(blockingUserId) + .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + + ObjectId blockedUserId = new ObjectId(strBlockedUserId); // 중복 차단 방지 @@ -37,16 +44,17 @@ public void blockUser(String strBlockedUserId) { blockRepository.save(block); - // 유저 엔티티 조회 후 수정 - UserEntity user = userRepository.findById(blockingUserId) - .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); - - user.getBlockedUsers().add(blockedUserId); // 차단 유저 추가 + //유저 차단 리스트에 추가 ( 조회 필터링 목적) + user.getBlockedUsers().add(blockedUserId); userRepository.save(user); } public void unblockUser(String strBlockedUserId) { ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + // 유저 엔티티 조회 + UserEntity user = userRepository.findById(blockingUserId) + .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + ObjectId blockedUserId = new ObjectId(strBlockedUserId); // 차단 정보 조회 BlockEntity block = blockRepository.findByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId) @@ -55,11 +63,9 @@ public void unblockUser(String strBlockedUserId) { // 차단 해제 blockRepository.delete(block); - // 유저 엔티티 조회 후 수정 - UserEntity user = userRepository.findById(blockingUserId) - .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); - user.getBlockedUsers().remove(blockedUserId); // 차단 유저 추가 + //유저 차단 리스트에서 삭제 ( 조회 필터링 목적) + user.getBlockedUsers().remove(blockedUserId); userRepository.save(user); } @@ -68,4 +74,11 @@ public void checkUserBlocked(ObjectId blockingUserId, ObjectId blockedUserId) { throw new AlreadyBlockedException("상대방을 차단했거나 차단당했습니다."); } } + + public List getBlockedUsers() { + ObjectId userId = SecurityUtils.getCurrentUserId(); + return userRepository.findById(userId) + .map(UserEntity::getBlockedUsers) + .orElseThrow(() -> new NotFoundException("존재하지 않는 회원입니다.")); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index a99b3753..49b65438 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -17,14 +18,17 @@ public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(ObjectId Id); - @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': ?0 }") - Page findAllByCategoryOrderByCreatedAt(PostCategory postCategory, PageRequest pageRequest); + + @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': { $regex: '^?0' } , 'userId': { $nin: ?1 }}") + Page getPostsByCategoryWithBlockedUsers(String postCategory, List blockedUsersId, PageRequest pageRequest); @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); Page findByPostCategoryStartingWithAndDeletedAtIsNullAndPostStatusInOrderByCreatedAt(String prefix, PostStatus postStatus, PageRequest pageRequest); - @Query("{ '$or': [ { 'content': { $regex: ?0, $options: 'i' } }, { 'title': { $regex: ?0, $options: 'i' } } ] }") - Page findAllByKeywordAndDeletedAtIsNull(String keyword, PageRequest pageRequest); + @Query("{ '$or': [ " + + "{ 'content': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } }, " + + "{ 'title': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } } ] }") + Page findAllByKeywordAndDeletedAtIsNull(String keyword, List blockedUsersId, PageRequest pageRequest); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 89bafd6d..3f12c873 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.like.entity.LikeType; @@ -42,10 +43,7 @@ import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Slf4j @Service @@ -62,6 +60,7 @@ public class PostService { private final ScrapService scrapService; private final HitsService hitsService; private final RedisService redisService; + private final BlockService blockService; public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); @@ -196,11 +195,13 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { + + // 차단 목록 조회 + List blockedUsersId = blockService.getBlockedUsers(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page; - if (postCategory.equals(PostCategory.REQUEST) || postCategory.equals(PostCategory.COMMUNICATION) || postCategory.equals(PostCategory.EXTRACURRICULAR)) - page = postRepository.findByPostCategoryStartingWithAndDeletedAtIsNullAndPostStatusInOrderByCreatedAt(postCategory.toString(), PostStatus.ACTIVE, pageRequest); - else page = postRepository.findAllByCategoryOrderByCreatedAt(postCategory, pageRequest); + page = postRepository.getPostsByCategoryWithBlockedUsers(postCategory.toString(), blockedUsersId ,pageRequest); log.info("모든 글 반환 성공 Category: {}, Page: {}", postCategory, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); @@ -272,12 +273,17 @@ public UserInfo getUserInfoAboutPost(ObjectId userId, ObjectId postId){ } public PostPageResponse searchPosts(String keyword, int pageNumber) { + // 차단 목록 조회 + List blockedUsersId = blockService.getBlockedUsers(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, pageRequest); + Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } + public List getTop3BestPosts() { + Map posts = redisService.getTopNPosts(3); List bestPosts = posts.entrySet().stream() .map(post -> { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index cd4bca11..97dd0845 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -12,6 +12,9 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.security.core.parameters.P; +import java.util.ArrayList; +import java.util.List; + @Document(collection = "users") @Getter public class UserEntity extends BaseTimeEntity { @@ -41,8 +44,10 @@ public class UserEntity extends BaseTimeEntity { private UserStatus status; + private List blockedUsers = new ArrayList<>(); + @Builder - public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status) { + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status, List blockedUsers) { this.email = email; this.password = password; this.studentId = studentId; @@ -54,6 +59,7 @@ public UserEntity(String email, String password, String studentId, String name, this.undergraduate = undergraduate; this.role = role; this.status = status; + this.blockedUsers = (blockedUsers != null) ? blockedUsers : new ArrayList<>(); // ✅ 기본값 설정 } public void updateNickname(UserNicknameRequestDto userNicknameRequestDto) { @@ -77,6 +83,7 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .profileImageUrl("") .role(UserRole.USER) .status(UserStatus.ACTIVE) + .blockedUsers(new ArrayList<>()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 8cea3e39..994bb532 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -16,5 +17,4 @@ public interface UserRepository extends MongoRepository { @Query("{'studentId': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByStudentId(String studentId); - } From c25eb249d40ec55d948dbbfdf8eb3556a4a354ef Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Feb 2025 21:13:27 +0900 Subject: [PATCH 0424/1002] =?UTF-8?q?feat=20::=20=EC=B0=A8=EB=8B=A8=20/=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20->=20=EC=B0=A8=EB=8B=A8=ED=95=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/repository/PostRepository.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 49b65438..f225c0e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -19,16 +19,17 @@ public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(ObjectId Id); - @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': { $regex: '^?0' } , 'userId': { $nin: ?1 }}") - Page getPostsByCategoryWithBlockedUsers(String postCategory, List blockedUsersId, PageRequest pageRequest); @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); - Page findByPostCategoryStartingWithAndDeletedAtIsNullAndPostStatusInOrderByCreatedAt(String prefix, PostStatus postStatus, PageRequest pageRequest); + @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': { $regex: '^?0' } , 'userId': { $nin: ?1 }}") + Page getPostsByCategoryWithBlockedUsers(String postCategory, List blockedUsersId, PageRequest pageRequest); - @Query("{ '$or': [ " + - "{ 'content': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } }, " + - "{ 'title': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } } ] }") + @Query("{ '$or': [ " + + + "{ 'content': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } }, " + + + "{ 'title': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } } ] }") Page findAllByKeywordAndDeletedAtIsNull(String keyword, List blockedUsersId, PageRequest pageRequest); } From 4d95b71d879adc1a1e7d5a2a08a82ad39113ccef Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 6 Feb 2025 14:30:57 +0900 Subject: [PATCH 0425/1002] =?UTF-8?q?feat=20::=20=EC=B0=A8=EB=8B=A8=20/=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=B1=84=ED=8C=85=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?->=20=EC=B0=A8=EB=8B=A8=ED=95=9C=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/repository/ChatRoomRepository.java | 4 ++-- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 6 +++++- .../inu/codin/codin/domain/report/entity/ReportEntity.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index cb0f06ee..527fd4fa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -13,6 +13,6 @@ public interface ChatRoomRepository extends MongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") Optional findById(ObjectId id); - @Query("{ 'participants': { '$elemMatch': { 'userId': ?0 } } , 'deleteAt': null }") - List findByParticipant(ObjectId userId); + @Query("{ 'participants': { '$elemMatch': { 'userId': ?0 } }, 'deleteAt': null, 'participants.userId': { '$nin': ?1 } }") + List findByParticipant(ObjectId userId, List blockedUsersId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 32ad9f30..ccf376db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.service; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; @@ -29,6 +30,7 @@ public class ChatRoomService { private final ChatRoomRepository chatRoomRepository; private final CustomChattingRepository customChattingRepository; private final UserRepository userRepository; + private final BlockService blockService; public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { ObjectId senderId = ((CustomUserDetails) userDetails).getId(); @@ -57,8 +59,10 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat public List getAllChatRoomByUser(UserDetails userDetails) { ObjectId userId = ((CustomUserDetails) userDetails).getId(); log.info("[유저의 채팅방 조회] 유저 ID: {}", userId); + // 차단 목록 조회 + List blockedUsersId = blockService.getBlockedUsers(); - List chatRooms = chatRoomRepository.findByParticipant(userId); + List chatRooms = chatRoomRepository.findByParticipant(userId, blockedUsersId); log.info("[채팅방 조회 결과] 유저 ID: {}가 참여 중인 채팅방 개수: {}", userId, chatRooms.size()); return chatRooms.stream() .map(chatRoom -> { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index 449e28ac..abae3560 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -21,7 +21,7 @@ public class ReportEntity extends BaseTimeEntity { //신고한 유저 private ObjectId reportingUserId; - //신고당 유저 + //신고당한 유저 private ObjectId reportedUserId; //신고 대상 타입 ( 유저, 게시물, 댓글, 대댓글) From 1cd0dea6ec2f7b10f71d0a663e8425764e72f021 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 6 Feb 2025 15:41:53 +0900 Subject: [PATCH 0426/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?/=20=EC=A0=95=EC=A7=80=20=ED=95=B4=EC=A0=9C=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=8B=A4=ED=96=89=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20->=201=EC=8B=9C=EA=B0=84=20=EB=A7=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/scheduler/SuspensionScheduler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java index d725a362..cee1de67 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java @@ -13,8 +13,8 @@ public class SuspensionScheduler { private final SuspensionService suspensionService; - //@Scheduled(cron = "0 0 * * * ?") // 매 정시마다 실행 - @Scheduled(cron = "0 * * * * ?") // 매 1분마다 실행 + @Scheduled(cron = "0 0 * * * ?") // 매 정시마다 실행 + //@Scheduled(cron = "0 * * * * ?") // 매 1분마다 실행(테스트) public void checkAndReleaseSuspendedUsers() { log.info("정지 해제 스케줄러 실행..."); suspensionService.releaseSuspendedUsers(); From acb73958fa25b46a473425c26ac02ac5148e15c7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 7 Feb 2025 23:37:04 +0900 Subject: [PATCH 0427/1002] =?UTF-8?q?submodule=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 0a32654e..09274699 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 0a32654e3f70e284db1d9545751af3f4b934375e +Subproject commit 09274699ea2ba2b9efd4fd9579c4dcf53efb8f65 From e9b4c8932f7fe3b96a7f7ce14b2f8693217c4dcf Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 7 Feb 2025 23:45:32 +0900 Subject: [PATCH 0428/1002] =?UTF-8?q?submodule=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 09274699..d2b76a7e 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 09274699ea2ba2b9efd4fd9579c4dcf53efb8f65 +Subproject commit d2b76a7e83ddd5f42df5448542183866270f4a53 From 12103dfe530705df045451a8e0428edba257991a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 8 Feb 2025 02:25:48 +0900 Subject: [PATCH 0429/1002] =?UTF-8?q?fix=20::=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=20Fcm=20Path=20=EC=9D=BD?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/infra/fcm/FcmConfig.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java index cd897270..28139ec6 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java @@ -10,6 +10,7 @@ import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; @Component @Slf4j @@ -19,16 +20,21 @@ public class FcmConfig { private String fcmKeyPath; @PostConstruct - public void init(){ + public void init() { try { - FileInputStream serviceAccount = new FileInputStream(fcmKeyPath); + InputStream serviceAccount = getClass().getClassLoader().getResourceAsStream(fcmKeyPath); + + if (serviceAccount == null) { + throw new RuntimeException("Firebase config file not found in classpath."); + } + FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .build(); FirebaseApp.initializeApp(options); log.info("[init] FirebaseApp initialized"); } catch (IOException e) { - throw new RuntimeException(e.getMessage()); + throw new RuntimeException("Error initializing Firebase: " + e.getMessage(), e); } } } From 012cc45dac99788ab27f8a6ecd84360d52fda686 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 8 Feb 2025 02:43:50 +0900 Subject: [PATCH 0430/1002] =?UTF-8?q?SubModule=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index d2b76a7e..bdeb7189 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit d2b76a7e83ddd5f42df5448542183866270f4a53 +Subproject commit bdeb7189f318ecb593430019eea975e05e55d54b From a9eb4fb660687a9d420eebc2f5d06de9576dbb2d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 8 Feb 2025 10:25:14 +0900 Subject: [PATCH 0431/1002] =?UTF-8?q?refactor=20::=20Cors=20=EC=97=90=20?= =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 5a2734d9..8caa5ebf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -148,7 +148,7 @@ public PasswordEncoder passwordEncoder() { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr")); + config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr" , "https://front-end-peach-two.vercel.app")); config.setAllowedHeaders(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); config.setExposedHeaders(List.of("Authorization", "x-refresh-token")); From eb725b381f03f7104d0e49afd4fe0e32bc1540f6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Feb 2025 16:43:37 +0900 Subject: [PATCH 0432/1002] =?UTF-8?q?fix=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EA=B3=BC=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=EC=9D=84=20?= =?UTF-8?q?=ED=95=98=EB=82=98=EB=A1=9C=20=EB=AC=B6=EC=96=B4=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 61 ++++++++----------- .../common/security/service/AuthService.java | 38 +++++++++--- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 50bc6f37..907e5525 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -11,12 +11,9 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -26,23 +23,37 @@ @RequiredArgsConstructor public class AuthController { - private final AuthenticationManager authenticationManager; private final JwtService jwtService; private final AuthService authService; - @Operation(summary = "로그인") - @PostMapping("/login") - public ResponseEntity login(@RequestBody SignUpAndLoginRequestDto loginRequestDto, HttpServletResponse response) { - UsernamePasswordAuthenticationToken authenticationToken - = new UsernamePasswordAuthenticationToken(loginRequestDto.getStudentId(), loginRequestDto.getPassword()); + @Operation( + summary = "포탈 로그인", + description = "포탈 아이디, 비밀번호를 통해 로그인 진행 및 학적 정보 반환" + ) + @PostMapping("/portal") + public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { + Object result = authService.signUp(signUpAndLoginRequestDto, response); + if (result == null){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "로그인 완료")); + } else { + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "새로운 유저 등록 완료", result)); + } - Authentication authentication = authenticationManager.authenticate(authenticationToken); - SecurityContextHolder.getContext().setAuthentication(authentication); - jwtService.createToken(response); + } - return ResponseEntity.ok().body(new SingleResponse<>(200, "로그인 성공", null)); + @Operation(summary = "회원가입") + @PostMapping(value = "/signup/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> signUpUser( + @PathVariable("studentId") String studentId, + @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, + @RequestPart(value = "userImage", required = false) MultipartFile userImage) { + authService.createUser(studentId, userNicknameRequestDto, userImage); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "회원가입 성공", null)); } @Operation(summary = "로그아웃") @@ -58,26 +69,4 @@ public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse jwtService.reissueToken(request, response); return ResponseEntity.ok().body(new SingleResponse<>(200, "토큰 재발급 성공", null)); } - - @Operation( - summary = "포탈 로그인", - description = "포탈 아이디, 비밀번호를 통해 로그인 진행 및 학적 정보 반환" - ) - @PostMapping("/portal") - public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { - authService.signUp(signUpAndLoginRequestDto); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "포탈 로그인을 통한 학적 정보 반환 완료", null)); - } - - @Operation(summary = "회원가입") - @PostMapping(value = "/signup/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> signUpUser( - @PathVariable("studentId") String studentId, - @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, - @RequestPart(value = "userImage", required = false) MultipartFile userImage) { - authService.createUser(studentId, userNicknameRequestDto, userImage); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "회원가입 성공", null)); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index bfc306af..b65b70bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -11,11 +11,16 @@ import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -33,21 +38,38 @@ @Slf4j public class AuthService { + private final AuthenticationManager authenticationManager; private final PortalClient portalClient; private final InuClient inuClient; private final UserRepository userRepository; private final S3Service s3Service; + private final JwtService jwtService; private final PasswordEncoder passwordEncoder; - public void signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { - //학번으로 회원가입 유무 판단 - Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); - if (user.isPresent()) throw new UserCreateFailException("이미 회원가입된 유저입니다."); + public Object signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ + try {//학번으로 회원가입 유무 판단 + Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); + if (user.isPresent()) { + UsernamePasswordAuthenticationToken authenticationToken + = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getStudentId(), signUpAndLoginRequestDto.getPassword()); + + Authentication authentication = authenticationManager.authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + + jwtService.createToken(response); + return null; + } + + PortalLoginResponseDto userPortalLoginResponseDto + = returnPortalInfo(signUpAndLoginRequestDto); + UserEntity userEntity = UserEntity.of(userPortalLoginResponseDto); + userRepository.save(userEntity); + return userEntity.get_id().toString(); + } catch (Exception e) { + log.error("로그인 중 오류 발생", e); + throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + } - PortalLoginResponseDto userPortalLoginResponseDto - = returnPortalInfo(signUpAndLoginRequestDto); - UserEntity userEntity = UserEntity.of(userPortalLoginResponseDto); - userRepository.save(userEntity); } private PortalLoginResponseDto returnPortalInfo(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { From 9c0d81e1fa44f669507058071672b20d9bed0f93 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Feb 2025 17:04:48 +0900 Subject: [PATCH 0433/1002] =?UTF-8?q?feat=20:=20=ED=95=99=EB=B2=88=209?= =?UTF-8?q?=EC=9E=90=EB=A6=AC=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=EB=B0=8F=20=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/exception/GlobalExceptionHandler.java | 6 ++++++ .../codin/common/security/dto/SignUpAndLoginRequestDto.java | 2 ++ 2 files changed, 8 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 7bc25fc6..819ea52b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.security.exception.JwtException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -32,4 +33,9 @@ protected ResponseEntity handleJwtException(JwtException e) { .body(new ExceptionResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.value())); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.UNAUTHORIZED.value())); } + } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java index 4d531c7a..629887d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java @@ -1,6 +1,7 @@ package inu.codin.codin.common.security.dto; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @@ -9,6 +10,7 @@ public class SignUpAndLoginRequestDto { @NotBlank + @Size(min = 9 , max = 9 , message = "학번은 9자리여야 합니다.") private String studentId; @NotBlank From 2d1e45154506c9c163d8e114a4d5310849f6fc70 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Feb 2025 17:07:13 +0900 Subject: [PATCH 0434/1002] =?UTF-8?q?fix=20:=20HttpStatus=20Code=20400?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/exception/GlobalExceptionHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 819ea52b..0ea9336c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -35,7 +35,7 @@ protected ResponseEntity handleJwtException(JwtException e) { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.UNAUTHORIZED.value())); } + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST.value())); } } From 7d9e6dfd2fef45400e6f358a0531cf425c26c222 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Feb 2025 23:11:34 +0900 Subject: [PATCH 0435/1002] =?UTF-8?q?fix=20:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=A0=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20redis?= =?UTF-8?q?=EC=97=90=20=EC=9E=84=EC=8B=9C=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 10 +++---- .../common/security/service/AuthService.java | 16 +++++------ .../codin/infra/redis/config/RedisConfig.java | 3 ++- .../infra/redis/service/RedisAuthService.java | 27 +++++++++++++++++++ 4 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 907e5525..3d18d91e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -33,16 +33,14 @@ public class AuthController { ) @PostMapping("/portal") public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { - Object result = authService.signUp(signUpAndLoginRequestDto, response); - if (result == null){ + Integer result = authService.signUp(signUpAndLoginRequestDto, response); + if (result.equals(0)) { return ResponseEntity.ok() - .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "로그인 완료")); + .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); } else { return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "새로운 유저 등록 완료", result)); + .body(new SingleResponse<>(201, "포탈 로그인 진행 완료", "새로운 유저 등록 완료")); } - - } @Operation(summary = "회원가입") diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index b65b70bf..8b78ffcf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -10,6 +10,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisAuthService; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; @@ -44,9 +45,10 @@ public class AuthService { private final UserRepository userRepository; private final S3Service s3Service; private final JwtService jwtService; + private final RedisAuthService redisAuthService; private final PasswordEncoder passwordEncoder; - public Object signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ + public Integer signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ try {//학번으로 회원가입 유무 판단 Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); if (user.isPresent()) { @@ -57,14 +59,12 @@ public Object signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServ SecurityContextHolder.getContext().setAuthentication(authentication); jwtService.createToken(response); - return null; + return 0; } - PortalLoginResponseDto userPortalLoginResponseDto = returnPortalInfo(signUpAndLoginRequestDto); - UserEntity userEntity = UserEntity.of(userPortalLoginResponseDto); - userRepository.save(userEntity); - return userEntity.get_id().toString(); + redisAuthService.saveUserData(signUpAndLoginRequestDto.getStudentId(), userPortalLoginResponseDto); + return 1; } catch (Exception e) { log.error("로그인 중 오류 발생", e); throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); @@ -118,8 +118,7 @@ public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginReq public void createUser(String studentId, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage) { - UserEntity user = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UserCreateFailException("존재하지 않는 학번입니다. 포탈 로그인부터 진행해주세요.")); + PortalLoginResponseDto userData = redisAuthService.getUserData(studentId); log.info("[createUser] 요청 데이터: {}", studentId); String imageUrl = null; @@ -135,6 +134,7 @@ public void createUser(String studentId, UserNicknameRequestDto userNicknameRequ } + UserEntity user = UserEntity.of(userData); user.updateNickname(new UserNicknameRequestDto(userNicknameRequestDto.getNickname())); user.updateProfileImageUrl(imageUrl); userRepository.save(user); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java index 9aee12d0..7ecf3fea 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/config/RedisConfig.java @@ -10,6 +10,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -47,9 +48,9 @@ public RedisTemplate redisTemplate(RedisConnectionFactory redisC RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); redisTemplate.setDefaultSerializer(RedisSerializer.string()); redisTemplate.setEnableTransactionSupport(true); + redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 객체를 JSON으로 직렬화 return redisTemplate; } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java new file mode 100644 index 00000000..bb2ef148 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java @@ -0,0 +1,27 @@ +package inu.codin.codin.infra.redis.service; + +import inu.codin.codin.common.security.dto.PortalLoginResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class RedisAuthService { + + private final RedisTemplate redisTemplate; + private static final long EXPIRE_TIME = 5; + + // 데이터 저장 + public void saveUserData(String studentId, PortalLoginResponseDto userData) { + redisTemplate.opsForValue().set(studentId, userData, EXPIRE_TIME, TimeUnit.MINUTES); // 5분 후 자동 삭제 + } + + // 데이터 조회 + public PortalLoginResponseDto getUserData(String studentId) { + return (PortalLoginResponseDto) redisTemplate.opsForValue().get(studentId); + } + +} From 8e801d4fda150fee79062cbf711e2e0bf72284ce Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Feb 2025 23:11:49 +0900 Subject: [PATCH 0436/1002] =?UTF-8?q?fix=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20api=20=EA=B6=8C=ED=95=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 17 ++++++++--------- .../chatting/controller/ChattingController.java | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 8caa5ebf..bf03eabc 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -100,19 +100,18 @@ public PasswordEncoder passwordEncoder() { // 토큰 없이 접근 가능한 URL private static final String[] PERMIT_ALL = { - "/auth/login", "/auth/reissue", "/auth/logout", - "/users/signup", - "/email/auth/check", - "/email/auth/send", - "/email/auth/password", - "/email/auth/password/check", - "/users/password", + "/auth/portal", + "/auth/signup/**", +// "/email/auth/check", +// "/email/auth/send", +// "/email/auth/password", +// "/email/auth/password/check", "/v3/api/test1", "/ws-stomp/**", - "/chat", - "/chat/image", +// "/chat", +// "/chat/image", "/chats/**" }; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 75b15895..89ffd05d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.chat.chatting.controller; -import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.chat.chatting.service.ChattingService; From d749d9107b4539eb3751510a83c456ea8352b60d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:02:55 +0900 Subject: [PATCH 0437/1002] =?UTF-8?q?refactor=20::=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=90=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/service/BlockService.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 4a0fcc42..31a57f87 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -32,11 +32,8 @@ public void blockUser(String strBlockedUserId) { // 중복 차단 방지 checkUserBlocked(blockingUserId,blockedUserId); - if (blockRepository.existsByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId)) { - throw new AlreadyBlockedException("상대방을 차단했거나 차단당했습니다."); - } - // 차단 정보 저장 + // 차단 정보 저 BlockEntity block = BlockEntity.builder() .blockingUserId(blockingUserId) .blockedUserId(blockedUserId) @@ -71,7 +68,7 @@ public void unblockUser(String strBlockedUserId) { public void checkUserBlocked(ObjectId blockingUserId, ObjectId blockedUserId) { if (blockRepository.existsByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId)) { - throw new AlreadyBlockedException("상대방을 차단했거나 차단당했습니다."); + throw new AlreadyBlockedException("이미 상대방을 차단했습니다."); } } From ba82b551d00c79e3b9dd7cb2e7423e2bddd1bb4b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:05:03 +0900 Subject: [PATCH 0438/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EC=9C=A0=ED=98=95=20=EA=B8=B0=ED=9A=8D=20=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/entity/ReportType.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java index 5a64c0e2..2ae245b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportType.java @@ -4,13 +4,13 @@ @Getter public enum ReportType { - INAPPROPRIATE_CONTENT("게시판 성격에 부적절"), - POLITICAL("부적절한 정치적 의견"), - FRAUD("사칭/사기"), - SPAM("도배/낚시"), - COMMERCIAL_AD("상업적 광고"), - ABUSE("욕설"), - OBSCENE("음란물 및 불건전함"); + POLITICAL("정치 및 선거운동"), + ABUSE("욕설 및 폭력"), + FRAUD("사기 및 사칭"), + SPAM("도배 및 불쾌함"), + COMMERCIAL_AD("홍보 및 부적절"), + OBSCENE("음란물 및 불건전"), + ETC("기타"); private final String description; From 3d81eac9cfb2a2294918bf4a529bc2f090bd9245 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:07:56 +0900 Subject: [PATCH 0439/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EB=8C=80=EC=83=81=20=EC=95=84=EC=9D=B4=EB=94=94=EA=B0=92?= =?UTF-8?q?=EC=9D=B4=20=EA=B3=A0=EC=9C=A0=ED=95=98=EA=B8=B0=20=EB=95=8C?= =?UTF-8?q?=EB=AC=B8=EC=97=90=20=EC=8B=A0=EA=B3=A0=EC=9C=A0=ED=98=95?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=A1=B0=ED=9A=8C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/report/service/ReportService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index d49587cd..ce7c2121 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -71,10 +71,9 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { log.info("reportExists: {}", reportExists); if (reportExists) { - log.warn("중복 신고 발견: reportingUserId={}, reportTargetId={}, reportTargetType={}", + log.warn("중복 신고 발견: reportingUserId={}, reportTargetId={},", userId, - reportCreateRequestDto.getReportTargetId(), - reportCreateRequestDto.getReportTargetType()); + reportCreateRequestDto.getReportTargetId()); throw new ReportAlreadyExistsException("중복신고 : 이미 해당 대상에 대한 신고를 시행했습니다."); } From b62d24c8c0d33527f283c70e707d63a6a08d9fbd Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:09:49 +0900 Subject: [PATCH 0440/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EB=8C=80=EC=83=81=20=EC=95=84=EC=9D=B4=EB=94=94=EA=B0=92?= =?UTF-8?q?=EC=9D=B4=20=EA=B3=A0=EC=9C=A0=ED=95=98=EA=B8=B0=20=EB=95=8C?= =?UTF-8?q?=EB=AC=B8=EC=97=90=20=EC=8B=A0=EA=B3=A0=EC=9C=A0=ED=98=95?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=A1=B0=ED=9A=8C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=20=EC=88=98=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/service/ReportService.java | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index ce7c2121..1a331f8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -55,20 +55,12 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { ObjectId userId = SecurityUtils.getCurrentUserId(); ObjectId reportTargetId = new ObjectId(reportCreateRequestDto.getReportTargetId()); - log.info("신고 유저 검증 완료: userId={}", userId); - - // 중복 신고 방지 - log.info("중복 신고 검증: reportingUserId={}, reportTargetId={}, reportTargetType={}", - userId, - reportCreateRequestDto.getReportTargetId(), - reportCreateRequestDto.getReportTargetType()); boolean reportExists = reportRepository.existsByReportingUserIdAndReportTargetIdAndReportTargetType( userId, reportTargetId, reportCreateRequestDto.getReportTargetType() ); - log.info("reportExists: {}", reportExists); if (reportExists) { log.warn("중복 신고 발견: reportingUserId={}, reportTargetId={},", @@ -77,18 +69,9 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { throw new ReportAlreadyExistsException("중복신고 : 이미 해당 대상에 대한 신고를 시행했습니다."); } - // 신고 대상 타입 별 유효성 검증 - log.info("신고 대상 유효성 검증: reportTargetType={}, reportTargetId={}", - reportCreateRequestDto.getReportTargetType(), - reportCreateRequestDto.getReportTargetId()); - // 신고 대상 검증 및 userId 가져오기 ObjectId reportedUserId = validateAndGetReportedUserId(reportCreateRequestDto.getReportTargetType(), reportTargetId); - log.info("신고 대상 유효성 검증 완료: reportTargetType={}, reportTargetId={}", - reportCreateRequestDto.getReportTargetType(), - reportCreateRequestDto.getReportTargetId()); - // 신고 엔티티 생성 ReportEntity report = ReportEntity.builder() .reportingUserId(userId) @@ -98,8 +81,6 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { .reportType(reportCreateRequestDto.getReportType()) .build(); - log.info("신고 엔티티 생성 완료: {}", report); - // 신고 저장 reportRepository.save(report); log.info("신고 저장 완료: reportId={}, reportingUserId={}, reportTargetId={}", @@ -124,7 +105,6 @@ private void updatePostReportCount(ObjectId postId) { // 게시물 저장 postRepository.save(post); - log.info("게시물 신고 수 업데이트 완료: postId={}, reportCount={}", postId, post.getReportCount()); } /** @@ -139,13 +119,11 @@ private ObjectId validateAndGetReportedUserId(ReportTargetType reportTargetType, ReportTargetType.REPLY, id -> replyRepository.findById(id).map(ReplyCommentEntity::getUserId) ); - log.info("신고 대상 검증 및 userId 조회: reportTargetType={}, reportTargetId={}", reportTargetType, reportTargetId); - // 검증 및 userId 조회 return Optional.ofNullable(validators.get(reportTargetType)) .flatMap(validator -> validator.apply(reportTargetId)) .orElseThrow(() -> { - log.error("유효하지 않은 신고 대상: reportTargetId={}, reportTargetType={}", reportTargetId, reportTargetType); + log.warn("유효하지 않은 신고 대상: reportTargetId={}, reportTargetType={}", reportTargetId, reportTargetType); return new NotFoundException("신고 대상(ID: " + reportTargetId + ", Type: " + reportTargetType + ")이 존재하지 않습니다."); }); } From 9e7ebc897b9a846073d9a77a22bf36fd02c85246 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:12:25 +0900 Subject: [PATCH 0441/1002] =?UTF-8?q?refactor=20::=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=8B=A0=EA=B3=A0=EC=B2=98=EB=A6=AC=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20Void=20=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20=EB=B0=98=ED=99=98=EA=B0=92=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/report/service/ReportService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 1a331f8f..06d478ce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -219,7 +219,7 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { //userRepository.save(user); log.info("신고가 처리되었습니다. ID: {}, 처리자: {}", report.get_id(), userId); - ReportResponseDto.from(report); + //ReportResponseDto.from(report); } From 98683ece0c97612a8d80ee5d9c8a30ca39207ce9 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:15:57 +0900 Subject: [PATCH 0442/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EC=8B=9C=20=EC=BD=94=EB=A9=98=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=A3=BC=EC=84=9D=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/dto/request/ReportExecuteRequestDto.java | 2 +- .../inu/codin/codin/domain/report/entity/ReportEntity.java | 6 ++---- .../codin/codin/domain/report/service/ReportService.java | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java index 0d43f8d1..557c2647 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java @@ -12,7 +12,7 @@ @NoArgsConstructor public class ReportExecuteRequestDto { private String reportId; // 신고 ID - private String comment; // 신고 처리에 대한 코멘트 + //private String comment; // 신고 처리에 대한 코멘트 private SuspensionPeriod suspensionPeriod; // 정지 기간 } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index abae3560..af1e6d28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -84,7 +84,7 @@ public static class ReportActionEntity extends BaseTimeEntity { private ObjectId actionTakenById; //신고에 대한 코멘트 - private String comment; + //private String comment; // 정지 기간 Enum private SuspensionPeriod suspensionPeriod; @@ -93,10 +93,8 @@ public static class ReportActionEntity extends BaseTimeEntity { private LocalDateTime suspensionEndDate; @Builder - public ReportActionEntity(ObjectId actionTakenById, String comment, - SuspensionPeriod suspensionPeriod, LocalDateTime suspensionEndDate) { + public ReportActionEntity(ObjectId actionTakenById, SuspensionPeriod suspensionPeriod, LocalDateTime suspensionEndDate) { this.actionTakenById = actionTakenById; - this.comment = comment; this.suspensionPeriod = suspensionPeriod; this.suspensionEndDate = suspensionEndDate; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 06d478ce..890a834e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -189,7 +189,6 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { // 신고 처리 정보 생성 ReportEntity.ReportActionEntity action = ReportEntity.ReportActionEntity.builder() .actionTakenById(userId) - .comment(requestDto.getComment()) .suspensionPeriod(requestDto.getSuspensionPeriod()) .suspensionEndDate(LocalDateTime.now().plusDays(requestDto.getSuspensionPeriod().getDays())) .build(); From 63d2220762c1a31e5eb33235dfd0934f48343d04 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 14:20:24 +0900 Subject: [PATCH 0443/1002] =?UTF-8?q?refactor=20::=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=82=AC=EC=9A=A9=EC=9E=90->=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/controller/ReportController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 3900bf9e..2db029d5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -38,8 +38,9 @@ public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto .body(new SingleResponse<>(201, "신고 생성 완료", null)); } - //(User) 특정 게시물의 신고 정보 조회 API - @Operation(summary = "특정 게시물의 신고 내역 조회(사용자)") + //(Admin) 특정 게시물의 신고 정보 조회 API + @Operation(summary = "특정 게시물의 신고 내역 조회(관리자)") + @PreAuthorize("hasRole('ADMIN')") @GetMapping("/summary/{postId}") public ResponseEntity getReportSummary(@PathVariable String postId) { ReportSummaryResponseDTO reportSummary = reportService.getReportSummary(postId); From 688a7582a7b45f6383f03c9a134170fbb9aa5b9d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 15:11:35 +0900 Subject: [PATCH 0444/1002] =?UTF-8?q?feat=20::=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EB=82=B4=EC=97=AD=EC=9D=B4=20=EC=9E=88=EB=8A=94=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EB=AC=BC=EB=A7=8C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/controller/PostController.java | 11 +++++++++++ .../domain/post/repository/PostRepository.java | 8 ++++++++ .../codin/domain/post/service/PostService.java | 13 +++++++++++++ 3 files changed, 32 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index e665840e..f502cf9c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -100,6 +100,17 @@ public ResponseEntity> getAllPosts(@RequestPara .body(new SingleResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); } + @Operation( + summary = "삭제되지 않은 신고된 모든 게시물 조회" + ) + @GetMapping("/reported") + public ResponseEntity> getAllPosts(@RequestParam("page") @NotNull int pageNumber) { + PostPageResponse postpages= postService.getReportedPosts(pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "삭제되지 않은 신고된 모든 게시물 조회 성공", postpages)); + } + + @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") @GetMapping("/{postId}") public ResponseEntity> getPostWithDetail(@PathVariable String postId) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index f225c0e7..162790a1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -26,6 +26,14 @@ public interface PostRepository extends MongoRepository { @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'postCategory': { $regex: '^?0' } , 'userId': { $nin: ?1 }}") Page getPostsByCategoryWithBlockedUsers(String postCategory, List blockedUsersId, PageRequest pageRequest); + @Query("{'deletedAt': null, " + + "'postStatus': { $in: ['ACTIVE'] }, " + + "'userId': { $nin: ?0 }, " + + "'reportCount': { $gte: 1 } }") // 신고 카운트가 1 이상인 경우만 반환 + Page getPostsWithReportedAndBlockedUsers(List blockedUsersId, PageRequest pageRequest); + + + @Query("{ '$or': [ " + "{ 'content': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } }, " diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 3f12c873..b28a7797 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -207,6 +207,19 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } + // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + public PostPageResponse getReportedPosts(int pageNumber) { + + // 차단 목록 조회 + List blockedUsersId = blockService.getBlockedUsers(); + + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page; + page = postRepository.getPostsWithReportedAndBlockedUsers(blockedUsersId ,pageRequest); + + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } + // 게시물 리스트 가져오기 public List getPostListResponseDtos(List posts) { return posts.stream() From 406a620c362412d793d80c8d766cf309595de409 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Feb 2025 18:35:59 +0900 Subject: [PATCH 0445/1002] =?UTF-8?q?feat=20::=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=3D=3D=20Pending=20=EC=9D=B8=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=EB=82=B4=EC=97=AD=20Count=20=EC=A7=91=EA=B3=84=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EC=9D=B4=ED=9B=84=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/controller/ReportController.java | 9 ++- .../dto/response/ReportCountResponseDto.java | 56 +++++++++++++++++++ .../dto/response/ReportResponseDto.java | 17 +++++- .../repository/CustomReportRepository.java | 38 +++++++++++++ .../report/repository/ReportRepository.java | 8 ++- .../domain/report/service/ReportService.java | 33 +++++------ 6 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 2db029d5..8ca4031e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; +import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; import inu.codin.codin.domain.report.dto.response.ReportResponseDto; import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; import inu.codin.codin.domain.report.entity.ReportTargetType; @@ -50,14 +51,12 @@ public ResponseEntity getReportSummary(@PathVariable String postId) { // 특정 신고 타입 목록 조회 (관리자) - @Operation(summary = "특정 신고 타입 목록 조회 (관리자)") + @Operation(summary = "Pending(신고 처리 대기) 신고 내역 오름차순 정렬 조회 (관리자)") @PreAuthorize("hasRole('ADMIN')") @GetMapping - public ResponseEntity getReports( - @RequestParam(required = false) ReportTargetType reportTargetType, - @RequestParam(required = false) Integer minReportCount) { + public ResponseEntity getReports(){ - List reports = reportService.getAllReports(reportTargetType, minReportCount); + List reports = reportService.getAllReports(); return ResponseEntity.ok() .body(new SingleResponse<>(200, "특정 신고 타입 목록 조회", reports)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java new file mode 100644 index 00000000..40a2b430 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java @@ -0,0 +1,56 @@ +package inu.codin.codin.domain.report.dto.response; + +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.entity.ReportStatus; +import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.entity.ReportType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.types.ObjectId; + +import java.util.List; +import java.util.stream.Collectors; + +@Builder +@Getter +@Slf4j +public class ReportCountResponseDto { + @Schema(description = "report target id", example = "targetId1") + @NotNull + private String reportTargetId; // reportTargetId + + @Schema(description = "신고 개수", example = "5") + @NotNull + private Integer count; // 신고 개수 + + @Schema(description = "신고 목록", example = "[...]") + @NotNull + private List reports; // 신고 목록 + + // 팩토리 메서드 추가 + @Builder + public static ReportCountResponseDto from(Document document) { + String targetId = document.getObjectId("_id").toString(); + Integer count = document.getInteger("count"); + + log.info("document: {}" , document); + List reportDocs = document.getList("reports", Document.class); + log.info("reportDocs: {}" ,reportDocs); + + List reports = reportDocs.stream() + .map(ReportResponseDto::fromDoc) + .collect(Collectors.toList()); + log.info("reports: {}" ,reports); + + return ReportCountResponseDto.builder() + .reportTargetId(targetId) + .count(count) + .reports(reports) + .build(); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java index 6f81fa67..10e3f91e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.report.entity.*; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import org.bson.Document; import org.bson.types.ObjectId; import inu.codin.codin.domain.report.entity.ReportTargetType; @@ -56,7 +57,7 @@ public static class ReportActionDto { private String actionTakenById; // 신고에 대한 코멘트 - private String comment; + //private String comment; // 정지 기간 Enum private String suspensionPeriod; @@ -68,7 +69,6 @@ public static class ReportActionDto { public ReportActionDto(ReportActionEntity actionEntity) { if (actionEntity != null) { this.actionTakenById = actionEntity.getActionTakenById().toString(); - this.comment = actionEntity.getComment(); this.suspensionPeriod = actionEntity.getSuspensionPeriod() != null ? actionEntity.getSuspensionPeriod().name() : null; this.suspensionEndDate = actionEntity.getSuspensionEndDate() != null ? actionEntity.getSuspensionEndDate().toString() : null; } @@ -88,4 +88,17 @@ public static ReportResponseDto from(ReportEntity reportEntity) { .action(reportEntity.getAction() != null ? new ReportActionDto(reportEntity.getAction()) : null) .build(); } + + // Document를 ReportResponseDto로 변환 + public static ReportResponseDto fromDoc(Document doc) { + return ReportResponseDto.builder() + ._id(doc.getObjectId("_id").toString()) + .reportingUserId(doc.getObjectId("reportingUserId").toString()) + .reportedUserId(doc.getObjectId("reportedUserId").toString()) + .reportTargetType(ReportTargetType.valueOf(doc.getString("reportTargetType"))) + .reportTargetId(doc.getObjectId("reportTargetId").toString()) + .reportType(ReportType.valueOf(doc.getString("reportType"))) // Enum 변환 + .reportStatus(ReportStatus.valueOf(doc.getString("reportStatus"))) // Enum 변환 + .build(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java new file mode 100644 index 00000000..b703f92f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java @@ -0,0 +1,38 @@ +package inu.codin.codin.domain.report.repository; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.report.entity.ReportEntity; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Repository; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import java.util.List; + +@Repository +public class CustomReportRepository { + + private final MongoTemplate mongoTemplate; + + public CustomReportRepository(MongoTemplate mongoTemplate) { + + this.mongoTemplate = mongoTemplate; + } + + public List findPendingReportsOrderedGroupedBy() { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("reportStatus").is("PENDING")), // 'PENDING' 상태인 항목만 필터링 + Aggregation.group("reportTargetId") // reportTargetId 기준으로 그룹화 + .count().as("count") // 신고 개수 카운팅 + .push("$$ROOT").as("reports"), // 신고 목록을 reports 필드로 밀어넣기 + Aggregation.sort(Sort.by(Sort.Order.desc("count"))) // 신고 개수 기준 내림차순 정렬 + ); + + AggregationResults results = mongoTemplate.aggregate(aggregation, ReportEntity.class, Document.class); + return results.getMappedResults(); // Document로 결과 반환 + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java index 81bd245d..959533cf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -1,10 +1,11 @@ package inu.codin.codin.domain.report.repository; +import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.entity.ReportType; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.MongoRepository; @@ -13,7 +14,7 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; + @Repository public interface ReportRepository extends MongoRepository { @@ -40,4 +41,5 @@ public interface ReportRepository extends MongoRepository getAllReports(ReportTargetType reportTargetType, Integer minReportCount) { - log.info("신고 목록 조회: reportType={}, minReportCount={}", reportTargetType, minReportCount); - - List reports; - if (reportTargetType != null) { - reports = reportRepository.findByReportTargetType(reportTargetType); - } else if (minReportCount != null) { - reports = reportRepository.findReportsByMinReportCount(minReportCount); - } else { - reports = reportRepository.findAll(); + + public List getAllReports() { + + List aggregationResults = customReportRepository.findPendingReportsOrderedGroupedBy(); + + return aggregationResults.stream() + .map(ReportCountResponseDto::from) // 변환 메서드 호출 + .collect(Collectors.toList()); } - // ReportEntity를 ReportResponseDto로 변환 - return reports.stream() - .map(ReportResponseDto::from) - .collect(Collectors.toList()); - } + // 특정 유저가 신고 내역 조회 (관리자) public List getReportsByUserId(String userId) { From f13fc24af91f1094269e1cde43f1c2ef7815d22c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 12 Feb 2025 20:49:42 +0900 Subject: [PATCH 0446/1002] =?UTF-8?q?fix=20:=20=EC=8B=A0=EA=B3=A0=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EB=B0=98=ED=99=98=20=EB=B3=B4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 10 -- .../dto/response/PostDetailResponseDTO.java | 9 +- .../response/PostPollDetailResponseDTO.java | 2 +- .../post/repository/PostRepository.java | 4 +- .../domain/post/service/PostService.java | 18 +-- .../report/controller/ReportController.java | 11 ++ .../dto/response/ReportListResponseDto.java | 124 ++++++++++++++++++ .../dto/response/ReportPageResponse.java | 32 +++++ .../domain/report/service/ReportService.java | 17 ++- 9 files changed, 189 insertions(+), 38 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index f502cf9c..cadcec35 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -100,16 +100,6 @@ public ResponseEntity> getAllPosts(@RequestPara .body(new SingleResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); } - @Operation( - summary = "삭제되지 않은 신고된 모든 게시물 조회" - ) - @GetMapping("/reported") - public ResponseEntity> getAllPosts(@RequestParam("page") @NotNull int pageNumber) { - PostPageResponse postpages= postService.getReportedPosts(pageNumber); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "삭제되지 않은 신고된 모든 게시물 조회 성공", postpages)); - } - @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") @GetMapping("/{postId}") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 929c824f..186c3e65 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -59,9 +59,6 @@ public class PostDetailResponseDTO { @Schema(description = "조회수", example = "0") private final int hits; - private final int reportCount; - - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") @@ -71,7 +68,7 @@ public class PostDetailResponseDTO { private final UserInfo userInfo; public PostDetailResponseDTO(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, - boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, int reportCount, UserInfo userInfo){ + boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfo userInfo){ this.userId = userId; this._id = _id; this.title = title; @@ -86,7 +83,6 @@ public PostDetailResponseDTO(String userId, String _id, String title, String con this.commentCount = commentCount; this.hits = hits; this.createdAt = createdAt; - this.reportCount = reportCount; this.userInfo = userInfo; } @@ -102,7 +98,7 @@ public UserInfo(boolean isLike, boolean isScrap) { } } - public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount, int reportCount ,UserInfo userInfo) { + public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount ,UserInfo userInfo) { return new PostDetailResponseDTO( post.getUserId().toString(), post.get_id().toString(), @@ -118,7 +114,6 @@ public static PostDetailResponseDTO of(PostEntity post, String nickname, String hitsCount, post.getCreatedAt(), commentCount, - reportCount, userInfo); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 0fe70c2c..568829c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -16,7 +16,7 @@ public class PostPollDetailResponseDTO extends PostDetailResponseDTO { public PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfo poll) { super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), - baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getReportCount(), baseDTO.getUserInfo()); + baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); this.poll = poll; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 162790a1..4bc37088 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -1,8 +1,6 @@ package inu.codin.codin.domain.post.repository; -import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.entity.PostStatus; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -30,7 +28,7 @@ public interface PostRepository extends MongoRepository { "'postStatus': { $in: ['ACTIVE'] }, " + "'userId': { $nin: ?0 }, " + "'reportCount': { $gte: 1 } }") // 신고 카운트가 1 이상인 경우만 반환 - Page getPostsWithReportedAndBlockedUsers(List blockedUsersId, PageRequest pageRequest); + Page getPostsWithReported(PageRequest pageRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index b28a7797..dcd5ade9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -157,7 +157,6 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { int scrapCount = scrapService.getScrapCount(post.get_id()); int hitsCount = hitsService.getHitsCount(post.get_id()); int commentCount = post.getCommentCount(); - int reportCount = post.getReportCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -183,14 +182,14 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { //게시물 + 투표 DTO 생성 return PostPollDetailResponseDTO.of( - PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, reportCount, userInfo), + PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), pollInfo ); } log.info("일반 게시물 상세정보 생성 성공 PostId: {}", post.get_id()); // 일반 게시물 처리 - return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, reportCount ,userInfo); + return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount ,userInfo); } // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 @@ -207,19 +206,6 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 - public PostPageResponse getReportedPosts(int pageNumber) { - - // 차단 목록 조회 - List blockedUsersId = blockService.getBlockedUsers(); - - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page; - page = postRepository.getPostsWithReportedAndBlockedUsers(blockedUsersId ,pageRequest); - - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); - } - // 게시물 리스트 가져오기 public List getPostListResponseDtos(List posts) { return posts.stream() diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 8ca4031e..4b737527 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; @@ -39,6 +40,16 @@ public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto .body(new SingleResponse<>(201, "신고 생성 완료", null)); } +// @Operation( +// summary = "삭제되지 않은 신고된 모든 게시물 조회" +// ) +// @GetMapping("/post") +// public ResponseEntity> getAllPosts(@RequestParam("page") @NotNull int pageNumber) { +// PostPageResponse postpages= reportService.getReportedPosts(pageNumber); +// return ResponseEntity.ok() +// .body(new SingleResponse<>(200, "삭제되지 않은 신고된 모든 게시물 조회 성공", postpages)); +// } + //(Admin) 특정 게시물의 신고 정보 조회 API @Operation(summary = "특정 게시물의 신고 내역 조회(관리자)") @PreAuthorize("hasRole('ADMIN')") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java new file mode 100644 index 00000000..60ed075e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java @@ -0,0 +1,124 @@ +//package inu.codin.codin.domain.report.dto.response; +// +//import com.fasterxml.jackson.annotation.JsonFormat; +//import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +//import inu.codin.codin.domain.post.entity.PostCategory; +//import inu.codin.codin.domain.post.entity.PostEntity; +//import io.swagger.v3.oas.annotations.media.Schema; +//import jakarta.validation.constraints.NotBlank; +//import jakarta.validation.constraints.NotNull; +//import lombok.Builder; +//import lombok.Getter; +// +//import java.time.LocalDateTime; +//import java.util.List; +// +//@Getter +//public class ReportListResponseDto { +// @Schema(description = "게시물 ID", example = "111111") +// @NotBlank +// private final String _id; +// +// @Schema(description = "유저 ID", example = "111111") +// @NotBlank +// private final String userId; +// +// @Schema(description = "게시물 종류", example = "구해요") +// @NotBlank +// private final PostCategory postCategory; +// +// @Schema(description = "게시물 제목", example = "Example") +// @NotBlank +// private final String title; +// +// @Schema(description = "게시물 내용", example = "example content") +// @NotBlank +// private final String content; +// +// @Schema(description = "유저 nickname 익명시 익명으로 표시됨") +// private final String nickname; +// +// @Schema(description = "유저 이미지 url", example = "https://~") +// private final String userImageUrl; +// +// @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") +// private final List postImageUrl; +// +// @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") +// @NotNull +// private final boolean isAnonymous; +// +// @Schema(description = "좋아요 count", example = "0") +// private final int likeCount; +// +// @Schema(description = "스크랩 count", example = "0") +// private final int scrapCount; +// +// @Schema(description = "댓글 및 대댓글 count", example = "0") +// private final int commentCount; +// +// @Schema(description = "조회수", example = "0") +// private final int hits; +// +// private final int reportCount; +// +// @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") +// @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") +// private final LocalDateTime createdAt; +// +// @Schema(description = "해당 게시글에 대한 유저 반응 여부") +// private final UserInfo userInfo; +// +// public ReportListResponseDto(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, +// boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, int reportCount, UserInfo userInfo){ +// this.userId = userId; +// this._id = _id; +// this.title = title; +// this.content = content; +// this.nickname = nickname; +// this.postCategory = postCategory; +// this.userImageUrl = userImageUrl; +// this.postImageUrl = postImageUrls; +// this.isAnonymous = isAnonymous; +// this.likeCount = likeCount; +// this.scrapCount = scrapCount; +// this.commentCount = commentCount; +// this.reportCount = reportCount; +// this.hits = hits; +// this.createdAt = createdAt; +// this.userInfo = userInfo; +// } +// +// @Getter +// public static class UserInfo { +// private final boolean isLike; +// private final boolean isScrap; +// +// @Builder +// public UserInfo(boolean isLike, boolean isScrap) { +// this.isLike = isLike; +// this.isScrap = isScrap; +// } +// } +// +// public static ReportListResponseDto of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount, int reportCount, UserInfo userInfo) { +// return new ReportListResponseDto( +// post.getUserId().toString(), +// post.get_id().toString(), +// post.getTitle(), +// post.getContent(), +// nickname, +// post.getPostCategory(), +// userImageUrl, +// post.getPostImageUrls(), +// post.isAnonymous(), +// likeCount, +// scrapCount, +// hitsCount, +// post.getCreatedAt(), +// commentCount, +// reportCount, +// userInfo); +// } +//} +// diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java new file mode 100644 index 00000000..c9552582 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java @@ -0,0 +1,32 @@ +//package inu.codin.codin.domain.report.dto.response; +// +//import lombok.AccessLevel; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +// +//import java.util.ArrayList; +//import java.util.List; +// +//@Getter +//@NoArgsConstructor(access = AccessLevel.PROTECTED) +//public class ReportPageResponse { +// +// private List contents = new ArrayList<>(); +// private long lastPage; +// private long nextPage; +// +// private ReportPageResponse(List contents, long lastPage, long nextPage) { +// this.contents = contents; +// this.lastPage = lastPage; +// this.nextPage = nextPage; +// } +// +// public static ReportPageResponse of(List reportPaging, long totalElements, long nextPage) { +// return ReportPageResponse.newPagingHasNext(reportPaging, totalElements, nextPage); +// } +// +// private static ReportPageResponse newPagingHasNext(List reports, long totalElements, long nextPage) { +// return new ReportPageResponse(reports, totalElements, nextPage); +// } +// +//} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 66bc2c48..03137fa8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; @@ -28,7 +29,10 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -36,6 +40,8 @@ @RequiredArgsConstructor @Slf4j public class ReportService { + + private final BlockService blockService; private final ReportRepository reportRepository; private final UserRepository userRepository; private final PostRepository postRepository; @@ -95,6 +101,15 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { } } +// public ReportPageResponse getReportedPosts(int pageNumber) { +// +// PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); +// Page page; +// page = postRepository.getPostsWithReported(pageRequest); +// +// return ReportPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); +// } + //post 총 신고수 증가 private void updatePostReportCount(ObjectId postId) { // 게시물 조회 From 1f0306814b649c35351af311f2c37c9f712dcbbf Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 12 Feb 2025 20:57:16 +0900 Subject: [PATCH 0447/1002] =?UTF-8?q?feat=20:=20=EC=9E=90=EA=B8=B0=20?= =?UTF-8?q?=EC=9E=90=EC=8B=A0=20=EC=B0=A8=EB=8B=A8=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B0=A8=EB=8B=A8=ED=95=A0=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/block/exception/SelfBlockedException.java | 7 +++++++ .../inu/codin/codin/domain/block/service/BlockService.java | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java new file mode 100644 index 00000000..878b28e7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.block.exception; + +public class SelfBlockedException extends RuntimeException{ + public SelfBlockedException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 31a57f87..3fe064b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import inu.codin.codin.domain.block.exception.NotBlockedException; +import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; @@ -22,12 +23,18 @@ public class BlockService { public void blockUser(String strBlockedUserId) { ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + + if (blockingUserId.equals(new ObjectId(strBlockedUserId))){ + throw new SelfBlockedException("자기 자신을 차단할 수 없습니다."); + } // 유저 엔티티 조회 UserEntity user = userRepository.findById(blockingUserId) .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); ObjectId blockedUserId = new ObjectId(strBlockedUserId); + userRepository.findById(blockedUserId) + .orElseThrow(() -> new NotFoundException("차단할 사용자를 찾을 수 없습니다.")); // 중복 차단 방지 checkUserBlocked(blockingUserId,blockedUserId); From eb1c88fe396ac3dbfb95f6e75f9b0a2ac760b7e5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Feb 2025 00:26:21 +0900 Subject: [PATCH 0448/1002] =?UTF-8?q?feat=20:=20=EC=9D=B5=EB=AA=85=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B6=80=EC=97=AC=20=EB=B0=8F=20=EA=B8=80?= =?UTF-8?q?=EC=93=B4=EC=9D=B4=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EB=B6=80?= =?UTF-8?q?=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/service/CommentService.java | 28 +++++++++---- .../reply/service/ReplyCommentService.java | 19 ++++++++- .../infra/redis/service/RedisAnonService.java | 39 +++++++++++++++++++ 3 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 3696c3db..03e86768 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -2,28 +2,29 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisAnonService; import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; + import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -35,7 +36,6 @@ public class CommentService { private final PostRepository postRepository; private final CommentRepository commentRepository; - private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; private final LikeService likeService; @@ -43,6 +43,7 @@ public class CommentService { private final NotificationService notificationService; private final RedisService redisService; private final S3Service s3Service; + private final RedisAnonService redisAnonService; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -64,12 +65,21 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 수 증가 post.updateCommentCount(post.getCommentCount() + 1); redisService.applyBestScore(1, postId); + setAnonNumber(post, userId); postRepository.save(post); log.info("댓글 추가완료 postId: {}.", postId); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); } + private void setAnonNumber(PostEntity post, ObjectId userId) { + if (post.getUserId().equals(userId)){ //글쓴이 + redisAnonService.setWriter(post.get_id().toString(), userId.toString()); + } else { + redisAnonService.getAnonNumber(post.get_id().toString(), userId.toString()); + } + } + // 댓글 삭제 (Soft Delete) public void softDeleteComment(String id) { log.info("댓글 삭제 요청. commentId: {}", id); @@ -126,7 +136,7 @@ public List getCommentsByPostId(String id) { return comments.stream() .map(comment -> { UserDto userDto = userMap.get(comment.getUserId()); - + int anonNum = redisAnonService.getAnonNumber(postId.toString(), comment.getUserId().toString()); String nickname; String userImageUrl; @@ -134,7 +144,9 @@ public List getCommentsByPostId(String id) { nickname = "탈퇴한 사용자"; userImageUrl = defaultImageUrl; } else { - nickname = comment.isAnonymous()? "익명" : userMap.get(comment.getUserId()).nickname(); + nickname = comment.isAnonymous()? + anonNum==0? "글쓴이" : "익명" + anonNum + : userMap.get(comment.getUserId()).nickname(); userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); } return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index cd5c7037..927e3ee0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -17,6 +17,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisAnonService; import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; @@ -43,6 +44,7 @@ public class ReplyCommentService { private final NotificationService notificationService; private final RedisService redisService; private final S3Service s3Service; + private final RedisAnonService redisAnonService; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -67,6 +69,9 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); post.updateCommentCount(post.getCommentCount() + 1); redisService.applyBestScore(1, post.get_id()); + setAnonNumber(post, userId); + redisAnonService.getAnonNumber(post.get_id().toString(), userId.toString()); + postRepository.save(post); log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); @@ -75,6 +80,14 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } + private void setAnonNumber(PostEntity post, ObjectId userId) { + if (post.getUserId().equals(userId)){ //글쓴이 + redisAnonService.setWriter(post.get_id().toString(), userId.toString()); + } else { + redisAnonService.getAnonNumber(post.get_id().toString(), userId.toString()); + } + } + // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) @@ -113,7 +126,7 @@ public List getRepliesByCommentId(ObjectId commentId) { return replies.stream() .map(reply -> { UserDto userDto = userMap.get(reply.getUserId()); - + int anonNum = redisAnonService.getAnonNumber(commentRepository.findById(reply.getCommentId()).get().getPostId().toString(), reply.getUserId().toString()); String nickname; String userImageUrl; @@ -121,7 +134,9 @@ public List getRepliesByCommentId(ObjectId commentId) { nickname = "탈퇴한 사용자"; userImageUrl = defaultImageUrl; } else { - nickname = reply.isAnonymous()? "익명" : userMap.get(reply.getUserId()).nickname(); + nickname = reply.isAnonymous()? + anonNum==0? "글쓴이" : "익명"+anonNum + : userMap.get(reply.getUserId()).nickname(); userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); } return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java new file mode 100644 index 00000000..9aca35d7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java @@ -0,0 +1,39 @@ +package inu.codin.codin.infra.redis.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RedisAnonService { + + private final RedisTemplate redisTemplate; + private static final String ANON_KEY = "anon:"; + private static final String COUNTER_KEY = "anon_counter:"; + + public Integer getAnonNumber(String postId, String userId){ + String anonKey = ANON_KEY + postId + ":" + userId; + String counterKey = COUNTER_KEY + postId; + + Object existingAnonNum = redisTemplate.opsForValue().get(anonKey); + if (existingAnonNum != null){ + return Integer.parseInt(existingAnonNum.toString()); + } + + Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); + if (counterAnonNum == null){ + redisTemplate.opsForValue().set(counterKey, 0); //카운터 1부터 시작 + } + + redisTemplate.opsForValue().increment(counterKey); + int counter = Integer.parseInt(redisTemplate.opsForValue().get(counterKey).toString()); + redisTemplate.opsForValue().set(anonKey, counter); //유저에게 익명 번호 설정 + return counter; + } + + public void setWriter(String postId, String userId){ + String anonKey = ANON_KEY + postId + ":" + userId; + redisTemplate.opsForValue().set(anonKey, 0); + } +} From 553d7526c2b1ad2eed7661355375f840dc0d5e50 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Feb 2025 00:47:28 +0900 Subject: [PATCH 0449/1002] =?UTF-8?q?fix=20:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EC=98=A4=EB=A5=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/AuthService.java | 7 ++++++- .../UserNicknameDuplicateException.java | 7 +++++++ .../UserPasswordChangeFailException.java | 7 ------- .../domain/user/repository/UserRepository.java | 2 ++ .../codin/domain/user/service/UserService.java | 16 ++++++++++++---- 5 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserNicknameDuplicateException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 8b78ffcf..284502e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisAuthService; import inu.codin.codin.infra.s3.S3Service; @@ -118,6 +119,10 @@ public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginReq public void createUser(String studentId, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage) { + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); + if (nickNameDuplicate.isPresent()){ + throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); + } PortalLoginResponseDto userData = redisAuthService.getUserData(studentId); log.info("[createUser] 요청 데이터: {}", studentId); @@ -135,7 +140,7 @@ public void createUser(String studentId, UserNicknameRequestDto userNicknameRequ } UserEntity user = UserEntity.of(userData); - user.updateNickname(new UserNicknameRequestDto(userNicknameRequestDto.getNickname())); + user.updateNickname(userNicknameRequestDto); user.updateProfileImageUrl(imageUrl); userRepository.save(user); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserNicknameDuplicateException.java b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserNicknameDuplicateException.java new file mode 100644 index 00000000..f1b25984 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserNicknameDuplicateException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.user.exception; + +public class UserNicknameDuplicateException extends RuntimeException{ + public UserNicknameDuplicateException(String message){ + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java deleted file mode 100644 index 1b6473f1..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/exception/UserPasswordChangeFailException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.user.exception; - -public class UserPasswordChangeFailException extends RuntimeException{ - public UserPasswordChangeFailException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index d3ea7599..9d122e64 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -18,4 +18,6 @@ public interface UserRepository extends MongoRepository { @Query("{'studentId': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByStudentId(String studentId); + + Optional findByNicknameAndDeletedAtIsNull(String nickname); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 9f4d0edb..4243b502 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -3,20 +3,21 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.email.repository.EmailAuthRepository; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; -import inu.codin.codin.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; +import inu.codin.codin.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; @@ -31,6 +32,7 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Slf4j @@ -144,6 +146,12 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) { + + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); + if (nickNameDuplicate.isPresent()){ + throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); + } + ObjectId userId = SecurityUtils.getCurrentUserId(); log.info("[유저 정보 업데이트] 현재 사용자 ID: {}", userId); From 0839ff2e3e947868259ef6f7fe1171915efe5881 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Feb 2025 12:41:19 +0900 Subject: [PATCH 0450/1002] =?UTF-8?q?fix=20:=20=ED=83=88=ED=87=B4=ED=95=9C?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=EC=9D=98=20nickname,=20profileimage?= =?UTF-8?q?=EB=A5=BC=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/service/CommentService.java | 4 ++-- .../domain/reply/service/ReplyCommentService.java | 4 ++-- .../codin/domain/post/service/PostService.java | 4 ++-- .../codin/domain/user/service/UserService.java | 14 ++++++-------- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 03e86768..5327d132 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -141,8 +141,8 @@ public List getCommentsByPostId(String id) { String userImageUrl; if (userDto.isDeleted()){ - nickname = "탈퇴한 사용자"; - userImageUrl = defaultImageUrl; + nickname = userMap.get(comment.getUserId()).nickname(); + userImageUrl = userMap.get(comment.getUserId()).imageUrl(); } else { nickname = comment.isAnonymous()? anonNum==0? "글쓴이" : "익명" + anonNum diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 927e3ee0..251ccdf4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -131,8 +131,8 @@ public List getRepliesByCommentId(ObjectId commentId) { String userImageUrl; if (userDto.isDeleted()){ - nickname = "탈퇴한 사용자"; - userImageUrl = defaultImageUrl; + nickname = userMap.get(reply.getUserId()).nickname(); + userImageUrl = userMap.get(reply.getUserId()).imageUrl(); } else { nickname = reply.isAnonymous()? anonNum==0? "글쓴이" : "익명"+anonNum diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 707b7b72..bf9a6ee7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -153,8 +153,8 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { userImageUrl = user.getProfileImageUrl(); } } else { - nickname = "탈퇴한 사용자"; - userImageUrl = s3Service.getDefaultProfileImageUrl(); + nickname = user.getNickname(); + userImageUrl = user.getProfileImageUrl(); } //Post 관련 인자 처리 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 9f4d0edb..6b59abf8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -2,18 +2,17 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.email.repository.EmailAuthRepository; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; -import inu.codin.codin.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; +import inu.codin.codin.domain.scrap.entity.ScrapEntity; +import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; import inu.codin.codin.domain.user.entity.UserEntity; @@ -26,7 +25,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -38,14 +36,12 @@ @RequiredArgsConstructor public class UserService { - private final EmailAuthRepository emailAuthRepository; private final UserRepository userRepository; private final LikeRepository likeRepository; private final PostRepository postRepository; private final ScrapRepository scrapRepository; private final CommentRepository commentRepository; - private final PasswordEncoder passwordEncoder; private final PostService postService; private final S3Service s3Service; @@ -126,6 +122,8 @@ public void deleteUser() { return new NotFoundException("해당 id에 대한 유저 정보를 찾을 수 없습니다."); }); user.delete(); + user.updateNickname(new UserNicknameRequestDto("탈퇴한 사용자")); + user.updateProfileImageUrl(s3Service.getDefaultProfileImageUrl()); userRepository.save(user); log.info("[회원 탈퇴 성공] _id: {}", userId); } From afb8cf858b6393dc0403a50c31e4c43c167acc53 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 14 Feb 2025 21:03:25 +0900 Subject: [PATCH 0451/1002] =?UTF-8?q?chore=20::=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index bdeb7189..34ca0edc 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit bdeb7189f318ecb593430019eea975e05e55d54b +Subproject commit 34ca0edc6c5961124a80e3a8ebdb024118dc7a9a From b88b8f685a9c8ac6193d0e5b58cd81f1a0307146 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Feb 2025 21:20:38 +0900 Subject: [PATCH 0452/1002] =?UTF-8?q?fix=20:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=ED=82=A4=EC=9B=8C=EB=93=9C=20Optional=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/controller/LectureController.java | 17 ++++------------- .../lecture/repository/LectureRepository.java | 6 +++--- .../domain/lecture/service/LectureService.java | 15 +++++++-------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index 29e9d735..a0702947 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -22,18 +22,20 @@ public class LectureController { @Operation( summary = "학과명 및 과목/교수 정렬 페이지", - description = "학과명과 과목/교수 라디오 토클을 통해 정렬한 리스트 반환
"+ + description = "학과명과 검색 키워드(optional), 과목/교수 라디오 토클을 통해 정렬한 리스트 반환
"+ "department : COMPUTER_SCI, INFO_COMM, EMBEDDED, OTHER(공통)
"+ + "keyword : 검색 키워드 (Optional)"+ "option : LEC(과목명) , PROF(교수명)" ) @GetMapping("/list") public ResponseEntity> sortListOfLectures(@RequestParam("department") Department department, + @RequestParam(value = "keyword", required = false) String keyword, @RequestParam("option") Option option, @RequestParam("page") int page){ return ResponseEntity.ok() .body(new SingleResponse<>(200, department.getDescription()+" 강의들 "+option.getDescription()+"순으로 정렬 반환", - lectureService.sortListOfLectures(department, option, page))); + lectureService.sortListOfLectures(department, keyword, option, page))); } @Operation( @@ -46,17 +48,6 @@ public ResponseEntity> getLectureDetails(@PathVariable("lectur .body(new SingleResponse<>(200, "강의 별점 정보 반환", lectureService.getLectureDetails(lectureId))); } - @Operation( - summary = "교수명, 과목명 검색", - description = "keyword 입력을 통해 (교수명, 과목명) 중 일치하는 결과 반환" - ) - @GetMapping("/search") - public ResponseEntity> searchLectures(@RequestParam("keyword") String keyword, - @RequestParam("page") int page){ - return ResponseEntity.ok() - .body(new SingleResponse<>(200, keyword+" 의 검색 결과 반환", lectureService.searchLectures(keyword, page))); - } - @Operation( summary = "학과, 학년, 수강학기 로 강의 검색", description = "수강 후기 작성 시 필요한 검색엔진
" + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java index ad03191a..00c2abb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -13,8 +13,8 @@ public interface LectureRepository extends MongoRepository { Page findAllByDepartment(Pageable pageable, Department department); - @Query("{ '$or': [ { 'lectureNm': { $regex: ?0, $options: 'i' } }, " + - "{ 'professor': { $regex: ?0, $options: 'i' } } ] }") - Page findAllByKeyword(String keyword, Pageable pageable); + @Query("{ 'department': ?0, '$or': [ { 'lectureNm': { $regex: ?1, $options: 'i' } }, " + + "{ 'professor': { $regex: ?1, $options: 'i' } } ] }") + Page findAllByKeywordAndDepartment(Department department, String keyword, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 84a7d076..8744a661 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -37,20 +37,19 @@ public LectureDetailResponseDto getLectureDetails(String lectureId) { return LectureDetailResponseDto.of(lectureEntity, ave, emotion, participants); } - public LecturePageResponse sortListOfLectures(Department department, Option option, int page) { + public LecturePageResponse sortListOfLectures(Department department, String keyword, Option option, int page) { if (department.equals(Department.EMBEDDED) || department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.OTHERS)) { PageRequest pageRequest = PageRequest.of(page, 20, option==Option.LEC? Sort.by("lectureNm"):Sort.by("professor")); - Page lecturePage = lectureRepository.findAllByDepartment(pageRequest, department); + Page lecturePage; + if (keyword == null){ + lecturePage = lectureRepository.findAllByDepartment(pageRequest, department); + } else { + lecturePage = lectureRepository.findAllByKeywordAndDepartment(department, keyword, pageRequest); + } return getLecturePageResponse(lecturePage); } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); } - public LecturePageResponse searchLectures(String keyword, int page) { - PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); - Page lecturePage = lectureRepository.findAllByKeyword(keyword, pageRequest); - return getLecturePageResponse(lecturePage); - } - public LecturePageResponse searchLecturesToReview(Department department, Integer grade, String semester, int page) { PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); Page lecturePage = findLectures(department, grade, semester, pageRequest); From c18fd8b253504c0818e10d688d1dad7c848df10e Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 14 Feb 2025 22:00:24 +0900 Subject: [PATCH 0453/1002] =?UTF-8?q?chore=20::=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 34ca0edc..ce53ddee 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 34ca0edc6c5961124a80e3a8ebdb024118dc7a9a +Subproject commit ce53ddee25d3b0e12f96d4a67f8a1529757bb059 From 1cd7641dc2d19e0a506385685927bfc934699a51 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 7 Feb 2025 23:37:04 +0900 Subject: [PATCH 0454/1002] =?UTF-8?q?submodule=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 0a32654e..09274699 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 0a32654e3f70e284db1d9545751af3f4b934375e +Subproject commit 09274699ea2ba2b9efd4fd9579c4dcf53efb8f65 From 7a4bdf78750a5591efca1b92910f5f6f3b74ec1f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 7 Feb 2025 23:45:32 +0900 Subject: [PATCH 0455/1002] =?UTF-8?q?submodule=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 09274699..d2b76a7e 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 09274699ea2ba2b9efd4fd9579c4dcf53efb8f65 +Subproject commit d2b76a7e83ddd5f42df5448542183866270f4a53 From 8769d956306434803295ffc9c286c8cf882ad98c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 8 Feb 2025 02:43:50 +0900 Subject: [PATCH 0456/1002] =?UTF-8?q?SubModule=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index d2b76a7e..bdeb7189 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit d2b76a7e83ddd5f42df5448542183866270f4a53 +Subproject commit bdeb7189f318ecb593430019eea975e05e55d54b From 082ee11fa7d1d5aad430c627b5c4f208bca7ad8d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 14 Feb 2025 21:03:25 +0900 Subject: [PATCH 0457/1002] =?UTF-8?q?chore=20::=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index bdeb7189..34ca0edc 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit bdeb7189f318ecb593430019eea975e05e55d54b +Subproject commit 34ca0edc6c5961124a80e3a8ebdb024118dc7a9a From bd894faf8cf9b1ebf5ebf46d5919527666e4514a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 14 Feb 2025 22:00:24 +0900 Subject: [PATCH 0458/1002] =?UTF-8?q?chore=20::=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 34ca0edc..ce53ddee 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 34ca0edc6c5961124a80e3a8ebdb024118dc7a9a +Subproject commit ce53ddee25d3b0e12f96d4a67f8a1529757bb059 From 6d0d3aa7544941e2359f77629fb741de42de31ff Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 00:44:02 +0900 Subject: [PATCH 0459/1002] =?UTF-8?q?fix=20:=20=EC=95=88=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=80=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20ChatRoom=20entity=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/dto/ChatRoomListResponseDto.java | 21 ++++---- .../domain/chat/chatroom/entity/ChatRoom.java | 20 ++++--- .../chat/chatroom/entity/ParticipantInfo.java | 52 +++++++++++++++++++ .../chat/chatroom/entity/Participants.java | 45 ++++++++++++---- .../dto/response/ChattingResponseDto.java | 12 +++-- .../domain/chat/chatting/entity/Chatting.java | 21 +++++--- 6 files changed, 135 insertions(+), 36 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java index 124002e4..ae731165 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java @@ -2,12 +2,12 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.bson.types.ObjectId; import java.time.LocalDateTime; @@ -24,30 +24,31 @@ public class ChatRoomListResponseDto { private final String roomName; @Schema(description = "가장 최근 채팅 내역 메세지", example = "안녕하세요") - private final String message; + private final String lastMessage; @Schema(description = "가장 최근 채팅 내역 시간", example = "2024-11-29") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private final LocalDateTime currentMessageDate; - @Schema(description = "채팅방 알림 설정", example = "true") - private final boolean notificationEnabled; + @Schema(description = "안읽은 채팅 수", example = "99") + private final int unread; @Builder - public ChatRoomListResponseDto(String chatRoomId, String roomName, String message, LocalDateTime currentMessageDate, boolean notificationEnabled) { + public ChatRoomListResponseDto(String chatRoomId, String roomName, String lastMessage, LocalDateTime currentMessageDate, int unread) { this.chatRoomId = chatRoomId; this.roomName = roomName; - this.message = message; + this.lastMessage = lastMessage; this.currentMessageDate = currentMessageDate; - this.notificationEnabled = notificationEnabled; + this.unread = unread; } - public static ChatRoomListResponseDto of(ChatRoom chatRoom, Chatting chatting) { + public static ChatRoomListResponseDto of(ChatRoom chatRoom, ObjectId userId) { return ChatRoomListResponseDto.builder() .chatRoomId(chatRoom.get_id().toString()) .roomName(chatRoom.getRoomName()) - .message(chatting==null ? null : chatting.getContent()) - .currentMessageDate(chatting==null ? null : chatting.getCreatedAt()) + .lastMessage(chatRoom.getLastMessage()==null ? null : chatRoom.getLastMessage()) + .currentMessageDate(chatRoom.getUpdatedAt()==null ? null : chatRoom.getUpdatedAt()) + .unread(chatRoom.getParticipants().getInfo().get(userId).getUnreadMessage()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index e3a301a0..1f9b42b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -11,9 +11,6 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import java.util.ArrayList; -import java.util.List; - @Document(collection = "chatroom") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -26,22 +23,29 @@ public class ChatRoom extends BaseTimeEntity { private String roomName; @NotBlank - private List participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) + private Participants participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) + + private String lastMessage; @Builder - public ChatRoom(String roomName, List participants) { + public ChatRoom(String roomName, Participants participants, String lastMessage) { this.roomName = roomName; this.participants = participants; + this.lastMessage = lastMessage; } public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, ObjectId senderId){ - ArrayList participants = new ArrayList<>(2); - participants.add(new Participants(new ObjectId(chatRoomCreateRequestDto.getReceiverId()), true)); - participants.add(new Participants(senderId, true)); + Participants participants = new Participants(); + participants.create(senderId); + participants.create(new ObjectId(chatRoomCreateRequestDto.getReceiverId())); return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) .participants(participants) .build(); } + + public void updateLastMessage(String message){ + this.lastMessage = message; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java new file mode 100644 index 00000000..3f8a75bd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -0,0 +1,52 @@ +package inu.codin.codin.domain.chat.chatroom.entity; + +import inu.codin.codin.common.BaseTimeEntity; +import lombok.*; +import org.bson.types.ObjectId; + +@Getter +@NoArgsConstructor +public class ParticipantInfo extends BaseTimeEntity { + + private ObjectId userId; + private boolean isConnected = false; + private int unreadMessage = 0; + private boolean notificationsEnabled = true; + + @Builder + public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled) { + this.userId = userId; + this.isConnected = isConnected; + this.unreadMessage = unreadMessage; + this.notificationsEnabled = notificationsEnabled; + } + + public void updateNotification() { + this.notificationsEnabled = !notificationsEnabled; + } + + public static ParticipantInfo enter(ObjectId userId){ + return ParticipantInfo.builder() + .userId(userId) + .isConnected(false) + .unreadMessage(0) + .notificationsEnabled(true) + .build(); + } + + public void plusUnread(){ + this.unreadMessage++; + } + + public void connect(){ + this.isConnected = true; + this.unreadMessage = 0; + } + + public void disconnect(){ + this.isConnected = false; + this.unreadMessage = 0; + setUpdatedAt(); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java index e9e510b9..1ba548c1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java @@ -1,24 +1,51 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import lombok.Builder; +import inu.codin.codin.common.BaseTimeEntity; import lombok.Getter; import lombok.Setter; import org.bson.types.ObjectId; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + @Getter @Setter public class Participants { - private final ObjectId userId; - private boolean notificationsEnabled; + private Map info = new ConcurrentHashMap<>(); + + public void create(ObjectId memberId){ + info.put(memberId, ParticipantInfo.enter(memberId)); + } - @Builder - public Participants(ObjectId userId, boolean notificationsEnabled) { - this.userId = userId; - this.notificationsEnabled = notificationsEnabled; + public boolean getMessage(ObjectId receiver) { + ParticipantInfo member; + if((member=info.get(receiver))==null) { + return false; + } + member.plusUnread(); + return true; } - public void updateNotification() { - this.notificationsEnabled = !notificationsEnabled; + public boolean enter(ObjectId memberId){ + ParticipantInfo participantInfo; + if ((participantInfo = info.get(memberId))==null) { + return false; + } + + participantInfo.connect(); + info.put(memberId, participantInfo); + return true; + } + + public boolean exit(ObjectId memberId) { + ParticipantInfo participantInfo; + if ((participantInfo = info.get(memberId))==null) { + return false; + } + + participantInfo.disconnect(); + info.put(memberId, participantInfo); + return true; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java index 498204ca..632918df 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/response/ChattingResponseDto.java @@ -37,8 +37,11 @@ public class ChattingResponseDto { private final String currentUserId; + private final int unread; + + @Builder - public ChattingResponseDto(String id, String senderId, String content, ContentType contentType, LocalDateTime createdAt, String chatRoomId, String currentUserId) { + public ChattingResponseDto(String id, String senderId, String content, ContentType contentType, LocalDateTime createdAt, String chatRoomId, String currentUserId, int unread) { this.id = id; this.senderId = senderId; this.content = content; @@ -46,6 +49,7 @@ public ChattingResponseDto(String id, String senderId, String content, ContentTy this.createdAt = createdAt; this.chatRoomId = chatRoomId; this.currentUserId = currentUserId; + this.unread = unread; } public static ChattingResponseDto of(Chatting chatting){ @@ -54,8 +58,9 @@ public static ChattingResponseDto of(Chatting chatting){ .senderId(chatting.getSenderId().toString()) .content(chatting.getContent()) .createdAt(chatting.getCreatedAt()) - .contentType(chatting.getType()) + .contentType(chatting.getContentType()) .chatRoomId(chatting.getChatRoomId().toString()) + .unread(chatting.getUnreadCount()) .build(); } @@ -65,9 +70,10 @@ public static ChattingResponseDto of(Chatting chatting, ObjectId currentUserId){ .senderId(chatting.getSenderId().toString()) .content(chatting.getContent()) .createdAt(chatting.getCreatedAt()) - .contentType(chatting.getType()) + .contentType(chatting.getContentType()) .chatRoomId(chatting.getChatRoomId().toString()) .currentUserId(currentUserId.toString()) + .unread(chatting.getUnreadCount()) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java index 3bc83d03..7fe8eb1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.BaseTimeEntity; import inu.codin.codin.domain.chat.chatting.dto.ContentType; +import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; @@ -27,22 +28,30 @@ public class Chatting extends BaseTimeEntity { private ObjectId chatRoomId; - private ContentType type; + private ContentType contentType; + + private int unreadCount; @Builder - public Chatting(ObjectId senderId, String content, ObjectId chatRoomId, ContentType type) { + public Chatting(ObjectId senderId, String content, ObjectId chatRoomId, ContentType contentType, int unreadCount) { this.senderId = senderId; this.content = content; this.chatRoomId = chatRoomId; - this.type = type; + this.contentType = contentType; + this.unreadCount = unreadCount; } - public static Chatting of(ObjectId chatRoomId, String content, ObjectId senderId, ContentType type) { + public static Chatting of(ObjectId chatRoomId, ChattingRequestDto chattingRequestDto, ObjectId senderId, int unreadCount) { return Chatting.builder() .senderId(senderId) - .content(content) + .content(chattingRequestDto.getContent()) .chatRoomId(chatRoomId) - .type(type) + .contentType(chattingRequestDto.getContentType()) + .unreadCount(unreadCount) .build(); } + + public void minusUnread(){ + this.unreadCount--; + } } From 90eee34b0af330b6a279b06375c467bea090d7e9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 00:44:41 +0900 Subject: [PATCH 0460/1002] =?UTF-8?q?fix=20:=20=EC=95=88=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=80=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20ChatRoom=20entity=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/chat/chatroom/entity/Participants.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java index 1ba548c1..3201fcac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import inu.codin.codin.common.BaseTimeEntity; import lombok.Getter; import lombok.Setter; import org.bson.types.ObjectId; From 17b0345d14b0fddf4a341be3de51398199354123 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 00:46:24 +0900 Subject: [PATCH 0461/1002] =?UTF-8?q?feat=20:=20=EC=95=88=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=80=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/WebSocketConfig.java | 6 +- .../domain/chat/StompMessageProcessor.java | 144 ++++++++++++++++++ .../dto/event/ChatRoomNotificationEvent.java | 21 +++ .../repository/ChatRoomRepository.java | 4 +- .../service/ChatRoomEventListener.java | 26 ++++ .../chatroom/service/ChatRoomService.java | 71 +++++---- .../dto/event/ChattingArrivedEvent.java | 16 ++ .../service/ChattingEventListener.java | 41 +++++ .../chatting/service/ChattingService.java | 31 ++-- 9 files changed, 305 insertions(+), 55 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/event/ChatRoomNotificationEvent.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomEventListener.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingArrivedEvent.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 63fc7455..fcdb2be8 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.config; -import inu.codin.codin.domain.chat.chatting.ChatPreHandler; +import inu.codin.codin.domain.chat.StompMessageProcessor; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; @@ -15,7 +15,7 @@ @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private final ChatPreHandler chatPreHandler; + private final StompMessageProcessor stompMessageProcessor; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint @@ -39,6 +39,6 @@ public void configureWebSocketTransport(WebSocketTransportRegistration registrat @Override public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(chatPreHandler); + registration.interceptors(stompMessageProcessor); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java new file mode 100644 index 00000000..e0f6df4f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java @@ -0,0 +1,144 @@ +package inu.codin.codin.domain.chat; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.security.CustomUserDetailsService; +import io.jsonwebtoken.MalformedJwtException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.context.event.EventListener; +import org.springframework.http.HttpStatus; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@RequiredArgsConstructor +@Slf4j +public class StompMessageProcessor implements ChannelInterceptor { + + private final JwtTokenProvider jwtTokenProvider; + private final CustomUserDetailsService customUserDetailsService; + private final Map sessionStore = new ConcurrentHashMap<>(); + private final ChatRoomRepository chatRoomRepository; + private final UserRepository userRepository; + private static final String BEARER_PREFIX="Bearer "; + + @Override + public Message preSend(Message message, MessageChannel messageChannel){ + StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + handleMessage(headerAccessor); + return message; + } + + public void handleMessage(StompHeaderAccessor headerAccessor){ + if (headerAccessor == null || headerAccessor.getCommand() == null){ + throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString()); + } + + switch (headerAccessor.getCommand()){ + case CONNECT -> { + getTokenByHeader(headerAccessor); + connectSession(headerAccessor); + } + case SUBSCRIBE -> { + log.info("[STOMP] Subscribe" ); + enterToChatRoom(headerAccessor); + } + case UNSUBSCRIBE -> { + log.info("[STOMP] UnSubscribe" ); + exitToChatRoom(headerAccessor); + } + case DISCONNECT -> { + log.info("[STOMP] DISCONNECT"); + exitToChatRoom(headerAccessor); + disconnectSession(headerAccessor); + } + } + } + + @EventListener + private void connectSession(StompHeaderAccessor headerAccessor) { + String sessionId = headerAccessor.getSessionId(); + String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); + sessionStore.put(sessionId, chatroomId); + } + private void exitToChatRoom(StompHeaderAccessor headerAccessor) { + Result result = getResult(headerAccessor); + result.chatroom().getParticipants().exit(result.user().get_id()); + chatRoomRepository.save(result.chatroom()); + } + + private void enterToChatRoom(StompHeaderAccessor headerAccessor){ + Result result = getResult(headerAccessor); + result.chatroom.getParticipants().enter(result.user.get_id()); + chatRoomRepository.save(result.chatroom); + } + + private void disconnectSession(StompHeaderAccessor headerAccessor){ + sessionStore.remove(headerAccessor.getSessionId()); + } + + private Result getResult(StompHeaderAccessor headerAccessor) { + String studentId; + if (headerAccessor.getUser() != null) { + studentId = headerAccessor.getUser().getName(); + } else { + throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); + } + String chatroomId = sessionStore.get(headerAccessor.getSessionId()); + if (chatroomId == null || !ObjectId.isValid(chatroomId)) { + throw new IllegalArgumentException("올바른 chatRoomId가 아닙니다: " + chatroomId); + } + ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) + .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); + UserEntity user = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + Result result = new Result(chatroom, user); + return result; + } + + private record Result(ChatRoom chatroom, UserEntity user) { + } + + private void getTokenByHeader(StompHeaderAccessor headerAccessor) { + // 헤더 토큰 얻기 + String authorizationHeader = String.valueOf(headerAccessor.getNativeHeader("Authorization")); + if (authorizationHeader == null || authorizationHeader.equals("null")) { + throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); + } + String token = authorizationHeader.substring(BEARER_PREFIX.length()); + + // 토큰 인증 + try { + if (jwtTokenProvider.validateAccessToken(token)) { + String studentId = jwtTokenProvider.getUsername(token); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(studentId); + headerAccessor.setUser(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())); + } + } catch (MessageDeliveryException e) { + throw new MessageDeliveryException("[Chatting] Jwt로 인한 메세지 전송 오류입니다."); + } catch (MalformedJwtException e) { + throw new MalformedJwtException("[Chatting] 비정상적인 jwt 토큰 입니다."); + } catch (Exception e) { + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "[Chatting] 인증되지 않은 jwt 토큰 입니다."); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/event/ChatRoomNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/event/ChatRoomNotificationEvent.java new file mode 100644 index 00000000..b3c9695a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/event/ChatRoomNotificationEvent.java @@ -0,0 +1,21 @@ +package inu.codin.codin.domain.chat.chatroom.dto.event; + +import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class ChatRoomNotificationEvent extends ApplicationEvent { + + private final ObjectId chatRoomId; + private final ObjectId receiverId; + private final Participants participants; + + public ChatRoomNotificationEvent(Object source, ObjectId chatRoomId, ObjectId receiverId, Participants participants) { + super(source); + this.chatRoomId = chatRoomId; + this.receiverId = receiverId; + this.participants = participants; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index 527fd4fa..fe59273a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -13,6 +13,6 @@ public interface ChatRoomRepository extends MongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") Optional findById(ObjectId id); - @Query("{ 'participants': { '$elemMatch': { 'userId': ?0 } }, 'deleteAt': null, 'participants.userId': { '$nin': ?1 } }") - List findByParticipant(ObjectId userId, List blockedUsersId); + @Query("{ 'participants.info.?0.userId': ?0 }") + List findByParticipant(ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomEventListener.java new file mode 100644 index 00000000..8bbb5b8e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomEventListener.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.chat.chatroom.service; + +import inu.codin.codin.domain.chat.chatroom.dto.event.ChatRoomNotificationEvent; +import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; +import inu.codin.codin.domain.notification.service.NotificationService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChatRoomEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleChatRoomNotification(ChatRoomNotificationEvent event){ + if (event.getParticipants().getInfo().containsKey(event.getReceiverId())){ + ParticipantInfo participant = event.getParticipants().getInfo().get(event.getReceiverId()); + if (participant.isNotificationsEnabled()) + notificationService.sendNotificationMessageByChat(participant.getUserId(), event.getChatRoomId()); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index ccbca0df..042c7aa9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,15 +1,16 @@ package inu.codin.codin.domain.chat.chatroom.service; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.dto.event.ChatRoomNotificationEvent; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.repository.UserRepository; @@ -17,6 +18,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; @@ -29,15 +31,16 @@ @Slf4j public class ChatRoomService { - private final NotificationService notificationService; - private final ChatRoomRepository chatRoomRepository; - private final CustomChattingRepository customChattingRepository; private final UserRepository userRepository; + private final BlockService blockService; + private final NotificationService notificationService; + private final ApplicationEventPublisher eventPublisher; + - public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto, UserDetails userDetails) { - ObjectId senderId = ((CustomUserDetails) userDetails).getId(); + public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto) { + ObjectId senderId = SecurityUtils.getCurrentUserId(); if (senderId.toString().equals(chatRoomCreateRequestDto.getReceiverId())){ throw new ChatRoomCreateFailException("자기 자신과는 채팅방을 생성할 수 없습니다."); } @@ -54,11 +57,8 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat chatRoomRepository.save(chatRoom); log.info("[채팅방 생성 완료] 채팅방 ID: {}, 송신자 ID: {}, 수신자 ID: {}", chatRoom.get_id(), senderId, chatRoomCreateRequestDto.getReceiverId()); - for (Participants participant : chatRoom.getParticipants()){ - if (!participant.getUserId().equals(senderId) && participant.isNotificationsEnabled()){ - notificationService.sendNotificationMessageByChat(participant.getUserId(), chatRoom); - } - } + eventPublisher.publishEvent(new ChatRoomNotificationEvent(this, + chatRoom.get_id(), new ObjectId(chatRoomCreateRequestDto.getReceiverId()), chatRoom.getParticipants())); Map response = new HashMap<>(); response.put("chatRoomId", chatRoom.get_id().toString()); @@ -72,18 +72,16 @@ public List getAllChatRoomByUser(UserDetails userDetail // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); - List chatRooms = chatRoomRepository.findByParticipant(userId, blockedUsersId); + List chatRooms = chatRoomRepository.findByParticipant(userId); log.info("[채팅방 조회 결과] 유저 ID: {}가 참여 중인 채팅방 개수: {}", userId, chatRooms.size()); return chatRooms.stream() - .map(chatRoom -> { - Chatting chat = customChattingRepository.findMostRecentByChatRoomId(chatRoom.get_id()); - log.info("[최근 채팅 조회] 채팅방 ID: {}, 최근 채팅 내용: {}", chatRoom.get_id(), chat != null ? chat.getContent() : "없음"); - return ChatRoomListResponseDto.of(chatRoom, chat); - }).toList(); + .filter(chatRoom -> chatRoom.getParticipants().getInfo().keySet().stream() + .noneMatch(blockedUsersId::contains)) + .map(chatRoom -> ChatRoomListResponseDto.of(chatRoom, userId)).toList(); } - public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { - ObjectId userId = ((CustomUserDetails) userDetails).getId(); + public void leaveChatRoom(String chatRoomId) { + ObjectId userId = SecurityUtils.getCurrentUserId(); log.info("[채팅방 탈퇴 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) @@ -91,26 +89,29 @@ public void leaveChatRoom(String chatRoomId, UserDetails userDetails) { log.error("[채팅방 확인 실패] 채팅방 ID: {}를 찾을 수 없습니다.", chatRoomId); return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); }); - log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, chatRoom.getParticipants().size()); - boolean isRemoved = chatRoom.getParticipants() - .removeIf(participant -> participant.getUserId().equals(userId)); - if (!isRemoved) { + Map info = chatRoom.getParticipants().getInfo(); + log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, info.size()); + + if (info.containsKey(userId)){ + info.remove(userId); + } else { log.warn("[채팅방 탈퇴 실패] 유저 ID: {}는 채팅방에 참여하지 않았습니다.", userId); throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); } log.info("[채팅방 탈퇴 성공] 유저 ID: {}가 채팅방에서 탈퇴", userId); - if (chatRoom.getParticipants().isEmpty()) { - chatRoom.delete(); - log.info("[채팅방 삭제] 채팅방 ID: {}에 더 이상 참여자가 없어 채팅방을 삭제합니다.", chatRoomId); - } +// if (chatRoom.getParticipants().isEmpty()) { +// chatRoom.delete(); +// log.info("[채팅방 삭제] 채팅방 ID: {}에 더 이상 참여자가 없어 채팅방을 삭제합니다.", chatRoomId); +// } chatRoomRepository.save(chatRoom); log.info("[채팅방 삭제 후 저장] 채팅방 ID: {} 저장 완료", chatRoomId); + } - public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) { - ObjectId userId = ((CustomUserDetails) userDetails).getId(); + public void setNotificationChatRoom(String chatRoomId) { + ObjectId userId = SecurityUtils.getCurrentUserId(); log.info("[알림 설정 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) @@ -118,15 +119,11 @@ public void setNotificationChatRoom(String chatRoomId, UserDetails userDetails) log.error("[알림 설정 실패] 채팅방을 찾을 수 없습니다. 채팅방 ID: {}", chatRoomId); return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); }); - log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, chatRoom.getParticipants().size()); - chatRoom.getParticipants().stream() - .filter(participants -> participants.getUserId().equals(userId)) - .forEach(participants -> { - participants.updateNotification(); - log.info("[알림 설정] 유저 ID: {}의 알림 설정 완료", userId); - }); + Map info = chatRoom.getParticipants().getInfo(); + log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, info.size()); + info.get(userId).updateNotification(); chatRoomRepository.save(chatRoom); log.info("[알림 설정 완료] 채팅방 ID: {}에 알림 설정 완료", chatRoomId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingArrivedEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingArrivedEvent.java new file mode 100644 index 00000000..352bbe03 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingArrivedEvent.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.chat.chatting.dto.event; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class ChattingArrivedEvent extends ApplicationEvent { + + private final Chatting chatting; + + public ChattingArrivedEvent(Object source, Chatting chatting) { + super(source); + this.chatting = chatting; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java new file mode 100644 index 00000000..3d6dff40 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.chat.chatting.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChattingEventListener { + + private final ChatRoomRepository chatRoomRepository; + private final ChattingRepository chattingRepository; + + @Async + @EventListener + public void handleChattingArrivedEvent(ChattingArrivedEvent event){ + Chatting chatting = event.getChatting(); + ChatRoom chatRoom = chatRoomRepository.findById(chatting.getChatRoomId()) + .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다. ID: "+ chatting.getChatRoomId())); + chatRoom.getParticipants().getInfo().forEach( + (id, participantInfo) -> { + if (participantInfo.isConnected()) { + chatting.minusUnread(); + } else { + participantInfo.plusUnread(); + } + } + ); + chatRoom.updateLastMessage(chatting.getContent()); + chatRoomRepository.save(chatRoom); + chattingRepository.save(chatting); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index e454eb6c..b1bdae18 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingAndUserIdResponseDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; @@ -15,6 +16,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -33,6 +35,7 @@ public class ChattingService { private final ChattingRepository chattingRepository; private final S3Service s3Service; private final NotificationService notificationService; + private final ApplicationEventPublisher eventPublisher; public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { log.info("[메시지 전송] 채팅방 ID: {}, 내용: {}", id, chattingRequestDto.getContent()); @@ -44,37 +47,39 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq }); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); - Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto.getContent(), userId, chattingRequestDto.getContentType()); + Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId, chatRoom.getParticipants().getInfo().size()); log.info("[메시지 전송 성공] 메시지: [{}], 송신자 ID: {}, 채팅방 ID: {}", chattingRequestDto.getContent(), userId, id); chattingRepository.save(chatting); - log.info("Message [{}] send by member: {} to chatting room: {}", chattingRequestDto.getContent(), userId, id); + // //Receiver의 알림 체크 후, 메세지 전송 // for (Participants participant : chatRoom.getParticipants()){ // if (participant.getUserId() != userId && participant.isNotificationsEnabled()){ // notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom); // } // } + eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting)); + return ChattingResponseDto.of(chatting); } public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { - log.info("[메시지 조회] 채팅방 ID: {}, 페이지: {}", id, page); + log.info("[메시지 조회] 채팅방 ID: {}, 페이지: {}", id, page); - Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); - chatRoomRepository.findById(new ObjectId(id)) - .orElseThrow(() -> { - log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); - return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); - }); + Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); + chatRoomRepository.findById(new ObjectId(id)) + .orElseThrow(() -> { + log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); - List chattingResponseDto = chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) - .stream().map(ChattingResponseDto::of).toList(); + List chattingResponseDto = chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) + .stream().map(ChattingResponseDto::of).toList(); - log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); + log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); - return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); + return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); } public List sendImageMessage(List chatImages) { From 9e0dc2a69bc9dfcfc011aa872f039f18317b3f09 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 00:47:41 +0900 Subject: [PATCH 0462/1002] refactor :: --- .../chatroom/controller/ChatRoomController.java | 14 +++++++------- .../notification/service/NotificationService.java | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index c86323bd..1116a18b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -28,9 +28,9 @@ public class ChatRoomController { summary = "채팅방 생성" ) @PostMapping - public ResponseEntity> createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto, @AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> createChatRoom(@RequestBody ChatRoomCreateRequestDto chatRoomCreateRequestDto){ return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "채팅방 생성 완료", chatRoomService.createChatRoom(chatRoomCreateRequestDto, userDetails))); + .body(new SingleResponse<>(201, "채팅방 생성 완료", chatRoomService.createChatRoom(chatRoomCreateRequestDto))); } @Operation( @@ -39,15 +39,15 @@ public ResponseEntity> createChatRoom(@RequestBody ChatRoomCre @GetMapping public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ return ResponseEntity.ok() - .body(new ListResponse<>(200, "채팅방 리스트 반환 완료",chatRoomService.getAllChatRoomByUser(userDetails))); + .body(new ListResponse<>(200, "채팅방 리스트 반환 완료", chatRoomService.getAllChatRoomByUser(userDetails))); } @Operation( summary = "채팅방 나가기" ) @DeleteMapping("/{chatRoomId}") - public ResponseEntity> leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ - chatRoomService.leaveChatRoom(chatRoomId, userDetails); + public ResponseEntity> leaveChatRoom(@PathVariable("chatRoomId") String chatRoomId){ + chatRoomService.leaveChatRoom(chatRoomId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "채팅방 나가기 완료", null)); } @@ -56,8 +56,8 @@ public ResponseEntity> leaveChatRoom(@PathVariable("chatRoomId summary = "채팅방 알림 여부 수정" ) @GetMapping("/notification/{chatRoomId}") - public ResponseEntity> setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId, @AuthenticationPrincipal UserDetails userDetails){ - chatRoomService.setNotificationChatRoom(chatRoomId, userDetails); + public ResponseEntity> setNotificationChatRoom(@PathVariable("chatRoomId") String chatRoomId){ + chatRoomService.setNotificationChatRoom(chatRoomId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "채팅방 알림 여부 수정 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 07996bac..396385bd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -186,11 +186,11 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { } } - public void sendNotificationMessageByChat(ObjectId userId, ChatRoom chatRoom) { + public void sendNotificationMessageByChat(ObjectId userId, ObjectId chatRoomId) { UserEntity user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map chat = new HashMap<>(); - chat.put("id", chatRoom.get_id().toString()); + chat.put("id", chatRoomId.toString()); sendFcmMessageToUser("익명 채팅방", NOTI_CHAT, chat, user); } From 04a66d10b6c9c6feb26a94490956d1b2518b7080 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 00:48:05 +0900 Subject: [PATCH 0463/1002] refactor :: --- .../src/main/java/inu/codin/codin/common/BaseTimeEntity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java index ba302115..4febdb31 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java @@ -39,4 +39,8 @@ public void recreatedAt(){ this.createdAt = LocalDateTime.now(); } + public void setUpdatedAt() { + this.updatedAt = LocalDateTime.now(); + } + } \ No newline at end of file From c4922bafa6e2efb832697dd437c2dbd34c9bf634 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 00:48:13 +0900 Subject: [PATCH 0464/1002] refactor :: --- .../domain/chat/chatting/ChatPreHandler.java | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java deleted file mode 100644 index fffcb15f..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/ChatPreHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -package inu.codin.codin.domain.chat.chatting; - -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; -import inu.codin.codin.domain.user.security.CustomUserDetailsService; -import io.jsonwebtoken.MalformedJwtException; -import lombok.RequiredArgsConstructor; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.simp.stomp.StompCommand; -import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class ChatPreHandler implements ChannelInterceptor { - private final JwtTokenProvider jwtTokenProvider; - private final CustomUserDetailsService customUserDetailsService; - private static final String BEARER_PREFIX="Bearer "; - - @Override - public Message preSend(Message message, MessageChannel messageChannel){ - StompHeaderAccessor headerAccessor = StompHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - - if (headerAccessor.getCommand() == StompCommand.CONNECT) { - // 헤더 토큰 얻기 - String authorizationHeader = String.valueOf(headerAccessor.getNativeHeader("Authorization")); - if (authorizationHeader == null || authorizationHeader.equals("null")) { - throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); - } - String token = authorizationHeader.substring(BEARER_PREFIX.length()); - - // 토큰 인증 - try { - if (jwtTokenProvider.validateAccessToken(token)) { - String email = jwtTokenProvider.getUsername(token); - UserDetails userDetails = customUserDetailsService.loadUserByUsername(email); - headerAccessor.setUser(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())); - } - } catch (MessageDeliveryException e) { - throw new MessageDeliveryException("[Chatting] 메세지 에러"); - } catch (MalformedJwtException e) { - throw new MalformedJwtException("[Chatting] JWT 오류"); - } catch (Exception e) { - throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "[Chatting] JWT 오류"); - } - } - - return message; - } -} From 7b26be627ec180786d0a8d3183c3a04c90de741e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Feb 2025 01:37:20 +0900 Subject: [PATCH 0465/1002] =?UTF-8?q?feat=20:=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C=20=ED=91=9C=EC=8B=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/StompMessageProcessor.java | 3 +++ .../chat/chatroom/entity/Participants.java | 3 +++ .../repository/ChattingRepository.java | 4 +++- .../chat/chatting/service/ChattingService.java | 18 +++++++++++++++--- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java index e0f6df4f..927da9c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java @@ -6,6 +6,7 @@ import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.service.ChattingService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.domain.user.security.CustomUserDetailsService; @@ -36,6 +37,7 @@ public class StompMessageProcessor implements ChannelInterceptor { private final JwtTokenProvider jwtTokenProvider; private final CustomUserDetailsService customUserDetailsService; + private final ChattingService chattingService; private final Map sessionStore = new ConcurrentHashMap<>(); private final ChatRoomRepository chatRoomRepository; private final UserRepository userRepository; @@ -88,6 +90,7 @@ private void exitToChatRoom(StompHeaderAccessor headerAccessor) { private void enterToChatRoom(StompHeaderAccessor headerAccessor){ Result result = getResult(headerAccessor); + chattingService.updateUnreadCount(result.chatroom.get_id(), result.user.get_id()); result.chatroom.getParticipants().enter(result.user.get_id()); chatRoomRepository.save(result.chatroom); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java index 3201fcac..c4ee06a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java @@ -1,6 +1,9 @@ package inu.codin.codin.domain.chat.chatroom.entity; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.Setter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 4689bdfc..6b96ee39 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -9,5 +9,7 @@ public interface ChattingRepository extends MongoRepository { - List findAllByChatRoomIdOrderByCreatedAt(ObjectId id, Pageable pageable); + List findAllByChatRoomIdOrderByCreatedAtDesc(ObjectId chatRoomId); + + List findAllByChatRoomId(ObjectId id, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index b1bdae18..4dd2293c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.chat.chatting.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; @@ -38,8 +39,6 @@ public class ChattingService { private final ApplicationEventPublisher eventPublisher; public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { - log.info("[메시지 전송] 채팅방 ID: {}, 내용: {}", id, chattingRequestDto.getContent()); - ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); @@ -74,7 +73,7 @@ public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); }); - List chattingResponseDto = chattingRepository.findAllByChatRoomIdOrderByCreatedAt(new ObjectId(id), pageable) + List chattingResponseDto = chattingRepository.findAllByChatRoomId(new ObjectId(id), pageable) .stream().map(ChattingResponseDto::of).toList(); log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); @@ -91,4 +90,17 @@ public List sendImageMessage(List chatImages) { return imageUrls; } + + public void updateUnreadCount(ObjectId chatRoomId, ObjectId userId){ + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(()-> new NotFoundException("채팅방을 찾을 수 없습니다.")); + chattingRepository.findAllByChatRoomIdOrderByCreatedAtDesc(chatRoomId) + .stream() + .limit(chatRoom.getParticipants().getInfo().get(userId).getUnreadMessage()) + .forEach(chatting -> { + chatting.minusUnread(); + chattingRepository.save(chatting); + }); + + } } From 10d4a6a569a1bcc1c406e44e51a74487882cc54b Mon Sep 17 00:00:00 2001 From: gisu1102 <76506573+gisu1102@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:35:32 +0900 Subject: [PATCH 0466/1002] =?UTF-8?q?refactor=20::=20deleted=20null=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor :: deleted null 조건 추가 --- .../domain/chat/chatroom/repository/ChatRoomRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index fe59273a..41045363 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -13,6 +13,6 @@ public interface ChatRoomRepository extends MongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") Optional findById(ObjectId id); - @Query("{ 'participants.info.?0.userId': ?0 }") + @Query("{ 'participants.info.?0.userId': ?0, 'deletedAt': null }") List findByParticipant(ObjectId userId); } From 921d239e1392897b16939faf83ba433a1dce0385 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 20 Feb 2025 01:44:21 +0900 Subject: [PATCH 0467/1002] =?UTF-8?q?fix=20:=20info=20api=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index bf03eabc..dac7bae7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -112,7 +112,8 @@ public PasswordEncoder passwordEncoder() { "/ws-stomp/**", // "/chat", // "/chat/image", - "/chats/**" + "/chats/**", + "/info/**" }; // Swagger 접근 가능한 URL From c18321598ff9e057631b0d39b58ae1671a378a43 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 21 Feb 2025 03:08:32 +0900 Subject: [PATCH 0468/1002] =?UTF-8?q?feat=20::=20security=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EC=B2=B4=EC=9D=B8=20oauth=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 19 +++++-- .../util/OAuth2LoginFailureHandler.java | 37 +++++++++++++ .../util/OAuth2LoginSuccessHandler.java | 54 +++++++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index bf03eabc..887bd08e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -4,6 +4,9 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.common.security.service.CustomOAuth2UserService; +import inu.codin.codin.common.security.util.OAuth2LoginFailureHandler; +import inu.codin.codin.common.security.util.OAuth2LoginSuccessHandler; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,6 +41,9 @@ public class SecurityConfig { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; private final JwtUtils jwtUtils; + private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; + private final CustomOAuth2UserService customOAuth2UserService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -53,7 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PERMIT_ALL).permitAll() - .requestMatchers(SWAGGER_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(SWAGGER_AUTH_PATHS).permitAll() .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") @@ -68,6 +74,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) // 예외 처리 필터 추가 .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) + //oauth2 로그인 설정 추가 + .oauth2Login(oauth2 -> oauth2 + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + ) + .successHandler(oAuth2LoginSuccessHandler) + .failureHandler(oAuth2LoginFailureHandler) + ) // Content-Security-Policy 및 Frame-Options 설정 .headers(headers -> headers .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // X-Frame-Options 비활성화 @@ -75,9 +89,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.setSharedObject(AuthenticationManager.class, authenticationManager(http)); - http.setSharedObject(RoleHierarchy.class, roleHierarchy()); - return http.build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java new file mode 100644 index 00000000..1d9a5038 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -0,0 +1,37 @@ +package inu.codin.codin.common.security.util; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.PrintWriter; + +@Component +public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { + @Override + public void onAuthenticationFailure(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) throws IOException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + String errorMessage = null; + if (exception instanceof OAuth2AuthenticationException) { + OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); + errorMessage = error.getDescription(); + } + if (errorMessage == null || errorMessage.isEmpty()) { + errorMessage = "인증 실패"; // 기본 오류 메시지 + } + + PrintWriter writer = response.getWriter(); + writer.write("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); + writer.flush(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java new file mode 100644 index 00000000..6fe039b8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -0,0 +1,54 @@ +package inu.codin.codin.common.security.util; + +import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.jwt.JwtTokenProvider; +import inu.codin.codin.common.security.service.AuthService; +import inu.codin.codin.common.security.service.JwtService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Map; + +@Component +@RequiredArgsConstructor +@Slf4j +public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + private final AuthService authService; + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + // OAuth2 회원가입/로그인 처리 후 JWT 발급 + AuthResultStatus result = authService.oauthLogin(oAuth2User, response); + + response.setContentType("application/json;charset=UTF-8"); + PrintWriter writer = response.getWriter(); + + // 상황에 따라 서로 다른 응답 메시지를 반환 + switch (result) { + case LOGIN_SUCCESS: + writer.write("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); + break; + case NEW_USER_REGISTERED: + writer.write("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); + break; + case PROFILE_INCOMPLETE: + writer.write("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); + break; + default: + writer.write("{\"code\":500, \"message\":\"알 수 없는 오류 발생\"}"); + break; + } + writer.flush(); + + } +} From 2b82fa1bfd2bfd1a7eb421f60f1e895c368ce2ab Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 21 Feb 2025 03:09:21 +0900 Subject: [PATCH 0469/1002] =?UTF-8?q?feat=20::=20token=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=8B=9C=20studentId=20->=20email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CustomOAuth2UserService.java | 43 +++++++++++++++++++ .../user/security/CustomUserDetails.java | 2 +- .../security/CustomUserDetailsService.java | 6 +-- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java new file mode 100644 index 00000000..57e16ef2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java @@ -0,0 +1,43 @@ +package inu.codin.codin.common.security.service; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +/** + * OAuth2 로그인 후 사용자 정보를 가로채고 이메일을 검증하는 로직 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + // Get User Email + String email = oAuth2User.getAttribute("email"); + log.info("email: {}", email); + + // Only Allow @inu.ac.kr + if (email == null || !email.trim().endsWith("@inu.ac.kr")) { + OAuth2Error oauth2Error = new OAuth2Error( + "invalid_email_domain", + "허용되지 않은 이메일 도메인입니다. @inu.ac.kr 이어야합니다", + null + ); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); + } + + log.info("email2: {}", email); + return oAuth2User; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index 5bdffc10..41ac5c07 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -77,7 +77,7 @@ public String getPassword() { */ @Override public String getUsername() { - return studentId; + return email; } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java index 8e5c4a3e..e4a5d995 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java @@ -17,10 +17,10 @@ public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String studentId) throws UsernameNotFoundException { + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - UserEntity user = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음, studentId :" + studentId)); + UserEntity user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음, email :" + email)); if (!UserStatus.ACTIVE.equals(user.getStatus())) { throw new UserDisabledException("유저가 활성화되지 않았습니다"); From 4c2f7c8ea567bd3e89f165d1280d240261944246 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 21 Feb 2025 03:10:37 +0900 Subject: [PATCH 0470/1002] =?UTF-8?q?feat=20::=20oauth=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 66 +++-- .../security/enums/AuthResultStatus.java | 7 + .../common/security/service/AuthService.java | 277 +++++++++++++----- .../codin/domain/user/entity/UserEntity.java | 6 + .../user/repository/UserRepository.java | 3 + 5 files changed, 257 insertions(+), 102 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 3d18d91e..1071ca7b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -14,9 +14,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + @RestController @RequestMapping(value = "/auth") @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @@ -26,34 +29,53 @@ public class AuthController { private final JwtService jwtService; private final AuthService authService; - - @Operation( - summary = "포탈 로그인", - description = "포탈 아이디, 비밀번호를 통해 로그인 진행 및 학적 정보 반환" - ) - @PostMapping("/portal") - public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { - Integer result = authService.signUp(signUpAndLoginRequestDto, response); - if (result.equals(0)) { - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); - } else { - return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "포탈 로그인 진행 완료", "새로운 유저 등록 완료")); - } + @GetMapping("/google") + public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { + response.sendRedirect("/oauth2/authorization/google"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } - @Operation(summary = "회원가입") - @PostMapping(value = "/signup/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> signUpUser( - @PathVariable("studentId") String studentId, + @Operation(summary = "회원 정보 입력 마무리") + @PostMapping(value = "/signup/{email}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> completeUserProfile( + @PathVariable("email") String email, @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, - @RequestPart(value = "userImage", required = false) MultipartFile userImage) { - authService.createUser(studentId, userNicknameRequestDto, userImage); + @RequestPart(value = "userImage", required = false) MultipartFile userImage, + HttpServletResponse response) { + authService.completeUserProfile(email, userNicknameRequestDto, userImage, response); return ResponseEntity.ok() - .body(new SingleResponse<>(200, "회원가입 성공", null)); + .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); } +// +// @Operation( +// summary = "포탈 로그인", +// description = "포탈 아이디, 비밀번호를 통해 로그인 진행 및 학적 정보 반환" +// ) +// @PostMapping("/portal") +// public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { +// Integer result = authService.signUp(signUpAndLoginRequestDto, response); +// if (result.equals(0)) { +// return ResponseEntity.ok() +// .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); +// } else { +// return ResponseEntity.status(HttpStatus.CREATED) +// .body(new SingleResponse<>(201, "포탈 로그인 진행 완료", "새로운 유저 등록 완료")); +// } +// } +// +// @Operation(summary = "회원가입") +// @PostMapping(value = "/signup/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +// public ResponseEntity> signUpUser( +// @PathVariable("studentId") String studentId, +// @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, +// @RequestPart(value = "userImage", required = false) MultipartFile userImage) { +// authService.createUser(studentId, userNicknameRequestDto, userImage); +// return ResponseEntity.ok() +// .body(new SingleResponse<>(200, "회원가입 성공", null)); +// } + @Operation(summary = "로그아웃") @PostMapping("/logout") public ResponseEntity logout() { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java b/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java new file mode 100644 index 00000000..8ac4492d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java @@ -0,0 +1,7 @@ +package inu.codin.codin.common.security.enums; + +public enum AuthResultStatus { + LOGIN_SUCCESS, // 기존 회원이며, 프로필이 완료되어 정상 로그인 + NEW_USER_REGISTERED, // 신규 회원 등록 완료 (프로필 설정 미완료) + PROFILE_INCOMPLETE // 기존 회원이지만 프로필 설정이 미완료됨 +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 284502e4..3bf4be36 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -1,13 +1,17 @@ package inu.codin.codin.common.security.service; import inu.codin.codin.common.Department; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.common.security.enums.AuthResultStatus; import inu.codin.codin.common.security.feign.inu.InuClient; import inu.codin.codin.common.security.feign.portal.PortalClient; import inu.codin.codin.common.util.AESUtil; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; @@ -23,14 +27,14 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; import static feign.Util.ISO_8859_1; @@ -40,110 +44,223 @@ @Slf4j public class AuthService { - private final AuthenticationManager authenticationManager; - private final PortalClient portalClient; - private final InuClient inuClient; private final UserRepository userRepository; private final S3Service s3Service; private final JwtService jwtService; - private final RedisAuthService redisAuthService; - private final PasswordEncoder passwordEncoder; + private final UserDetailsService userDetailsService; - public Integer signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ - try {//학번으로 회원가입 유무 판단 - Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); - if (user.isPresent()) { - UsernamePasswordAuthenticationToken authenticationToken - = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getStudentId(), signUpAndLoginRequestDto.getPassword()); - Authentication authentication = authenticationManager.authenticate(authenticationToken); - SecurityContextHolder.getContext().setAuthentication(authentication); + /** + * Google OAuth2를 통해 사용자 정보를 등록하고 로그인 처리하는 메소드 + * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) + * - 기존 회원이면, userStatus가 ACTIVE인 경우에만 JWT 토큰을 발급하여 정식 로그인 처리 + * 만약 userStatus가 DISABLED이면 프로필 설정이 필요하다는 상태로 처리 + */ + public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { - jwtService.createToken(response); - return 0; - } - PortalLoginResponseDto userPortalLoginResponseDto - = returnPortalInfo(signUpAndLoginRequestDto); - redisAuthService.saveUserData(signUpAndLoginRequestDto.getStudentId(), userPortalLoginResponseDto); - return 1; - } catch (Exception e) { - log.error("로그인 중 오류 발생", e); - throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); - } + // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) + Map attributes = oAuth2User.getAttributes(); + attributes.forEach((key, value) -> { + log.info("OAuth2 attribute: {} = {}", key, value); + }); - } + // 기본 속성 추출 + String email = (String) attributes.get("email"); + String sub = (String) attributes.get("sub"); // 고유 식별자 (필요시 저장) + String name = (String) attributes.get("family_name"); // 예: "김기수" + String department = (String) attributes.get("given_name"); // 예: "/컴퓨터공학부" - private PortalLoginResponseDto returnPortalInfo(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { - String html = portalClient.signUp( - "submit", - AESUtil.encrypt(signUpAndLoginRequestDto.getStudentId()), - AESUtil.encrypt(signUpAndLoginRequestDto.getPassword()) - ); - Document doc = Jsoup.parse(html); - String value = doc.select(".main").select("input[type=hidden]").attr("value"); - PortalLoginResponseDto userPortalLoginResponseDto = readJson(value); - String password = passwordEncoder.encode(signUpAndLoginRequestDto.getPassword()); - userPortalLoginResponseDto.setPassword(password); - userPortalLoginResponseDto.setUndergraduate(isUnderGraduate(signUpAndLoginRequestDto)); - return userPortalLoginResponseDto; - } + log.info("OAuth2 login: email={}, sub={}, name={}, department={}", + email, sub, name, department); - private PortalLoginResponseDto readJson(String value) { - String[] arrayList = value.substring(1).split(", "); - PortalLoginResponseDto userPortalLoginResponseDto = new PortalLoginResponseDto(); - - Map> fieldSetters = Map.of( - "studId", userPortalLoginResponseDto::setStudentId, - "userNm", userPortalLoginResponseDto::setName, - "userEml", userPortalLoginResponseDto::setEmail, - "userDpmtNm", v -> userPortalLoginResponseDto.setDepartment(Department.fromDescription(v)), - "userCollNm", userPortalLoginResponseDto::setCollege - ); - - for (String user : arrayList) { - String[] info = user.split("="); - if (info.length == 2 && fieldSetters.containsKey(info[0])) { // Only process known fields - fieldSetters.get(info[0]).accept(info[1]); - } - } + // 회원 존재 여부 판단: email 기준 + Optional optionalUser = userRepository.findByEmail(email); + if (optionalUser.isPresent()) { + UserEntity existingUser = optionalUser.get(); + log.info("기존 회원 로그인: {}", email); + if (existingUser.getStatus() != null && existingUser.getStatus().equals(UserStatus.ACTIVE)) { - return userPortalLoginResponseDto; - } + // 프로필 설정 완료된 회원: 정상 로그인 처리 + issueJwtToken(email, response); + log.info("정상 로그인 완료: {}", email); + return AuthResultStatus.LOGIN_SUCCESS; + } else { + // 프로필 설정 미완료: JWT 토큰 미발급, 프론트엔드에서 프로필 설정 페이지로 유도 + log.info("회원 프로필 설정 미완료 (userStatus={}) : {}", existingUser.getStatus(), email); + return AuthResultStatus.PROFILE_INCOMPLETE; + } + } else { + log.info("신규 회원 등록: {}", email); + String deptDesc = (department != null) ? department.replace("/", "").trim() : ""; + Department dept = Department.fromDescription(deptDesc); - public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto){ - String basic = "Basic " + Base64.getEncoder().encodeToString((signUpAndLoginRequestDto.getStudentId() + ":" + signUpAndLoginRequestDto.getPassword()).getBytes(ISO_8859_1)); - Map graduate = inuClient.status(basic); - return graduate.get("undergraduate").equals("true"); + // 신규 회원 등록 시, userStatus를 DISABLED(비활성)으로 설정하여 프로필 설정 미완료 상태로 처리 + UserEntity newUser = UserEntity.builder() + .email(email) + .name(name) + .department(dept) + .profileImageUrl(s3Service.getDefaultProfileImageUrl()) // 기본 프로필 이미지 사용 + .status(UserStatus.DISABLED) + .role(UserRole.USER) + .build(); + userRepository.save(newUser); + log.info("신규 회원 등록 완료 (프로필 미완료): {}", newUser); + return AuthResultStatus.NEW_USER_REGISTERED; + // 신규 회원은 프로필 설정 완료 전까지는 JWT 토큰을 발급 안 함. + } } - public void createUser(String studentId, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage) { - + /** + * 최초 로그인 후, 사용자에게 닉네임과 프로필 이미지를 설정받아 DB를 업데이트하는 메소드. + * 프로필 설정 완료 후 userStatus를 ACTIVE로 업데이트하여 정식 회원가입을 완료. + */ + public void completeUserProfile(String email, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage, HttpServletResponse response) { + // 중복 닉네임 검사 Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } - PortalLoginResponseDto userData = redisAuthService.getUserData(studentId); - log.info("[createUser] 요청 데이터: {}", studentId); + // DB에서 해당 사용자를 이메일로 조회 + Optional userOpt = userRepository.findByEmailAndDisabled(email); + if (userOpt.isEmpty()){ + throw new NotFoundException("사용자를 찾을 수 없습니다."); + } + UserEntity user = userOpt.get(); + log.info("[completeUserProfile] 사용자 조회 성공: {}", email); + + // 프로필 이미지 업로드 처리 String imageUrl = null; - if (userImage != null) { - log.info("[회원가입] 프로필 이미지 업로드 중..."); + if (userImage != null && !userImage.isEmpty()) { + log.info("[프로필 설정] 프로필 이미지 업로드 중..."); imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); - log.info("[회원가입] 프로필 이미지 업로드 완료: {}", imageUrl); + log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); } - - // imageUrl이 null이면 기본 이미지로 설정 + // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 if (imageUrl == null) { - imageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 - + imageUrl = s3Service.getDefaultProfileImageUrl(); } - UserEntity user = UserEntity.of(userData); + // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 user.updateNickname(userNicknameRequestDto); user.updateProfileImageUrl(imageUrl); + user.disabledToactivateUser(); userRepository.save(user); - log.info("[signUpUser] SIGN UP SUCCESS!! : {}", user.getStudentId()); + log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); + + // 프로필 설정 완료 후 정식 회원으로 간주하여 JWT 토큰 재발급 + issueJwtToken(user.getEmail(), response); + } + + private void issueJwtToken(String email, HttpServletResponse response) { + UserDetails userDetails = userDetailsService.loadUserByUsername(email); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + jwtService.createToken(response); } + + + + +// +// public Integer signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ +// try {//학번으로 회원가입 유무 판단 +// Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); +// if (user.isPresent()) { +// UsernamePasswordAuthenticationToken authenticationToken +// = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getStudentId(), signUpAndLoginRequestDto.getPassword()); +// +// Authentication authentication = authenticationManager.authenticate(authenticationToken); +// SecurityContextHolder.getContext().setAuthentication(authentication); +// +// jwtService.createToken(response); +// return 0; +// } +// PortalLoginResponseDto userPortalLoginResponseDto +// = returnPortalInfo(signUpAndLoginRequestDto); +// redisAuthService.saveUserData(signUpAndLoginRequestDto.getStudentId(), userPortalLoginResponseDto); +// return 1; +// } catch (Exception e) { +// log.error("로그인 중 오류 발생", e); +// throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); +// } +// +// } +// +// private PortalLoginResponseDto returnPortalInfo(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { +// String html = portalClient.signUp( +// "submit", +// AESUtil.encrypt(signUpAndLoginRequestDto.getStudentId()), +// AESUtil.encrypt(signUpAndLoginRequestDto.getPassword()) +// ); +// Document doc = Jsoup.parse(html); +// String value = doc.select(".main").select("input[type=hidden]").attr("value"); +// PortalLoginResponseDto userPortalLoginResponseDto = readJson(value); +// String password = passwordEncoder.encode(signUpAndLoginRequestDto.getPassword()); +// userPortalLoginResponseDto.setPassword(password); +// userPortalLoginResponseDto.setUndergraduate(isUnderGraduate(signUpAndLoginRequestDto)); +// return userPortalLoginResponseDto; +// } +// +// private PortalLoginResponseDto readJson(String value) { +// String[] arrayList = value.substring(1).split(", "); +// PortalLoginResponseDto userPortalLoginResponseDto = new PortalLoginResponseDto(); +// +// Map> fieldSetters = Map.of( +// "studId", userPortalLoginResponseDto::setStudentId, +// "userNm", userPortalLoginResponseDto::setName, +// "userEml", userPortalLoginResponseDto::setEmail, +// "userDpmtNm", v -> userPortalLoginResponseDto.setDepartment(Department.fromDescription(v)), +// "userCollNm", userPortalLoginResponseDto::setCollege +// ); +// +// for (String user : arrayList) { +// String[] info = user.split("="); +// if (info.length == 2 && fieldSetters.containsKey(info[0])) { // Only process known fields +// fieldSetters.get(info[0]).accept(info[1]); +// } +// } +// +// return userPortalLoginResponseDto; +// } +// +// +// public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto){ +// String basic = "Basic " + Base64.getEncoder().encodeToString((signUpAndLoginRequestDto.getStudentId() + ":" + signUpAndLoginRequestDto.getPassword()).getBytes(ISO_8859_1)); +// Map graduate = inuClient.status(basic); +// return graduate.get("undergraduate").equals("true"); +// } +// +// public void createUser(String studentId, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage) { +// +// Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); +// if (nickNameDuplicate.isPresent()){ +// throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); +// } +// PortalLoginResponseDto userData = redisAuthService.getUserData(studentId); +// log.info("[createUser] 요청 데이터: {}", studentId); +// +// String imageUrl = null; +// if (userImage != null) { +// log.info("[회원가입] 프로필 이미지 업로드 중..."); +// imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); +// log.info("[회원가입] 프로필 이미지 업로드 완료: {}", imageUrl); +// } +// +// // imageUrl이 null이면 기본 이미지로 설정 +// if (imageUrl == null) { +// imageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 +// +// } +// +// UserEntity user = UserEntity.of(userData); +// user.updateNickname(userNicknameRequestDto); +// user.updateProfileImageUrl(imageUrl); +// userRepository.save(user); +// +// log.info("[signUpUser] SIGN UP SUCCESS!! : {}", user.getStudentId()); +// } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 24b5b8eb..7ed75a0e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -100,4 +100,10 @@ public void activateUser() { this.status = UserStatus.ACTIVE; } } + + public void disabledToactivateUser() { + if ( this.status == UserStatus.DISABLED) { + this.status = UserStatus.ACTIVE; + } + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 9d122e64..4ebf8044 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -20,4 +20,7 @@ public interface UserRepository extends MongoRepository { Optional findByStudentId(String studentId); Optional findByNicknameAndDeletedAtIsNull(String nickname); + + @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") + Optional findByEmailAndDisabled(String email); } From 30a44b5a33ffffe3cecaf5e7d39ce613ecdb97c4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 21 Feb 2025 03:30:42 +0900 Subject: [PATCH 0471/1002] =?UTF-8?q?chore=20::=20oauth=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20resources=20=EB=A1=9C=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88=20=EC=BB=A4=EB=B0=8B=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=84=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index ce53ddee..e09a7621 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit ce53ddee25d3b0e12f96d4a67f8a1529757bb059 +Subproject commit e09a7621c8a68c26ea46e4627f18038ed204b7e9 From 1a26b2e9a8621a265e9c8207ec8330bb66e32929 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 21 Feb 2025 03:56:20 +0900 Subject: [PATCH 0472/1002] =?UTF-8?q?fix=20::=20findbyEmail=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20disabled=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EB=AA=BB=ED=96=88=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/AuthService.java | 2 +- .../inu/codin/codin/domain/user/repository/UserRepository.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 3bf4be36..37ee8d61 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -74,7 +74,7 @@ public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse re email, sub, name, department); // 회원 존재 여부 판단: email 기준 - Optional optionalUser = userRepository.findByEmail(email); + Optional optionalUser = userRepository.findByEmailAndDisabledAndActive(email); if (optionalUser.isPresent()) { UserEntity existingUser = optionalUser.get(); log.info("기존 회원 로그인: {}", email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index 4ebf8044..f0b6b1e8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -23,4 +23,7 @@ public interface UserRepository extends MongoRepository { @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") Optional findByEmailAndDisabled(String email); + + @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED', 'ACTIVE'] }}") + Optional findByEmailAndDisabledAndActive(String email); } From abd24bc564d8e6991e070de47f6c22f352fe3808 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 02:21:20 +0900 Subject: [PATCH 0473/1002] =?UTF-8?q?refactor=20:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20+=20=EB=8B=89=EB=84=A4=EC=9E=84=20Requestbody=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 117 ++++++++++-------- .../security/controller/AuthController.java | 12 +- .../common/security/service/AuthService.java | 37 ++---- .../dto/request/UserProfileRequestDto.java | 20 +++ .../codin/domain/user/entity/UserEntity.java | 6 +- .../domain/user/service/UserService.java | 4 +- 6 files changed, 103 insertions(+), 93 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserProfileRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 887bd08e..584379d8 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -49,23 +49,18 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) // cors 설정 - .csrf(CsrfConfigurer::disable) // csrf 비활성화 - .formLogin(FormLoginConfigurer::disable) // form login 비활성화 - .sessionManagement(sessionManagement -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // CORS 설정 + .csrf(CsrfConfigurer::disable) // CSRF 비활성화 + .formLogin(FormLoginConfigurer::disable) // Form 로그인 비활성화 + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용 안 함 + .authorizeHttpRequests(auth -> auth + .requestMatchers(SWAGGER_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(PERMIT_ALL).permitAll() + .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") + .requestMatchers(USER_AUTH_PATHS).hasRole("USER") + .anyRequest().hasRole("USER") ) - // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 - .authorizeHttpRequests((authorizeHttpRequests) -> - authorizeHttpRequests - .requestMatchers(PERMIT_ALL).permitAll() - .requestMatchers(SWAGGER_AUTH_PATHS).permitAll() - .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") - .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") - .requestMatchers(USER_AUTH_PATHS).hasRole("USER") - .anyRequest().hasRole("USER") - ) - // Swagger 접근 시 httpBasic 인증 사용 .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( @@ -76,9 +71,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 - .userInfoEndpoint(userInfo -> userInfo - .userService(customOAuth2UserService) - ) + .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) .successHandler(oAuth2LoginSuccessHandler) .failureHandler(oAuth2LoginFailureHandler) ) @@ -92,6 +85,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } + + /** + * AuthenticationManager 설정 + */ @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); @@ -99,72 +96,86 @@ public AuthenticationManager authenticationManager(HttpSecurity http) throws Exc return authenticationManagerBuilder.build(); } + /** + * 역할 계층 설정 + */ @Bean public RoleHierarchy roleHierarchy() { return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_MANAGER > ROLE_USER"); } + /** + * 비밀번호 암호화 설정 + */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - // 토큰 없이 접근 가능한 URL + /** + * CORS 설정 + */ + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.setAllowedOrigins(List.of( + "http://localhost:3000", + "https://www.codin.co.kr", + "https://codin.inu.ac.kr", + "https://front-end-peach-two.vercel.app", + "http://localhost:8080" + )); + config.setAllowedHeaders(List.of("*")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + config.setExposedHeaders(List.of("Authorization", "x-refresh-token")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } + + /** + * 토큰 없이 접근 가능한 URL + */ private static final String[] PERMIT_ALL = { "/auth/reissue", "/auth/logout", - "/auth/portal", "/auth/signup/**", -// "/email/auth/check", -// "/email/auth/send", -// "/email/auth/password", -// "/email/auth/password/check", + "/auth/google", "/v3/api/test1", "/ws-stomp/**", -// "/chat", -// "/chat/image", "/chats/**" }; - // Swagger 접근 가능한 URL + /** + * Swagger 접근 가능한 URL + */ private static final String[] SWAGGER_AUTH_PATHS = { "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs", - "/swagger-resources/**", + "/swagger-resources/**" }; - // User 권한 URL + /** + * User 권한 URL + */ private static final String[] USER_AUTH_PATHS = { "/v3/api/test2", - "/v3/api/test3", + "/v3/api/test3" }; - // Admin 권한 URL + /** + * Admin 권한 URL + */ private static final String[] ADMIN_AUTH_PATHS = { - "/v3/api/test4", + "/v3/api/test4" }; - // Manager 권한 URL - private static final String[] MANAGER_AUTH_PATHS = { - "/v3/api/test5", - }; - - /** - * CORS 설정 + * Manager 권한 URL */ - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codin.co.kr" , "https://front-end-peach-two.vercel.app")); - config.setAllowedHeaders(List.of("*")); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); - config.setExposedHeaders(List.of("Authorization", "x-refresh-token")); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; - } - + private static final String[] MANAGER_AUTH_PATHS = { + "/v3/api/test5" + }; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 1071ca7b..431fdb98 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,20 +1,17 @@ package inu.codin.codin.common.security.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthService; import inu.codin.codin.common.security.service.JwtService; -import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -37,13 +34,12 @@ public ResponseEntity> googleLogin(HttpServletResponse respons } @Operation(summary = "회원 정보 입력 마무리") - @PostMapping(value = "/signup/{email}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> completeUserProfile( - @PathVariable("email") String email, - @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, + @RequestPart @Valid UserProfileRequestDto userProfileRequestDto, @RequestPart(value = "userImage", required = false) MultipartFile userImage, HttpServletResponse response) { - authService.completeUserProfile(email, userNicknameRequestDto, userImage, response); + authService.completeUserProfile(userProfileRequestDto, userImage, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 37ee8d61..fcb5f8c1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -2,42 +2,28 @@ import inu.codin.codin.common.Department; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.PortalLoginResponseDto; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.feign.inu.InuClient; -import inu.codin.codin.common.security.feign.portal.PortalClient; -import inu.codin.codin.common.util.AESUtil; -import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; -import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisAuthService; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.*; -import java.util.function.Consumer; - -import static feign.Util.ISO_8859_1; +import java.util.List; +import java.util.Map; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -60,9 +46,6 @@ public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse re // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) Map attributes = oAuth2User.getAttributes(); - attributes.forEach((key, value) -> { - log.info("OAuth2 attribute: {} = {}", key, value); - }); // 기본 속성 추출 String email = (String) attributes.get("email"); @@ -115,20 +98,20 @@ public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse re * 최초 로그인 후, 사용자에게 닉네임과 프로필 이미지를 설정받아 DB를 업데이트하는 메소드. * 프로필 설정 완료 후 userStatus를 ACTIVE로 업데이트하여 정식 회원가입을 완료. */ - public void completeUserProfile(String email, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage, HttpServletResponse response) { + public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { // 중복 닉네임 검사 - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } // DB에서 해당 사용자를 이메일로 조회 - Optional userOpt = userRepository.findByEmailAndDisabled(email); + Optional userOpt = userRepository.findByEmailAndDisabled(userProfileRequestDto.getEmail()); if (userOpt.isEmpty()){ throw new NotFoundException("사용자를 찾을 수 없습니다."); } UserEntity user = userOpt.get(); - log.info("[completeUserProfile] 사용자 조회 성공: {}", email); + log.info("[completeUserProfile] 사용자 조회 성공: {}", userProfileRequestDto.getEmail()); // 프로필 이미지 업로드 처리 String imageUrl = null; @@ -143,9 +126,9 @@ public void completeUserProfile(String email, UserNicknameRequestDto userNicknam } // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 - user.updateNickname(userNicknameRequestDto); + user.updateNickname(userProfileRequestDto.getNickname()); user.updateProfileImageUrl(imageUrl); - user.disabledToactivateUser(); + user.activation(); userRepository.save(user); log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserProfileRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserProfileRequestDto.java new file mode 100644 index 00000000..c7f1952c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserProfileRequestDto.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.user.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class UserProfileRequestDto { + + @Schema(description = "이메일", example = "1234@inu.ac.kr") + @Email + @NotBlank + private String email; + + @Schema(description = "닉네임", example = "코딩") + @NotBlank + private String nickname; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 7ed75a0e..cef9c6e6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -64,8 +64,8 @@ public UserEntity(String email, String password, String studentId, String name, this.blockedUsers = (blockedUsers != null) ? blockedUsers : new ArrayList<>(); // ✅ 기본값 설정 } - public void updateNickname(UserNicknameRequestDto userNicknameRequestDto) { - this.nickname = userNicknameRequestDto.getNickname(); + public void updateNickname(String nickname) { + this.nickname = nickname; } public void updateProfileImageUrl(String profileImageUrl) { @@ -101,7 +101,7 @@ public void activateUser() { } } - public void disabledToactivateUser() { + public void activation() { if ( this.status == UserStatus.DISABLED) { this.status = UserStatus.ACTIVE; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 34d4154f..e88b47f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -125,7 +125,7 @@ public void deleteUser() { return new NotFoundException("해당 id에 대한 유저 정보를 찾을 수 없습니다."); }); user.delete(); - user.updateNickname(new UserNicknameRequestDto("탈퇴한 사용자")); + user.updateNickname("탈퇴한 사용자"); user.updateProfileImageUrl(s3Service.getDefaultProfileImageUrl()); userRepository.save(user); log.info("[회원 탈퇴 성공] _id: {}", userId); @@ -160,7 +160,7 @@ public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) return new NotFoundException("유저 정보를 찾을 수 없습니다."); }); - user.updateNickname(userNicknameRequestDto); + user.updateNickname(userNicknameRequestDto.getNickname()); userRepository.save(user); log.info("[유저 정보 업데이트 성공] 사용자 ID: {}, 업데이트된 정보: {}", userId, userNicknameRequestDto); } From eb43ec60e51695b95e0ab98a423e24dbc532b5e3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 02:24:14 +0900 Subject: [PATCH 0474/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index e09a7621..8aca9f9b 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit e09a7621c8a68c26ea46e4627f18038ed204b7e9 +Subproject commit 8aca9f9bd8b3559c3dbdb1c324714a68b8614f2d From 2ee283556e4d23546f296451010cbeada4b69e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=EA=B5=AD?= <121088189+X1n9fU@users.noreply.github.com> Date: Sat, 22 Feb 2025 03:28:15 +0900 Subject: [PATCH 0475/1002] Update SecurityConfig.java --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 5f4f2308..a12eb52a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -87,8 +87,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.setSharedObject(AuthenticationManager.class, authenticationManager(http)); - http.setSharedObject(RoleHierarchy.class, roleHierarchy()); + // http.setSharedObject(AuthenticationManager.class, authenticationManager(http)); + // http.setSharedObject(RoleHierarchy.class, roleHierarchy()); return http.build(); } From 22d97ab4a08fbca4adda2996776971b9ba29844a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 14:50:29 +0900 Subject: [PATCH 0476/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8aca9f9b..95eddde0 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8aca9f9bd8b3559c3dbdb1c324714a68b8614f2d +Subproject commit 95eddde06a51b3c1f1c69ea4e8ac4b80a2cefcb2 From 0c10832f9e7bae3fbfff7f06d0341065a5d68c97 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 15:27:12 +0900 Subject: [PATCH 0477/1002] =?UTF-8?q?fix=20:=20Oauth2=20redirect=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 6 ++++-- .../codin/common/security/controller/AuthController.java | 2 +- .../common/security/util/OAuth2LoginSuccessHandler.java | 6 ++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index a12eb52a..ec7c5214 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -59,7 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PERMIT_ALL).permitAll() - .requestMatchers(SWAGGER_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(SWAGGER_AUTH_PATHS).permitAll() .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") @@ -141,7 +141,9 @@ public CorsConfigurationSource corsConfigurationSource() { "/auth/google", "/v3/api/test1", "/ws-stomp/**", - "/chats/**" + "/chats/**", + "/oauth2/**", + "/login/**" }; // Swagger 접근 가능한 URL diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 431fdb98..68ae3f6b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -28,7 +28,7 @@ public class AuthController { @GetMapping("/google") public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/oauth2/authorization/google"); + response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 6fe039b8..ad456d5b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -33,10 +33,14 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); + String redirectUrl = "/api/login/oauth2/code"; + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { case LOGIN_SUCCESS: writer.write("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); + log.info("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); break; case NEW_USER_REGISTERED: writer.write("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); @@ -50,5 +54,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo } writer.flush(); + + } } From 167652501431ef9d89b3d442c170835e464e6237 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 15:35:52 +0900 Subject: [PATCH 0478/1002] =?UTF-8?q?fix=20:=20Oauth2=20redirect=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/OAuth2LoginSuccessHandler.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index ad456d5b..69c1da30 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,23 +1,18 @@ package inu.codin.codin.common.security.util; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.service.AuthService; -import inu.codin.codin.common.security.service.JwtService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; - import java.io.IOException; import java.io.PrintWriter; -import java.util.Map; @Component @RequiredArgsConstructor @@ -33,9 +28,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - String redirectUrl = "/api/login/oauth2/code"; - getRedirectStrategy().sendRedirect(request, response, redirectUrl); - // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { case LOGIN_SUCCESS: From f9741a2412c1f115f4826f37eb15a6857fd30117 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 22 Feb 2025 16:55:35 +0900 Subject: [PATCH 0479/1002] =?UTF-8?q?refactor=20::=20oauth2=20Redirect=20U?= =?UTF-8?q?rl=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 887bd08e..f85fc465 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -76,6 +76,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 + .loginProcessingUrl("/api/login/oauth2/code/google") .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) @@ -123,7 +124,7 @@ public PasswordEncoder passwordEncoder() { "/ws-stomp/**", // "/chat", // "/chat/image", - "/chats/**" + "/chats/**", }; // Swagger 접근 가능한 URL From a26c89a5e491715ec3610ba9218605daddcb165c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 22 Feb 2025 17:09:39 +0900 Subject: [PATCH 0480/1002] =?UTF-8?q?chore=20:;=20=EC=84=9C=EB=B8=8C=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8aca9f9b..653f9cd8 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8aca9f9bd8b3559c3dbdb1c324714a68b8614f2d +Subproject commit 653f9cd892adc3fe4e1b6a1d9ef75e22c0fbd92a From f86e0ee7fbc0d4e1408ae1810f5b2d2f7a4ae76b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 22 Feb 2025 17:12:20 +0900 Subject: [PATCH 0481/1002] =?UTF-8?q?refactor=20::=20security=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 --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 40b9f5c7..db81627c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -87,12 +87,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .headers(headers -> headers .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // X-Frame-Options 비활성화 ); - - - - http.setSharedObject(AuthenticationManager.class, authenticationManager(http)); - http.setSharedObject(RoleHierarchy.class, roleHierarchy()); - return http.build(); } From f49564e8ef55e36169dafe5b12a3741bb6a13b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=EA=B5=AD?= <121088189+X1n9fU@users.noreply.github.com> Date: Sat, 22 Feb 2025 17:38:54 +0900 Subject: [PATCH 0482/1002] =?UTF-8?q?refactor=20::=20Secutiry=20api=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index db81627c..7209e129 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -138,7 +138,8 @@ public CorsConfigurationSource corsConfigurationSource() { "/auth/google", "/v3/api/test1", "/ws-stomp/**", - "/chats/**" + "/chats/**", + "/login/oauth2/code/**" }; // Swagger 접근 가능한 URL From 370f9e21da440f93158eb1a47d0637987cc32fd7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 17:55:00 +0900 Subject: [PATCH 0483/1002] =?UTF-8?q?fix=20:=20Oauth2=20endpoint=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/OAuth2Controller.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java new file mode 100644 index 00000000..8ccfe01f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java @@ -0,0 +1,18 @@ +package inu.codin.codin.common.security.controller; + +import inu.codin.codin.common.response.SingleResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/login/oauth2/code") +public class OAuth2Controller { + + @GetMapping("/google") + public ResponseEntity> googleLogin() { + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "OAuth2 로그인", "OAuth2 로그인 완료")); + } +} From b89c29bace6fd384b93a14d3f38577d8e0bc6999 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 18:02:52 +0900 Subject: [PATCH 0484/1002] =?UTF-8?q?fix=20:=20redirect-uri=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 --- .../codin/codin/common/security/controller/AuthController.java | 2 +- .../codin/common/security/controller/OAuth2Controller.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 431fdb98..68ae3f6b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -28,7 +28,7 @@ public class AuthController { @GetMapping("/google") public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/oauth2/authorization/google"); + response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java index 8ccfe01f..f1cec011 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/login/oauth2/code") +@RequestMapping("/login/oauth2/code") public class OAuth2Controller { @GetMapping("/google") From 18ffea62e3ae323cfccb9a78f1dceaf19211b084 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 18:36:22 +0900 Subject: [PATCH 0485/1002] =?UTF-8?q?fix=20:=20Header=20result=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 2 +- .../security/controller/OAuth2Controller.java | 18 ------------------ .../util/OAuth2LoginSuccessHandler.java | 6 +----- 3 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 7209e129..cc9a25c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -76,7 +76,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 - .loginProcessingUrl("/api/login/oauth2/code/google") +// .loginProcessingUrl("/api/login/oauth2/code/google") .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java deleted file mode 100644 index f1cec011..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/OAuth2Controller.java +++ /dev/null @@ -1,18 +0,0 @@ -package inu.codin.codin.common.security.controller; - -import inu.codin.codin.common.response.SingleResponse; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/login/oauth2/code") -public class OAuth2Controller { - - @GetMapping("/google") - public ResponseEntity> googleLogin() { - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "OAuth2 로그인", "OAuth2 로그인 완료")); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 6fe039b8..7e76ae1e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,23 +1,18 @@ package inu.codin.codin.common.security.util; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.service.AuthService; -import inu.codin.codin.common.security.service.JwtService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; - import java.io.IOException; import java.io.PrintWriter; -import java.util.Map; @Component @RequiredArgsConstructor @@ -33,6 +28,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); + response.setHeader("result", String.valueOf(result)); // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { case LOGIN_SUCCESS: From f4e9b772bf0b4db00d75bb07b05820b1e388edb2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 22:34:08 +0900 Subject: [PATCH 0486/1002] =?UTF-8?q?feat=20:=20Cookie=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 8 +++-- .../codin/common/config/SwaggerConfig.java | 6 +++- .../codin/common/config/WebSocketConfig.java | 6 +++- .../codin/common/security/jwt/JwtUtils.java | 36 ++++++++++++++----- .../service/CustomOAuth2UserService.java | 3 +- .../common/security/service/JwtService.java | 24 ++++++++++--- .../util/OAuth2LoginSuccessHandler.java | 19 +++++++--- 7 files changed, 78 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index cc9a25c5..98a5dfec 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -8,6 +8,7 @@ import inu.codin.codin.common.security.util.OAuth2LoginFailureHandler; import inu.codin.codin.common.security.util.OAuth2LoginSuccessHandler; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; @@ -45,6 +46,9 @@ public class SecurityConfig { private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; private final CustomOAuth2UserService customOAuth2UserService; + @Value("${server.domain}") + private String BASEURL; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -116,13 +120,11 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowCredentials(true); config.setAllowedOrigins(List.of( "http://localhost:3000", - "https://www.codin.co.kr", + BASEURL, "https://codin.inu.ac.kr", "https://front-end-peach-two.vercel.app" )); - config.setAllowedHeaders(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); - config.setExposedHeaders(List.of("Authorization", "x-refresh-token")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index 4d9e2f89..e9cbb6a9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.ForwardedHeaderFilter; @@ -19,6 +20,9 @@ @RequiredArgsConstructor public class SwaggerConfig { + @Value("${server.domain}") + private String BASEURL; + @Bean public OpenAPI customOpenAPI() { @@ -45,7 +49,7 @@ public OpenAPI customOpenAPI() { .components(new Components().addSecuritySchemes("JWT", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), // Local Server - new Server().url("https://www.codin.co.kr/api").description("Production Server") // Production Server + new Server().url(BASEURL+"/api").description("Production Server") // Production Server )); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index fcdb2be8..13a06329 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.chat.StompMessageProcessor; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; @@ -15,11 +16,14 @@ @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + @Value("${server.domain}") + private String BASEURL; + private final StompMessageProcessor stompMessageProcessor; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint - .setAllowedOriginPatterns("http://localhost:3000", "http://localhost:8080", "https://www.codin.co.kr") + .setAllowedOriginPatterns("http://localhost:3000", "http://localhost:8080", BASEURL) .withSockJS(); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index a590a29d..80a41ff9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.security.jwt; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -8,25 +9,44 @@ public class JwtUtils { /** - * 헤더에서 Access 토큰 추출 - * HTTP Header : "Authorization" : "Bearer ..." - * @return (null, 빈 문자열, "Bearer ")로 시작하지 않는 경우 null 반환 + * 쿠키에서 Access 토큰 추출 + * HTTP Cookies : "Authorization" : "..." + * @return (null, 빈 문자열)의 경우 null 반환 */ public String getAccessToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { +// String bearerToken = request.getHeader("Authorization"); + String bearerToken = null; + if (request.getCookies() != null){ + for (Cookie cookie : request. getCookies()){ + if ("Authorization".equals(cookie.getName())){ + bearerToken = cookie.getValue(); + break; + } + } + } + + if (StringUtils.hasText(bearerToken)) { return bearerToken.substring(7); } return null; } /** - * 헤더에서 Refresh 토큰 추출 - * HTTP Header : "X-Refresh-Token" : "..." + * 쿠키에서 Refresh 토큰 추출 + * HTTP Cookies : "RefreshToken" : "..." * @return RefreshToken이 없는 경우 null 반환 */ public String getRefreshToken(HttpServletRequest request) { - String refreshToken = request.getHeader("X-Refresh-Token"); + String refreshToken = null; + if (request.getCookies() != null){ + for (Cookie cookie : request. getCookies()){ + if ("RefreshToken".equals(cookie.getName())){ + refreshToken = cookie.getValue(); + break; + } + } + } + if (StringUtils.hasText(refreshToken)) { return refreshToken; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java index 57e16ef2..14c94672 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java @@ -27,6 +27,8 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String email = oAuth2User.getAttribute("email"); log.info("email: {}", email); + if (email.equals("inu.codin@gmail.com")) return oAuth2User; + // Only Allow @inu.ac.kr if (email == null || !email.trim().endsWith("@inu.ac.kr")) { OAuth2Error oauth2Error = new OAuth2Error( @@ -37,7 +39,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); } - log.info("email2: {}", email); return oAuth2User; } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 08e7891e..8f852567 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -6,6 +6,7 @@ import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; import inu.codin.codin.infra.redis.RedisStorageService; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -87,11 +88,24 @@ private void createBothToken(HttpServletResponse response) { var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - // 응답 헤더에 Access Token 추가 - response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); - - // 헤더에 Access Token 추가 - response.addHeader("X-Refresh-Token", newToken.getRefreshToken()); + Cookie jwtCookie = new Cookie("Authorization", newToken.getAccessToken()); + jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 + jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 + jwtCookie.setPath("/"); // 모든 요청에 포함 + jwtCookie.setMaxAge(60 * 60); // 1시간 유지 + jwtCookie.setDomain("www.codin.co.kr"); + jwtCookie.setAttribute("SameSite", "None"); + response.addCookie(jwtCookie); + + + Cookie refreshCookie = new Cookie("RefreshToken", newToken.getRefreshToken()); + refreshCookie.setHttpOnly(true); + refreshCookie.setSecure(true); + refreshCookie.setPath("/"); + refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 + refreshCookie.setDomain("www.codin.co.kr"); + refreshCookie.setAttribute("SameSite", "None"); + response.addCookie(refreshCookie); log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Refresh : {}",authentication.getName(), newToken.getRefreshToken()); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 7e76ae1e..fce08b44 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @@ -18,6 +19,10 @@ @RequiredArgsConstructor @Slf4j public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + @Value("${server.domain}") + private String BASEURL; + private final AuthService authService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { @@ -28,20 +33,24 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - response.setHeader("result", String.valueOf(result)); // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { case LOGIN_SUCCESS: - writer.write("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/main"); + log.info("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); break; case NEW_USER_REGISTERED: - writer.write("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile"); + log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); + break; case PROFILE_INCOMPLETE: - writer.write("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile"); + log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); break; default: - writer.write("{\"code\":500, \"message\":\"알 수 없는 오류 발생\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); + log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생\"}"); break; } writer.flush(); From ee6960221a297b30faac327405c5dab2987212bc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 22:35:18 +0900 Subject: [PATCH 0487/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 653f9cd8..bc28b19d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 653f9cd892adc3fe4e1b6a1d9ef75e22c0fbd92a +Subproject commit bc28b19dd2fb12192294e023988cb3b4662e42ac From 01562ecdfae32f130d51794ed4bd20e0c18cee4a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 22:49:19 +0900 Subject: [PATCH 0488/1002] =?UTF-8?q?fix=20:=20httpBasic=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 98a5dfec..7c59f91f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -14,7 +14,6 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -70,7 +69,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().hasRole("USER") ) // Swagger 접근 시 httpBasic 인증 사용 - .httpBasic(Customizer.withDefaults()) +// .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtUtils), @@ -80,7 +79,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 -// .loginProcessingUrl("/api/login/oauth2/code/google") .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) From 2f0fafe57ca103aed3d5dd024a603316c7e7e2e5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 23:09:43 +0900 Subject: [PATCH 0489/1002] =?UTF-8?q?fix=20:=20Bearer=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/security/jwt/JwtUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index 80a41ff9..2c67dcc6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -26,7 +26,7 @@ public String getAccessToken(HttpServletRequest request) { } if (StringUtils.hasText(bearerToken)) { - return bearerToken.substring(7); + return bearerToken; } return null; } From da819d8ab8db6f8f4d648263f6dbc2006065c643 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 22 Feb 2025 23:20:16 +0900 Subject: [PATCH 0490/1002] =?UTF-8?q?fix=20:=20redirect-uri=EC=97=90=20que?= =?UTF-8?q?ry=EB=A1=9C=20email=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/util/OAuth2LoginSuccessHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index fce08b44..71f51bd5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -40,12 +40,12 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); break; case NEW_USER_REGISTERED: - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile?email="+oAuth2User.getAttribute("email")); log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); break; case PROFILE_INCOMPLETE: - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile?email="+oAuth2User.getAttribute("email")); log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); break; default: From a1b5ffbd5028a298a95b901fe0ac57e60c61b9d1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 00:25:03 +0900 Subject: [PATCH 0491/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index bc28b19d..a7878db3 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit bc28b19dd2fb12192294e023988cb3b4662e42ac +Subproject commit a7878db3d9d50832a60ca9414ca22e280a0055fd From 00e9a01f4f3a2d8f73dfb23e117c420f790abb06 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 00:54:02 +0900 Subject: [PATCH 0492/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a7878db3..fc9b8403 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a7878db3d9d50832a60ca9414ca22e280a0055fd +Subproject commit fc9b8403d41c5e9d77452adb25e83136c3bcf0d9 From 4c44a8843839eb06dc70206d2cc39903cfe1dd1e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 01:18:59 +0900 Subject: [PATCH 0493/1002] =?UTF-8?q?fix=20:=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20domain=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/config/SecurityConfig.java | 1 - .../codin/codin/common/security/service/JwtService.java | 8 ++++++-- .../common/security/util/OAuth2LoginSuccessHandler.java | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 7c59f91f..a8dc07f1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -119,7 +119,6 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedOrigins(List.of( "http://localhost:3000", BASEURL, - "https://codin.inu.ac.kr", "https://front-end-peach-two.vercel.app" )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 8f852567..90441c7d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -11,6 +11,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -30,6 +31,9 @@ public class JwtService { private final JwtUtils jwtUtils; private final UserDetailsService userDetailsService; + @Value("${server.domain}") + private String BASERURL; + /** * 최초 로그인 시 Access Token, Refresh Token 발급 * @param response @@ -93,7 +97,7 @@ private void createBothToken(HttpServletResponse response) { jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 - jwtCookie.setDomain("www.codin.co.kr"); + jwtCookie.setDomain(BASERURL.split("//")[1]); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); @@ -103,7 +107,7 @@ private void createBothToken(HttpServletResponse response) { refreshCookie.setSecure(true); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 - refreshCookie.setDomain("www.codin.co.kr"); + refreshCookie.setDomain(BASERURL.split("//")[1]); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 71f51bd5..d8fb6952 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -33,6 +33,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); + log.info(BASEURL); + // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { case LOGIN_SUCCESS: From 72787622c5053034407325909ae21218c7d1533f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 01:35:52 +0900 Subject: [PATCH 0494/1002] =?UTF-8?q?feat=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EB=B0=8F=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=8B=9C=20token=20=EB=AC=B4=ED=9A=A8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 1 + .../security/controller/AuthController.java | 4 ++-- .../common/security/service/JwtService.java | 19 ++++++++++++++++++- .../user/controller/UserController.java | 5 +++-- .../domain/user/service/UserService.java | 7 ++++++- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index a8dc07f1..d678a012 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -119,6 +119,7 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedOrigins(List.of( "http://localhost:3000", BASEURL, + "https://www.codin.co.kr", "https://front-end-peach-two.vercel.app" )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 68ae3f6b..5183852d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -74,8 +74,8 @@ public ResponseEntity> completeUserProfile( @Operation(summary = "로그아웃") @PostMapping("/logout") - public ResponseEntity logout() { - jwtService.deleteToken(); + public ResponseEntity logout(HttpServletResponse response) { + jwtService.deleteToken(response); return ResponseEntity.ok().body(new SingleResponse<>(200, "로그아웃 성공", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 90441c7d..43492135 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -117,11 +117,28 @@ private void createBothToken(HttpServletResponse response) { /** * 로그아웃 - Refresh Token 삭제 */ - public void deleteToken() { + public void deleteToken(HttpServletResponse response) { // 어차피 JwtAuthenticationFilter 단에서 토큰을 검증하여 인증을 처리하므로 // SecurityContext에 Authentication 객체가 없는 경우는 없다. var authentication = SecurityContextHolder.getContext().getAuthentication(); redisStorageService.deleteRefreshToken(authentication.getName()); + deleteCookie(response); log.info("[deleteToken] Refresh Token 삭제 완료"); } + + private void deleteCookie(HttpServletResponse response) { + Cookie jwtCookie = new Cookie("Authorization", ""); + jwtCookie.setMaxAge(0); // 쿠키 삭제 + jwtCookie.setHttpOnly(true); + jwtCookie.setSecure(true); + jwtCookie.setPath("/"); // 쿠키가 적용될 경로 + response.addCookie(jwtCookie); + + Cookie refreshCookie = new Cookie("RefreshToken", ""); + refreshCookie.setHttpOnly(true); + refreshCookie.setSecure(true); + refreshCookie.setPath("/"); + refreshCookie.setMaxAge(0); // 7일 + response.addCookie(refreshCookie); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 9223fb31..414a9afc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -66,8 +67,8 @@ public ResponseEntity> getUserComment(@RequestP summary = "회원 탈퇴" ) @DeleteMapping - public ResponseEntity> deleteUser(){ - userService.deleteUser(); + public ResponseEntity> deleteUser(HttpServletResponse response){ + userService.deleteUser(response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 탈퇴 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index e88b47f3..620aaf24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.user.service; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; @@ -19,6 +20,7 @@ import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -46,6 +48,7 @@ public class UserService { private final PostService postService; private final S3Service s3Service; + private final JwtService jwtService; //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 @@ -116,7 +119,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } } - public void deleteUser() { + public void deleteUser(HttpServletResponse response) { ObjectId userId = SecurityUtils.getCurrentUserId(); UserEntity user = userRepository.findByUserId(userId) @@ -128,6 +131,8 @@ public void deleteUser() { user.updateNickname("탈퇴한 사용자"); user.updateProfileImageUrl(s3Service.getDefaultProfileImageUrl()); userRepository.save(user); + + jwtService.deleteToken(response); log.info("[회원 탈퇴 성공] _id: {}", userId); } From 04b023c67af3e594647c8cbb6a54857a277bc792 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 01:42:09 +0900 Subject: [PATCH 0495/1002] =?UTF-8?q?fix:=20cookie=20domain=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 --- .../inu/codin/codin/common/security/service/JwtService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 43492135..e172fdcf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -97,7 +97,7 @@ private void createBothToken(HttpServletResponse response) { jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 - jwtCookie.setDomain(BASERURL.split("//")[1]); + jwtCookie.setDomain("www.codin.co.kr"); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); @@ -107,7 +107,7 @@ private void createBothToken(HttpServletResponse response) { refreshCookie.setSecure(true); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 - refreshCookie.setDomain(BASERURL.split("//")[1]); + refreshCookie.setDomain("www.codin.co.kr"); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); From 122fd16237bfc2626c2e7915731bd56702fba454 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 01:47:44 +0900 Subject: [PATCH 0496/1002] =?UTF-8?q?fix:=20cookie=20domain=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 --- .../inu/codin/codin/common/security/service/JwtService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index e172fdcf..43492135 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -97,7 +97,7 @@ private void createBothToken(HttpServletResponse response) { jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 - jwtCookie.setDomain("www.codin.co.kr"); + jwtCookie.setDomain(BASERURL.split("//")[1]); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); @@ -107,7 +107,7 @@ private void createBothToken(HttpServletResponse response) { refreshCookie.setSecure(true); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 - refreshCookie.setDomain("www.codin.co.kr"); + refreshCookie.setDomain(BASERURL.split("//")[1]); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); From b4368b047341959dcf4dade1bcb953820d629e87 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 01:48:26 +0900 Subject: [PATCH 0497/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index fc9b8403..fb09c129 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit fc9b8403d41c5e9d77452adb25e83136c3bcf0d9 +Subproject commit fb09c1298cb825fdecc6ae9a5bbe9a75b71e0ddb From 918aa3e727b0bfac2442b0b68eb979d6e4142a21 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 23 Feb 2025 01:52:55 +0900 Subject: [PATCH 0498/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index fb09c129..1916e4f7 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit fb09c1298cb825fdecc6ae9a5bbe9a75b71e0ddb +Subproject commit 1916e4f72d408bb6099b2a5db21cb4e6a2467f9a From 30b6d01eddffab4122d597407749494b15b6a3fd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 01:08:32 +0900 Subject: [PATCH 0499/1002] =?UTF-8?q?feat:=20=EB=B9=84=EA=B5=90=EA=B3=BC?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=9F=AC=EB=A1=9C=20=EC=9E=90=EB=8F=99=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/CodinApplication.java | 2 + .../common/schedular/PostsScheduler.java | 53 +++++++++++++++++++ .../exception/SchedulerException.java | 7 +++ 3 files changed, 62 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java diff --git a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java index 21619914..ffae68b0 100644 --- a/codin-core/src/main/java/inu/codin/codin/CodinApplication.java +++ b/codin-core/src/main/java/inu/codin/codin/CodinApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.mongodb.config.EnableMongoAuditing; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -14,6 +15,7 @@ @EnableMethodSecurity @EnableScheduling @EnableFeignClients +@EnableAsync public class CodinApplication { public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); diff --git a/codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java new file mode 100644 index 00000000..f1d832db --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java @@ -0,0 +1,53 @@ +package inu.codin.codin.common.schedular; + +import inu.codin.codin.common.schedular.exception.SchedulerException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/* + 비교과 게시글 매일 새벽 2시 업데이트 + */ +@Slf4j +@Component +public class PostsScheduler { + + private static final String PATH = "/home/codin-ubuntu/codin-main/backend-data/post_data/"; + + @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") + @Async + public void departmentPostsScheduler() { + String fileName = "department.py"; + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + PATH+fileName); + try { + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + log.warn("Exited department python with error code" + exitCode); + log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); + } catch (IOException | InterruptedException e) { + log.error(e.getMessage(), e.getStackTrace()[0]); + throw new SchedulerException(e.getMessage()); + } + } + + @Scheduled(cron = "0 30 0 * * *", zone = "Asia/Seoul") + @Async + public void starinuPostsScheduler(){ + String fileName = "starinu.py"; + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + PATH+fileName); + try { + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + log.warn("Exited starinu python with error code" + exitCode); + log.info("[PostsScheduler] STARINU 게시글 업데이트 완료"); + } catch (IOException | InterruptedException e) { + log.error(e.getMessage(), e.getStackTrace()[0]); + throw new SchedulerException(e.getMessage()); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java b/codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java new file mode 100644 index 00000000..b2c0b326 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.common.schedular.exception; + +public class SchedulerException extends RuntimeException{ + public SchedulerException(String message){ + super(message); + } +} From 358723601bfbb8f01d3c0eea6e7a4bab03cb8fea Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 02:10:49 +0900 Subject: [PATCH 0500/1002] =?UTF-8?q?fix=20:=20=EB=B9=88=20=EA=B0=95?= =?UTF-8?q?=EC=9D=98=EC=8B=A4=20=ED=98=84=ED=99=A9=20=EC=A4=91=20=EC=88=98?= =?UTF-8?q?=EC=97=85=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EA=B0=95=EC=9D=98?= =?UTF-8?q?=EC=8B=A4=EA=B9=8C=EC=A7=80=20=EB=AA=A8=EB=91=90=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/JwtService.java | 2 +- .../room/service/LectureRoomService.java | 37 ++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 43492135..7810f341 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -111,7 +111,7 @@ private void createBothToken(HttpServletResponse response) { refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); - log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Refresh : {}",authentication.getName(), newToken.getRefreshToken()); + log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); } /** diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java index ef8a95f4..9a8d22e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java @@ -15,9 +15,9 @@ import java.time.DayOfWeek; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -25,6 +25,7 @@ public class LectureRoomService { private final MongoTemplate mongoTemplate; + public List getLecturesByFloor(int floor) { AggregationOperation match = Aggregation.match( @@ -51,17 +52,33 @@ public List getLecturesByFloor(int floor) { public List>> statusOfEmptyRoom() { LocalDateTime now = LocalDateTime.now(); DayOfWeek today = now.getDayOfWeek(); - ArrayList>> lectureRoom = new ArrayList<>(); - for (int floor=1; floor<=5; floor++) { + ArrayList>> result = new ArrayList<>(); + int[] rooms = new int[]{102, 104, 105, 107, 111, 205, 211, 302, 304, 305, 306, 311, 313, 314, 317, 406, 407, 408, 415, 416, 417, 501, 502, 504, 505, 511}; + + for (int floor = 1; floor <= 5; floor++) { + Map> lectureRoom = new HashMap<>(); + for (int room : rooms){ + if ((room / 100) == floor) { + lectureRoom.put(room, new ArrayList<>()); + } + } + List roomEntities = getLecturesByFloor(floor); - lectureRoom.add(roomEntities.stream() + List dtos = roomEntities.stream() .filter(lecture -> lecture.getDayTime().containsKey(today)) .flatMap(lecture -> lecture.getDayTime().get(today).stream() - .map(time -> EmptyRoomResponseDto.of(lecture, time))) - .collect(Collectors.groupingBy( - EmptyRoomResponseDto::getRoomNum - ))); - } - return lectureRoom; + .map(time -> { + EmptyRoomResponseDto dto = EmptyRoomResponseDto.of(lecture, time); + if (lectureRoom.containsKey(dto.getRoomNum())) { + lectureRoom.get(dto.getRoomNum()).add(dto); + } else { + lectureRoom.put(dto.getRoomNum(), List.of(dto)); + } + return dto; + })).toList(); + + result.add(lectureRoom); + } + return result; } } From b6bf2892b50897f5ea0983087709f62f5305435a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 02:24:39 +0900 Subject: [PATCH 0501/1002] =?UTF-8?q?refactor=20:=20scheduler=EC=9D=98=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post}/exception/SchedulerException.java | 2 +- .../{common => domain/post}/schedular/PostsScheduler.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename codin-core/src/main/java/inu/codin/codin/{common/schedular => domain/post}/exception/SchedulerException.java (72%) rename codin-core/src/main/java/inu/codin/codin/{common => domain/post}/schedular/PostsScheduler.java (94%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java similarity index 72% rename from codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java index b2c0b326..5979ae7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/schedular/exception/SchedulerException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.schedular.exception; +package inu.codin.codin.domain.post.exception; public class SchedulerException extends RuntimeException{ public SchedulerException(String message){ diff --git a/codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index f1d832db..63431289 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -1,6 +1,6 @@ -package inu.codin.codin.common.schedular; +package inu.codin.codin.domain.post.schedular; -import inu.codin.codin.common.schedular.exception.SchedulerException; +import inu.codin.codin.domain.post.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; From e4ed2955a445f6037bcdd8ab5f85ea7c398fbd9e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 02:37:15 +0900 Subject: [PATCH 0502/1002] Update domain --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 1916e4f7..d2796468 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 1916e4f72d408bb6099b2a5db21cb4e6a2467f9a +Subproject commit d27964681026d83060405f4df238fe6af065e152 From 8beec68422d16b84f512662bf422d3546397f9e7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 16:50:52 +0900 Subject: [PATCH 0503/1002] =?UTF-8?q?refactor=20:=20cookie=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20Swagger=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/config/SecurityConfig.java | 11 ++--------- .../codin/codin/common/config/SwaggerConfig.java | 15 +++++++-------- .../codin/codin/common/security/jwt/JwtUtils.java | 8 ++++---- .../codin/common/security/service/JwtService.java | 8 ++++---- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index d678a012..a2c9fd5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -19,7 +19,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; -import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -84,10 +83,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .successHandler(oAuth2LoginSuccessHandler) .failureHandler(oAuth2LoginFailureHandler) - ) - // Content-Security-Policy 및 Frame-Options 설정 - .headers(headers -> headers - .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // X-Frame-Options 비활성화 ); return http.build(); } @@ -118,6 +113,7 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowCredentials(true); config.setAllowedOrigins(List.of( "http://localhost:3000", + "http://localhost:8080", BASEURL, "https://www.codin.co.kr", "https://front-end-peach-two.vercel.app" @@ -132,10 +128,7 @@ public CorsConfigurationSource corsConfigurationSource() { * 토큰 없이 접근 가능한 URL */ private static final String[] PERMIT_ALL = { - "/auth/reissue", - "/auth/logout", - "/auth/signup/**", - "/auth/google", + "/auth/**", "/v3/api/test1", "/ws-stomp/**", "/chats/**", diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index e9cbb6a9..0de1059a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -33,23 +33,22 @@ public OpenAPI customOpenAPI() { // Bearer Auth 설정 SecurityScheme securityScheme = new SecurityScheme() - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - .in(SecurityScheme.In.HEADER) - .name("Authorization"); + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.COOKIE) + .name("access_token"); // Bearer Auth를 사용하는 Security Requirement 설정 SecurityRequirement securityRequirement = new SecurityRequirement() - .addList("JWT"); + .addList("cookieAuth"); return new OpenAPI() .info(info) .security(List.of(securityRequirement)) - .components(new Components().addSecuritySchemes("JWT", securityScheme)) + .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), // Local Server - new Server().url(BASEURL+"/api").description("Production Server") // Production Server + new Server().url(BASEURL+"/api").description("Production Server"), // Production Server + new Server().url("https://www.codin.co.kr/api").description("Production Server") )); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index 2c67dcc6..a31e9a9f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -10,7 +10,7 @@ public class JwtUtils { /** * 쿠키에서 Access 토큰 추출 - * HTTP Cookies : "Authorization" : "..." + * HTTP Cookies : "access_token" : "..." * @return (null, 빈 문자열)의 경우 null 반환 */ public String getAccessToken(HttpServletRequest request) { @@ -18,7 +18,7 @@ public String getAccessToken(HttpServletRequest request) { String bearerToken = null; if (request.getCookies() != null){ for (Cookie cookie : request. getCookies()){ - if ("Authorization".equals(cookie.getName())){ + if ("access_token".equals(cookie.getName())){ bearerToken = cookie.getValue(); break; } @@ -33,14 +33,14 @@ public String getAccessToken(HttpServletRequest request) { /** * 쿠키에서 Refresh 토큰 추출 - * HTTP Cookies : "RefreshToken" : "..." + * HTTP Cookies : "refresh_token" : "..." * @return RefreshToken이 없는 경우 null 반환 */ public String getRefreshToken(HttpServletRequest request) { String refreshToken = null; if (request.getCookies() != null){ for (Cookie cookie : request. getCookies()){ - if ("RefreshToken".equals(cookie.getName())){ + if ("refresh_token".equals(cookie.getName())){ refreshToken = cookie.getValue(); break; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 7810f341..e2164309 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -92,7 +92,7 @@ private void createBothToken(HttpServletResponse response) { var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - Cookie jwtCookie = new Cookie("Authorization", newToken.getAccessToken()); + Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 @@ -102,7 +102,7 @@ private void createBothToken(HttpServletResponse response) { response.addCookie(jwtCookie); - Cookie refreshCookie = new Cookie("RefreshToken", newToken.getRefreshToken()); + Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); refreshCookie.setSecure(true); refreshCookie.setPath("/"); @@ -127,14 +127,14 @@ public void deleteToken(HttpServletResponse response) { } private void deleteCookie(HttpServletResponse response) { - Cookie jwtCookie = new Cookie("Authorization", ""); + Cookie jwtCookie = new Cookie("access_token", ""); jwtCookie.setMaxAge(0); // 쿠키 삭제 jwtCookie.setHttpOnly(true); jwtCookie.setSecure(true); jwtCookie.setPath("/"); // 쿠키가 적용될 경로 response.addCookie(jwtCookie); - Cookie refreshCookie = new Cookie("RefreshToken", ""); + Cookie refreshCookie = new Cookie("refresh_token", ""); refreshCookie.setHttpOnly(true); refreshCookie.setSecure(true); refreshCookie.setPath("/"); From ac19139094c77c62f4c327d2d6850ab1fbc40347 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 17:00:56 +0900 Subject: [PATCH 0504/1002] =?UTF-8?q?refactor=20:=20domain=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index d2796468..e5fe64c7 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit d27964681026d83060405f4df238fe6af065e152 +Subproject commit e5fe64c7c1db27b5b1d15ab668e12ec09bf4c953 From ae1a8eb91fd21adf632edd92a7a888f849e576e1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 17:40:28 +0900 Subject: [PATCH 0505/1002] =?UTF-8?q?fix=20:=20admin,=20test=20=EA=B3=84?= =?UTF-8?q?=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20login=20api=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 60 +++++----- .../dto/SignUpAndLoginRequestDto.java | 6 +- .../common/security/service/AuthService.java | 104 +----------------- 3 files changed, 42 insertions(+), 128 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 5183852d..73357b1c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,21 +1,31 @@ package inu.codin.codin.common.security.controller; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.repository.UserRepository; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.Optional; @RestController @RequestMapping(value = "/auth") @@ -25,6 +35,8 @@ public class AuthController { private final JwtService jwtService; private final AuthService authService; + private final UserRepository userRepository; + private final AuthenticationManager authenticationManager; @GetMapping("/google") public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { @@ -44,33 +56,27 @@ public ResponseEntity> completeUserProfile( .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); } -// -// @Operation( -// summary = "포탈 로그인", -// description = "포탈 아이디, 비밀번호를 통해 로그인 진행 및 학적 정보 반환" -// ) -// @PostMapping("/portal") -// public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { -// Integer result = authService.signUp(signUpAndLoginRequestDto, response); -// if (result.equals(0)) { -// return ResponseEntity.ok() -// .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); -// } else { -// return ResponseEntity.status(HttpStatus.CREATED) -// .body(new SingleResponse<>(201, "포탈 로그인 진행 완료", "새로운 유저 등록 완료")); -// } -// } -// -// @Operation(summary = "회원가입") -// @PostMapping(value = "/signup/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) -// public ResponseEntity> signUpUser( -// @PathVariable("studentId") String studentId, -// @RequestPart @Valid UserNicknameRequestDto userNicknameRequestDto, -// @RequestPart(value = "userImage", required = false) MultipartFile userImage) { -// authService.createUser(studentId, userNicknameRequestDto, userImage); -// return ResponseEntity.ok() -// .body(new SingleResponse<>(200, "회원가입 성공", null)); -// } + + @Operation( + summary = "Admin, test 계정 로그인" + ) + @PostMapping("/login") + public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { +// authService.login(signUpAndLoginRequestDto, response); + Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); + if (user.isPresent()) { + UsernamePasswordAuthenticationToken authenticationToken + = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getEmail(), signUpAndLoginRequestDto.getPassword()); + + Authentication authentication = authenticationManager.authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + + jwtService.createToken(response); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); + } throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + + } @Operation(summary = "로그아웃") @PostMapping("/logout") diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java index 629887d6..50acc6ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/SignUpAndLoginRequestDto.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.dto; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @@ -10,8 +10,8 @@ public class SignUpAndLoginRequestDto { @NotBlank - @Size(min = 9 , max = 9 , message = "학번은 9자리여야 합니다.") - private String studentId; + @Email + private String email; @NotBlank private String password; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index fcb5f8c1..e8ef77de 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.Department; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.enums.AuthResultStatus; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; @@ -148,102 +149,9 @@ private void issueJwtToken(String email, HttpServletResponse response) { -// -// public Integer signUp(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ -// try {//학번으로 회원가입 유무 판단 -// Optional user = userRepository.findByStudentId(signUpAndLoginRequestDto.getStudentId()); -// if (user.isPresent()) { -// UsernamePasswordAuthenticationToken authenticationToken -// = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getStudentId(), signUpAndLoginRequestDto.getPassword()); -// -// Authentication authentication = authenticationManager.authenticate(authenticationToken); -// SecurityContextHolder.getContext().setAuthentication(authentication); -// -// jwtService.createToken(response); -// return 0; -// } -// PortalLoginResponseDto userPortalLoginResponseDto -// = returnPortalInfo(signUpAndLoginRequestDto); -// redisAuthService.saveUserData(signUpAndLoginRequestDto.getStudentId(), userPortalLoginResponseDto); -// return 1; -// } catch (Exception e) { -// log.error("로그인 중 오류 발생", e); -// throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); -// } -// -// } -// -// private PortalLoginResponseDto returnPortalInfo(SignUpAndLoginRequestDto signUpAndLoginRequestDto) throws Exception { -// String html = portalClient.signUp( -// "submit", -// AESUtil.encrypt(signUpAndLoginRequestDto.getStudentId()), -// AESUtil.encrypt(signUpAndLoginRequestDto.getPassword()) -// ); -// Document doc = Jsoup.parse(html); -// String value = doc.select(".main").select("input[type=hidden]").attr("value"); -// PortalLoginResponseDto userPortalLoginResponseDto = readJson(value); -// String password = passwordEncoder.encode(signUpAndLoginRequestDto.getPassword()); -// userPortalLoginResponseDto.setPassword(password); -// userPortalLoginResponseDto.setUndergraduate(isUnderGraduate(signUpAndLoginRequestDto)); -// return userPortalLoginResponseDto; -// } -// -// private PortalLoginResponseDto readJson(String value) { -// String[] arrayList = value.substring(1).split(", "); -// PortalLoginResponseDto userPortalLoginResponseDto = new PortalLoginResponseDto(); -// -// Map> fieldSetters = Map.of( -// "studId", userPortalLoginResponseDto::setStudentId, -// "userNm", userPortalLoginResponseDto::setName, -// "userEml", userPortalLoginResponseDto::setEmail, -// "userDpmtNm", v -> userPortalLoginResponseDto.setDepartment(Department.fromDescription(v)), -// "userCollNm", userPortalLoginResponseDto::setCollege -// ); -// -// for (String user : arrayList) { -// String[] info = user.split("="); -// if (info.length == 2 && fieldSetters.containsKey(info[0])) { // Only process known fields -// fieldSetters.get(info[0]).accept(info[1]); -// } -// } -// -// return userPortalLoginResponseDto; -// } -// -// -// public boolean isUnderGraduate(@Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto){ -// String basic = "Basic " + Base64.getEncoder().encodeToString((signUpAndLoginRequestDto.getStudentId() + ":" + signUpAndLoginRequestDto.getPassword()).getBytes(ISO_8859_1)); -// Map graduate = inuClient.status(basic); -// return graduate.get("undergraduate").equals("true"); -// } -// -// public void createUser(String studentId, UserNicknameRequestDto userNicknameRequestDto, MultipartFile userImage) { -// -// Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); -// if (nickNameDuplicate.isPresent()){ -// throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); -// } -// PortalLoginResponseDto userData = redisAuthService.getUserData(studentId); -// log.info("[createUser] 요청 데이터: {}", studentId); -// -// String imageUrl = null; -// if (userImage != null) { -// log.info("[회원가입] 프로필 이미지 업로드 중..."); -// imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); -// log.info("[회원가입] 프로필 이미지 업로드 완료: {}", imageUrl); -// } -// -// // imageUrl이 null이면 기본 이미지로 설정 -// if (imageUrl == null) { -// imageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 -// -// } -// -// UserEntity user = UserEntity.of(userData); -// user.updateNickname(userNicknameRequestDto); -// user.updateProfileImageUrl(imageUrl); -// userRepository.save(user); -// -// log.info("[signUpUser] SIGN UP SUCCESS!! : {}", user.getStudentId()); -// } + + public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ + + } + } From 18e4b18e3621c6134337b2d9ab820ee369ab558d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 17:55:59 +0900 Subject: [PATCH 0506/1002] =?UTF-8?q?fix=20:=20Cookie=20token=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=ED=9B=84=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 73357b1c..1753e142 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -14,7 +14,6 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; @@ -65,6 +64,7 @@ public ResponseEntity> portalSignUp(@RequestBody @Valid SignUp // authService.login(signUpAndLoginRequestDto, response); Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); if (user.isPresent()) { + jwtService.deleteToken(response); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getEmail(), signUpAndLoginRequestDto.getPassword()); From 68fff7bd383df12a4ba52b4b6d875b7ef99bf91e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 21:07:58 +0900 Subject: [PATCH 0507/1002] =?UTF-8?q?refactor=20:=20code=20=EB=A6=AC?= =?UTF-8?q?=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 16 +++----------- .../common/security/service/AuthService.java | 22 +++++++++---------- .../util/OAuth2LoginSuccessHandler.java | 3 ++- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 1753e142..f32f59ca 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -61,20 +61,10 @@ public ResponseEntity> completeUserProfile( ) @PostMapping("/login") public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { -// authService.login(signUpAndLoginRequestDto, response); - Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); - if (user.isPresent()) { - jwtService.deleteToken(response); - UsernamePasswordAuthenticationToken authenticationToken - = new UsernamePasswordAuthenticationToken(signUpAndLoginRequestDto.getEmail(), signUpAndLoginRequestDto.getPassword()); + authService.login(signUpAndLoginRequestDto, response); - Authentication authentication = authenticationManager.authenticate(authenticationToken); - SecurityContextHolder.getContext().setAuthentication(authentication); - - jwtService.createToken(response); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); - } throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index e8ef77de..9f683de3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; +import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; @@ -36,7 +37,6 @@ public class AuthService { private final JwtService jwtService; private final UserDetailsService userDetailsService; - /** * Google OAuth2를 통해 사용자 정보를 등록하고 로그인 처리하는 메소드 * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) @@ -138,20 +138,20 @@ public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, Mul issueJwtToken(user.getEmail(), response); } - private void issueJwtToken(String email, HttpServletResponse response) { + + public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { + Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); + if (user.isPresent()) { + issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); + } throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + } + + public void issueJwtToken(String email, HttpServletResponse response) { + jwtService.deleteToken(response); UserDetails userDetails = userDetailsService.loadUserByUsername(email); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); jwtService.createToken(response); } - - - - - - public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response){ - - } - } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index d8fb6952..49c1f0b0 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -23,7 +23,8 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan @Value("${server.domain}") private String BASEURL; - private final AuthService authService; + private final AuthService authService; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); From bc3618deea201094734e1af6e5281229ca32ea35 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 21:17:28 +0900 Subject: [PATCH 0508/1002] =?UTF-8?q?refactor=20:=20scheduler=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20yml=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/schedular/PostsScheduler.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 63431289..d46becc9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -15,36 +16,45 @@ @Component public class PostsScheduler { - private static final String PATH = "/home/codin-ubuntu/codin-main/backend-data/post_data/"; + @Value("${schedule.path}") + private static String PATH; - @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") + @Value("${schedule.use}") + private static boolean useSchedule; + + @Scheduled(cron = "${schedule.department.cron}", zone = "Asia/Seoul") @Async public void departmentPostsScheduler() { - String fileName = "department.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", - PATH+fileName); - try { - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - log.warn("Exited department python with error code" + exitCode); - log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); - } catch (IOException | InterruptedException e) { - log.error(e.getMessage(), e.getStackTrace()[0]); - throw new SchedulerException(e.getMessage()); - } + try { + if (useSchedule) { + String fileName = "department.py"; + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + PATH + fileName); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + log.warn("Exited department python with error code" + exitCode); + log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); + } else log.info("[PostsScheduler] 스케줄러 작동 false"); + } catch (IOException | InterruptedException e) { + log.error(e.getMessage(), e.getStackTrace()[0]); + throw new SchedulerException(e.getMessage()); + } + } - @Scheduled(cron = "0 30 0 * * *", zone = "Asia/Seoul") + @Scheduled(cron = "${schedule.starinu.cron}", zone = "Asia/Seoul") @Async public void starinuPostsScheduler(){ - String fileName = "starinu.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", - PATH+fileName); try { - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - log.warn("Exited starinu python with error code" + exitCode); - log.info("[PostsScheduler] STARINU 게시글 업데이트 완료"); + if (useSchedule) { + String fileName = "starinu.py"; + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + PATH + fileName); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + log.warn("Exited starinu python with error code" + exitCode); + log.info("[PostsScheduler] STARINU 게시글 업데이트 완료"); + } else log.info("[PostsScheduler] 스케줄러 작동 false"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); throw new SchedulerException(e.getMessage()); From f26fe6a962af803281ce6afd87285d8085f2fa9d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 21:17:52 +0900 Subject: [PATCH 0509/1002] =?UTF-8?q?refactor=20:=20scheduler=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20yml=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index e5fe64c7..de90131b 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit e5fe64c7c1db27b5b1d15ab668e12ec09bf4c953 +Subproject commit de90131b9f6300b64b7da0855494355b498281bf From a0065e5cf4dbde235b42c5887b457482e024b1d8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 21:53:10 +0900 Subject: [PATCH 0510/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index de90131b..0f7ac2c2 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit de90131b9f6300b64b7da0855494355b498281bf +Subproject commit 0f7ac2c2cb4667e18c8b0d824e0b71213a491a8c From e5c880314d4dc641341ce1c3052d5a1bd26640c1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 24 Feb 2025 23:39:13 +0900 Subject: [PATCH 0511/1002] =?UTF-8?q?fix=20:=20HandshakeInterceptor?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=EC=BF=A0=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=B8=EC=A6=9D=20=ED=9B=84=20?= =?UTF-8?q?stomp=20=EC=A0=91=EC=86=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 3 +- .../codin/common/config/WebSocketConfig.java | 9 +++- .../common/security/service/JwtService.java | 28 +++++++++- .../stomp/HttpHandShakeInterceptor.java | 47 +++++++++++++++++ .../stomp}/StompMessageProcessor.java | 52 ++++--------------- 5 files changed, 92 insertions(+), 47 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java rename codin-core/src/main/java/inu/codin/codin/{domain/chat => common/stomp}/StompMessageProcessor.java (65%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index a2c9fd5d..c7eb9089 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -132,7 +132,8 @@ public CorsConfigurationSource corsConfigurationSource() { "/v3/api/test1", "/ws-stomp/**", "/chats/**", - "/login/oauth2/code/**" + "/login/oauth2/code/**", + "/chat/**" }; // Swagger 접근 가능한 URL diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 13a06329..006bf393 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -1,6 +1,8 @@ package inu.codin.codin.common.config; -import inu.codin.codin.domain.chat.StompMessageProcessor; +import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.stomp.HttpHandShakeInterceptor; +import inu.codin.codin.common.stomp.StompMessageProcessor; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -20,11 +22,14 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { private String BASEURL; private final StompMessageProcessor stompMessageProcessor; + private final JwtService jwtService; + @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //handshake endpoint .setAllowedOriginPatterns("http://localhost:3000", "http://localhost:8080", BASEURL) - .withSockJS(); + .withSockJS() + .setInterceptors(new HttpHandShakeInterceptor(jwtService)); } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index e2164309..01f5d491 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -5,16 +5,18 @@ import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.domain.user.security.CustomUserDetailsService; import inu.codin.codin.infra.redis.RedisStorageService; +import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; /** @@ -29,7 +31,7 @@ public class JwtService { private final RedisStorageService redisStorageService; private final JwtTokenProvider jwtTokenProvider; private final JwtUtils jwtUtils; - private final UserDetailsService userDetailsService; + private final CustomUserDetailsService userDetailsService; @Value("${server.domain}") private String BASERURL; @@ -141,4 +143,26 @@ private void deleteCookie(HttpServletResponse response) { refreshCookie.setMaxAge(0); // 7일 response.addCookie(refreshCookie); } + + public void setAuthentication(ServletServerHttpRequest serverHttpRequest){ + String accessToken = jwtUtils.getAccessToken(serverHttpRequest.getServletRequest()); + + // Access Token이 있는 경우 + if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { + if (jwtTokenProvider.validateAccessToken(accessToken)) { + String email = jwtTokenProvider.getUsername(accessToken); + UserDetails userDetails = userDetailsService.loadUserByUsername(email); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } else { + SecurityContextHolder.clearContext(); + throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); + } + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java new file mode 100644 index 00000000..20397ac7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java @@ -0,0 +1,47 @@ +package inu.codin.codin.common.stomp; + +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.service.JwtService; +import io.jsonwebtoken.MalformedJwtException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +@RequiredArgsConstructor +public class HttpHandShakeInterceptor implements HandshakeInterceptor { + + private final JwtService jwtService; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + if (request instanceof ServletServerHttpRequest){ + ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; + + try { + jwtService.setAuthentication(serverHttpRequest); + } catch (MessageDeliveryException e) { + throw new MessageDeliveryException("[Chatting] Jwt로 인한 메세지 전송 오류입니다."); + } catch (MalformedJwtException e) { + throw new MalformedJwtException("[Chatting] 비정상적인 jwt 토큰 입니다."); + } catch (Exception e) { + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "[Chatting] 인증되지 않은 jwt 토큰 입니다."); + } + } + + return true; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java similarity index 65% rename from codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java rename to codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 927da9c5..69198dde 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -1,16 +1,12 @@ -package inu.codin.codin.domain.chat; +package inu.codin.codin.common.stomp; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.service.ChattingService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.security.CustomUserDetailsService; -import io.jsonwebtoken.MalformedJwtException; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -22,8 +18,6 @@ import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @@ -35,29 +29,27 @@ @Slf4j public class StompMessageProcessor implements ChannelInterceptor { - private final JwtTokenProvider jwtTokenProvider; - private final CustomUserDetailsService customUserDetailsService; private final ChattingService chattingService; private final Map sessionStore = new ConcurrentHashMap<>(); private final ChatRoomRepository chatRoomRepository; private final UserRepository userRepository; - private static final String BEARER_PREFIX="Bearer "; + + private final HttpServletRequest request; @Override public Message preSend(Message message, MessageChannel messageChannel){ StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - handleMessage(headerAccessor); + handleMessage(headerAccessor, request); return message; } - public void handleMessage(StompHeaderAccessor headerAccessor){ + public void handleMessage(StompHeaderAccessor headerAccessor, HttpServletRequest request){ if (headerAccessor == null || headerAccessor.getCommand() == null){ throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString()); } switch (headerAccessor.getCommand()){ case CONNECT -> { - getTokenByHeader(headerAccessor); connectSession(headerAccessor); } case SUBSCRIBE -> { @@ -100,19 +92,19 @@ private void disconnectSession(StompHeaderAccessor headerAccessor){ } private Result getResult(StompHeaderAccessor headerAccessor) { - String studentId; + String email; if (headerAccessor.getUser() != null) { - studentId = headerAccessor.getUser().getName(); + email = headerAccessor.getUser().getName(); } else { throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); } String chatroomId = sessionStore.get(headerAccessor.getSessionId()); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { - throw new IllegalArgumentException("올바른 chatRoomId가 아닙니다: " + chatroomId); + throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); } ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); - UserEntity user = userRepository.findByStudentId(studentId) + UserEntity user = userRepository.findByEmailAndDisabledAndActive(email) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); Result result = new Result(chatroom, user); return result; @@ -120,28 +112,4 @@ private Result getResult(StompHeaderAccessor headerAccessor) { private record Result(ChatRoom chatroom, UserEntity user) { } - - private void getTokenByHeader(StompHeaderAccessor headerAccessor) { - // 헤더 토큰 얻기 - String authorizationHeader = String.valueOf(headerAccessor.getNativeHeader("Authorization")); - if (authorizationHeader == null || authorizationHeader.equals("null")) { - throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); - } - String token = authorizationHeader.substring(BEARER_PREFIX.length()); - - // 토큰 인증 - try { - if (jwtTokenProvider.validateAccessToken(token)) { - String studentId = jwtTokenProvider.getUsername(token); - UserDetails userDetails = customUserDetailsService.loadUserByUsername(studentId); - headerAccessor.setUser(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())); - } - } catch (MessageDeliveryException e) { - throw new MessageDeliveryException("[Chatting] Jwt로 인한 메세지 전송 오류입니다."); - } catch (MalformedJwtException e) { - throw new MalformedJwtException("[Chatting] 비정상적인 jwt 토큰 입니다."); - } catch (Exception e) { - throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "[Chatting] 인증되지 않은 jwt 토큰 입니다."); - } - } } From 9546ef15cff8beb51e273c39f711920796c1fde6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 25 Feb 2025 03:12:49 +0900 Subject: [PATCH 0512/1002] =?UTF-8?q?refactor=20:=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 2 -- .../inu/codin/codin/common/security/service/AuthService.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index f32f59ca..9eb4d9d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -34,8 +34,6 @@ public class AuthController { private final JwtService jwtService; private final AuthService authService; - private final UserRepository userRepository; - private final AuthenticationManager authenticationManager; @GetMapping("/google") public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 9f683de3..7d0ef235 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -143,7 +143,7 @@ public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServlet Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); if (user.isPresent()) { issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); - } throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + } else throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); } public void issueJwtToken(String email, HttpServletResponse response) { From 1ff3ea2e0eeb86b4f95e11f27a64cb1a7d601d18 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 25 Feb 2025 18:44:53 +0900 Subject: [PATCH 0513/1002] =?UTF-8?q?chore=20:=20Security=20cors=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/config/SecurityConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index c7eb9089..8866fc18 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -119,6 +119,14 @@ public CorsConfigurationSource corsConfigurationSource() { "https://front-end-peach-two.vercel.app" )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + config.setAllowedHeaders(List.of( + "Authorization", + "Content-Type", + "X-Requested-With", + "Accept" + )); + config.setExposedHeaders(List.of("Authorization")); + config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; From 92a90fac9409a395e78f89904f86eb80b2581032 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 25 Feb 2025 23:15:04 +0900 Subject: [PATCH 0514/1002] =?UTF-8?q?chore=20:=20inu.ac.kr=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EB=A7=8C=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 3 +-- .../main/java/inu/codin/codin/common/config/SwaggerConfig.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 8866fc18..acd2cc89 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -115,7 +115,6 @@ public CorsConfigurationSource corsConfigurationSource() { "http://localhost:3000", "http://localhost:8080", BASEURL, - "https://www.codin.co.kr", "https://front-end-peach-two.vercel.app" )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); @@ -141,7 +140,7 @@ public CorsConfigurationSource corsConfigurationSource() { "/ws-stomp/**", "/chats/**", "/login/oauth2/code/**", - "/chat/**" +// "/chat/**" }; // Swagger 접근 가능한 URL diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index 0de1059a..bfd6f45a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -47,8 +47,7 @@ public OpenAPI customOpenAPI() { .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), // Local Server - new Server().url(BASEURL+"/api").description("Production Server"), // Production Server - new Server().url("https://www.codin.co.kr/api").description("Production Server") + new Server().url(BASEURL+"/api").description("Production Server") // Production Server )); } From fc52ec0c139c40ddd8b7902d477ae7f7ddab88dc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 25 Feb 2025 23:15:13 +0900 Subject: [PATCH 0515/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 0f7ac2c2..2b40fb11 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 0f7ac2c2cb4667e18c8b0d824e0b71213a491a8c +Subproject commit 2b40fb11acc679934206cadd5215def412b1fb0d From 31cce18d1adddb6b7cb5d6181bf40662f8e16a93 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 26 Feb 2025 22:55:01 +0900 Subject: [PATCH 0516/1002] =?UTF-8?q?perf:=20=EC=88=98=EA=B0=95=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EC=9E=91=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EA=B0=95=EC=9D=98=20=EA=B2=80=EC=83=89=20Page=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20->=20LectureSearchListResponseDto=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/controller/LectureController.java | 12 ++++---- .../dto/LectureSearchListResponseDto.java | 29 +++++++++++++++++++ .../lecture/service/LectureService.java | 18 +++++------- 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index a0702947..dddb9af2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.lecture.controller; import inu.codin.codin.common.Department; +import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.dto.Option; import inu.codin.codin.domain.lecture.service.LectureService; @@ -54,15 +55,12 @@ public ResponseEntity> getLectureDetails(@PathVariable("lectur "학과, 학년, 수강학기 중 하나만으로도 검색 가능" ) @GetMapping("/search-review") - public ResponseEntity> searchLecturesToReview(@RequestParam(required = false) Department department, + public ResponseEntity> searchLecturesToReview(@RequestParam(required = false) Department department, @RequestParam(required = false) @Min(1) @Max(4) Integer grade, - @RequestParam(required = false) String semester, - @RequestParam int page){ + @RequestParam(required = false) String semester){ return ResponseEntity.ok() - .body(new SingleResponse<>(200, "필터링 된 강의들 반환 완료", - lectureService.searchLecturesToReview(department, grade, semester, page))); + .body(new ListResponse<>(200, "필터링 된 강의들 반환 완료", + lectureService.searchLecturesToReview(department, grade, semester))); } - - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java new file mode 100644 index 00000000..c6968c7f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java @@ -0,0 +1,29 @@ +package inu.codin.codin.domain.lecture.dto; + +import inu.codin.codin.domain.lecture.entity.LectureEntity; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class LectureSearchListResponseDto { + + private String _id; + private String lectureNm; + private String professor; + + @Builder + public LectureSearchListResponseDto(String _id, String lectureNm, String professor) { + this._id = _id; + this.lectureNm = lectureNm; + this.professor = professor; + }; + + + public static LectureSearchListResponseDto of(LectureEntity lecture) { + return LectureSearchListResponseDto.builder() + ._id(lecture.get_id().toString()) + .lectureNm(lecture.getLectureNm()) + .professor(lecture.getProfessor()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 8744a661..7aeef74e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -10,7 +10,6 @@ import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; @@ -50,10 +49,11 @@ public LecturePageResponse sortListOfLectures(Department department, String keyw } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); } - public LecturePageResponse searchLecturesToReview(Department department, Integer grade, String semester, int page) { - PageRequest pageRequest = PageRequest.of(page, 20, Sort.by("lectureNm")); - Page lecturePage = findLectures(department, grade, semester, pageRequest); - return getLecturePageResponse(lecturePage); + public List searchLecturesToReview(Department department, Integer grade, String semester) { + List lectures = findLectures(department, grade, semester); + return lectures.stream() + .map(LectureSearchListResponseDto::of) + .toList(); } private LecturePageResponse getLecturePageResponse(Page lecturePage) { @@ -66,7 +66,7 @@ private LecturePageResponse getLecturePageResponse(Page lecturePa lecturePage.hasNext() ? lecturePage.getPageable().getPageNumber() + 1 : -1); } - public Page findLectures(Department department, Integer grade, String semester, PageRequest pageRequest) { + public List findLectures(Department department, Integer grade, String semester) { Query query = new Query(); if (department != null) { @@ -81,10 +81,8 @@ public Page findLectures(Department department, Integer grade, St query.addCriteria(Criteria.where("semester").in(List.of(semester))); } - long total = mongoTemplate.count(query, LectureEntity.class); - query.with(pageRequest); + query.with(Sort.by(Sort.Direction.ASC, "lectureNm")); - List lectures = mongoTemplate.find(query, LectureEntity.class); - return new PageImpl<>(lectures, pageRequest, total); + return mongoTemplate.find(query, LectureEntity.class); } } From 449bb75893cc1c50cf72189d957eb1c85c10665c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 2 Mar 2025 21:46:44 +0900 Subject: [PATCH 0517/1002] =?UTF-8?q?fix=20:=20lectures/search-reviews=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/LectureSearchListResponseDto.java | 33 +++++++++++++++---- .../lecture/service/LectureService.java | 11 +++++-- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java index c6968c7f..1bcd055f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java @@ -4,26 +4,45 @@ import lombok.Builder; import lombok.Getter; +import java.util.ArrayList; +import java.util.List; + @Getter public class LectureSearchListResponseDto { private String _id; private String lectureNm; private String professor; + private String semester; @Builder - public LectureSearchListResponseDto(String _id, String lectureNm, String professor) { + public LectureSearchListResponseDto(String _id, String lectureNm, String professor, String semester) { this._id = _id; this.lectureNm = lectureNm; this.professor = professor; + this.semester = semester; }; - public static LectureSearchListResponseDto of(LectureEntity lecture) { - return LectureSearchListResponseDto.builder() - ._id(lecture.get_id().toString()) - .lectureNm(lecture.getLectureNm()) - .professor(lecture.getProfessor()) - .build(); + public static List of(LectureEntity lecture) { + List listResponseDtos = new ArrayList<>(); + for (String semester: lecture.getSemester()){ + listResponseDtos.add(LectureSearchListResponseDto.builder() + ._id(lecture.get_id().toString()) + .lectureNm(lecture.getLectureNm()) + .professor(lecture.getProfessor()) + .semester(semester) + .build()); + } + return listResponseDtos; + } + + public static LectureSearchListResponseDto of(LectureEntity lecture, String semester) { + return LectureSearchListResponseDto.builder() + ._id(lecture.get_id().toString()) + .lectureNm(lecture.getLectureNm()) + .professor(lecture.getProfessor()) + .semester(semester) + .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 7aeef74e..d460e0f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -51,9 +51,14 @@ public LecturePageResponse sortListOfLectures(Department department, String keyw public List searchLecturesToReview(Department department, Integer grade, String semester) { List lectures = findLectures(department, grade, semester); - return lectures.stream() - .map(LectureSearchListResponseDto::of) - .toList(); + if (semester != null) return lectures.stream() + .map(lecture -> LectureSearchListResponseDto.of(lecture, semester)) + .toList(); + + else return lectures.stream() + .map(LectureSearchListResponseDto::of) + .flatMap(List::stream) + .toList(); } private LecturePageResponse getLecturePageResponse(Page lecturePage) { From 3b5d3ab1406b3f201cf7ede62703ef0f39777151 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 2 Mar 2025 23:31:06 +0900 Subject: [PATCH 0518/1002] =?UTF-8?q?fix=20:=20PermitAll=20url=EB=93=A4?= =?UTF-8?q?=EC=9D=84=20yml=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=ED=95=84=ED=84=B0=EB=A5=BC=20=EA=B1=B0?= =?UTF-8?q?=EC=B9=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 46 ++++++------------- .../codin/common/dto/PermitAllProperties.java | 16 +++++++ .../filter/JwtAuthenticationFilter.java | 11 +++++ .../common/security/jwt/JwtTokenProvider.java | 15 +++--- 4 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index acd2cc89..fb643152 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.config; +import inu.codin.codin.common.dto.PermitAllProperties; import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; @@ -43,6 +44,7 @@ public class SecurityConfig { private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; private final CustomOAuth2UserService customOAuth2UserService; + private final PermitAllProperties permitAllProperties; @Value("${server.domain}") private String BASEURL; @@ -60,30 +62,29 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests - .requestMatchers(PERMIT_ALL).permitAll() - .requestMatchers(SWAGGER_AUTH_PATHS).permitAll() + .requestMatchers(permitAllProperties.getUrls().toArray(new String[0])).permitAll() .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") .anyRequest().hasRole("USER") ) - // Swagger 접근 시 httpBasic 인증 사용 + //oauth2 로그인 설정 추가 + .oauth2Login(oauth2 -> oauth2 + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + ) + .successHandler(oAuth2LoginSuccessHandler) + .failureHandler(oAuth2LoginFailureHandler) + ) + // Swagger 접근 시 httpBasic 인증 사용 // .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( - new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtUtils), + new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtUtils, permitAllProperties), UsernamePasswordAuthenticationFilter.class ) // 예외 처리 필터 추가 - .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class) - //oauth2 로그인 설정 추가 - .oauth2Login(oauth2 -> oauth2 - .userInfoEndpoint(userInfo -> userInfo - .userService(customOAuth2UserService) - ) - .successHandler(oAuth2LoginSuccessHandler) - .failureHandler(oAuth2LoginFailureHandler) - ); + .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class); return http.build(); } @@ -131,25 +132,6 @@ public CorsConfigurationSource corsConfigurationSource() { return source; } - /** - * 토큰 없이 접근 가능한 URL - */ - private static final String[] PERMIT_ALL = { - "/auth/**", - "/v3/api/test1", - "/ws-stomp/**", - "/chats/**", - "/login/oauth2/code/**", -// "/chat/**" - }; - - // Swagger 접근 가능한 URL - private static final String[] SWAGGER_AUTH_PATHS = { - "/swagger-ui/**", - "/v3/api-docs/**", - "/v3/api-docs", - "/swagger-resources/**", - }; // User 권한 URL private static final String[] USER_AUTH_PATHS = { diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java b/codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java new file mode 100644 index 00000000..7fdc58c2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java @@ -0,0 +1,16 @@ +package inu.codin.codin.common.dto; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "security.permit-all") +public class PermitAllProperties { + private List urls; +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index ed5d3b1f..9a4c8a26 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.security.filter; +import inu.codin.codin.common.dto.PermitAllProperties; import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; @@ -8,12 +9,14 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.List; /** * JWT 토큰을 검증하여 인증하는 필터 @@ -24,10 +27,18 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; private final JwtUtils jwtUtils; + private final PermitAllProperties permitAllProperties; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String requestURI = request.getRequestURI(); + + if (permitAllProperties.getUrls().stream().anyMatch(url -> url.split("/")[1].startsWith(requestURI.split("/")[1]))) { + filterChain.doFilter(request, response); + return; + } + String accessToken = jwtUtils.getAccessToken(request); // Access Token이 있는 경우 diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index 94d4269d..f85a437a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -1,10 +1,9 @@ package inu.codin.codin.common.security.jwt; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.infra.redis.RedisStorageService; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import lombok.*; @@ -112,10 +111,10 @@ public boolean validateAccessToken(String accessToken) { return true; } catch (ExpiredJwtException e) { // 토큰 만료 log.error("[validateAccessToken] 토큰 만료 : {}", e.getMessage()); - return false; + throw new JwtException(SecurityErrorCode.EXPIRED_TOKEN, "access_token"); } catch (Exception e) { // 토큰 변조 log.error("[validateAccessToken] 유효하지 않은 토큰 : {}", e.getMessage()); - return false; + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "access_token"); } } @@ -139,10 +138,10 @@ public boolean validateRefreshToken(String refreshToken) { return true; } catch (ExpiredJwtException e) { // 토큰 만료 log.error("[validateRefreshToken] 토큰 만료 : {}", e.getMessage()); - return false; + throw new JwtException(SecurityErrorCode.EXPIRED_TOKEN, "refresh_token"); } catch (Exception e) { // 토큰 변조 log.error("[validateRefreshToken] 유효하지 않은 토큰 : {}", e.getMessage()); - return false; + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "refresh_token"); } } From 6f9a386584660e02fab050dc009a4fa58f079313 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 2 Mar 2025 23:32:08 +0900 Subject: [PATCH 0519/1002] =?UTF-8?q?refactor=20:=20common=EC=97=90?= =?UTF-8?q?=EC=84=9C=EC=9D=98=20dto=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/{ => dto}/BaseTimeEntity.java | 2 +- .../java/inu/codin/codin/common/{ => dto}/Department.java | 2 +- .../codin/common/security/dto/PortalLoginResponseDto.java | 2 +- .../inu/codin/codin/common/security/service/AuthService.java | 2 +- .../inu/codin/codin/domain/block/entity/BlockEntity.java | 2 +- .../codin/codin/domain/chat/chatroom/entity/ChatRoom.java | 2 +- .../codin/domain/chat/chatroom/entity/ParticipantInfo.java | 2 +- .../codin/codin/domain/chat/chatting/entity/Chatting.java | 2 +- .../inu/codin/codin/domain/email/entity/EmailAuthEntity.java | 2 +- .../domain/lab/dto/request/LabCreateUpdateRequestDto.java | 2 +- .../domain/lab/dto/response/LabThumbnailResponseDto.java | 2 +- .../inu/codin/codin/domain/info/domain/lab/entity/Lab.java | 2 +- .../info/domain/office/controller/OfficeController.java | 2 +- .../domain/office/dto/response/OfficeDetailsResponseDto.java | 3 +-- .../codin/domain/info/domain/office/entity/OfficeMember.java | 2 +- .../domain/info/domain/office/service/OfficeService.java | 2 +- .../domain/professor/controller/ProfessorController.java | 2 +- .../dto/request/ProfessorCreateUpdateRequestDto.java | 2 +- .../professor/dto/response/ProfessorListResponseDto.java | 2 +- .../dto/response/ProfessorThumbnailResponseDto.java | 2 +- .../codin/domain/info/domain/professor/entity/Professor.java | 2 +- .../info/domain/professor/service/ProfessorService.java | 2 +- .../main/java/inu/codin/codin/domain/info/entity/Info.java | 4 ++-- .../codin/codin/domain/info/repository/InfoRepository.java | 2 +- .../codin/domain/lecture/controller/LectureController.java | 2 +- .../domain/lecture/domain/review/entity/ReviewEntity.java | 2 +- .../inu/codin/codin/domain/lecture/entity/LectureEntity.java | 2 +- .../codin/domain/lecture/repository/LectureRepository.java | 2 +- .../codin/codin/domain/lecture/service/LectureService.java | 2 +- .../java/inu/codin/codin/domain/like/entity/LikeEntity.java | 2 +- .../codin/domain/notification/entity/NotificationEntity.java | 2 +- .../domain/post/domain/comment/entity/CommentEntity.java | 2 +- .../codin/domain/post/domain/poll/entity/PollEntity.java | 3 +-- .../domain/post/domain/reply/entity/ReplyCommentEntity.java | 2 +- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 2 +- .../inu/codin/codin/domain/report/entity/ReportEntity.java | 2 +- .../inu/codin/codin/domain/scrap/entity/ScrapEntity.java | 2 +- .../codin/domain/user/dto/response/UserInfoResponseDto.java | 2 +- .../java/inu/codin/codin/domain/user/entity/UserEntity.java | 5 ++--- .../codin/codin/domain/user/security/CustomUserDetails.java | 2 +- .../inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java | 2 +- 41 files changed, 43 insertions(+), 46 deletions(-) rename codin-core/src/main/java/inu/codin/codin/common/{ => dto}/BaseTimeEntity.java (96%) rename codin-core/src/main/java/inu/codin/codin/common/{ => dto}/Department.java (95%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java b/codin-core/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java rename to codin-core/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java index 4febdb31..595a1377 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/BaseTimeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common; +package inu.codin.codin.common.dto; import lombok.Getter; import org.springframework.data.annotation.CreatedBy; diff --git a/codin-core/src/main/java/inu/codin/codin/common/Department.java b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/common/Department.java rename to codin-core/src/main/java/inu/codin/codin/common/dto/Department.java index 625752bc..f49f8966 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common; +package inu.codin.codin.common.dto; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java index 55848f7b..018bdeae 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.security.dto; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 7d0ef235..a24bc597 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.security.service; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.enums.AuthResultStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index fe73e754..74aa2bb7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 1f9b42b3..08f5602e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index 3f8a75bd..274c443d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import lombok.*; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java index 7fe8eb1a..03bc7323 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatting.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.chat.chatting.dto.ContentType; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 6d20f237..cf94a02f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java index 1cfe9ea8..9b75e7f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.lab.dto.request; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java index 4c6a5c23..606b57c8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.lab.dto.response; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.lab.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java index 8fef91cf..ca0fc14c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java @@ -2,7 +2,7 @@ import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java index dc40e8bf..05a7a6d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.office.controller; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java index 3410c67d..9e22e6c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java @@ -1,12 +1,11 @@ package inu.codin.codin.domain.info.domain.office.dto.response; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.office.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.Setter; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java index e77c5359..16ce3e1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.office.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java index 823b98bd..760b0eb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java @@ -4,7 +4,7 @@ import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.domain.office.dto.response.OfficeDetailsResponseDto; import inu.codin.codin.domain.info.domain.office.entity.Office; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java index 05b3f74d..072c30de 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.professor.controller; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java index de86a82a..56103dd6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.professor.dto.request; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java index 030c6ea8..ad0e58d5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.professor.dto.response; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java index 9e2dc1e7..4095f694 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.professor.dto.response; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.professor.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java index 456646a1..54c5ae04 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.professor.entity; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.domain.info.entity.InfoType; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java index d2246b72..0f6b70ef 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.domain.professor.service; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorListResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorThumbnailResponseDto; import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java index f2dae04b..462c0276 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.dto.Department; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 9ef45c87..416fa37e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.repository; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.domain.lab.entity.Lab; import inu.codin.codin.domain.info.domain.office.entity.Office; import inu.codin.codin.domain.info.domain.professor.entity.Professor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index dddb9af2..efa4ce9e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.controller; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.dto.Option; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index 21bbd65e..17b5dbc9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.domain.review.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index 35efd2b3..ce9266f1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.entity; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java index 00c2abb0..cfec9fa6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.repository; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.lecture.entity.LectureEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index d460e0f0..65050719 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.service; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.dto.*; import inu.codin.codin.domain.lecture.entity.LectureEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java index c2615fb2..f6beb2e9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.like.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index a6f98f05..39edcb64 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.notification.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.user.entity.UserEntity; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 6df1cd7f..8043ec3f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 2e63c596..7103b58c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.domain.poll.entity; import com.fasterxml.jackson.annotation.JsonFormat; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; @@ -9,7 +9,6 @@ import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 8ce269f8..58c2ffb3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.reply.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 937e2f78..1ecd7c1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index af1e6d28..46627c13 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.report.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java index 0e3626e6..85a1e6a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.scrap.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java index e7b0de71..3e165775 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.dto.response; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.user.entity.UserEntity; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index cef9c6e6..257a0506 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -1,10 +1,9 @@ package inu.codin.codin.domain.user.entity; -import inu.codin.codin.common.BaseTimeEntity; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.domain.notification.entity.NotificationPreference; -import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index 41ac5c07..8b4401a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.security; -import inu.codin.codin.common.Department; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index 2445dfa0..f9d1ba7a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.infra.fcm.entity; -import inu.codin.codin.common.BaseTimeEntity; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; From 076f0e122df1e211c62ef846153f90ba7d54498e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 2 Mar 2025 23:32:48 +0900 Subject: [PATCH 0520/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 2b40fb11..4ae59d41 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 2b40fb11acc679934206cadd5215def412b1fb0d +Subproject commit 4ae59d41f8aba33a4f71d6dafb3983228b0027b6 From beec02a6f38f478dcd303b0985f12b451c6b9791 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 2 Mar 2025 23:34:09 +0900 Subject: [PATCH 0521/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 4ae59d41..cd1ff65e 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 4ae59d41f8aba33a4f71d6dafb3983228b0027b6 +Subproject commit cd1ff65e1f68d3f8ff58e9bd7316f6edcf208a42 From f66ad8ad882373b889c2150fe167cded3a820057 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 2 Mar 2025 23:43:16 +0900 Subject: [PATCH 0522/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index cd1ff65e..c049f8b7 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit cd1ff65e1f68d3f8ff58e9bd7316f6edcf208a42 +Subproject commit c049f8b74ddaa16f2438069205e2398ea9539b97 From d11c585ae25a48b7c5e5b44ea4f0e5e4aeb6cbdd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 3 Mar 2025 00:16:04 +0900 Subject: [PATCH 0523/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index c049f8b7..09935ca9 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit c049f8b74ddaa16f2438069205e2398ea9539b97 +Subproject commit 09935ca9e430a3436b9d41a4543021a25753c5cf From bb60b0fd8e2b5ef81fb6022603282db746a4fc6d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 14:33:44 +0900 Subject: [PATCH 0524/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 09935ca9..62bf55aa 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 09935ca9e430a3436b9d41a4543021a25753c5cf +Subproject commit 62bf55aa0ab053e50681f9643ead27d0cd7f73cb From 3e7963341429b52e6f25611ef0014fcf91594108 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 15:40:00 +0900 Subject: [PATCH 0525/1002] =?UTF-8?q?chore=20:=20Scheduler=20use=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/schedular/PostsScheduler.java | 47 ++++++++----------- codin-core/src/main/resources | 2 +- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index d46becc9..b2f04e4f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -10,7 +10,7 @@ import java.io.IOException; /* - 비교과 게시글 매일 새벽 2시 업데이트 + 비교과 게시글 매일 새벽 4시 업데이트 */ @Slf4j @Component @@ -19,26 +19,21 @@ public class PostsScheduler { @Value("${schedule.path}") private static String PATH; - @Value("${schedule.use}") - private static boolean useSchedule; - @Scheduled(cron = "${schedule.department.cron}", zone = "Asia/Seoul") @Async public void departmentPostsScheduler() { - try { - if (useSchedule) { - String fileName = "department.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", - PATH + fileName); - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - log.warn("Exited department python with error code" + exitCode); - log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); - } else log.info("[PostsScheduler] 스케줄러 작동 false"); - } catch (IOException | InterruptedException e) { - log.error(e.getMessage(), e.getStackTrace()[0]); - throw new SchedulerException(e.getMessage()); - } + try { + String fileName = "department.py"; + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + PATH + fileName); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + log.warn("Exited department python with error code" + exitCode); + log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); + } catch (IOException | InterruptedException e) { + log.error(e.getMessage(), e.getStackTrace()[0]); + throw new SchedulerException(e.getMessage()); + } } @@ -46,15 +41,13 @@ public void departmentPostsScheduler() { @Async public void starinuPostsScheduler(){ try { - if (useSchedule) { - String fileName = "starinu.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", - PATH + fileName); - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - log.warn("Exited starinu python with error code" + exitCode); - log.info("[PostsScheduler] STARINU 게시글 업데이트 완료"); - } else log.info("[PostsScheduler] 스케줄러 작동 false"); + String fileName = "starinu.py"; + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + PATH + fileName); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + log.warn("Exited starinu python with error code" + exitCode); + log.info("[PostsScheduler] STARINU 게시글 업데이트 완료"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); throw new SchedulerException(e.getMessage()); diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 2b40fb11..62facd84 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 2b40fb11acc679934206cadd5215def412b1fb0d +Subproject commit 62facd8412d5b0e188359d1ed2f76074df284834 From 556a52b20693ce0f6a7566223a62b421e5eaac73 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 16:50:18 +0900 Subject: [PATCH 0526/1002] chore : Scheduler test --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 62facd84..8c4aecbe 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 62facd8412d5b0e188359d1ed2f76074df284834 +Subproject commit 8c4aecbe2fa0486d31053f28e659fd45ceaba17f From e84e0613d2ace187ba2d793689e042574a1d832f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:00:41 +0900 Subject: [PATCH 0527/1002] chore : Scheduler test --- .../inu/codin/codin/domain/post/schedular/PostsScheduler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index b2f04e4f..186d289a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -26,6 +26,7 @@ public void departmentPostsScheduler() { String fileName = "department.py"; ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", PATH + fileName); + System.out.println("Running command: " + processBuilder.command()); Process process = processBuilder.start(); int exitCode = process.waitFor(); log.warn("Exited department python with error code" + exitCode); From 89a312d3daf56dbcd7989f9524f0a33fd9b5b37b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:06:40 +0900 Subject: [PATCH 0528/1002] chore : Scheduler test --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8c4aecbe..1ebef9b7 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8c4aecbe2fa0486d31053f28e659fd45ceaba17f +Subproject commit 1ebef9b7023d39dee300470297511489edc025aa From ac07ef4834eaa1cb607a37dac2a5b9aaec219d53 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:07:16 +0900 Subject: [PATCH 0529/1002] chore : Scheduler test --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 1ebef9b7..8631d030 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 1ebef9b7023d39dee300470297511489edc025aa +Subproject commit 8631d0302bbefe75edcf5ab77fee3b155ae1217a From 2b7c3a940bfc606b20764a80d3de5e26c9960896 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:13:44 +0900 Subject: [PATCH 0530/1002] chore : Scheduler test --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8631d030..afbda8f3 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8631d0302bbefe75edcf5ab77fee3b155ae1217a +Subproject commit afbda8f30c43a7a57992abe74ac83108febb831b From e0c4dd8db66bbbdee8abbd22cad1e66e323202d2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:36:07 +0900 Subject: [PATCH 0531/1002] =?UTF-8?q?perf=20:=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=9D=BD=EC=9D=8C=20=EC=B2=98=EB=A6=AC=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=EC=9E=90=20=EC=88=98=20Redis=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageProcessor.java | 4 ++ .../chatting/service/ChattingService.java | 6 ++- .../infra/redis/service/RedisChatService.java | 53 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 69198dde..94db9726 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -6,6 +6,7 @@ import inu.codin.codin.domain.chat.chatting.service.ChattingService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisChatService; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,6 +31,7 @@ public class StompMessageProcessor implements ChannelInterceptor { private final ChattingService chattingService; + private final RedisChatService redisChatService; private final Map sessionStore = new ConcurrentHashMap<>(); private final ChatRoomRepository chatRoomRepository; private final UserRepository userRepository; @@ -76,12 +78,14 @@ private void connectSession(StompHeaderAccessor headerAccessor) { } private void exitToChatRoom(StompHeaderAccessor headerAccessor) { Result result = getResult(headerAccessor); + redisChatService.exitToChatroom(result.chatroom().get_id().toString()); result.chatroom().getParticipants().exit(result.user().get_id()); chatRoomRepository.save(result.chatroom()); } private void enterToChatRoom(StompHeaderAccessor headerAccessor){ Result result = getResult(headerAccessor); + redisChatService.enterToChatroom(result.chatroom().get_id().toString()); chattingService.updateUnreadCount(result.chatroom.get_id(), result.user.get_id()); result.chatroom.getParticipants().enter(result.user.get_id()); chatRoomRepository.save(result.chatroom); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 4dd2293c..ceed6ddb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -13,6 +13,7 @@ import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.security.CustomUserDetails; +import inu.codin.codin.infra.redis.service.RedisChatService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -36,6 +37,7 @@ public class ChattingService { private final ChattingRepository chattingRepository; private final S3Service s3Service; private final NotificationService notificationService; + private final RedisChatService redisChatService; private final ApplicationEventPublisher eventPublisher; public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { @@ -46,7 +48,9 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq }); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); - Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId, chatRoom.getParticipants().getInfo().size()); + Integer countOfParticipating = redisChatService.countOfChatRoomParticipant(chatRoom.get_id().toString()); + Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId, + chatRoom.getParticipants().getInfo().size()-countOfParticipating); log.info("[메시지 전송 성공] 메시지: [{}], 송신자 ID: {}, 채팅방 ID: {}", chattingRequestDto.getContent(), userId, id); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java new file mode 100644 index 00000000..b8a10960 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java @@ -0,0 +1,53 @@ +package inu.codin.codin.infra.redis.service; + +import inu.codin.codin.common.exception.NotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RedisChatService { + + private final RedisTemplate redisTemplate; + + private final String CHATROOM_KEY = "chat:participant_counter:"; + + public void enterToChatroom(String chatRoomId){ + String counterKey = CHATROOM_KEY + chatRoomId; + Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); + if (counterAnonNum == null){ + redisTemplate.opsForValue().set(counterKey, 1); //카운터 1부터 시작 + } + redisTemplate.opsForValue().increment(counterKey); + } + + public void exitToChatroom(String chatRoomId){ + String counterKey = CHATROOM_KEY + chatRoomId; + Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); + + if (counterAnonNum != null) + if (counterAnonNum.toString().equals("0")) + log.error("[RedisChatService] 채팅방을 나가지만 참여자 수가 0 이라 음수가 됩니다."); + else redisTemplate.opsForValue().set(counterKey, Integer.parseInt(counterAnonNum.toString())-1); + else { + log.error("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다. chatRoomId : " + chatRoomId); + throw new NotFoundException("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다."); + } + } + + public Integer countOfChatRoomParticipant(String chatRoomId) { + String counterKey = CHATROOM_KEY + chatRoomId; + Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); + + if (counterAnonNum != null) + return Integer.parseInt(counterAnonNum.toString()); + else { + log.error("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다. chatRoomId : " + chatRoomId); + throw new NotFoundException("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다."); + } + } + +} From e50f4bea27159b4ee5805e979240741b47efe9fa Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:44:14 +0900 Subject: [PATCH 0532/1002] =?UTF-8?q?fix=20:=20static=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/schedular/PostsScheduler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 186d289a..085ca834 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; + import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; @@ -17,7 +18,7 @@ public class PostsScheduler { @Value("${schedule.path}") - private static String PATH; + private String PATH; @Scheduled(cron = "${schedule.department.cron}", zone = "Asia/Seoul") @Async @@ -26,6 +27,7 @@ public void departmentPostsScheduler() { String fileName = "department.py"; ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", PATH + fileName); + System.out.println(PATH); System.out.println("Running command: " + processBuilder.command()); Process process = processBuilder.start(); int exitCode = process.waitFor(); From f5fd87d3bc088464a42b3dfd65f7cb59ae35ac43 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:44:24 +0900 Subject: [PATCH 0533/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index afbda8f3..fb7dba27 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit afbda8f30c43a7a57992abe74ac83108febb831b +Subproject commit fb7dba27c5b7d2daf3feac86c572b199e3cc392b From fd596adb3e9c7cce4f2738575161aa36039c6a71 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 17:45:19 +0900 Subject: [PATCH 0534/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index fb7dba27..43618961 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit fb7dba27c5b7d2daf3feac86c572b199e3cc392b +Subproject commit 43618961b0f958851d2d5107cf1c5f8b517c977c From f6563b7ec7726fb8b238edd3f2bf1bce6e816707 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 20:14:41 +0900 Subject: [PATCH 0535/1002] chore : Test Scheduler --- .../codin/codin/domain/post/schedular/PostsScheduler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 085ca834..04153590 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -32,7 +32,9 @@ public void departmentPostsScheduler() { Process process = processBuilder.start(); int exitCode = process.waitFor(); log.warn("Exited department python with error code" + exitCode); - log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); + if (exitCode == 0) + log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); + else log.warn("[PostsScheduler] 학과 공지사항 업데이트 실패"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); throw new SchedulerException(e.getMessage()); @@ -50,7 +52,9 @@ public void starinuPostsScheduler(){ Process process = processBuilder.start(); int exitCode = process.waitFor(); log.warn("Exited starinu python with error code" + exitCode); - log.info("[PostsScheduler] STARINU 게시글 업데이트 완료"); + if (exitCode == 0) + log.info("[PostsScheduler] STARINU 공지사항 업데이트 완료"); + else log.warn("[PostsScheduler] STARINU 공지사항 업데이트 실패"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); throw new SchedulerException(e.getMessage()); From 45996cd78f4053c8a4bc3f49035712d5385eeeec Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 20:14:57 +0900 Subject: [PATCH 0536/1002] chore : Test Scheduler --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 43618961..33f6df93 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 43618961b0f958851d2d5107cf1c5f8b517c977c +Subproject commit 33f6df931feabf76030e6a57707736cee4e683f5 From 778d9d54bf74e193b345493c0682570b5f702358 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 20:47:30 +0900 Subject: [PATCH 0537/1002] =?UTF-8?q?fix=20:=20Chatting=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=8C=20=ED=91=9C=EC=8B=9C=20=EC=98=A4=EB=A5=98=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 --- .../common/stomp/StompMessageProcessor.java | 4 -- .../chatroom/service/ChatRoomService.java | 11 ++++ .../chatting/service/ChattingService.java | 6 +-- .../infra/redis/service/RedisChatService.java | 53 ------------------- 4 files changed, 14 insertions(+), 60 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 94db9726..69198dde 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -6,7 +6,6 @@ import inu.codin.codin.domain.chat.chatting.service.ChattingService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisChatService; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,7 +30,6 @@ public class StompMessageProcessor implements ChannelInterceptor { private final ChattingService chattingService; - private final RedisChatService redisChatService; private final Map sessionStore = new ConcurrentHashMap<>(); private final ChatRoomRepository chatRoomRepository; private final UserRepository userRepository; @@ -78,14 +76,12 @@ private void connectSession(StompHeaderAccessor headerAccessor) { } private void exitToChatRoom(StompHeaderAccessor headerAccessor) { Result result = getResult(headerAccessor); - redisChatService.exitToChatroom(result.chatroom().get_id().toString()); result.chatroom().getParticipants().exit(result.user().get_id()); chatRoomRepository.save(result.chatroom()); } private void enterToChatRoom(StompHeaderAccessor headerAccessor){ Result result = getResult(headerAccessor); - redisChatService.enterToChatroom(result.chatroom().get_id().toString()); chattingService.updateUnreadCount(result.chatroom.get_id(), result.user.get_id()); result.chatroom.getParticipants().enter(result.user.get_id()); chatRoomRepository.save(result.chatroom); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 042c7aa9..6bbe110c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.chat.chatroom.dto.event.ChatRoomNotificationEvent; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; +import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; @@ -127,4 +128,14 @@ public void setNotificationChatRoom(String chatRoomId) { chatRoomRepository.save(chatRoom); log.info("[알림 설정 완료] 채팅방 ID: {}에 알림 설정 완료", chatRoomId); } + + public Integer countOfParticipating(ObjectId chatRoomId){ + ChatRoom chatroom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); + int count = 0; + for (ParticipantInfo info : chatroom.getParticipants().getInfo().values()){ + if (info.isConnected()) count++; + } + return count; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index ceed6ddb..144cc4b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingAndUserIdResponseDto; @@ -13,7 +14,6 @@ import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.security.CustomUserDetails; -import inu.codin.codin.infra.redis.service.RedisChatService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,7 +37,7 @@ public class ChattingService { private final ChattingRepository chattingRepository; private final S3Service s3Service; private final NotificationService notificationService; - private final RedisChatService redisChatService; + private final ChatRoomService chatRoomService; private final ApplicationEventPublisher eventPublisher; public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingRequestDto, Authentication authentication) { @@ -48,7 +48,7 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq }); ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); - Integer countOfParticipating = redisChatService.countOfChatRoomParticipant(chatRoom.get_id().toString()); + Integer countOfParticipating = chatRoomService.countOfParticipating(chatRoom.get_id()); Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId, chatRoom.getParticipants().getInfo().size()-countOfParticipating); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java deleted file mode 100644 index b8a10960..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisChatService.java +++ /dev/null @@ -1,53 +0,0 @@ -package inu.codin.codin.infra.redis.service; - -import inu.codin.codin.common.exception.NotFoundException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Slf4j -public class RedisChatService { - - private final RedisTemplate redisTemplate; - - private final String CHATROOM_KEY = "chat:participant_counter:"; - - public void enterToChatroom(String chatRoomId){ - String counterKey = CHATROOM_KEY + chatRoomId; - Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); - if (counterAnonNum == null){ - redisTemplate.opsForValue().set(counterKey, 1); //카운터 1부터 시작 - } - redisTemplate.opsForValue().increment(counterKey); - } - - public void exitToChatroom(String chatRoomId){ - String counterKey = CHATROOM_KEY + chatRoomId; - Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); - - if (counterAnonNum != null) - if (counterAnonNum.toString().equals("0")) - log.error("[RedisChatService] 채팅방을 나가지만 참여자 수가 0 이라 음수가 됩니다."); - else redisTemplate.opsForValue().set(counterKey, Integer.parseInt(counterAnonNum.toString())-1); - else { - log.error("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다. chatRoomId : " + chatRoomId); - throw new NotFoundException("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다."); - } - } - - public Integer countOfChatRoomParticipant(String chatRoomId) { - String counterKey = CHATROOM_KEY + chatRoomId; - Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); - - if (counterAnonNum != null) - return Integer.parseInt(counterAnonNum.toString()); - else { - log.error("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다. chatRoomId : " + chatRoomId); - throw new NotFoundException("[RedisChatService] 해당 chatroom의 참여자 수를 가져올 수 없습니다."); - } - } - -} From 1e536c332478991cacd20808ea4558b6a939feef Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 20:47:53 +0900 Subject: [PATCH 0538/1002] =?UTF-8?q?fix=20:=20Scheduler=20python3=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/schedular/PostsScheduler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 04153590..19ab71b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -25,7 +25,7 @@ public class PostsScheduler { public void departmentPostsScheduler() { try { String fileName = "department.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("python3", PATH + fileName); System.out.println(PATH); System.out.println("Running command: " + processBuilder.command()); @@ -47,7 +47,7 @@ public void departmentPostsScheduler() { public void starinuPostsScheduler(){ try { String fileName = "starinu.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("python3", PATH + fileName); Process process = processBuilder.start(); int exitCode = process.waitFor(); From d1040a6b7b67c91e700162a53b9cd2f232a4cf69 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 20:49:13 +0900 Subject: [PATCH 0539/1002] chore : Test Scheduler --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 33f6df93..93d1c659 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 33f6df931feabf76030e6a57707736cee4e683f5 +Subproject commit 93d1c659f9f034759855b6ccf396698c519f40bf From 914cd2cbd1bb56f019dc4efa5eac089e6ce6ec77 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 21:08:21 +0900 Subject: [PATCH 0540/1002] =?UTF-8?q?fix=20:=20event=EC=97=90=EC=84=9C=20U?= =?UTF-8?q?nread=EB=A5=BC=20=EC=A4=84=EC=9D=B4=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=B9=BC=EA=B3=A0,=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8A=94=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=A0=84=EC=97=90=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/service/ChattingEventListener.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index 3d6dff40..0d771812 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -26,9 +26,7 @@ public void handleChattingArrivedEvent(ChattingArrivedEvent event){ .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다. ID: "+ chatting.getChatRoomId())); chatRoom.getParticipants().getInfo().forEach( (id, participantInfo) -> { - if (participantInfo.isConnected()) { - chatting.minusUnread(); - } else { + if (!participantInfo.isConnected()) { participantInfo.plusUnread(); } } @@ -36,6 +34,7 @@ public void handleChattingArrivedEvent(ChattingArrivedEvent event){ chatRoom.updateLastMessage(chatting.getContent()); chatRoomRepository.save(chatRoom); chattingRepository.save(chatting); + } } From 372be292ac8838f050df46a4f2a9fc82e5b59a17 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 21:13:22 +0900 Subject: [PATCH 0541/1002] chore : Test Scheduler --- .../codin/codin/domain/post/schedular/PostsScheduler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 19ab71b7..e5d8166c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -25,8 +25,8 @@ public class PostsScheduler { public void departmentPostsScheduler() { try { String fileName = "department.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("python3", - PATH + fileName); + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", "\""+ + PATH + fileName+"\""); System.out.println(PATH); System.out.println("Running command: " + processBuilder.command()); Process process = processBuilder.start(); @@ -47,7 +47,7 @@ public void departmentPostsScheduler() { public void starinuPostsScheduler(){ try { String fileName = "starinu.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("python3", + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", PATH + fileName); Process process = processBuilder.start(); int exitCode = process.waitFor(); From 235ab6be3141e1d806ce28dd8ecebf257a30106c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 4 Mar 2025 21:13:27 +0900 Subject: [PATCH 0542/1002] chore : Test Scheduler --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 93d1c659..5c48aede 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 93d1c659f9f034759855b6ccf396698c519f40bf +Subproject commit 5c48aede2ba1c3f4c0f3dcddaf325441a3dddc46 From cd39163736aacfc25eac31b88b0eb6ed33da4615 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 01:40:52 +0900 Subject: [PATCH 0543/1002] =?UTF-8?q?perf=20:=20Chatting=20=EC=95=88?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C,=EC=9D=BD=EC=9D=8C=20event=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageProcessor.java | 77 ++------------- .../common/stomp/StompMessageService.java | 95 +++++++++++++++++++ .../service/ChattingEventListener.java | 23 +++++ .../chatting/service/ChattingService.java | 13 --- 4 files changed, 126 insertions(+), 82 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 69198dde..84b989f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -1,16 +1,7 @@ package inu.codin.codin.common.stomp; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.chat.chatting.service.ChattingService; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.context.event.EventListener; import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -18,98 +9,46 @@ import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - @Component @RequiredArgsConstructor @Slf4j public class StompMessageProcessor implements ChannelInterceptor { - private final ChattingService chattingService; - private final Map sessionStore = new ConcurrentHashMap<>(); - private final ChatRoomRepository chatRoomRepository; - private final UserRepository userRepository; - - private final HttpServletRequest request; + private final StompMessageService stompMessageService; @Override public Message preSend(Message message, MessageChannel messageChannel){ StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - handleMessage(headerAccessor, request); + handleMessage(headerAccessor); return message; } - public void handleMessage(StompHeaderAccessor headerAccessor, HttpServletRequest request){ + public void handleMessage(StompHeaderAccessor headerAccessor){ if (headerAccessor == null || headerAccessor.getCommand() == null){ throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString()); } switch (headerAccessor.getCommand()){ case CONNECT -> { - connectSession(headerAccessor); + stompMessageService.connectSession(headerAccessor); } case SUBSCRIBE -> { log.info("[STOMP] Subscribe" ); - enterToChatRoom(headerAccessor); + stompMessageService.enterToChatRoom(headerAccessor); } case UNSUBSCRIBE -> { log.info("[STOMP] UnSubscribe" ); - exitToChatRoom(headerAccessor); + stompMessageService.exitToChatRoom(headerAccessor); } case DISCONNECT -> { log.info("[STOMP] DISCONNECT"); - exitToChatRoom(headerAccessor); - disconnectSession(headerAccessor); + stompMessageService.exitToChatRoom(headerAccessor); + stompMessageService.disconnectSession(headerAccessor); } } } - @EventListener - private void connectSession(StompHeaderAccessor headerAccessor) { - String sessionId = headerAccessor.getSessionId(); - String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); - sessionStore.put(sessionId, chatroomId); - } - private void exitToChatRoom(StompHeaderAccessor headerAccessor) { - Result result = getResult(headerAccessor); - result.chatroom().getParticipants().exit(result.user().get_id()); - chatRoomRepository.save(result.chatroom()); - } - private void enterToChatRoom(StompHeaderAccessor headerAccessor){ - Result result = getResult(headerAccessor); - chattingService.updateUnreadCount(result.chatroom.get_id(), result.user.get_id()); - result.chatroom.getParticipants().enter(result.user.get_id()); - chatRoomRepository.save(result.chatroom); - } - - private void disconnectSession(StompHeaderAccessor headerAccessor){ - sessionStore.remove(headerAccessor.getSessionId()); - } - - private Result getResult(StompHeaderAccessor headerAccessor) { - String email; - if (headerAccessor.getUser() != null) { - email = headerAccessor.getUser().getName(); - } else { - throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); - } - String chatroomId = sessionStore.get(headerAccessor.getSessionId()); - if (chatroomId == null || !ObjectId.isValid(chatroomId)) { - throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); - } - ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) - .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); - UserEntity user = userRepository.findByEmailAndDisabledAndActive(email) - .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); - Result result = new Result(chatroom, user); - return result; - } - - private record Result(ChatRoom chatroom, UserEntity user) { - } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java new file mode 100644 index 00000000..b36105a0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -0,0 +1,95 @@ +package inu.codin.codin.common.stomp; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; +import inu.codin.codin.domain.chat.chatting.dto.event.UpdateUnreadCountEvent; +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +@RequiredArgsConstructor +public class StompMessageService { + + private final Map sessionStore = new ConcurrentHashMap<>(); + + private final ChatRoomRepository chatRoomRepository; + private final UserRepository userRepository; + private final ChattingRepository chattingRepository; + private final ApplicationEventPublisher eventPublisher; + + public void connectSession(StompHeaderAccessor headerAccessor) { + String sessionId = headerAccessor.getSessionId(); + String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); + sessionStore.put(sessionId, chatroomId); + } + + public void exitToChatRoom(StompHeaderAccessor headerAccessor) { + Result result = getResult(headerAccessor); + result.chatroom().getParticipants().exit(result.user().get_id()); + chatRoomRepository.save(result.chatroom()); + } + + public void enterToChatRoom(StompHeaderAccessor headerAccessor){ + Result result = getResult(headerAccessor); + List chattings = updateUnreadCount(result.chatroom.get_id(), result.user.get_id()); + result.chatroom.getParticipants().enter(result.user.get_id()); + chatRoomRepository.save(result.chatroom); + if (!chattings.isEmpty()) + eventPublisher.publishEvent(new UpdateUnreadCountEvent(this, chattings, result.chatroom.get_id().toString())); + } + + public void disconnectSession(StompHeaderAccessor headerAccessor){ + sessionStore.remove(headerAccessor.getSessionId()); + } + + private Result getResult(StompHeaderAccessor headerAccessor) { + String email; + if (headerAccessor.getUser() != null) { + email = headerAccessor.getUser().getName(); + } else { + throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); + } + String chatroomId = sessionStore.get(headerAccessor.getSessionId()); + if (chatroomId == null || !ObjectId.isValid(chatroomId)) { + throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); + } + ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) + .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); + UserEntity user = userRepository.findByEmailAndDisabledAndActive(email) + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + Result result = new Result(chatroom, user); + return result; + } + + + + private record Result(ChatRoom chatroom, UserEntity user) { + } + + private List updateUnreadCount(ObjectId chatRoomId, ObjectId userId){ + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(()-> new NotFoundException("채팅방을 찾을 수 없습니다.")); + List chattings = chattingRepository.findAllByChatRoomIdOrderByCreatedAtDesc(chatRoomId) + .stream() + .limit(chatRoom.getParticipants().getInfo().get(userId).getUnreadMessage()) + .map(chatting -> { + chatting.minusUnread(); + return chattingRepository.save(chatting); + }).toList(); + return chattings; + + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index 0d771812..7cc0b0eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -4,19 +4,26 @@ import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; +import inu.codin.codin.domain.chat.chatting.dto.event.UpdateUnreadCountEvent; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + @Service @RequiredArgsConstructor public class ChattingEventListener { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; + private final SimpMessageSendingOperations template; @Async @EventListener @@ -37,4 +44,20 @@ public void handleChattingArrivedEvent(ChattingArrivedEvent event){ } + + @EventListener + public void updateUnreadCountEvent(UpdateUnreadCountEvent updateUnreadCountEvent){ + List> result = new ArrayList<>(); + for (Chatting chat : updateUnreadCountEvent.getChattingList()){ + Map payload = Map.of( + "id", chat.get_id().toString(), + "unread", String.valueOf(chat.getUnreadCount()) + ); + result.add(payload); + } + + template.convertAndSend("/queue/unread/"+ updateUnreadCountEvent.getChatRoomId(), result); + + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 144cc4b5..82c8b173 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -94,17 +94,4 @@ public List sendImageMessage(List chatImages) { return imageUrls; } - - public void updateUnreadCount(ObjectId chatRoomId, ObjectId userId){ - ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) - .orElseThrow(()-> new NotFoundException("채팅방을 찾을 수 없습니다.")); - chattingRepository.findAllByChatRoomIdOrderByCreatedAtDesc(chatRoomId) - .stream() - .limit(chatRoom.getParticipants().getInfo().get(userId).getUnreadMessage()) - .forEach(chatting -> { - chatting.minusUnread(); - chattingRepository.save(chatting); - }); - - } } From 57fad15631ede8ebed2cc9f2a270b96b37979038 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 01:40:58 +0900 Subject: [PATCH 0544/1002] =?UTF-8?q?perf=20:=20Chatting=20=EC=95=88?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C,=EC=9D=BD=EC=9D=8C=20event=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/event/UpdateUnreadCountEvent.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/UpdateUnreadCountEvent.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/UpdateUnreadCountEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/UpdateUnreadCountEvent.java new file mode 100644 index 00000000..ce80d951 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/UpdateUnreadCountEvent.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.chat.chatting.dto.event; + +import inu.codin.codin.domain.chat.chatting.entity.Chatting; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import java.util.List; + +@Getter +public class UpdateUnreadCountEvent extends ApplicationEvent { + + private final List chattingList; + private final String chatRoomId; + + public UpdateUnreadCountEvent(Object source, List chattingList, String chatRoomId) { + super(source); + this.chattingList = chattingList; + this.chatRoomId = chatRoomId; + } +} From ce117efb4461434df47817b3ee92e9ee699e7db3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 01:42:28 +0900 Subject: [PATCH 0545/1002] chore : Test Scheduler --- .../inu/codin/codin/domain/post/schedular/PostsScheduler.java | 3 +-- codin-core/src/main/resources | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index e5d8166c..c80d3023 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -25,8 +25,7 @@ public class PostsScheduler { public void departmentPostsScheduler() { try { String fileName = "department.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", "\""+ - PATH + fileName+"\""); + ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3",PATH + fileName); System.out.println(PATH); System.out.println("Running command: " + processBuilder.command()); Process process = processBuilder.start(); diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 5c48aede..ef4e0de1 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 5c48aede2ba1c3f4c0f3dcddaf325441a3dddc46 +Subproject commit ef4e0de1d3e416b9b19de4d323c2bced3213b9a8 From 34e6997adbb3fec6a08f8e9717ee05a8c77c628e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 02:23:16 +0900 Subject: [PATCH 0546/1002] =?UTF-8?q?chore=20:=20python3=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=EB=A5=BC=20=EC=9C=84=ED=95=9C=20Dockerfile=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index ef4e0de1..1ee3a09a 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit ef4e0de1d3e416b9b19de4d323c2bced3213b9a8 +Subproject commit 1ee3a09aadf865e463d8f752d2464e243ffe076b From 2544a48ec6c8b19bb9c5ddc8b6b6131261b4292b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 03:02:23 +0900 Subject: [PATCH 0547/1002] =?UTF-8?q?chore=20:=20scheduler=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=EC=9D=84=20=EC=9C=84=ED=95=9C=20openjdk-17=20?= =?UTF-8?q?=EA=B2=BD=EB=9F=89=ED=99=94=20=EB=B0=8F=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?volume=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 1ee3a09a..8a3203e5 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 1ee3a09aadf865e463d8f752d2464e243ffe076b +Subproject commit 8a3203e528b1818236ca3b45c171d07a4ecb4a26 From feae5790d571fd2d0ae8f179b35d5013d5b6f420 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 03:17:02 +0900 Subject: [PATCH 0548/1002] =?UTF-8?q?chore=20:=20package=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 8a3203e5..12c4cafa 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 8a3203e528b1818236ca3b45c171d07a4ecb4a26 +Subproject commit 12c4cafa937b032f6ae1c3978b0b6e041507fc58 From 7ba51511db9ada918de6692475e042a6e6387ac0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 03:28:37 +0900 Subject: [PATCH 0549/1002] chore :test Scheduler --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 12c4cafa..f3b2a65f 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 12c4cafa937b032f6ae1c3978b0b6e041507fc58 +Subproject commit f3b2a65f0dd4579bc825d19f5d5b21e1cea14ce6 From df3e4857b41d02fcd29366e2f165548f29a6d5bb Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 5 Mar 2025 04:09:45 +0900 Subject: [PATCH 0550/1002] =?UTF-8?q?chore=20:=20Scheduler=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=8B=A4=EC=8B=9C=20=EC=9B=90=EC=83=81=EB=B3=B5?= =?UTF-8?q?=EA=B7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index f3b2a65f..f329cfb6 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit f3b2a65f0dd4579bc825d19f5d5b21e1cea14ce6 +Subproject commit f329cfb67c9f62f8c89325b390cca995549e467a From b22367f10ac9278fc6af25d83d3930325c122a2f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Mar 2025 16:31:23 +0900 Subject: [PATCH 0551/1002] =?UTF-8?q?refactor=20::=20RedisAuthService=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/service/RedisAuthService.java | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java deleted file mode 100644 index bb2ef148..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAuthService.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codin.infra.redis.service; - -import inu.codin.codin.common.security.dto.PortalLoginResponseDto; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; - -@Service -@RequiredArgsConstructor -public class RedisAuthService { - - private final RedisTemplate redisTemplate; - private static final long EXPIRE_TIME = 5; - - // 데이터 저장 - public void saveUserData(String studentId, PortalLoginResponseDto userData) { - redisTemplate.opsForValue().set(studentId, userData, EXPIRE_TIME, TimeUnit.MINUTES); // 5분 후 자동 삭제 - } - - // 데이터 조회 - public PortalLoginResponseDto getUserData(String studentId) { - return (PortalLoginResponseDto) redisTemplate.opsForValue().get(studentId); - } - -} From 709c238d0456aa38bd7f2e4dd368a89705bc5a05 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Mar 2025 16:33:23 +0900 Subject: [PATCH 0552/1002] fix :: Review Scrap Count Issue --- .../inu/codin/codin/domain/scrap/service/ScrapService.java | 2 +- .../codin/codin/infra/redis/service/RedisScrapService.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 75a4770f..03a7c08f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -60,7 +60,7 @@ private void addScrap(ObjectId postId, ObjectId userId) { scrap.restore(); scrapRepository.save(scrap); } else { - log.warn("스크랩 추가 실패 - 이미 스크랩된 상태 - postId: {}, userId: {}", postId, userId); + log.info("스크랩 추가 실패 - 이미 스크랩된 상태 - postId: {}, userId: {}", postId, userId); throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); } } else { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java index 5254541c..651be61a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java @@ -8,6 +8,8 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.util.Set; + @Service @RequiredArgsConstructor @Slf4j @@ -37,6 +39,11 @@ public int getScrapCount(ObjectId postId) { return scrapCount != null ? scrapCount.intValue() : 0; } + public Set getScrapedUsers(ObjectId postId) { + String redisKey = SCRAP_KEY + postId.toString(); + return redisTemplate.opsForSet().members(redisKey); + } + public boolean isPostScraped(ObjectId postId, ObjectId userId){ String redisKey = SCRAP_KEY + postId.toString(); return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); From a3aa8c5a45115c3c012e6d2d707bc5d193268e11 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Mar 2025 16:33:39 +0900 Subject: [PATCH 0553/1002] fix :: Review Scrap Count Issue --- .../domain/review/dto/ReviewListResposneDto.java | 10 +++++----- .../lecture/domain/review/entity/ReviewEntity.java | 12 ++++++++---- .../domain/review/repository/ReviewRepository.java | 3 +++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java index ec8d8e00..4d613755 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java @@ -25,7 +25,7 @@ public class ReviewListResposneDto { private double starRating; @Schema(description = "좋아요 수", example = "3") - private int likes; + private int likeCount; @Schema(description = "유저의 좋아요 반영 여부", example = "true") private boolean isLiked; @@ -34,25 +34,25 @@ public class ReviewListResposneDto { private String semester; @Builder - public ReviewListResposneDto(String _id, String lectureId, String userId, String content, double starRating, int likes, boolean isLiked, String semester) { + public ReviewListResposneDto(String _id, String lectureId, String userId, String content, double starRating, int likeCount, boolean isLiked, String semester) { this._id = _id; this.lectureId = lectureId; this.userId = userId; this.content = content; this.starRating = starRating; - this.likes = likes; + this.likeCount = likeCount; this.isLiked = isLiked; this.semester = semester; } - public static ReviewListResposneDto of(ReviewEntity reviewEntity, boolean isLiked, int likes){ + public static ReviewListResposneDto of(ReviewEntity reviewEntity, boolean isLiked, int likeCount){ return ReviewListResposneDto.builder() ._id(reviewEntity.get_id().toString()) .lectureId(reviewEntity.getLectureId().toString()) .userId(reviewEntity.getUserId().toString()) .content(reviewEntity.getContent()) .starRating(reviewEntity.getStarRating()) - .likes(likes) + .likeCount(likeCount) .isLiked(isLiked) .semester(reviewEntity.getSemester()) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index 17b5dbc9..624421ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -18,17 +18,17 @@ public class ReviewEntity extends BaseTimeEntity { private ObjectId userId; private String content; private double starRating; - private int likes; + private int likeCount; private String semester; @Builder - public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String content, double starRating, int likes, String semester) { + public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String content, double starRating, int likeCount, String semester) { this._id = _id; this.lectureId = lectureId; this.userId = userId; this.content = content; this.starRating = starRating; - this.likes = likes; + this.likeCount = likeCount; this.semester = semester; } @@ -39,7 +39,11 @@ public static ReviewEntity of(CreateReviewRequestDto createReviewRequestDto, Obj .lectureId(lectureId) .userId(userId) .semester(createReviewRequestDto.getSemester()) - .likes(0) + .likeCount(0) .build(); } + + public void updateLikeCount(int likeCount) { + this.likeCount = likeCount; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java index 79799217..e4fe6def 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.lecture.domain.review.repository; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -14,4 +15,6 @@ public interface ReviewRepository extends MongoRepository findAllByLectureIdAndDeletedAtIsNull(ObjectId lectureId, PageRequest pageRequest); Optional findByLectureIdAndUserIdAndDeletedAtIsNull(ObjectId lectureId, ObjectId userId); + + Optional findByLectureIdAndDeletedAtIsNull(ObjectId Id); } From 140d3f3f8adb5118e773a53540197b6b77b6ebc7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Mar 2025 16:35:17 +0900 Subject: [PATCH 0554/1002] =?UTF-8?q?fix=20::=20@Scheduled=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20review=20Entity=EC=97=90=20Count=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20Scrap=20db=20=EB=AF=B8=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/scheduler/SyncScheduler.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index c253e518..bb4e24a5 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -1,6 +1,8 @@ package inu.codin.codin.infra.redis.scheduler; import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -19,6 +21,7 @@ import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisHitsService; import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisScrapService; import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,6 +43,8 @@ public class SyncScheduler { private final PostRepository postRepository; private final CommentRepository commentRepository; private final ReplyCommentRepository replyCommentRepository; + private final ReviewRepository reviewRepository; + private final LikeRepository likeRepository; private final ScrapRepository scrapRepository; private final HitsRepository hitsRepository; @@ -48,6 +53,7 @@ public class SyncScheduler { private final RedisService redisService; private final RedisLikeService redisLikeService; private final RedisHitsService redisHitsService; + private final RedisScrapService redisScrapService; private final RedisHealthChecker redisHealthChecker; @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 @@ -60,6 +66,7 @@ public void syncLikes() { syncEntityLikes("POST", postRepository); syncEntityLikes("COMMENT", commentRepository); syncEntityLikes("REPLY", replyCommentRepository); + syncEntityLikes("REVIEW", reviewRepository); syncPostScraps(); synPostHits(); log.info(" 동기화 작업 완료"); @@ -75,13 +82,13 @@ private void syncEntityLikes(String entityType, MongoRepository for (String redisKey : redisKeys) { String likeTypeId = redisKey.replace(entityType + ":likes:", ""); Set likedUsers = redisLikeService.getLikedUsers(entityType, likeTypeId); - ObjectId likeId = new ObjectId(likeTypeId); + ObjectId entityId = new ObjectId(likeTypeId); // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeId); + List dbLikes = likeRepository.findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, entityId); for (LikeEntity dbLike : dbLikes) { if (!likedUsers.contains(dbLike.getUserId().toString())) { - log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), likeId); + log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), entityId); likeRepository.delete(dbLike); } } @@ -89,11 +96,11 @@ private void syncEntityLikes(String entityType, MongoRepository // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 for (String id : likedUsers) { ObjectId userId = new ObjectId(id); - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeId, userId)) { - log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, likeId); + if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, entityId, userId)) { + log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, entityId); LikeEntity dbLike = LikeEntity.builder() .likeType(likeType) - .likeTypeId(likeId) + .likeTypeId(entityId) .userId(userId) .build(); likeRepository.save(dbLike); @@ -103,30 +110,38 @@ private void syncEntityLikes(String entityType, MongoRepository // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 int likeCount = likedUsers.size(); if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findByIdAndNotDeleted(likeId).orElse(null); + PostEntity post = postRepo.findByIdAndNotDeleted(entityId).orElse(null); if (post != null && post.getLikeCount() != likeCount) { log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); post.updateLikeCount(likeCount); postRepo.save(post); } } else if (repository instanceof CommentRepository commentRepo) { - CommentEntity comment = commentRepo.findByIdAndNotDeleted(likeId).orElse(null); + CommentEntity comment = commentRepo.findByIdAndNotDeleted(entityId).orElse(null); if (comment != null && comment.getLikeCount() != likeCount) { log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); comment.updateLikeCount(likeCount); commentRepo.save(comment); } } else if (repository instanceof ReplyCommentRepository replyRepo) { - ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(likeId).orElse(null); + ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(entityId).orElse(null); if (reply != null && reply.getLikeCount() != likeCount) { log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); reply.updateLikeCount(likeCount); replyRepo.save(reply); } + } else if (repository instanceof ReviewRepository reviewRepo) { + ReviewEntity review = reviewRepo.findByLectureIdAndDeletedAtIsNull(entityId).orElse(null); + if (review != null && review.getLikeCount() != likeCount) { + log.info("ReviewEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); + review.updateLikeCount(likeCount); + reviewRepo.save(review); + } } } } + @Scheduled(fixedRate = 43200000) public void syncPostScraps() { Set redisKeys = redisService.getKeys("post:scraps:*"); @@ -136,8 +151,8 @@ public void syncPostScraps() { for (String redisKey : redisKeys) { String postId = redisKey.replace("post:scraps:", ""); - Set redisScrappedUsers = redisLikeService.getLikedUsers("post", postId); ObjectId id = new ObjectId(postId); + Set redisScrappedUsers = redisScrapService.getScrapedUsers(id); // MongoDB의 스크랩 데이터 가져오기 List dbScraps = scrapRepository.findByPostIdAndDeletedAtIsNull(id); @@ -178,6 +193,7 @@ public void syncPostScraps() { } } + @Scheduled(fixedRate = 43200000) public void synPostHits(){ Map> postHits = fetchAllPostHits(); //하나의 게시글에 조회한 user들 From 873eb587bf939cba6eeea3c6f09528ebcd74cb57 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 5 Mar 2025 16:35:35 +0900 Subject: [PATCH 0555/1002] =?UTF-8?q?refactor=20::=20review=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=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 --- .../java/inu/codin/codin/domain/like/service/LikeService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index bf44ce60..089b9f48 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.exception.LikeCreateFailException; @@ -26,6 +27,7 @@ public class LikeService { private final PostRepository postRepository; private final CommentRepository commentRepository; private final ReplyCommentRepository replyCommentRepository; + private final ReviewRepository reviewRepository; private final RedisLikeService redisLikeService; private final RedisService redisService; @@ -133,6 +135,8 @@ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); case COMMENT -> commentRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + case REVIEW -> reviewRepository.findByLectureIdAndDeletedAtIsNull(id) + .orElseThrow(() ->new NotFoundException("수강 후기를 찾을 수 없습니다")); } } From 6f3e1218b39bfc1b19190b9a935bfce99a669719 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 00:45:58 +0900 Subject: [PATCH 0556/1002] =?UTF-8?q?refactor=20:=20=EC=9E=A0=EC=8B=9C=20i?= =?UTF-8?q?nu.ac.kr=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=EB=A7=8C=20=EC=A0=91=EC=86=8D=20=ED=97=88=EC=9A=A9=20=ED=95=B4?= =?UTF-8?q?=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CustomOAuth2UserService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java index 14c94672..4aaeba51 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java @@ -27,17 +27,17 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String email = oAuth2User.getAttribute("email"); log.info("email: {}", email); - if (email.equals("inu.codin@gmail.com")) return oAuth2User; +// if (email.equals("inu.codin@gmail.com")) return oAuth2User; // Only Allow @inu.ac.kr - if (email == null || !email.trim().endsWith("@inu.ac.kr")) { - OAuth2Error oauth2Error = new OAuth2Error( - "invalid_email_domain", - "허용되지 않은 이메일 도메인입니다. @inu.ac.kr 이어야합니다", - null - ); - throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); - } +// if (email == null || !email.trim().endsWith("@inu.ac.kr")) { +// OAuth2Error oauth2Error = new OAuth2Error( +// "invalid_email_domain", +// "허용되지 않은 이메일 도메인입니다. @inu.ac.kr 이어야합니다", +// null +// ); +// throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); +// } return oAuth2User; } From c844218faab61fb9576d6f3b2c5937b8dc858379 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:19:04 +0900 Subject: [PATCH 0557/1002] =?UTF-8?q?perf=20:=20AuthResultStatus.java=20?= =?UTF-8?q?=EC=97=90=20SUSPEND=5FUSER(=EC=A0=95=EC=A7=80=EB=90=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80)=20=EC=86=8D=EC=84=B1=20=EB=B0=8F=20redirect?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/enums/AuthResultStatus.java | 3 +- .../common/security/service/AuthService.java | 105 +++++++++++------- .../util/OAuth2LoginSuccessHandler.java | 30 ++--- 3 files changed, 84 insertions(+), 54 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java b/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java index 8ac4492d..ace7fd8a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/enums/AuthResultStatus.java @@ -3,5 +3,6 @@ public enum AuthResultStatus { LOGIN_SUCCESS, // 기존 회원이며, 프로필이 완료되어 정상 로그인 NEW_USER_REGISTERED, // 신규 회원 등록 완료 (프로필 설정 미완료) - PROFILE_INCOMPLETE // 기존 회원이지만 프로필 설정이 미완료됨 + PROFILE_INCOMPLETE, // 기존 회원이지만 프로필 설정이 미완료됨 + SUSPENDED_USER //정지된 유저 } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index a24bc597..3fc4dda4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -23,6 +23,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -39,50 +40,51 @@ public class AuthService { /** * Google OAuth2를 통해 사용자 정보를 등록하고 로그인 처리하는 메소드 - * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) * - 기존 회원이면, userStatus가 ACTIVE인 경우에만 JWT 토큰을 발급하여 정식 로그인 처리 - * 만약 userStatus가 DISABLED이면 프로필 설정이 필요하다는 상태로 처리 + * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) + * userStatus가 DISABLED이면 프로필 설정이 필요하다는 상태로 처리 + * - userStatus가 SUSPEND이면 정지된 회원 */ public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { - // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) - Map attributes = oAuth2User.getAttributes(); - - // 기본 속성 추출 - String email = (String) attributes.get("email"); - String sub = (String) attributes.get("sub"); // 고유 식별자 (필요시 저장) - String name = (String) attributes.get("family_name"); // 예: "김기수" - String department = (String) attributes.get("given_name"); // 예: "/컴퓨터공학부" - - log.info("OAuth2 login: email={}, sub={}, name={}, department={}", - email, sub, name, department); + InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); // 회원 존재 여부 판단: email 기준 - Optional optionalUser = userRepository.findByEmailAndDisabledAndActive(email); + Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); if (optionalUser.isPresent()) { UserEntity existingUser = optionalUser.get(); - log.info("기존 회원 로그인: {}", email); - if (existingUser.getStatus() != null && existingUser.getStatus().equals(UserStatus.ACTIVE)) { - - // 프로필 설정 완료된 회원: 정상 로그인 처리 - issueJwtToken(email, response); - - log.info("정상 로그인 완료: {}", email); - return AuthResultStatus.LOGIN_SUCCESS; - } else { - // 프로필 설정 미완료: JWT 토큰 미발급, 프론트엔드에서 프로필 설정 페이지로 유도 - log.info("회원 프로필 설정 미완료 (userStatus={}) : {}", existingUser.getStatus(), email); - return AuthResultStatus.PROFILE_INCOMPLETE; + log.info("기존 회원 로그인: {}", info.email()); + + switch(existingUser.getStatus()){ + case ACTIVE -> { + // 프로필 설정 완료된 회원: 정상 로그인 처리 + issueJwtToken(info.email(), response); + log.info("정상 로그인 완료: {}", info.email()); + return AuthResultStatus.LOGIN_SUCCESS; + } + case DISABLED -> { + // 프로필 설정 미완료: JWT 토큰 미발급, 프론트엔드에서 프로필 설정 페이지로 유도 + log.info("회원 프로필 설정 미완료 (userStatus={}) : {}", existingUser.getStatus(), info.email()); + return AuthResultStatus.PROFILE_INCOMPLETE; + } case SUSPENDED -> { + //정지된 유저 + log.info("정지된 유저 : email - {} ", info.email()); + return AuthResultStatus.SUSPENDED_USER; + } default -> { + log.error("유저의 상태가 ACTIVE, DISABLED, SUSPENDED 외의 값을 가지고 있습니다. UserStatus : {}", existingUser.getStatus() ); + throw new NotFoundException("유저의 상태(Status)를 알 수 없습니다. _id : "+ existingUser.get_id().toString() + ", status : " + existingUser.getStatus()); + } } + } else { - log.info("신규 회원 등록: {}", email); - String deptDesc = (department != null) ? department.replace("/", "").trim() : ""; + log.info("신규 회원 등록: {}", info.email()); + String deptDesc = (info.department() != null) ? info.department().replace("/", "").trim() : ""; Department dept = Department.fromDescription(deptDesc); // 신규 회원 등록 시, userStatus를 DISABLED(비활성)으로 설정하여 프로필 설정 미완료 상태로 처리 UserEntity newUser = UserEntity.builder() - .email(email) - .name(name) + .email(info.email()) + .name(info.name()) .department(dept) .profileImageUrl(s3Service.getDefaultProfileImageUrl()) // 기본 프로필 이미지 사용 .status(UserStatus.DISABLED) @@ -95,6 +97,25 @@ public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse re } } + private static InfoFromOAuth2User getInfoFromOAuth2User(OAuth2User oAuth2User) { + // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) + Map attributes = oAuth2User.getAttributes(); + + // 기본 속성 추출 + String email = (String) attributes.get("email"); + String sub = (String) attributes.get("sub"); // 고유 식별자 (필요시 저장) + String name = (String) attributes.get("family_name"); // 예: "김기수" + String department = (String) attributes.get("given_name"); // 예: "/컴퓨터공학부" + + log.info("OAuth2 login: email={}, sub={}, name={}, department={}", + email, sub, name, department); + InfoFromOAuth2User info = new InfoFromOAuth2User(email, name, department); + return info; + } + + private record InfoFromOAuth2User(String email, String name, String department) { + } + /** * 최초 로그인 후, 사용자에게 닉네임과 프로필 이미지를 설정받아 DB를 업데이트하는 메소드. * 프로필 설정 완료 후 userStatus를 ACTIVE로 업데이트하여 정식 회원가입을 완료. @@ -114,18 +135,15 @@ public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, Mul UserEntity user = userOpt.get(); log.info("[completeUserProfile] 사용자 조회 성공: {}", userProfileRequestDto.getEmail()); - // 프로필 이미지 업로드 처리 + // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 String imageUrl = null; - if (userImage != null && !userImage.isEmpty()) { - log.info("[프로필 설정] 프로필 이미지 업로드 중..."); + if (userImage == null && userImage.isEmpty()) { + imageUrl = s3Service.getDefaultProfileImageUrl(); + } else { imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); } - // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 - if (imageUrl == null) { - imageUrl = s3Service.getDefaultProfileImageUrl(); - } - + // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 user.updateNickname(userProfileRequestDto.getNickname()); user.updateProfileImageUrl(imageUrl); @@ -133,7 +151,6 @@ public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, Mul userRepository.save(user); log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); - // 프로필 설정 완료 후 정식 회원으로 간주하여 JWT 토큰 재발급 issueJwtToken(user.getEmail(), response); } @@ -154,4 +171,14 @@ public void issueJwtToken(String email, HttpServletResponse response) { SecurityContextHolder.getContext().setAuthentication(authenticationToken); jwtService.createToken(response); } + + public LocalDateTime getSuspensionEndDate(OAuth2User oAuth2User){ + InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); + + Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); + if (optionalUser.isPresent()){ + UserEntity user = optionalUser.get(); + return user.getTotalSuspensionEndDate(); + } else throw new NotFoundException("유저를 찾을 수 없습니다. _id: " + optionalUser.get().get_id().toString()); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 49c1f0b0..a6329db0 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -34,27 +34,29 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - log.info(BASEURL); // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { - case LOGIN_SUCCESS: - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/main"); + case LOGIN_SUCCESS -> { + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/main"); log.info("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); - break; - case NEW_USER_REGISTERED: - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile?email="+oAuth2User.getAttribute("email")); + } + case NEW_USER_REGISTERED -> { + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + oAuth2User.getAttribute("email")); log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); - - break; - case PROFILE_INCOMPLETE: - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/auth/profile?email="+oAuth2User.getAttribute("email")); + } + case PROFILE_INCOMPLETE -> { + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + oAuth2User.getAttribute("email")); log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); - break; - default: - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); + } + case SUSPENDED_USER -> { + getRedirectStrategy().sendRedirect(request, response, "http://localhost:8080/suspends?endDate=" + authService.getSuspensionEndDate(oAuth2User)); + log.info("{\"code\":200, \"message\":\"정지된 회원에 대하여 정지 화면 호출\"}"); + } + default -> { + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login"); log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생\"}"); - break; + } } writer.flush(); From c8f7ea2f756de4da7619d05a4db8c37333e43da7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:20:14 +0900 Subject: [PATCH 0558/1002] =?UTF-8?q?fix=20:=20=EB=AA=A8=EB=93=A0=20Status?= =?UTF-8?q?=EC=9D=98=20=EC=9C=A0=EC=A0=80=EB=A5=BC=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20Query=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/stomp/StompMessageService.java | 2 +- .../codin/codin/domain/user/repository/UserRepository.java | 7 ++----- .../domain/user/security/CustomUserDetailsService.java | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index b36105a0..842fe774 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -68,7 +68,7 @@ private Result getResult(StompHeaderAccessor headerAccessor) { } ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); - UserEntity user = userRepository.findByEmailAndDisabledAndActive(email) + UserEntity user = userRepository.findByEmailAndStatusAll(email) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); Result result = new Result(chatroom, user); return result; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index f0b6b1e8..bf7957c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -16,14 +16,11 @@ public interface UserRepository extends MongoRepository { @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByEmail(String email); - @Query("{'studentId': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") - Optional findByStudentId(String studentId); - Optional findByNicknameAndDeletedAtIsNull(String nickname); @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") Optional findByEmailAndDisabled(String email); - @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED', 'ACTIVE'] }}") - Optional findByEmailAndDisabledAndActive(String email); + @Query("{'email': ?0, 'deletedAt': null }") + Optional findByEmailAndStatusAll(String email); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java index e4a5d995..bfd30493 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetailsService.java @@ -19,11 +19,11 @@ public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - UserEntity user = userRepository.findByEmail(email) + UserEntity user = userRepository.findByEmailAndStatusAll(email) .orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음, email :" + email)); if (!UserStatus.ACTIVE.equals(user.getStatus())) { - throw new UserDisabledException("유저가 활성화되지 않았습니다"); + throw new UserDisabledException("유저가 활성화되지 않았습니다, status : "+ user.getStatus()); } return CustomUserDetails.from(user); From ca3dd06d939ce1f04519c0574255fe9845bb89e6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:21:24 +0900 Subject: [PATCH 0559/1002] =?UTF-8?q?perf=20:=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=EA=B8=B0=EA=B0=84=EC=9D=84=20=EA=B3=84=EC=86=8D=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EC=A4=91=EC=B2=A9=EC=8B=9C=ED=82=A4=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20totalSuspensionEndDate=20=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=98=81=EA=B5=AC?= =?UTF-8?q?=20=EC=A0=95=EC=A7=80=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=B5=9C?= =?UTF-8?q?=EB=8C=80=EC=9D=98=20=EA=B8=B0=EA=B0=84=EC=9D=84=20=EB=B6=80?= =?UTF-8?q?=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/service/ReportService.java | 33 ++++--------------- .../codin/domain/user/entity/UserEntity.java | 12 ++++--- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 03137fa8..080a7999 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -29,10 +29,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -101,14 +98,6 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { } } -// public ReportPageResponse getReportedPosts(int pageNumber) { -// -// PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); -// Page page; -// page = postRepository.getPostsWithReported(pageRequest); -// -// return ReportPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); -// } //post 총 신고수 증가 private void updatePostReportCount(ObjectId postId) { @@ -166,8 +155,6 @@ public List getReportsByUserId(String userId) { log.info("특정 유저 신고 내역 조회: userId={}", userId); ObjectId ObjUserId = new ObjectId(userId); - - List reports = reportRepository.findByReportingUserId(ObjUserId); log.info("DB에서 가져온 ReportEntity 리스트:"); @@ -203,32 +190,28 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { .suspensionEndDate(LocalDateTime.now().plusDays(requestDto.getSuspensionPeriod().getDays())) .build(); - // 엔티티 내부에서 업데이트 메서드 호출 - //ReportEntity updatedReport = report.updateReport(action); - // 기존 객체의 필드를 직접 수정 (새 객체 생성 X) report.updateReportSuspended(action); - //유저 Suspended - 정지 상태로 변경 Optional user = userRepository.findById(report.getReportedUserId()); if (user.isEmpty()) throw new NotFoundException("존재하지 않는 회원입니다."); + user.get().suspendUser(); //영구 정지 if (requestDto.getSuspensionPeriod() == SuspensionPeriod.PERMANENT){ - user.get().disabledUser(); + user.get().updateTotalSuspensionEndDate(LocalDateTime.of(9999, 12 ,31, 22, 59)); } else { - user.get().suspendUser(); + LocalDateTime totalSuspensionEndDate = user.get().getTotalSuspensionEndDate(); + user.get().updateTotalSuspensionEndDate( + Objects.requireNonNullElseGet(totalSuspensionEndDate, LocalDateTime::now) + .plusDays(requestDto.getSuspensionPeriod().getDays())); } - - // 업데이트된 신고 저장 reportRepository.save(report); userRepository.save(user.get()); - //userRepository.save(user); log.info("신고가 처리되었습니다. ID: {}, 처리자: {}", report.get_id(), userId); - //ReportResponseDto.from(report); } @@ -236,8 +219,6 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { public ReportSummaryResponseDTO getReportSummary(String reportTargetId) { ObjectId targetId = new ObjectId(reportTargetId); - //int totalReports = reportRepository.countByReportTargetId(targetId); - // 모든 ReportType에 대해 개수 조회 Map reportTypeCounts = new HashMap<>(); for (ReportType reportType : ReportType.values()) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 257a0506..02fd094e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -11,6 +11,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -43,6 +44,8 @@ public class UserEntity extends BaseTimeEntity { private UserStatus status; + private LocalDateTime totalSuspensionEndDate; //정지 게시물이 늘어날수록 정지 종료일이 중첩 + private List blockedUsers = new ArrayList<>(); private NotificationPreference notificationPreference = new NotificationPreference(); @@ -91,18 +94,19 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ public void suspendUser() { this.status = UserStatus.SUSPENDED; } - public void disabledUser() { - this.status = UserStatus.DISABLED; - } + public void activateUser() { if ( this.status == UserStatus.SUSPENDED) { this.status = UserStatus.ACTIVE; } } - public void activation() { if ( this.status == UserStatus.DISABLED) { this.status = UserStatus.ACTIVE; } } + + public void updateTotalSuspensionEndDate(LocalDateTime totalSuspensionEndDate){ + this.totalSuspensionEndDate = totalSuspensionEndDate; + } } From e9c78df6464297a01ce03cacefa8f785aa2b04d2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:21:43 +0900 Subject: [PATCH 0560/1002] =?UTF-8?q?docs=20:=20Unused=20import=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/util/OAuth2LoginFailureHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 1d9a5038..c6f30a32 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -1,6 +1,5 @@ package inu.codin.codin.common.security.util; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; From e4e2f981b975c80051060bbe5f706fe39b9e4ee8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:22:36 +0900 Subject: [PATCH 0561/1002] =?UTF-8?q?feat=20:=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EC=97=90=EA=B2=8C=20=EB=B3=B4=EC=97=AC?= =?UTF-8?q?=EC=A4=84=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20MVC=20=EC=B2=98=EB=A6=AC=20(=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=B4=20=EC=83=9D=EA=B8=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=84=EA=B9=8C=EC=A7=80=20=EC=9E=84=EC=8B=9C=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SuspendMvcController.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java new file mode 100644 index 00000000..09ef9243 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.report.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.ui.Model; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Controller +@RequestMapping("/suspends") +public class SuspendMvcController { + + @GetMapping + public String getSuspendedView(@RequestParam("endDate") String endDate, Model model){ + LocalDateTime dateTime = LocalDateTime.parse(endDate); + LocalDateTime adjustedDateTime = dateTime.plusHours(1); + String formattedDate = adjustedDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH시")); + model.addAttribute("endDate", formattedDate); + return "suspends"; + } +} From e64b6118aa433df8e44cff2f64ad6be96d3aa101 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:25:09 +0900 Subject: [PATCH 0562/1002] =?UTF-8?q?docs=20:=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/controller/SuspendMvcController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java index 09ef9243..544b2b71 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java @@ -19,6 +19,6 @@ public String getSuspendedView(@RequestParam("endDate") String endDate, Model mo LocalDateTime adjustedDateTime = dateTime.plusHours(1); String formattedDate = adjustedDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH시")); model.addAttribute("endDate", formattedDate); - return "suspends"; + return "suspend"; } } From ee9817f746b52760c5692fbb98b0f7e1280ff86d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:50:20 +0900 Subject: [PATCH 0563/1002] =?UTF-8?q?perf=20:=20=EA=B0=81=EA=B0=81=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=A0=95=EC=A7=80=20=EA=B8=B0=EA=B0=84?= =?UTF-8?q?=EC=99=80=20=EC=9C=A0=EC=A0=80=EC=9D=98=20=EC=A4=91=EC=B2=A9?= =?UTF-8?q?=EB=90=9C=20=EC=A0=95=EC=A7=80=20=EA=B8=B0=EA=B0=84=EC=9D=84=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/repository/ReportRepository.java | 5 +--- .../report/scheduler/SuspensionScheduler.java | 5 ++-- .../report/service/SuspensionService.java | 25 ++++++++++--------- .../user/repository/UserRepository.java | 8 ++++++ 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java index 959533cf..aabd31ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -1,11 +1,8 @@ package inu.codin.codin.domain.report.repository; -import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.entity.ReportType; -import lombok.extern.slf4j.Slf4j; -import org.bson.Document; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.MongoRepository; @@ -34,7 +31,7 @@ public interface ReportRepository extends MongoRepository findSuspendedUsers(LocalDateTime now); + List findSuspendedReports(LocalDateTime now); // 특정 게시물의 전체 신고 개수 int countByReportTargetId(ObjectId reportTargetId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java index cee1de67..7c5f5400 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/scheduler/SuspensionScheduler.java @@ -14,9 +14,10 @@ public class SuspensionScheduler { private final SuspensionService suspensionService; @Scheduled(cron = "0 0 * * * ?") // 매 정시마다 실행 - //@Scheduled(cron = "0 * * * * ?") // 매 1분마다 실행(테스트) +// @Scheduled(cron = "0 * * * * ?") // 매 1분마다 실행(테스트) public void checkAndReleaseSuspendedUsers() { log.info("정지 해제 스케줄러 실행..."); - suspensionService.releaseSuspendedUsers(); + suspensionService.releaseSuspendedReports(); //정지 중이던 게시글 해제 + suspensionService.releaseSuspendedUsers(); //정지 중이던 유저 해제 } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java index 7461643b..dd3f9b20 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/SuspensionService.java @@ -10,7 +10,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; @Service @RequiredArgsConstructor @@ -20,23 +19,25 @@ public class SuspensionService { private final ReportRepository reportRepository; private final UserRepository userRepository; - public void releaseSuspendedUsers() { + public void releaseSuspendedReports() { LocalDateTime now = LocalDateTime.now(); - List suspendedUsers = reportRepository.findSuspendedUsers(now); + List suspendedUsers = reportRepository.findSuspendedReports(now); for (ReportEntity report : suspendedUsers) { - Optional userOpt = userRepository.findById(report.getReportedUserId()); - if (userOpt.isEmpty()) continue; - - UserEntity user = userOpt.get(); - user.activateUser(); // 정지 해제 report.updateReportResolved(); - - userRepository.save(user); // DB 반영 reportRepository.save(report); + log.info("신고 {} 정지 중 -> 처리 완료", report.get_id()); + } + } - - log.info("유저 {} 정지 해제 완료", user.get_id()); + public void releaseSuspendedUsers() { + LocalDateTime now = LocalDateTime.now(); + List userEntities = userRepository.findSuspendedUsers(now); + for (UserEntity user : userEntities) { + user.activateUser(); // 정지 해제 + user.updateTotalSuspensionEndDate(null); + userRepository.save(user); // DB 반영 + log.info("유저 {} 정지 해제", user.get_id()); } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index bf7957c6..b0e9f395 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -1,11 +1,14 @@ package inu.codin.codin.domain.user.repository; +import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.user.entity.UserEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; @Repository @@ -23,4 +26,9 @@ public interface UserRepository extends MongoRepository { @Query("{'email': ?0, 'deletedAt': null }") Optional findByEmailAndStatusAll(String email); + + // 현재 정지 상태이며, 정지 종료일이 아직 남아있는 유저 조회 + //정지 종료일(suspensionEndDate)이 현재 날짜보다 이전($lt) + @Query("{'status': 'SUSPENDED', 'totalSuspensionEndDate': { $lt: ?0 }}") + List findSuspendedUsers(LocalDateTime now); } From d7e58548a46e7c5419f556bb5e1fd8ceac5f2679 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:51:05 +0900 Subject: [PATCH 0564/1002] =?UTF-8?q?refactor=20:=20=EB=A7=A4=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=95=EC=8B=9C=EB=A7=88=EB=8B=A4=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=EB=9F=AC=EA=B0=80=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EA=B8=B0=EC=97=90=20=EC=A0=95=EC=A7=80=20=EA=B8=B0=EA=B0=84?= =?UTF-8?q?=EB=B3=B4=EB=8B=A4=20=ED=95=98=EB=A3=A8=EB=A5=BC=20=EB=8D=94=20?= =?UTF-8?q?=EB=8D=94=ED=95=98=EC=97=AC=20=EB=85=84,=EC=9B=94,=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=EB=A7=8C=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/controller/SuspendMvcController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java index 544b2b71..a6e5266d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java @@ -16,8 +16,8 @@ public class SuspendMvcController { @GetMapping public String getSuspendedView(@RequestParam("endDate") String endDate, Model model){ LocalDateTime dateTime = LocalDateTime.parse(endDate); - LocalDateTime adjustedDateTime = dateTime.plusHours(1); - String formattedDate = adjustedDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH시")); + LocalDateTime adjustedDateTime = dateTime.plusDays(1); + String formattedDate = adjustedDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); model.addAttribute("endDate", formattedDate); return "suspend"; } From 399623c2c9d6cd1f93e7df47f7fafea03dd8de85 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 02:56:08 +0900 Subject: [PATCH 0565/1002] =?UTF-8?q?refactor=20:=20=EC=98=81=EA=B5=AC=20?= =?UTF-8?q?=EC=A0=95=EC=A7=80=20=EC=9C=A0=EC=A0=80=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=209999=EB=85=84=2012=EC=9B=94=2030=EC=9D=BC?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20(=ED=99=94=EB=A9=B4=EC=97=90=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EC=8B=9C=201=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EB=8D=94=ED=95=98=EA=B8=B0=20=EB=95=8C=EB=AC=B8=EC=97=90=2030?= =?UTF-8?q?=EC=9D=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/report/service/ReportService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 080a7999..b36194cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -200,7 +200,7 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { user.get().suspendUser(); //영구 정지 if (requestDto.getSuspensionPeriod() == SuspensionPeriod.PERMANENT){ - user.get().updateTotalSuspensionEndDate(LocalDateTime.of(9999, 12 ,31, 22, 59)); + user.get().updateTotalSuspensionEndDate(LocalDateTime.of(9999, 12 ,30, 23, 59)); } else { LocalDateTime totalSuspensionEndDate = user.get().getTotalSuspensionEndDate(); user.get().updateTotalSuspensionEndDate( From 5d5785b7a0dd469c8a8359696c96028d0be61534 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 03:03:04 +0900 Subject: [PATCH 0566/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index f329cfb6..18a09628 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit f329cfb67c9f62f8c89325b390cca995549e467a +Subproject commit 18a0962889c01c40886b5cc4a7498228d4c16e91 From 4ffe659e83c5bc68e86250f4ac0a050f5fa0e72d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 03:08:53 +0900 Subject: [PATCH 0567/1002] =?UTF-8?q?perf=20:=20=EC=98=81=EA=B5=AC=20?= =?UTF-8?q?=EC=A0=95=EC=A7=80=20=EC=B2=98=EB=A6=AC=EC=8B=9C,=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=20=EC=A0=95=EC=A7=80=20=EC=A2=85=EB=A3=8C=20=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=EC=9D=84=20=EC=B5=9C=EB=8C=80=EA=B0=92=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/service/ReportService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index b36194cb..0b8f7542 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -184,7 +184,15 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { } // 신고 처리 정보 생성 - ReportEntity.ReportActionEntity action = ReportEntity.ReportActionEntity.builder() + ReportEntity.ReportActionEntity action = null; + if (requestDto.getSuspensionPeriod().equals(SuspensionPeriod.PERMANENT)){ + action = ReportEntity.ReportActionEntity.builder() + .actionTakenById(userId) + .suspensionPeriod(requestDto.getSuspensionPeriod()) + .suspensionEndDate(LocalDateTime.of(9999,12,31,23,59)) + .build(); + } + else action = ReportEntity.ReportActionEntity.builder() .actionTakenById(userId) .suspensionPeriod(requestDto.getSuspensionPeriod()) .suspensionEndDate(LocalDateTime.now().plusDays(requestDto.getSuspensionPeriod().getDays())) From 75a5405d80c209fb1b41ecd585666708f4ce6716 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 03:23:00 +0900 Subject: [PATCH 0568/1002] =?UTF-8?q?refactor=20:=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20/api=EB=A5=BC=20=EB=B6=99=EC=97=AC=20redir?= =?UTF-8?q?ect=20url=20=EC=82=AC=EC=9A=A9=20(=EC=9D=B4=ED=9B=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=98=88=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/util/OAuth2LoginSuccessHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index a6329db0..c013d997 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -50,7 +50,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); } case SUSPENDED_USER -> { - getRedirectStrategy().sendRedirect(request, response, "http://localhost:8080/suspends?endDate=" + authService.getSuspensionEndDate(oAuth2User)); + //todo MVC 호출을 위해 api가 붙음, 이후 삭제 예정 + getRedirectStrategy().sendRedirect(request, response, BASEURL+ "/api/suspends?endDate=" + authService.getSuspensionEndDate(oAuth2User)); log.info("{\"code\":200, \"message\":\"정지된 회원에 대하여 정지 화면 호출\"}"); } default -> { From 040a099ee041037100794a7f08046bb4ed1a22ba Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 14:55:06 +0900 Subject: [PATCH 0569/1002] =?UTF-8?q?chore=20:=20=EC=A0=95=EC=A7=80=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B0=98=ED=99=98=EC=9D=84=20=EB=AA=A8?= =?UTF-8?q?=EB=91=90=EC=97=90=EA=B2=8C=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 18a09628..2d9063ad 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 18a0962889c01c40886b5cc4a7498228d4c16e91 +Subproject commit 2d9063adc39bc818db78535fe3938a65bb1e519d From 8c7d8b5e35f61192fc8325aa30dbbdb385f7d5ae Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 14:55:53 +0900 Subject: [PATCH 0570/1002] =?UTF-8?q?docs=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EB=A5=BC=20stompMessageService=20=EB=8B=A8=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=98=AE=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/stomp/StompMessageProcessor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 84b989f3..1b462ebf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -35,15 +35,12 @@ public void handleMessage(StompHeaderAccessor headerAccessor){ stompMessageService.connectSession(headerAccessor); } case SUBSCRIBE -> { - log.info("[STOMP] Subscribe" ); stompMessageService.enterToChatRoom(headerAccessor); } case UNSUBSCRIBE -> { - log.info("[STOMP] UnSubscribe" ); stompMessageService.exitToChatRoom(headerAccessor); } case DISCONNECT -> { - log.info("[STOMP] DISCONNECT"); stompMessageService.exitToChatRoom(headerAccessor); stompMessageService.disconnectSession(headerAccessor); } From 3fbd48b6a54c576d29e90f2fde0d567ecfd4e620 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 14:56:32 +0900 Subject: [PATCH 0571/1002] =?UTF-8?q?fix=20:=20connect=20=EC=8B=9C?= =?UTF-8?q?=EC=97=90=20sessionId=EB=A7=8C=20=EC=A0=80=EC=9E=A5=20=ED=9B=84?= =?UTF-8?q?=20scribe,=20unscribe=20=EC=8B=9C=EC=97=90=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=5Fid=EB=A5=BC=20=EC=A0=80=EC=9E=A5/=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageService.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index 842fe774..04b788b4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.context.ApplicationEventPublisher; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; @@ -21,6 +22,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class StompMessageService { private final Map sessionStore = new ConcurrentHashMap<>(); @@ -32,18 +34,15 @@ public class StompMessageService { public void connectSession(StompHeaderAccessor headerAccessor) { String sessionId = headerAccessor.getSessionId(); - String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); - sessionStore.put(sessionId, chatroomId); - } - - public void exitToChatRoom(StompHeaderAccessor headerAccessor) { - Result result = getResult(headerAccessor); - result.chatroom().getParticipants().exit(result.user().get_id()); - chatRoomRepository.save(result.chatroom()); + sessionStore.put(sessionId, null); + log.info("[STOMP CONNECT] session 연결 : {}", sessionId); } public void enterToChatRoom(StompHeaderAccessor headerAccessor){ Result result = getResult(headerAccessor); + sessionStore.put(headerAccessor.getSessionId(), result.chatroom().get_id().toString()); + log.info("[STOMP SUBSCRIBE] session : {}, chatRoomId : {} ", headerAccessor.getSessionId(), result.chatroom().get_id().toString()); + List chattings = updateUnreadCount(result.chatroom.get_id(), result.user.get_id()); result.chatroom.getParticipants().enter(result.user.get_id()); chatRoomRepository.save(result.chatroom); @@ -51,8 +50,18 @@ public void enterToChatRoom(StompHeaderAccessor headerAccessor){ eventPublisher.publishEvent(new UpdateUnreadCountEvent(this, chattings, result.chatroom.get_id().toString())); } + public void exitToChatRoom(StompHeaderAccessor headerAccessor) { + Result result = getResult(headerAccessor); + result.chatroom().getParticipants().exit(result.user().get_id()); + chatRoomRepository.save(result.chatroom()); + sessionStore.remove(headerAccessor.getSessionId()); + log.info("[STOMP UNSUBSCRIBE] session : {}, chatRoomId : {} ", headerAccessor.getSessionId(), result.chatroom().get_id().toString()); + } + public void disconnectSession(StompHeaderAccessor headerAccessor){ sessionStore.remove(headerAccessor.getSessionId()); + log.info("[STOMP DISCONNECT] session : {} ", headerAccessor.getSessionId()); + } private Result getResult(StompHeaderAccessor headerAccessor) { From ccaac95af2c9f9a1d6ee745a587e56582ebcbdd1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 15:35:37 +0900 Subject: [PATCH 0572/1002] =?UTF-8?q?fix=20:=20null=EC=9D=B4=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=EB=B9=88=20String=EC=9C=BC=EB=A1=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/stomp/StompMessageService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index 04b788b4..cdc7c714 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -34,7 +34,7 @@ public class StompMessageService { public void connectSession(StompHeaderAccessor headerAccessor) { String sessionId = headerAccessor.getSessionId(); - sessionStore.put(sessionId, null); + sessionStore.put(sessionId, ""); log.info("[STOMP CONNECT] session 연결 : {}", sessionId); } @@ -71,7 +71,8 @@ private Result getResult(StompHeaderAccessor headerAccessor) { } else { throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); } - String chatroomId = sessionStore.get(headerAccessor.getSessionId()); + log.info(headerAccessor.toString()); + String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); } From 9fb8694267481416b3fabb073edf9ae953ad1657 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 22:21:27 +0900 Subject: [PATCH 0573/1002] =?UTF-8?q?chore=20:=20yml=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=20path=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 2d9063ad..fe38d739 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 2d9063adc39bc818db78535fe3938a65bb1e519d +Subproject commit fe38d739dad36202d91097afc13982310a136ab8 From 95f4ed1ffb9fbf3c10ad510df4187d100f0b9bbf Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 6 Mar 2025 22:27:23 +0900 Subject: [PATCH 0574/1002] =?UTF-8?q?fix=20:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20userImage=EC=9D=B4=20null=EC=9D=B8=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/service/AuthService.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 3fc4dda4..009a12da 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -137,13 +137,17 @@ public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, Mul // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 String imageUrl = null; - if (userImage == null && userImage.isEmpty()) { - imageUrl = s3Service.getDefaultProfileImageUrl(); - } else { + if (userImage != null && !userImage.isEmpty()) { + log.info("[프로필 설정] 프로필 이미지 업로드 중..."); imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); } - + // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 + if (imageUrl == null) { + imageUrl = s3Service.getDefaultProfileImageUrl(); + } + + // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 user.updateNickname(userProfileRequestDto.getNickname()); user.updateProfileImageUrl(imageUrl); From 99d7a2f0e2d3c87bcba0d685160cf21f4323be36 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 01:23:15 +0900 Subject: [PATCH 0575/1002] =?UTF-8?q?feat=20:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EB=B0=8F=20=EA=B0=95=EC=9D=98=EC=8B=A4=20?= =?UTF-8?q?=ED=98=84=ED=99=A9=EC=9D=84=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20python=20=ED=8C=8C=EC=9D=BC=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LectureUploadController.java | 52 ++++++++++++ .../exception/LecturePythonException.java | 7 ++ .../lecture/service/LectureUploadService.java | 83 +++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/LecturePythonException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java new file mode 100644 index 00000000..6bc0515d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java @@ -0,0 +1,52 @@ +package inu.codin.codin.domain.lecture.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.lecture.service.LectureUploadService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/upload") +@RequiredArgsConstructor +public class LectureUploadController { + + private final LectureUploadService lectureUploadService; + + @Operation( + summary = "새 학기의 강의 내역 업로드", + description = "강의 내역서(엑셀 파일) 이름을 '년도-학기'로 설정하여 업로드 ex) 24-1.xlsx, 24-2.xlsx" + ) + @PostMapping(value = "/lectures", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + public ResponseEntity> uploadNewSemesterLectures(@RequestParam("excelFile") MultipartFile file) { + lectureUploadService.uploadNewSemesterLectures(file); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, file.getOriginalFilename()+"의 강의 내역 업로드", null)); + + } + + @Operation( + summary = "강의실 현황 업데이트 (!! 꼭 '/lectures/upload_new' 이후에 실행할 것 !!)", + description = "!! 꼭 '/lectures/upload_new' 이후에 실행할 것 !!" + + "
강의 내역서(엑셀 파일) 이름을 '년도-학기'로 설정하여 업로드 ex) 24-1.xlsx, 24-2.xlsx" + ) + @PostMapping(value = "/rooms", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + public ResponseEntity uploadNewSemesterRooms(@RequestParam("excelFile") MultipartFile file) { + lectureUploadService.uploadNewSemesterRooms(file); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, file.getName()+"의 강의실 현황 업데이트", null)); + + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/LecturePythonException.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/LecturePythonException.java new file mode 100644 index 00000000..68ababdd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/exception/LecturePythonException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.lecture.exception; + +public class LecturePythonException extends RuntimeException{ + public LecturePythonException(String msg){ + super(msg); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java new file mode 100644 index 00000000..7e0d3487 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java @@ -0,0 +1,83 @@ +package inu.codin.codin.domain.lecture.service; + +import inu.codin.codin.domain.lecture.exception.LecturePythonException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +@Service +@Slf4j +public class LectureUploadService { + + @Value("${lecture.file.path}") + private String UPLOAD_DIR; + + @Value("${lecture.python.path}") + private String PYTHON_DIR; + + public void uploadNewSemesterRooms(MultipartFile file) { + try { + String pythonNm = "dayTimeOfRoom.py"; + ProcessBuilder processBuilder = new ProcessBuilder( + PYTHON_DIR, UPLOAD_DIR + pythonNm, UPLOAD_DIR+file.getOriginalFilename() + ); + log.info(processBuilder.command().toString()); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + String result = new String(process.getInputStream().readAllBytes()); + log.info(result); + log.warn("[uploadNewSemesterRooms] Exited dayTimeOfRoom.py with error code" + exitCode); + if (exitCode == 0) + log.info("[uploadNewSemesterRooms] {} 강의실 현황 업데이트 완료", file.getName()); + else { + log.error("[uploadNewSemesterRooms] {} 강의실 현황 업데이트 실패", file.getOriginalFilename()); + throw new LecturePythonException("강의실 현황 업데이트 실패, Python errorCode : " + exitCode); + } + } catch (IOException | InterruptedException e) { + log.error(e.getMessage(), e.getStackTrace()[0]); + throw new LecturePythonException(e.getMessage()); + } + } + + public void uploadNewSemesterLectures(MultipartFile file){ + try { + saveFile(file); + + String pythonNm = "infoOfLecture.py"; + ProcessBuilder processBuilder = new ProcessBuilder( + PYTHON_DIR, UPLOAD_DIR + pythonNm, UPLOAD_DIR+file.getOriginalFilename() + ); + log.info(processBuilder.command().toString()); + Process process = processBuilder.start(); + + String result = new String(process.getInputStream().readAllBytes()); + log.info(result); + int exitCode = process.waitFor(); + log.warn("[uploadNewSemesterLectures] Exited infoOfLecture.py with error code " + exitCode); + if (exitCode == 0) + log.info("[uploadNewSemesterLectures] {} 학기 강의 정보 업로드 완료", file.getName()); + else { + log.error("[uploadNewSemesterLectures] {} 업로드 실패", file.getOriginalFilename()); + throw new LecturePythonException("강의실 현황 업데이트 실패, Python errorCode : " + exitCode); + } + } catch (IOException | InterruptedException e) { + log.error(e.getMessage(), e.getStackTrace()[0]); + throw new LecturePythonException(e.getMessage()); + } + } + + private void saveFile(MultipartFile file) { + File savedFile = new File(UPLOAD_DIR + file.getOriginalFilename()); + try (FileOutputStream fos = new FileOutputStream(savedFile)) { + fos.write(file.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e.getMessage()); + } + } + +} From a0edbade1520be3d334fdf1af2dd0af7721b5f2e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 01:23:37 +0900 Subject: [PATCH 0576/1002] =?UTF-8?q?perf=20:=20python=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EB=A1=9C=20yml=EB=A1=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/schedular/PostsScheduler.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index c80d3023..1cc1bf38 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -20,14 +20,19 @@ public class PostsScheduler { @Value("${schedule.path}") private String PATH; + @Value("${lecture.python.path}") + private String PYTHON_DIR; + @Scheduled(cron = "${schedule.department.cron}", zone = "Asia/Seoul") @Async public void departmentPostsScheduler() { try { String fileName = "department.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3",PATH + fileName); - System.out.println(PATH); - System.out.println("Running command: " + processBuilder.command()); + ProcessBuilder processBuilder = + new ProcessBuilder().inheritIO().command( + PYTHON_DIR, + PATH + fileName + ); Process process = processBuilder.start(); int exitCode = process.waitFor(); log.warn("Exited department python with error code" + exitCode); @@ -46,8 +51,11 @@ public void departmentPostsScheduler() { public void starinuPostsScheduler(){ try { String fileName = "starinu.py"; - ProcessBuilder processBuilder = new ProcessBuilder().inheritIO().command("/usr/bin/python3", - PATH + fileName); + ProcessBuilder processBuilder = + new ProcessBuilder().inheritIO().command( + PYTHON_DIR, + PATH + fileName + ); Process process = processBuilder.start(); int exitCode = process.waitFor(); log.warn("Exited starinu python with error code" + exitCode); From c2a860eaaa96ba13671d84df0eb3dccbae91b41a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 01:25:18 +0900 Subject: [PATCH 0577/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 2d9063ad..da554d8c 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 2d9063adc39bc818db78535fe3938a65bb1e519d +Subproject commit da554d8ca4f2a136429348d5a27acded691191e5 From 38592906246c071715ee0ba9d149dd22a7657d5a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 01:47:59 +0900 Subject: [PATCH 0578/1002] =?UTF-8?q?refactor=20:=20feign=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 --- .../common/security/feign/inu/InuClient.java | 15 -------------- .../security/feign/portal/PortalClient.java | 20 ------------------- 2 files changed, 35 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java deleted file mode 100644 index 2ffad8be..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/feign/inu/InuClient.java +++ /dev/null @@ -1,15 +0,0 @@ -package inu.codin.codin.common.security.feign.inu; - -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; - -import java.util.Map; - -@FeignClient(name = "inu", url = "${feign.client.config.inu.url}") -public interface InuClient { - - @GetMapping - Map status(@RequestHeader("Authorization") String basic); - -} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java deleted file mode 100644 index 509bc440..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/feign/portal/PortalClient.java +++ /dev/null @@ -1,20 +0,0 @@ -package inu.codin.codin.common.security.feign.portal; - -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; - -@FeignClient(name = "portal", url = "${feign.client.config.portal.url}") -public interface PortalClient { - - @PostMapping( - consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE - ) - String signUp( - @RequestParam("_enpass_login_") String enpassLogin, - @RequestParam("username") String username, - @RequestParam("password") String password - ); - -} From c947b03c60f4f707a50d22e848252e1de3adaadd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 01:56:50 +0900 Subject: [PATCH 0579/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index da554d8c..9cedff3e 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit da554d8ca4f2a136429348d5a27acded691191e5 +Subproject commit 9cedff3e092f7fd0d8d36c15be7a6ce32ad7c347 From 95dc03e77e5e0cef0ae5cc4e034416a5ecf5de77 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 02:36:51 +0900 Subject: [PATCH 0580/1002] =?UTF-8?q?docs=20:=20ResponseEntity=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 2 +- .../domain/lecture/controller/LectureUploadController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 9eb4d9d6..21e7f452 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -62,7 +62,7 @@ public ResponseEntity> portalSignUp(@RequestBody @Valid SignUp authService.login(signUpAndLoginRequestDto, response); return ResponseEntity.ok() - .body(new SingleResponse<>(200, "포탈 로그인 진행 완료", "기존 유저 로그인 완료")); + .body(new SingleResponse<>(200, "로그인 성공", "기존 유저 로그인 완료")); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java index 6bc0515d..ede367d9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java @@ -44,7 +44,7 @@ public ResponseEntity> uploadNewSemesterLectures(@RequestParam public ResponseEntity uploadNewSemesterRooms(@RequestParam("excelFile") MultipartFile file) { lectureUploadService.uploadNewSemesterRooms(file); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, file.getName()+"의 강의실 현황 업데이트", null)); + .body(new SingleResponse<>(201, file.getOriginalFilename()+"의 강의실 현황 업데이트", null)); } From a3e2c9a0cce5e7b4310b0ee51b663606460483a1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 02:37:35 +0900 Subject: [PATCH 0581/1002] =?UTF-8?q?docs=20:=20@Tag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/lecture/controller/LectureUploadController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java index ede367d9..164433df 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.service.LectureUploadService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -17,6 +18,7 @@ @RestController @RequestMapping("/upload") @RequiredArgsConstructor +@Tag(name = "Lecture Upload API", description = "강의 내역 및 강의실 현황 업데이트 API") public class LectureUploadController { private final LectureUploadService lectureUploadService; From 3de0d040e24ad40999a4006fa9ee4dad076ea8f2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 02:50:00 +0900 Subject: [PATCH 0582/1002] =?UTF-8?q?perf=20:=20=EA=B0=95=EC=9D=98?= =?UTF-8?q?=EC=8B=A4=20=ED=98=84=ED=99=A9=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=EC=97=90=EB=8F=84=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B2=8C?= =?UTF-8?q?=EB=81=94=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/lecture/controller/LectureUploadController.java | 5 ++--- .../codin/domain/lecture/service/LectureUploadService.java | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java index 164433df..ea327b8b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java @@ -37,9 +37,8 @@ public ResponseEntity> uploadNewSemesterLectures(@RequestParam } @Operation( - summary = "강의실 현황 업데이트 (!! 꼭 '/lectures/upload_new' 이후에 실행할 것 !!)", - description = "!! 꼭 '/lectures/upload_new' 이후에 실행할 것 !!" + - "
강의 내역서(엑셀 파일) 이름을 '년도-학기'로 설정하여 업로드 ex) 24-1.xlsx, 24-2.xlsx" + summary = "강의실 현황 업데이트", + description = "강의 내역서(엑셀 파일) 이름을 '년도-학기'로 설정하여 업로드 ex) 24-1.xlsx, 24-2.xlsx" ) @PostMapping(value = "/rooms", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java index 7e0d3487..2cb29dae 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java @@ -22,6 +22,7 @@ public class LectureUploadService { public void uploadNewSemesterRooms(MultipartFile file) { try { + saveFile(file); String pythonNm = "dayTimeOfRoom.py"; ProcessBuilder processBuilder = new ProcessBuilder( PYTHON_DIR, UPLOAD_DIR + pythonNm, UPLOAD_DIR+file.getOriginalFilename() @@ -46,8 +47,6 @@ public void uploadNewSemesterRooms(MultipartFile file) { public void uploadNewSemesterLectures(MultipartFile file){ try { - saveFile(file); - String pythonNm = "infoOfLecture.py"; ProcessBuilder processBuilder = new ProcessBuilder( PYTHON_DIR, UPLOAD_DIR + pythonNm, UPLOAD_DIR+file.getOriginalFilename() From 44c8761656e1321d1df628415c312230ae260ecf Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 7 Mar 2025 16:23:02 +0900 Subject: [PATCH 0583/1002] =?UTF-8?q?fix=20:=20reivew=EB=A5=BC=20lectureId?= =?UTF-8?q?=EB=A1=9C=20=EA=B2=80=EC=83=89=ED=95=98=EB=8A=94=20=EA=B2=83?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=5Fid=EB=A1=9C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/review/repository/ReviewRepository.java | 3 +-- .../java/inu/codin/codin/domain/like/service/LikeService.java | 2 +- .../inu/codin/codin/infra/redis/scheduler/SyncScheduler.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java index e4fe6def..a9f15e9c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.lecture.domain.review.repository; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -16,5 +15,5 @@ public interface ReviewRepository extends MongoRepository findByLectureIdAndUserIdAndDeletedAtIsNull(ObjectId lectureId, ObjectId userId); - Optional findByLectureIdAndDeletedAtIsNull(ObjectId Id); + Optional findBy_idAndDeletedAtIsNull(ObjectId Id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 089b9f48..d1235cb4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -135,7 +135,7 @@ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); case COMMENT -> commentRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); - case REVIEW -> reviewRepository.findByLectureIdAndDeletedAtIsNull(id) + case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(id) .orElseThrow(() ->new NotFoundException("수강 후기를 찾을 수 없습니다")); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index bb4e24a5..11af5585 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -131,7 +131,7 @@ private void syncEntityLikes(String entityType, MongoRepository replyRepo.save(reply); } } else if (repository instanceof ReviewRepository reviewRepo) { - ReviewEntity review = reviewRepo.findByLectureIdAndDeletedAtIsNull(entityId).orElse(null); + ReviewEntity review = reviewRepo.findBy_idAndDeletedAtIsNull(entityId).orElse(null); if (review != null && review.getLikeCount() != likeCount) { log.info("ReviewEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); review.updateLikeCount(likeCount); From 33b3632b2ef933e0e7fa7948688d4431c5540722 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 8 Mar 2025 23:14:29 +0900 Subject: [PATCH 0584/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=EC=9E=90=EC=9D=98=20=EB=82=98=EA=B0=84=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=EC=99=80=20=EC=96=B8=EC=A0=9C=20=EB=82=98?= =?UTF-8?q?=EA=B0=94=EB=8A=94=EC=A7=80=EC=9D=98=20=EC=B9=BC=EB=9F=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageService.java | 6 +++++- .../chat/chatroom/entity/ParticipantInfo.java | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index cdc7c714..f82a62d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -12,12 +12,14 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @Service @@ -72,7 +74,9 @@ private Result getResult(StompHeaderAccessor headerAccessor) { throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); } log.info(headerAccessor.toString()); - String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); + String chatroomId =""; + if (Objects.equals(headerAccessor.getCommand(), StompCommand.DISCONNECT)) sessionStore.get(headerAccessor.getSessionId()); + else chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index 274c443d..ce4dbea3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -4,6 +4,8 @@ import lombok.*; import org.bson.types.ObjectId; +import java.time.LocalDateTime; + @Getter @NoArgsConstructor public class ParticipantInfo extends BaseTimeEntity { @@ -11,14 +13,19 @@ public class ParticipantInfo extends BaseTimeEntity { private ObjectId userId; private boolean isConnected = false; private int unreadMessage = 0; + + private boolean isLeaved = false; + private LocalDateTime whenLeaved; private boolean notificationsEnabled = true; @Builder - public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled) { + public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled, boolean isLeaved, LocalDateTime whenLeaved) { this.userId = userId; this.isConnected = isConnected; this.unreadMessage = unreadMessage; this.notificationsEnabled = notificationsEnabled; + this.isLeaved = isLeaved; + this.whenLeaved = whenLeaved; } public void updateNotification() { @@ -30,6 +37,8 @@ public static ParticipantInfo enter(ObjectId userId){ .userId(userId) .isConnected(false) .unreadMessage(0) + .isLeaved(false) + .whenLeaved(null) .notificationsEnabled(true) .build(); } @@ -49,4 +58,13 @@ public void disconnect(){ setUpdatedAt(); } + public void leave(){ + this.isLeaved = true; + this.whenLeaved = LocalDateTime.now(); + } + + public void remain(){ + this.isLeaved = false; + } + } From d38cdc142df490ce98cd2a91d4f5a15a794353f6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 8 Mar 2025 23:14:42 +0900 Subject: [PATCH 0585/1002] =?UTF-8?q?Revert=20"perf=20:=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EC=B0=B8=EC=97=AC=EC=9E=90=EC=9D=98=20=EB=82=98?= =?UTF-8?q?=EA=B0=84=20=EC=97=AC=EB=B6=80=EC=99=80=20=EC=96=B8=EC=A0=9C=20?= =?UTF-8?q?=EB=82=98=EA=B0=94=EB=8A=94=EC=A7=80=EC=9D=98=20=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 33b3632b2ef933e0e7fa7948688d4431c5540722. --- .../common/stomp/StompMessageService.java | 6 +----- .../chat/chatroom/entity/ParticipantInfo.java | 20 +------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index f82a62d1..cdc7c714 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -12,14 +12,12 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @Service @@ -74,9 +72,7 @@ private Result getResult(StompHeaderAccessor headerAccessor) { throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); } log.info(headerAccessor.toString()); - String chatroomId =""; - if (Objects.equals(headerAccessor.getCommand(), StompCommand.DISCONNECT)) sessionStore.get(headerAccessor.getSessionId()); - else chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); + String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index ce4dbea3..274c443d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -4,8 +4,6 @@ import lombok.*; import org.bson.types.ObjectId; -import java.time.LocalDateTime; - @Getter @NoArgsConstructor public class ParticipantInfo extends BaseTimeEntity { @@ -13,19 +11,14 @@ public class ParticipantInfo extends BaseTimeEntity { private ObjectId userId; private boolean isConnected = false; private int unreadMessage = 0; - - private boolean isLeaved = false; - private LocalDateTime whenLeaved; private boolean notificationsEnabled = true; @Builder - public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled, boolean isLeaved, LocalDateTime whenLeaved) { + public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled) { this.userId = userId; this.isConnected = isConnected; this.unreadMessage = unreadMessage; this.notificationsEnabled = notificationsEnabled; - this.isLeaved = isLeaved; - this.whenLeaved = whenLeaved; } public void updateNotification() { @@ -37,8 +30,6 @@ public static ParticipantInfo enter(ObjectId userId){ .userId(userId) .isConnected(false) .unreadMessage(0) - .isLeaved(false) - .whenLeaved(null) .notificationsEnabled(true) .build(); } @@ -58,13 +49,4 @@ public void disconnect(){ setUpdatedAt(); } - public void leave(){ - this.isLeaved = true; - this.whenLeaved = LocalDateTime.now(); - } - - public void remain(){ - this.isLeaved = false; - } - } From 940078dfa44c836fcdeed2197e320cc5f78c3a0d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 8 Mar 2025 23:15:55 +0900 Subject: [PATCH 0586/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=EC=9E=90=EC=9D=98=20=EB=82=98=EA=B0=84=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=EC=99=80=20=EC=96=B8=EC=A0=9C=20=EB=82=98?= =?UTF-8?q?=EA=B0=94=EB=8A=94=EC=A7=80=EC=9D=98=20=EC=B9=BC=EB=9F=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/chatroom/entity/ParticipantInfo.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index 274c443d..ce4dbea3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -4,6 +4,8 @@ import lombok.*; import org.bson.types.ObjectId; +import java.time.LocalDateTime; + @Getter @NoArgsConstructor public class ParticipantInfo extends BaseTimeEntity { @@ -11,14 +13,19 @@ public class ParticipantInfo extends BaseTimeEntity { private ObjectId userId; private boolean isConnected = false; private int unreadMessage = 0; + + private boolean isLeaved = false; + private LocalDateTime whenLeaved; private boolean notificationsEnabled = true; @Builder - public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled) { + public ParticipantInfo(ObjectId userId, boolean isConnected, int unreadMessage, boolean notificationsEnabled, boolean isLeaved, LocalDateTime whenLeaved) { this.userId = userId; this.isConnected = isConnected; this.unreadMessage = unreadMessage; this.notificationsEnabled = notificationsEnabled; + this.isLeaved = isLeaved; + this.whenLeaved = whenLeaved; } public void updateNotification() { @@ -30,6 +37,8 @@ public static ParticipantInfo enter(ObjectId userId){ .userId(userId) .isConnected(false) .unreadMessage(0) + .isLeaved(false) + .whenLeaved(null) .notificationsEnabled(true) .build(); } @@ -49,4 +58,13 @@ public void disconnect(){ setUpdatedAt(); } + public void leave(){ + this.isLeaved = true; + this.whenLeaved = LocalDateTime.now(); + } + + public void remain(){ + this.isLeaved = false; + } + } From 4c2897d9400b13c17e59c1392e82d0a1bebd47ac Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 9 Mar 2025 01:21:04 +0900 Subject: [PATCH 0587/1002] =?UTF-8?q?fix=20:=20UNSCRIBE=20=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EA=B3=A0=20DISCONNECT=20=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0,=20UNSCRIBE=20=EA=B3=BC=EC=A0=95?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/stomp/StompMessageService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index cdc7c714..63f70170 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -12,12 +12,14 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @Service @@ -72,7 +74,15 @@ private Result getResult(StompHeaderAccessor headerAccessor) { throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); } log.info(headerAccessor.toString()); - String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); + String chatroomId; + if (Objects.equals(headerAccessor.getCommand(), StompCommand.DISCONNECT)){ //UNSCRIBE 하지 않은 상태에서 DISCONNECT라면 UNSCRIBE도 같이 + if (!sessionStore.get(headerAccessor.getSessionId()).isEmpty()) { + chatroomId = sessionStore.get(headerAccessor.getSessionId()); + sessionStore.remove(headerAccessor.getSessionId()); + } else return null; + + } + else chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); } From 18759efce8b2e2626a937f39a4251d34eb0230da Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 9 Mar 2025 01:23:29 +0900 Subject: [PATCH 0588/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=EB=82=98=EA=B0=80=EA=B8=B0=20=EC=9D=B4=ED=9B=84=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=EB=B0=A9=EC=9D=B4=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EC=9D=84=20=EA=B1=B8=EC=97=88=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C,=20=EB=8B=A4=EC=8B=9C=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EC=98=A4=ED=94=88=20=EB=B0=8F=20=EB=82=98=EA=B0=80=EA=B8=B0?= =?UTF-8?q?=20=EC=9D=B4=ED=9B=84=EB=A1=9C=20=EB=8C=80=ED=99=94=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ChattingRepository.java | 4 ++ .../chatting/service/ChattingService.java | 49 +++++++++++++------ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java index 6b96ee39..2f9c21ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/ChattingRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; +import java.time.LocalDateTime; import java.util.List; public interface ChattingRepository extends MongoRepository { @@ -12,4 +13,7 @@ public interface ChattingRepository extends MongoRepository { List findAllByChatRoomIdOrderByCreatedAtDesc(ObjectId chatRoomId); List findAllByChatRoomId(ObjectId id, Pageable pageable); + + List findAllByChatRoomIdAndCreatedAtAfter(ObjectId id, LocalDateTime whenLeaved, Pageable pageable); + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 82c8b173..00924594 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.chat.chatting.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDateTime; import java.util.List; @Service @@ -46,9 +47,8 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); }); - ObjectId userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); - Integer countOfParticipating = chatRoomService.countOfParticipating(chatRoom.get_id()); + Integer countOfParticipating = chatRoomService.countOfParticipating(chatRoom.get_id()); //접속해 있는 사람 수 빼기 (읽은 count) Chatting chatting = Chatting.of(chatRoom.get_id(), chattingRequestDto, userId, chatRoom.getParticipants().getInfo().size()-countOfParticipating); @@ -56,33 +56,54 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq chattingRepository.save(chatting); + //상대가 채팅방을 나간 상태라면 다시 불러와서 채팅 시작 + chatRoom.getParticipants().getInfo().values().stream() + .filter(info -> !info.getUserId().equals(userId) && info.isLeaved()) + .forEach(ParticipantInfo::remain); + chatRoomRepository.save(chatRoom); + + eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting)); + //상대 유저가 접속하지 않은 상태라면 unread 개수 업데이트 및 마지막 대화 내용 업데이트 + // //Receiver의 알림 체크 후, 메세지 전송 // for (Participants participant : chatRoom.getParticipants()){ // if (participant.getUserId() != userId && participant.isNotificationsEnabled()){ // notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom); // } // } - eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting)); + return ChattingResponseDto.of(chatting); } public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { - log.info("[메시지 조회] 채팅방 ID: {}, 페이지: {}", id, page); + ObjectId userId = SecurityUtils.getCurrentUserId(); + ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) + .orElseThrow(() -> { + log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); + log.info("[메시지 조회] 채팅방 ID: {}, 페이지: {}", id, page); - Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); - chatRoomRepository.findById(new ObjectId(id)) - .orElseThrow(() -> { - log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); - return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); - }); + Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); + chatRoomRepository.findById(new ObjectId(id)) + .orElseThrow(() -> { + log.error("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); + return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); + }); - List chattingResponseDto = chattingRepository.findAllByChatRoomId(new ObjectId(id), pageable) + List chattingResponseDto; + LocalDateTime whenLeaved = chatRoom.getParticipants().getInfo().get(userId).getWhenLeaved(); + if (whenLeaved!= null) //나간 적이 있다면 그 이후의 채팅 내역만 반환 + chattingResponseDto = chattingRepository.findAllByChatRoomIdAndCreatedAtAfter(new ObjectId(id), whenLeaved, pageable) .stream().map(ChattingResponseDto::of).toList(); + else chattingResponseDto = chattingRepository.findAllByChatRoomId(new ObjectId(id), pageable) + .stream().map(ChattingResponseDto::of).toList(); + - log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); + log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); - return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); + return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); } public List sendImageMessage(List chatImages) { From 3696db21aefeda20cf03acb2d3ac220e70d655ee Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 9 Mar 2025 01:26:08 +0900 Subject: [PATCH 0589/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=EC=9D=84=20=EB=82=98=EA=B0=88=20=EC=8B=9C=EC=97=90=20?= =?UTF-8?q?isLeave=20&=20isConnect=20=ED=91=9C=EC=8B=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=B0=8F=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=EB=B0=98=ED=99=98=EC=8B=9C=20=EB=82=98=EA=B0=84=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatRoomController.java | 8 ++--- .../repository/ChatRoomRepository.java | 4 +-- .../chatroom/service/ChatRoomService.java | 32 ++++++++----------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 1116a18b..73344597 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -11,8 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @RestController @@ -34,12 +32,12 @@ public ResponseEntity> createChatRoom(@RequestBody ChatRoomCre } @Operation( - summary = "사용자가 포함된 모든 채팅방 리스트 반환" + summary = "사용자가 나가지 않은 채팅방 리스트 반환" ) @GetMapping - public ResponseEntity> getAllChatRoomByUser(@AuthenticationPrincipal UserDetails userDetails){ + public ResponseEntity> getAllChatRoomByUser(){ return ResponseEntity.ok() - .body(new ListResponse<>(200, "채팅방 리스트 반환 완료", chatRoomService.getAllChatRoomByUser(userDetails))); + .body(new ListResponse<>(200, "채팅방 리스트 반환 완료", chatRoomService.getAllChatRoomByUser())); } @Operation( diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index 41045363..e9b15c0e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -13,6 +13,6 @@ public interface ChatRoomRepository extends MongoRepository { @Query("{ '_id': ?0, 'deletedAt': null }") Optional findById(ObjectId id); - @Query("{ 'participants.info.?0.userId': ?0, 'deletedAt': null }") - List findByParticipant(ObjectId userId); + @Query("{ 'participants.info.?0.userId': ?0, 'participants.info.?0.isLeaved': false, 'deletedAt': null }") + List findByParticipantIsNotLeavedAndDeletedIsNull(ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 6bbe110c..6335576e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -8,19 +8,15 @@ import inu.codin.codin.domain.chat.chatroom.dto.event.ChatRoomNotificationEvent; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; -import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.chat.chatting.repository.CustomChattingRepository; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -67,13 +63,13 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat return response; } - public List getAllChatRoomByUser(UserDetails userDetails) { - ObjectId userId = ((CustomUserDetails) userDetails).getId(); + public List getAllChatRoomByUser() { + ObjectId userId = SecurityUtils.getCurrentUserId(); log.info("[유저의 채팅방 조회] 유저 ID: {}", userId); // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); - List chatRooms = chatRoomRepository.findByParticipant(userId); + List chatRooms = chatRoomRepository.findByParticipantIsNotLeavedAndDeletedIsNull(userId); log.info("[채팅방 조회 결과] 유저 ID: {}가 참여 중인 채팅방 개수: {}", userId, chatRooms.size()); return chatRooms.stream() .filter(chatRoom -> chatRoom.getParticipants().getInfo().keySet().stream() @@ -83,7 +79,7 @@ public List getAllChatRoomByUser(UserDetails userDetail public void leaveChatRoom(String chatRoomId) { ObjectId userId = SecurityUtils.getCurrentUserId(); - log.info("[채팅방 탈퇴 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); + log.info("[채팅방 나가기 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> { @@ -92,23 +88,23 @@ public void leaveChatRoom(String chatRoomId) { }); Map info = chatRoom.getParticipants().getInfo(); - log.info("[채팅방 확인] 채팅방 ID: {}, 참여자 수: {}", chatRoomId, info.size()); - if (info.containsKey(userId)){ - info.remove(userId); + info.get(userId).leave(); + info.get(userId).disconnect(); } else { log.warn("[채팅방 탈퇴 실패] 유저 ID: {}는 채팅방에 참여하지 않았습니다.", userId); throw new ChatRoomNotFoundException("회원이 포함된 채팅방을 찾을 수 없습니다."); } - log.info("[채팅방 탈퇴 성공] 유저 ID: {}가 채팅방에서 탈퇴", userId); -// if (chatRoom.getParticipants().isEmpty()) { -// chatRoom.delete(); -// log.info("[채팅방 삭제] 채팅방 ID: {}에 더 이상 참여자가 없어 채팅방을 삭제합니다.", chatRoomId); -// } - chatRoomRepository.save(chatRoom); - log.info("[채팅방 삭제 후 저장] 채팅방 ID: {} 저장 완료", chatRoomId); + boolean isAllLeaved = chatRoom.getParticipants().getInfo().values() + .stream() + .allMatch(ParticipantInfo::isLeaved); // 모든 참가자가 떠났는지 확인 + if (isAllLeaved){ + chatRoom.delete(); + log.info("[채팅방 삭제] 채팅방 ID: {}에 더 이상 참여자가 없어 채팅방을 삭제합니다.", chatRoomId); + } + chatRoomRepository.save(chatRoom); } public void setNotificationChatRoom(String chatRoomId) { From ed1a5386d3ced21759a4008941beb4470795d801 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:23:19 +0900 Subject: [PATCH 0590/1002] =?UTF-8?q?feat=20::=20post=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4/=EC=83=81=EC=84=B8=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 31 +++++++ .../dto/response/PostDetailResponseDTO.java | 3 + .../post/dto/response/PostReportResponse.java | 29 ++++++ .../domain/post/service/PostService.java | 88 ++++++++++++++++++- 4 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index cadcec35..a94e8159 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -109,6 +111,35 @@ public ResponseEntity> getPostWithDetail(@ .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } + @Operation( + summary = "신고된 게시물 전체 조회" + ) + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/Allreported") + public ResponseEntity> getAllReportedPosts(@RequestParam("page") @NotNull int pageNumber) { + PostPageResponse postpages= postService.getAllReportedPosts(pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>( + 200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); + } + + @Operation( + summary = "신고된 게시물 상세 조회" + ) + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/reported/posts/{postId}") + public ResponseEntity> getPostWithDetail( + @PathVariable String postId, + @RequestParam(required = false) String reportedEntityId) { + + ReportedPostDetailResponseDTO responseDTO=postService.getReportedPostWithDetail(postId, reportedEntityId); + return ResponseEntity.ok(new SingleResponse<>( + 200, "게시글 상세 조회 성공",responseDTO + )); + } + + + @Operation(summary = "게시물 이미지 삭제") @DeleteMapping("/{postId}/images") public ResponseEntity> deletePostImage( diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 186c3e65..f03413f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.report.dto.ReportInfo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -116,5 +117,7 @@ public static PostDetailResponseDTO of(PostEntity post, String nickname, String commentCount, userInfo); } + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java new file mode 100644 index 00000000..c36bfaa7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java @@ -0,0 +1,29 @@ +package inu.codin.codin.domain.post.dto.response; + + +import inu.codin.codin.domain.report.dto.ReportInfo; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class PostReportResponse extends PostDetailResponseDTO { + + private final ReportInfo reportInfo; + + @Builder + public PostReportResponse(PostDetailResponseDTO baseDTO, ReportInfo reportInfo) { + super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), + baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), + baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), + baseDTO.getCommentCount(), baseDTO.getUserInfo()); + this.reportInfo = reportInfo; + } + + + public static PostReportResponse from(PostDetailResponseDTO base, ReportInfo reportInfo) { + return PostReportResponse.builder() + .baseDTO(base) + .reportInfo(reportInfo) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index bf9a6ee7..883a3739 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -9,20 +9,22 @@ import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.hits.service.HitsService; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.dto.response.*; +import inu.codin.codin.domain.report.dto.ReportInfo; +import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO.UserInfo; -import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.post.dto.response.PostPollDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; @@ -37,6 +39,7 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -44,6 +47,7 @@ import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; @Slf4j @Service @@ -61,6 +65,9 @@ public class PostService { private final HitsService hitsService; private final RedisService redisService; private final BlockService blockService; + private final ReportRepository reportRepository; + private final CommentRepository commentRepository; + private final ReplyCommentRepository replyCommentRepository; public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); @@ -137,6 +144,8 @@ private void validateUserAndPost(PostEntity post) { } + + // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { String nickname; @@ -233,6 +242,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { + + public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); @@ -314,5 +325,76 @@ public PostPageResponse getBestPosts(int pageNumber) { .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } + + + + public PostPageResponse getAllReportedPosts(int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + + List reportInfos = reportRepository.findAllReportedEntities(); + + // Page 변환 + int start = Math.min((int) pageRequest.getOffset(), reportInfos.size()); + int end = Math.min((start + pageRequest.getPageSize()), reportInfos.size()); + Page reportInfoPage = new PageImpl<>(reportInfos.subList(start, end), pageRequest, reportInfos.size()); + + + List reportedPosts = reportInfoPage.getContent().stream() + .map(this::getReportedPostDetail) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + long lastPage = reportInfoPage.getTotalPages() - 1; + long nextPage = reportInfoPage.hasNext() ? pageNumber + 1 : -1; + + return PostPageResponse.of(reportedPosts, lastPage, nextPage); + } + + // 신고된 개별 엔터티를 PostReportDetailResponse로 변환하는 메서드 + private Optional getReportedPostDetail(ReportInfo reportInfo) { + ObjectId entityId = new ObjectId(reportInfo.getReportedEntityId()); + + return switch (reportInfo.getEntityType()) { + case POST -> postRepository.findById(entityId) + .map(this::createPostDetailResponse) + .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)); + + case COMMENT -> commentRepository.findById(entityId) + .flatMap(comment -> postRepository.findById(comment.getPostId()) + .map(this::createPostDetailResponse) + .map(postDTO -> PostReportResponse.from(postDTO, reportInfo))); + + case REPLY -> replyCommentRepository.findById(entityId) + .flatMap(reply -> commentRepository.findById(reply.getCommentId()) + .flatMap(comment -> postRepository.findById(comment.getPostId()) + .map(this::createPostDetailResponse) + .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)))); + + default -> Optional.empty(); + }; + } + + //신고된 게시물 상세조회 + public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, String reportedEntityId) { + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + + ObjectId userId = SecurityUtils.getCurrentUserId(); + if (hitsService.validateHits(post.get_id(), userId)) { + hitsService.addHits(post.get_id(), userId); + log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); + } + + // 게시글이 신고된 경우 표시 추가 + ObjectId ReportTargetId = new ObjectId(reportedEntityId); + boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); + boolean isReported = existsInReportDB && post.get_id().equals(ReportTargetId); + + PostDetailResponseDTO postDetailResponse = createPostDetailResponse(post); + //여기에 isReported 추가 + + return ReportedPostDetailResponseDTO.from(isReported, postDetailResponse); + } } From ec9e15cc4570c6f59290a6f0204e6177593ab289 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:23:28 +0900 Subject: [PATCH 0591/1002] =?UTF-8?q?feat=20::=20comment=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=20=EC=A0=84=EC=B2=B4/=EC=83=81=EC=84=B8=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 15 ++++++++ .../dto/response/CommentResponseDTO.java | 18 ++++++++++ .../comment/service/CommentService.java | 35 ++++++++++++++++++- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 57135262..c8503a68 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -5,12 +5,14 @@ import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -59,4 +61,17 @@ public ResponseEntity> updateComment(@PathVariable String comm body(new SingleResponse<>(200, "댓글 수정되었습니다.", null)); } + + // 신고된 댓글 목록 조회 + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/{postId}/reported-comments") + public ResponseEntity>> getReportedCommentsByPostId( + @PathVariable String postId, + @RequestParam String reportedEntityId) { + + return ResponseEntity.ok(new SingleResponse<>( + 200, "신고된 댓글 조회 성공", + commentService.getReportedCommentsByPostId(postId, reportedEntityId) + )); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 81fa537d..c9b94602 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -101,6 +101,24 @@ public static CommentResponseDTO replyOf(ReplyCommentEntity replyCommentEntity, .userInfo(userInfoAboutPost) .build(); } + // 기존 객체에서 replies 리스트만 변경 + public CommentResponseDTO repliesFrom(List updatedReplies) { + List commentReplies = updatedReplies.stream() + .map(reply -> (CommentResponseDTO) reply) // 변환 + .toList(); + + return CommentResponseDTO.builder() + ._id(this._id) + .content(this.content) + .nickname(this.nickname) + .userImageUrl(this.userImageUrl) + .anonymous(this.anonymous) + .replies(commentReplies) // 수정된 대댓글 리스트 적용 + .likeCount(this.likeCount) + .createdAt(this.createdAt) + .userInfo(this.userInfo) + .build(); + } @Getter public static class UserInfo { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 5327d132..079cfa49 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -9,12 +9,14 @@ import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; +import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisAnonService; @@ -36,6 +38,7 @@ public class CommentService { private final PostRepository postRepository; private final CommentRepository commentRepository; + private final ReportRepository reportRepository; private final UserRepository userRepository; private final LikeService likeService; @@ -146,7 +149,7 @@ public List getCommentsByPostId(String id) { } else { nickname = comment.isAnonymous()? anonNum==0? "글쓴이" : "익명" + anonNum - : userMap.get(comment.getUserId()).nickname(); + : userMap.get(comment.getUserId()).nickname(); userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); } return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, @@ -178,4 +181,34 @@ public UserInfo getUserInfoAboutPost(ObjectId commentId) { .build(); } + +// public List getReportedCommentsByPostId(String postId, String reportedEntityId) { +// // 기존 댓글 목록 조회 +// List comments = getCommentsByPostId(postId); +// +// // 신고 여부 추가 +// return comments.stream() +// .map(comment -> ReportedCommentDetailResponseDTO.from( comment.get_id().equals(reportedEntityId), comment)) +// .toList(); +// } + + public List getReportedCommentsByPostId(String postId, String reportedEntityId) { + List comments = getCommentsByPostId(postId); + + return comments.stream() + .map(comment -> { + ObjectId ReportTargetId = new ObjectId(reportedEntityId); + boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); + boolean isCommentReported = existsInReportDB && comment.get_id().equals(reportedEntityId); + log.info("🔸 댓글 ID: {}, 신고 여부: {}", comment.get_id(), isCommentReported); + + // ✅ 대댓글 리스트 변환 (신고 여부 반영) + List reportedReplies = replyCommentService.getReportedRepliesByCommentId(comment.get_id(), reportedEntityId); + + // ✅ `CommentResponseDTO`에서 `ReportedCommentResponseDTO`로 변환하여 신고 여부 추가 + return ReportedCommentDetailResponseDTO.from(comment.repliesFrom(reportedReplies), isCommentReported); + }) + .toList(); + } + } \ No newline at end of file From b1dead9f2e1953bf4be5922e523c319143a52a9b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:23:33 +0900 Subject: [PATCH 0592/1002] =?UTF-8?q?feat=20::=20comment=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=20=EC=A0=84=EC=B2=B4/=EC=83=81=EC=84=B8=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reply/service/ReplyCommentService.java | 21 +++++++++++++++ .../report/controller/ReportController.java | 26 ++++++++++++++----- .../dto/request/ReportCreateRequestDto.java | 2 +- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 251ccdf4..54e9551e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -6,6 +6,7 @@ import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; @@ -15,6 +16,7 @@ import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisAnonService; @@ -39,6 +41,7 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; + private final ReportRepository reportRepository; private final LikeService likeService; private final NotificationService notificationService; @@ -144,6 +147,7 @@ public List getRepliesByCommentId(ObjectId commentId) { getUserInfoAboutPost(reply.get_id())); }).toList(); } + public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); //log.info("대댓글 userInfo - replyId: {}, userId: {}", replyId, userId); @@ -165,4 +169,21 @@ public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { log.info("대댓글 수정 완료 - replyId: {}", replyId); } + + public List getReportedRepliesByCommentId(String id, String reportedEntityId) { + ObjectId commentId = new ObjectId(id); + List replies = getRepliesByCommentId(commentId); + + return replies.stream() + .map(reply -> { + ObjectId ReportTargetId = new ObjectId(reportedEntityId); + boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); + boolean isReplyReported = existsInReportDB && reply.get_id().equals(reportedEntityId); + + log.info("🔹 대댓글 ID: {}, 신고 여부: {}", reply.get_id(), isReplyReported); + + return ReportedCommentDetailResponseDTO.from(reply, isReplyReported); + }) + .toList(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 4b737527..3186c6e8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -50,14 +50,14 @@ public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto // .body(new SingleResponse<>(200, "삭제되지 않은 신고된 모든 게시물 조회 성공", postpages)); // } - //(Admin) 특정 게시물의 신고 정보 조회 API + //(Admin) 특정 대상 신고 정보 조회 API @Operation(summary = "특정 게시물의 신고 내역 조회(관리자)") @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/summary/{postId}") - public ResponseEntity getReportSummary(@PathVariable String postId) { - ReportSummaryResponseDTO reportSummary = reportService.getReportSummary(postId); + @GetMapping("/summary/{reportedEntityId}") + public ResponseEntity getReportSummary(@PathVariable String reportedEntityId) { + ReportSummaryResponseDTO reportSummary = reportService.getReportSummary(reportedEntityId); return ResponseEntity.ok() - .body(new SingleResponse<>(200, "특정 게시물의 신고 내역 조회 완료", reportSummary)); + .body(new SingleResponse<>(200, "특정 대상 신고 내역 조회 완료", reportSummary)); } @@ -93,10 +93,24 @@ public ResponseEntity getReportsByUserId( public ResponseEntity handleReport( @RequestBody ReportExecuteRequestDto reportExecuteRequestDto) { - reportService.resolveReport(reportExecuteRequestDto); + reportService.handleReport(reportExecuteRequestDto); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(200, "(관리자) 신고 처리",null)); } + // 신고 처리 (관리자) + @Operation(summary = "신고 처리-유지하기 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @PutMapping("/resolveReport/{reportId}") + public ResponseEntity resolveReport( + @PathVariable String reportId + ) { + + reportService.resolveReport(reportId); + + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(200, "(관리자) 신고 처리 - 유지하기",null)); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java index 18536cc6..b26f9343 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java @@ -19,7 +19,7 @@ public class ReportCreateRequestDto { @NotBlank private String reportTargetId; - @Schema(description = " 신고 유형 ( 게시글 부적절, 스팸 ,,.)", example = "INAPPROPRIATE_CONTENT, COMMERCIAL_AD , ABUSE , OBSCENE, POLITICAL,FRAUD , SPAM ,,") + @Schema(description = " 신고 유형 ( 게시글 부적절, 스팸 ,,.)", example = "FRAUD, ABUSE, COMMERCIAL_AD, OBSCENE, POLITICAL, ETC, SPAM") @NotNull private ReportType reportType; } From 4aca756cb1914086ef5574ae7a866e0c4f4c4db4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:23:56 +0900 Subject: [PATCH 0593/1002] =?UTF-8?q?feat=20::=20post=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4/=EC=83=81=EC=84=B8=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReportedPostDetailResponseDTO.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java new file mode 100644 index 00000000..e7843957 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.post.dto.response; + +import inu.codin.codin.domain.report.dto.ReportInfo; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +public class ReportedPostDetailResponseDTO extends PostDetailResponseDTO{ + private final boolean isReported; + + @Builder + public ReportedPostDetailResponseDTO(PostDetailResponseDTO baseDTO, boolean isReported) { + super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), + baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), + baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), + baseDTO.getCommentCount(), baseDTO.getUserInfo()); + this.isReported = isReported; + } + + public static ReportedPostDetailResponseDTO from(boolean isReported, PostDetailResponseDTO postDetail) { + return ReportedPostDetailResponseDTO.builder() + .isReported(isReported) + .baseDTO(postDetail) + .build(); + } +} From 0f6919e6cb20a34e010682bea8f3db7d5705405c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:24:01 +0900 Subject: [PATCH 0594/1002] =?UTF-8?q?feat=20::=20comment=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=20=EC=A0=84=EC=B2=B4/=EC=83=81=EC=84=B8=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReportedCommentDetailResponseDTO.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java new file mode 100644 index 00000000..8cf3a1a8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.post.domain.comment.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ReportedCommentDetailResponseDTO extends CommentResponseDTO { + private final boolean isReported; + + private ReportedCommentDetailResponseDTO(CommentResponseDTO base, boolean isReported) { + super(base.get_id(), base.getUserId(), base.getContent(), base.getNickname(), + base.getUserImageUrl(), base.isAnonymous(), base.getReplies(), + base.getLikeCount(), base.isDeleted(), base.getCreatedAt(), base.getUserInfo()); + this.isReported = isReported; + } + + public static ReportedCommentDetailResponseDTO from(CommentResponseDTO base, boolean isReported) { + return new ReportedCommentDetailResponseDTO(base, isReported); + } +} From 8d423d4453cb699bc2f1da5f40962989a2ced534 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:24:24 +0900 Subject: [PATCH 0595/1002] =?UTF-8?q?feat=20::=20=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=9D=91=EB=8B=B5=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/report/dto/ReportInfo.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/ReportInfo.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/ReportInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/ReportInfo.java new file mode 100644 index 00000000..bfc4f822 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/ReportInfo.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.report.dto; + +import inu.codin.codin.domain.report.entity.ReportTargetType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class ReportInfo { + private String reportedEntityId; // 신고 대상 엔터티 ID + private int reportCount; // 신고 누적 횟수 + private ReportTargetType entityType; // 신고 대상 타입 (POST, COMMENT, REPLY) + private String userId; // 해당 엔터티의 작성자 ID +} From 806220e1bc70be81c0a0951b2fd8f3f19fd83f96 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 03:27:50 +0900 Subject: [PATCH 0596/1002] =?UTF-8?q?feat=20::=20=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20-=20=EC=9C=A0=EC=A7=80=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/entity/ReportEntity.java | 9 ++- .../report/repository/ReportRepository.java | 11 ++++ .../domain/report/service/ReportService.java | 59 ++++++++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index 46627c13..e4115baa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -75,7 +75,14 @@ public void updateReportResolved() { this.reportStatus = ReportStatus.RESOLVED; // 상태 변경 } - + public void ReportResolved(ObjectId actionTakenById) { + this.reportStatus = ReportStatus.RESOLVED; // ✅ 신고 상태 변경 (유지) + this.action = ReportActionEntity.builder() + .actionTakenById(actionTakenById) // ✅ 관리자 ID 저장 + .suspensionPeriod(null) // ✅ 유지하는 경우 정지 기간 없음 + .suspensionEndDate(null) // ✅ 유지하는 경우 정지 종료일 없음 + .build(); + } @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java index aabd31ab..4c00f300 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.report.repository; +import inu.codin.codin.domain.report.dto.ReportInfo; import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.entity.ReportType; @@ -39,4 +40,14 @@ public interface ReportRepository extends MongoRepository findAllReportedEntities(); + + boolean existsByReportTargetId(ObjectId reportTargetId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 0b8f7542..eef88995 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -168,12 +168,12 @@ public List getReportsByUserId(String userId) { @Transactional - public void resolveReport(ReportExecuteRequestDto requestDto) { + public void handleReport(ReportExecuteRequestDto requestDto) { log.info("신고 처리 요청: {}", requestDto.getReportId()); + //현재 관리자 ID ObjectId userId = SecurityUtils.getCurrentUserId(); ObjectId ReportId = new ObjectId(requestDto.getReportId()); - // 신고 존재 확인 ReportEntity report = reportRepository.findById(ReportId) .orElseThrow(() -> new IllegalArgumentException("해당 신고가 존재하지 않습니다. ID: " + requestDto.getReportId())); @@ -215,6 +215,8 @@ public void resolveReport(ReportExecuteRequestDto requestDto) { Objects.requireNonNullElseGet(totalSuspensionEndDate, LocalDateTime::now) .plusDays(requestDto.getSuspensionPeriod().getDays())); } + + deleteReportedEntity(report.getReportTargetId(), report.getReportTargetType()); // 업데이트된 신고 저장 reportRepository.save(report); userRepository.save(user.get()); @@ -238,5 +240,58 @@ public ReportSummaryResponseDTO getReportSummary(String reportTargetId) { return new ReportSummaryResponseDTO(reportTypeCounts); } + + @Transactional + public void deleteReportedEntity(ObjectId reportTargetId, ReportTargetType targetType) { + + switch (targetType) { + case POST -> { + PostEntity post = postRepository.findById(reportTargetId) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + post.delete(); + postRepository.save(post); + log.info(" 신고된 게시글 삭제: {}", reportTargetId); + } + case COMMENT -> { + CommentEntity comment = commentRepository.findById(reportTargetId) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + comment.delete(); + commentRepository.save(comment); + log.info(" 신고된 댓글 삭제: {}", reportTargetId); + } + case REPLY -> { + ReplyCommentEntity reply = replyRepository.findById(reportTargetId) + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + reply.delete(); + replyRepository.save(reply); + log.info("신고된 대댓글 삭제: {}", reportTargetId); + } + default -> throw new IllegalArgumentException("잘못된 신고 대상 타입입니다."); + } + } + + public void resolveReport(String reportId) { + log.info(" 신고대상 유지 요청: 신고 ID: {}", reportId); + + ObjectId reportObjectId = new ObjectId(reportId); + ObjectId userId = SecurityUtils.getCurrentUserId(); // 현재 유저 ID + + // 신고 존재 확인 + ReportEntity report = reportRepository.findById(reportObjectId) + .orElseThrow(() -> new NotFoundException("해당 신고가 존재하지 않습니다. ID: " + reportId)); + + // 이미 처리된 신고인지 확인 (RESOLVED, SUSPENDED 상태면 예외 발생) + if (report.getReportStatus() == ReportStatus.RESOLVED || report.getReportStatus() == ReportStatus.SUSPENDED) { + throw new IllegalStateException("이미 처리된 신고입니다. 상태: " + report.getReportStatus()); + } + + // 신고 상태를 `RESOLVED`로 변경 + report.ReportResolved(userId); + + // 변경된 신고 저장 + reportRepository.save(report); + + log.info(" 신고 유지 완료: 신고 ID: {}, 처리자: {}", report.get_id(), userId); + } } From eb51e2b132ee0964fcbe7c3523fac2b71e5f5ff2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 04:01:16 +0900 Subject: [PATCH 0597/1002] =?UTF-8?q?fix=20::=20reportId=20->=20ReportTarg?= =?UTF-8?q?etId=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EC=B2=98=EB=A6=AC-=20(=EC=A0=95=EC=A7=80,=20=EC=9C=A0=EC=A7=80?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/controller/ReportController.java | 10 +- .../dto/request/ReportExecuteRequestDto.java | 3 +- .../report/repository/ReportRepository.java | 2 + .../domain/report/service/ReportService.java | 109 ++++++++++-------- 4 files changed, 67 insertions(+), 57 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 3186c6e8..572a609f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -89,25 +89,25 @@ public ResponseEntity getReportsByUserId( // 신고 처리 (관리자) @Operation(summary = "신고 처리 (관리자)") @PreAuthorize("hasRole('ADMIN')") - @PutMapping("/{reportId}") + @PutMapping("/{reportTargetId}") public ResponseEntity handleReport( @RequestBody ReportExecuteRequestDto reportExecuteRequestDto) { reportService.handleReport(reportExecuteRequestDto); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(200, "(관리자) 신고 처리",null)); + .body(new SingleResponse<>(200, "(관리자) 신고 처리 - 정지하기",null)); } // 신고 처리 (관리자) @Operation(summary = "신고 처리-유지하기 (관리자)") @PreAuthorize("hasRole('ADMIN')") - @PutMapping("/resolveReport/{reportId}") + @PutMapping("/resolveReport/{reportTargetId}") public ResponseEntity resolveReport( - @PathVariable String reportId + @PathVariable String reportTargetId ) { - reportService.resolveReport(reportId); + reportService.resolveReport(reportTargetId); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(200, "(관리자) 신고 처리 - 유지하기",null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java index 557c2647..37412852 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java @@ -11,8 +11,7 @@ @Getter @NoArgsConstructor public class ReportExecuteRequestDto { - private String reportId; // 신고 ID - //private String comment; // 신고 처리에 대한 코멘트 + private String reportTargetId; // 신고 ID private SuspensionPeriod suspensionPeriod; // 정지 기간 } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java index 4c00f300..69dede20 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/ReportRepository.java @@ -50,4 +50,6 @@ public interface ReportRepository extends MongoRepository findAllReportedEntities(); boolean existsByReportTargetId(ObjectId reportTargetId); + + List findByReportTargetId(ObjectId targetObjectId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index eef88995..360f6f0b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -169,58 +169,65 @@ public List getReportsByUserId(String userId) { @Transactional public void handleReport(ReportExecuteRequestDto requestDto) { - log.info("신고 처리 요청: {}", requestDto.getReportId()); + log.info("신고 처리 요청: {}", requestDto.getReportTargetId()); //현재 관리자 ID ObjectId userId = SecurityUtils.getCurrentUserId(); - ObjectId ReportId = new ObjectId(requestDto.getReportId()); + ObjectId targetObjectId = new ObjectId(requestDto.getReportTargetId()); - // 신고 존재 확인 - ReportEntity report = reportRepository.findById(ReportId) - .orElseThrow(() -> new IllegalArgumentException("해당 신고가 존재하지 않습니다. ID: " + requestDto.getReportId())); + // 해당 신고 대상에 대한 모든 신고 가져오기 + List reports = reportRepository.findByReportTargetId(targetObjectId); + if (reports.isEmpty()) { + throw new NotFoundException("해당 신고 대상에 대한 신고가 존재하지 않습니다. 대상 ID: " + targetObjectId); + } + + // 처리되지 않은 신고만! + List pendingReports = reports.stream() + .filter(report -> report.getReportStatus() == ReportStatus.PENDING) + .toList(); - // 이미 처리된 신고인지 확인 - if (report.getReportStatus() == ReportStatus.SUSPENDED || report.getReportStatus() == ReportStatus.RESOLVED) { + if (pendingReports.isEmpty()) { throw new IllegalStateException("이미 처리된 신고입니다."); } - // 신고 처리 정보 생성 - ReportEntity.ReportActionEntity action = null; - if (requestDto.getSuspensionPeriod().equals(SuspensionPeriod.PERMANENT)){ - action = ReportEntity.ReportActionEntity.builder() - .actionTakenById(userId) - .suspensionPeriod(requestDto.getSuspensionPeriod()) - .suspensionEndDate(LocalDateTime.of(9999,12,31,23,59)) - .build(); - } - else action = ReportEntity.ReportActionEntity.builder() + // 정지 종료일 계산 + LocalDateTime suspensionEndDate = (requestDto.getSuspensionPeriod() == SuspensionPeriod.PERMANENT) + ? LocalDateTime.of(9999, 12, 31, 23, 59) + : LocalDateTime.now().plusDays(requestDto.getSuspensionPeriod().getDays()); + + // 신고 처리 정보 생성 + ReportEntity.ReportActionEntity action = ReportEntity.ReportActionEntity.builder() .actionTakenById(userId) .suspensionPeriod(requestDto.getSuspensionPeriod()) - .suspensionEndDate(LocalDateTime.now().plusDays(requestDto.getSuspensionPeriod().getDays())) + .suspensionEndDate(suspensionEndDate) .build(); - // 기존 객체의 필드를 직접 수정 (새 객체 생성 X) - report.updateReportSuspended(action); + // 신고 상태 업데이트 (모든 관련 신고 SUSPENDED로 변경) + pendingReports.forEach(report -> report.updateReportSuspended(action)); + + // 신고 대상 삭제 처리 (Soft Delete) + ReportTargetType targetType = reports.get(0).getReportTargetType(); + log.info("신고 대상 삭제 처리: 대상 ID: {}, 대상 타입: {}", targetObjectId, targetType); + deleteReportedEntity(targetObjectId, targetType); + //유저 Suspended - 정지 상태로 변경 - Optional user = userRepository.findById(report.getReportedUserId()); - if (user.isEmpty()) throw new NotFoundException("존재하지 않는 회원입니다."); - - user.get().suspendUser(); - //영구 정지 - if (requestDto.getSuspensionPeriod() == SuspensionPeriod.PERMANENT){ - user.get().updateTotalSuspensionEndDate(LocalDateTime.of(9999, 12 ,30, 23, 59)); - } else { - LocalDateTime totalSuspensionEndDate = user.get().getTotalSuspensionEndDate(); - user.get().updateTotalSuspensionEndDate( - Objects.requireNonNullElseGet(totalSuspensionEndDate, LocalDateTime::now) - .plusDays(requestDto.getSuspensionPeriod().getDays())); - } + ObjectId reportedUserId = reports.get(0).getReportedUserId(); + UserEntity user = userRepository.findById(reportedUserId) + .orElseThrow(() -> new NotFoundException("존재하지 않는 회원입니다.")); + + //유저 정지시키기 + user.suspendUser(); + user.updateTotalSuspensionEndDate( + (requestDto.getSuspensionPeriod().equals(SuspensionPeriod.PERMANENT)) + ? LocalDateTime.of(9999, 12, 30, 23, 59) + : Objects.requireNonNullElseGet(user.getTotalSuspensionEndDate(), LocalDateTime::now) + .plusDays(requestDto.getSuspensionPeriod().getDays()) + ); - deleteReportedEntity(report.getReportTargetId(), report.getReportTargetType()); // 업데이트된 신고 저장 - reportRepository.save(report); - userRepository.save(user.get()); - log.info("신고가 처리되었습니다. ID: {}, 처리자: {}", report.get_id(), userId); + reportRepository.saveAll(pendingReports); + userRepository.save(user); + log.info(" 신고 처리 완료: 신고 대상 ID: {}, reportedUserId: {}", requestDto.getReportTargetId(), reportedUserId); } @@ -270,28 +277,30 @@ public void deleteReportedEntity(ObjectId reportTargetId, ReportTargetType targe } } - public void resolveReport(String reportId) { - log.info(" 신고대상 유지 요청: 신고 ID: {}", reportId); + public void resolveReport(String reportTargetId) { + log.info(" 신고대상 유지 요청: 신고 ID: {}", reportTargetId); - ObjectId reportObjectId = new ObjectId(reportId); + ObjectId targetObjectId = new ObjectId(reportTargetId); ObjectId userId = SecurityUtils.getCurrentUserId(); // 현재 유저 ID // 신고 존재 확인 - ReportEntity report = reportRepository.findById(reportObjectId) - .orElseThrow(() -> new NotFoundException("해당 신고가 존재하지 않습니다. ID: " + reportId)); + List reports = reportRepository.findByReportTargetId(targetObjectId); - // 이미 처리된 신고인지 확인 (RESOLVED, SUSPENDED 상태면 예외 발생) - if (report.getReportStatus() == ReportStatus.RESOLVED || report.getReportStatus() == ReportStatus.SUSPENDED) { - throw new IllegalStateException("이미 처리된 신고입니다. 상태: " + report.getReportStatus()); + if (reports.isEmpty()) { + throw new NotFoundException("해당 신고 대상에 대한 신고가 존재하지 않습니다. 대상 ID: " + reportTargetId); } - // 신고 상태를 `RESOLVED`로 변경 - report.ReportResolved(userId); + //이미 RESOLVED 또는 SUSPENDED 상태인지 확인 후, 처리되지 않은 신고만 변경 + reports.stream() + .filter(report -> report.getReportStatus() == ReportStatus.PENDING) + .forEach(report -> { + report.ReportResolved(userId); // 신고 상태를 `RESOLVED`로 변경 + log.info(" 신고 유지 처리 완료: 신고 ID: {}", report.get_id()); + }); - // 변경된 신고 저장 - reportRepository.save(report); + reportRepository.saveAll(reports); - log.info(" 신고 유지 완료: 신고 ID: {}, 처리자: {}", report.get_id(), userId); + log.info("총 {}개의 신고가 유지 처리되었습니다. 대상 ID: {}", reports.size(), reportTargetId); } } From dfe13d1d2cc2050cde79089fe9ada8278b08ef77 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 04:05:34 +0900 Subject: [PATCH 0598/1002] =?UTF-8?q?refactor=20-=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/report/service/ReportService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 360f6f0b..baf6c0f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -16,6 +16,7 @@ import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; import inu.codin.codin.domain.report.entity.*; import inu.codin.codin.domain.report.exception.ReportAlreadyExistsException; +import inu.codin.codin.domain.report.exception.ReportUnsupportedTypeException; import inu.codin.codin.domain.report.repository.CustomReportRepository; import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -186,7 +187,7 @@ public void handleReport(ReportExecuteRequestDto requestDto) { .toList(); if (pendingReports.isEmpty()) { - throw new IllegalStateException("이미 처리된 신고입니다."); + throw new ReportAlreadyExistsException("이미 처리된 신고입니다."); } // 정지 종료일 계산 @@ -273,7 +274,7 @@ public void deleteReportedEntity(ObjectId reportTargetId, ReportTargetType targe replyRepository.save(reply); log.info("신고된 대댓글 삭제: {}", reportTargetId); } - default -> throw new IllegalArgumentException("잘못된 신고 대상 타입입니다."); + default -> throw new ReportUnsupportedTypeException("잘못된 신고 대상 타입입니다."); } } From fccfe0f750854a6dc7ee2015388913c2102a19b2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 9 Mar 2025 04:05:52 +0900 Subject: [PATCH 0599/1002] =?UTF-8?q?refactor=20-=20swagger=20operation=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/controller/PostController.java | 4 ++-- .../post/domain/comment/controller/CommentController.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index a94e8159..758d6b28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -112,7 +112,7 @@ public ResponseEntity> getPostWithDetail(@ } @Operation( - summary = "신고된 게시물 전체 조회" + summary = "신고된 게시물 전체 조회 - 관리자" ) @PreAuthorize("hasRole('ADMIN')") @GetMapping("/Allreported") @@ -124,7 +124,7 @@ public ResponseEntity> getAllReportedPosts(@Req } @Operation( - summary = "신고된 게시물 상세 조회" + summary = "신고된 게시물 상세 조회 - 관리자" ) @PreAuthorize("hasRole('ADMIN')") @GetMapping("/reported/posts/{postId}") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index c8503a68..d0ede6ff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -62,6 +62,7 @@ public ResponseEntity> updateComment(@PathVariable String comm } + @Operation(summary = "신고된 댓글 목록 조회 - 관리자") // 신고된 댓글 목록 조회 @PreAuthorize("hasRole('ADMIN')") @GetMapping("/{postId}/reported-comments") From a358b1fd87ff77823ba5cb0b9422f30fce4cd8a9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 10 Mar 2025 02:21:08 +0900 Subject: [PATCH 0600/1002] Update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 9cedff3e..a3835af8 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 9cedff3e092f7fd0d8d36c15be7a6ce32ad7c347 +Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 From 84f310b6ad53036647bf3b618ad220ed46bc5221 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 10 Mar 2025 02:21:27 +0900 Subject: [PATCH 0601/1002] =?UTF-8?q?docs=20:=20error=20log=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageService.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index 63f70170..79d78d03 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -71,6 +71,7 @@ private Result getResult(StompHeaderAccessor headerAccessor) { if (headerAccessor.getUser() != null) { email = headerAccessor.getUser().getName(); } else { + log.error("헤더에서 유저를 찾을 수 없습니다. command : {}, sessionId : {}", headerAccessor.getCommand(), headerAccessor.getSessionId()); throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); } log.info(headerAccessor.toString()); @@ -84,14 +85,23 @@ private Result getResult(StompHeaderAccessor headerAccessor) { } else chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { - throw new IllegalArgumentException("세션에서 가져올 수 없거나, 올바른 chatRoomId가 아닙니다: " + chatroomId); + log.error("chatRoomId을 찾을 수 없습니다. command : {}, sessionId : {}, chatRoomId : {}", + headerAccessor.getCommand(), headerAccessor.getSessionId(), chatroomId); + throw new NotFoundException("chatRoomId을 찾을 수 없습니다. chatroomId : " + chatroomId); } ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) - .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다.")); + .orElseThrow(() -> { + log.error("채팅방을 찾을 수 없습니다. command : {}, sessionId : {}, chatroomId : {}", + headerAccessor.getCommand(), headerAccessor.getSessionId(), chatroomId); + return new NotFoundException("채팅방을 찾을 수 없습니다."); + }); UserEntity user = userRepository.findByEmailAndStatusAll(email) - .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); - Result result = new Result(chatroom, user); - return result; + .orElseThrow(() -> { + log.error("유저를 찾을 수 없습니다. command : {}, sessionId : {}, email : {}", + headerAccessor.getCommand(), headerAccessor.getSessionId(), email); + return new NotFoundException("유저를 찾을 수 없습니다."); + }); + return new Result(chatroom, user); } From 6b4ecd04ba1a835b286c8d8691514b64cf5d6820 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 10 Mar 2025 13:03:31 +0900 Subject: [PATCH 0602/1002] =?UTF-8?q?fix=20:=20LectureResponseDto=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=97=90=20semesters,=20grade=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/dto/LectureDetailResponseDto.java | 8 ++++++-- .../lecture/dto/LectureListResponseDto.java | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java index 541527d7..42c80181 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java @@ -4,14 +4,16 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; +import java.util.List; + @Getter public class LectureDetailResponseDto extends LectureListResponseDto{ @Schema(description = "후기 평점들의 범위마다 100분율 계산", example = "hard : 30, ok : 20, best : 50") private Emotion emotion; - public LectureDetailResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, Emotion emotion) { - super(_id, lectureNm, professor, starRating, participants); + public LectureDetailResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, List semesters, int grade, Emotion emotion) { + super(_id, lectureNm, professor, starRating, participants, semesters, grade); this.emotion = emotion; } @@ -22,6 +24,8 @@ public static LectureDetailResponseDto of(LectureEntity lectureEntity, double av lectureEntity.getProfessor(), ave, //평균 평점 participants, //참여 인원 수 + lectureEntity.getSemester(), + lectureEntity.getGrade(), emotion //평점 당 인원 평균 ); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java index a80069d4..074d4b0c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java @@ -6,6 +6,9 @@ import lombok.Getter; import lombok.Setter; +import java.util.ArrayList; +import java.util.List; + @Setter @Getter public class LectureListResponseDto { @@ -25,13 +28,21 @@ public class LectureListResponseDto { @Schema(description = "수강 후기 작성자 수", example = "10") private long participants; + @Schema(description = "해당 강의가 열린 학기들", example = "23-1, 24-1, 25-1") + private List semesters; + + @Schema(description = "학년", example = "2") + private int grade; + @Builder - public LectureListResponseDto(String _id, String lectureNm, String professor, double starRating, long participants) { + public LectureListResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, List semesters, int grade) { this._id = _id; this.lectureNm = lectureNm; this.professor = professor; this.starRating = starRating; this.participants = participants; + this.semesters = semesters; + this.grade = grade; } public static LectureListResponseDto of(LectureEntity lectureEntity, double starRating, long participants){ @@ -41,6 +52,8 @@ public static LectureListResponseDto of(LectureEntity lectureEntity, double star .professor(lectureEntity.getProfessor()) .starRating(starRating) .participants(participants) + .semesters(lectureEntity.getSemester()) + .grade(lectureEntity.getGrade()) .build(); } } From d79bfc1e00eee49e1c47a19d356e2fc94ee0f95c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 00:57:53 +0900 Subject: [PATCH 0603/1002] =?UTF-8?q?fix=20:=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20PATH=20=EB=B9=84=EA=B5=90=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20index=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/filter/JwtAuthenticationFilter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 9a4c8a26..b45e9737 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -9,14 +9,13 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.List; /** * JWT 토큰을 검증하여 인증하는 필터 @@ -28,13 +27,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; private final JwtUtils jwtUtils; private final PermitAllProperties permitAllProperties; + private final AntPathMatcher pathMatcher = new AntPathMatcher(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestURI = request.getRequestURI(); - if (permitAllProperties.getUrls().stream().anyMatch(url -> url.split("/")[1].startsWith(requestURI.split("/")[1]))) { + if (permitAllProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, requestURI))) { filterChain.doFilter(request, response); return; } From dda1d50bf4dba41754c2345fde123a1961d316c4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 01:04:18 +0900 Subject: [PATCH 0604/1002] =?UTF-8?q?docs=20:=20OAuth2LoginFailureHandler?= =?UTF-8?q?=20log=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/util/OAuth2LoginFailureHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index c6f30a32..75fa3a89 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -12,6 +13,7 @@ import java.io.PrintWriter; @Component +@Slf4j public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, @@ -31,6 +33,7 @@ public void onAuthenticationFailure(HttpServletRequest request, PrintWriter writer = response.getWriter(); writer.write("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); + log.error("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); writer.flush(); } } From 84a035888c40c39ebfb7ab9a5e29fdb33e8be559 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 14:57:20 +0900 Subject: [PATCH 0605/1002] =?UTF-8?q?fix=20:=20Chatroom.java=EC=97=90=20re?= =?UTF-8?q?ferenceId=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=96=B4=EB=94=94=EC=97=90=EC=84=9C=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=EC=9D=B4=20=EC=8B=9C=EC=9E=91=EB=90=98=EC=97=88?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/entity/ChatRoom.java | 7 ++++++- .../exception/ChatRoomExistedException.java | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 08f5602e..8eb503a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -22,6 +22,9 @@ public class ChatRoom extends BaseTimeEntity { @NotBlank private String roomName; + @NotBlank + private ObjectId referenceId; //채팅방이 시작한 곳의 id + @NotBlank private Participants participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) @@ -29,8 +32,9 @@ public class ChatRoom extends BaseTimeEntity { @Builder - public ChatRoom(String roomName, Participants participants, String lastMessage) { + public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage) { this.roomName = roomName; + this.referenceId = referenceId; this.participants = participants; this.lastMessage = lastMessage; } @@ -41,6 +45,7 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obj participants.create(new ObjectId(chatRoomCreateRequestDto.getReceiverId())); return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) + .referenceId(new ObjectId(chatRoomCreateRequestDto.getReferenceId())) .participants(participants) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java new file mode 100644 index 00000000..3daa6779 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.chat.chatroom.exception; + +import lombok.Getter; +import org.bson.types.ObjectId; + +@Getter +public class ChatRoomExistedException extends RuntimeException{ + + private int errorCode; + private ObjectId chatRoomId; + + public ChatRoomExistedException(String msg, int errorCode, ObjectId chatRoomId){ + super(msg); + this.errorCode = errorCode; + this.chatRoomId = chatRoomId; + } +} From 467e5df30e2270f72f14a543f875911b5bf33e5e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 14:57:43 +0900 Subject: [PATCH 0606/1002] =?UTF-8?q?Revert=20"fix=20:=20Chatroom.java?= =?UTF-8?q?=EC=97=90=20referenceId=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=96=B4=EB=94=94=EC=97=90=EC=84=9C=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=EC=9D=B4=20=EC=8B=9C=EC=9E=91=EB=90=98?= =?UTF-8?q?=EC=97=88=EB=8A=94=EC=A7=80=20=ED=8C=90=EB=8B=A8"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 84a035888c40c39ebfb7ab9a5e29fdb33e8be559. --- .../domain/chat/chatroom/entity/ChatRoom.java | 7 +------ .../exception/ChatRoomExistedException.java | 17 ----------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 8eb503a6..08f5602e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -22,9 +22,6 @@ public class ChatRoom extends BaseTimeEntity { @NotBlank private String roomName; - @NotBlank - private ObjectId referenceId; //채팅방이 시작한 곳의 id - @NotBlank private Participants participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) @@ -32,9 +29,8 @@ public class ChatRoom extends BaseTimeEntity { @Builder - public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage) { + public ChatRoom(String roomName, Participants participants, String lastMessage) { this.roomName = roomName; - this.referenceId = referenceId; this.participants = participants; this.lastMessage = lastMessage; } @@ -45,7 +41,6 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obj participants.create(new ObjectId(chatRoomCreateRequestDto.getReceiverId())); return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) - .referenceId(new ObjectId(chatRoomCreateRequestDto.getReferenceId())) .participants(participants) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java deleted file mode 100644 index 3daa6779..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java +++ /dev/null @@ -1,17 +0,0 @@ -package inu.codin.codin.domain.chat.chatroom.exception; - -import lombok.Getter; -import org.bson.types.ObjectId; - -@Getter -public class ChatRoomExistedException extends RuntimeException{ - - private int errorCode; - private ObjectId chatRoomId; - - public ChatRoomExistedException(String msg, int errorCode, ObjectId chatRoomId){ - super(msg); - this.errorCode = errorCode; - this.chatRoomId = chatRoomId; - } -} From 24496a6e9c2c1ec33ee21bbcf53a7cb600ae219c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 14:58:23 +0900 Subject: [PATCH 0607/1002] =?UTF-8?q?fix=20:=20Chatroom.java=EC=97=90=20re?= =?UTF-8?q?ferenceId=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=96=B4=EB=94=94=EC=97=90=EC=84=9C=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=EC=9D=B4=20=EC=8B=9C=EC=9E=91=EB=90=98=EC=97=88?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/chat/chatroom/entity/ChatRoom.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 08f5602e..8eb503a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -22,6 +22,9 @@ public class ChatRoom extends BaseTimeEntity { @NotBlank private String roomName; + @NotBlank + private ObjectId referenceId; //채팅방이 시작한 곳의 id + @NotBlank private Participants participants; //참가자들의 userId (1:1 채팅에서는 두 명의 id만 들어감) @@ -29,8 +32,9 @@ public class ChatRoom extends BaseTimeEntity { @Builder - public ChatRoom(String roomName, Participants participants, String lastMessage) { + public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage) { this.roomName = roomName; + this.referenceId = referenceId; this.participants = participants; this.lastMessage = lastMessage; } @@ -41,6 +45,7 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obj participants.create(new ObjectId(chatRoomCreateRequestDto.getReceiverId())); return ChatRoom.builder() .roomName(chatRoomCreateRequestDto.getRoomName()) + .referenceId(new ObjectId(chatRoomCreateRequestDto.getReferenceId())) .participants(participants) .build(); } From 25294aaca762282f3e1bbca33b01c1a321de841b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 14:59:10 +0900 Subject: [PATCH 0608/1002] =?UTF-8?q?fix=20:=20referenceId=EA=B3=BC=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=B0=B8=EC=97=AC=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20id=EA=B0=80=20=EB=91=98=20=EB=8B=A4=20=EC=9D=BC?= =?UTF-8?q?=EC=B9=98=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20403=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/ChatRoomCreateRequestDto.java | 4 ++++ .../exception/ChatRoomExistedException.java | 17 ++++++++++++++++ .../repository/ChatRoomRepository.java | 3 +++ .../chatroom/service/ChatRoomService.java | 20 +++++++++++++++---- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java index 2a7b94d2..ac998c98 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java @@ -16,4 +16,8 @@ public class ChatRoomCreateRequestDto { @NotBlank @Schema(description = "채팅 수신자", example = "1111111") private String receiverId; + + @NotBlank + @Schema(description = "채팅이 시작된 게시글, 댓글, 댓글의 id", example = "65asdf") + private String referenceId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java new file mode 100644 index 00000000..99617999 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/exception/ChatRoomExistedException.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.chat.chatroom.exception; + +import lombok.Getter; +import org.bson.types.ObjectId; + +@Getter +public class ChatRoomExistedException extends RuntimeException{ + + private final int errorCode; + private final ObjectId chatRoomId; + + public ChatRoomExistedException(String msg, int errorCode, ObjectId chatRoomId){ + super(msg); + this.errorCode = errorCode; + this.chatRoomId = chatRoomId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java index e9b15c0e..d3db3f56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/repository/ChatRoomRepository.java @@ -15,4 +15,7 @@ public interface ChatRoomRepository extends MongoRepository { @Query("{ 'participants.info.?0.userId': ?0, 'participants.info.?0.isLeaved': false, 'deletedAt': null }") List findByParticipantIsNotLeavedAndDeletedIsNull(ObjectId userId); + + @Query("{ 'referenceId': ?0, 'participants.info.?1.userId': ?1, 'participants.info.?2.userId': ?2, 'deletedAt': null }") + Optional findByReferenceIdAndParticipantsContaining(ObjectId referenceId, ObjectId userId, ObjectId receiverId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 6335576e..3ee36127 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomCreateFailException; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.notification.service.NotificationService; @@ -22,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -38,11 +40,9 @@ public class ChatRoomService { public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto) { ObjectId senderId = SecurityUtils.getCurrentUserId(); - if (senderId.toString().equals(chatRoomCreateRequestDto.getReceiverId())){ - throw new ChatRoomCreateFailException("자기 자신과는 채팅방을 생성할 수 없습니다."); - } - log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); + isValidated(chatRoomCreateRequestDto, senderId); //유효성 검사 + log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); userRepository.findById(new ObjectId(chatRoomCreateRequestDto.getReceiverId())) .orElseThrow(() -> { log.error("[Receive 유저 확인 실패] 수신자 ID: {}를 찾을 수 없습니다.", chatRoomCreateRequestDto.getReceiverId()); @@ -63,6 +63,18 @@ public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreat return response; } + private void isValidated(ChatRoomCreateRequestDto chatRoomCreateRequestDto, ObjectId senderId) { + if (senderId.toString().equals(chatRoomCreateRequestDto.getReceiverId())){ + throw new ChatRoomCreateFailException("자기 자신과는 채팅방을 생성할 수 없습니다."); + } + + Optional existedChatroom = chatRoomRepository.findByReferenceIdAndParticipantsContaining(new ObjectId(chatRoomCreateRequestDto.getReferenceId()), + senderId, new ObjectId(chatRoomCreateRequestDto.getReceiverId())); + if (existedChatroom.isPresent()){ + throw new ChatRoomExistedException("해당 reference에서 시작된 채팅방이 존재합니다.", 403, existedChatroom.get().get_id()); + } + } + public List getAllChatRoomByUser() { ObjectId userId = SecurityUtils.getCurrentUserId(); log.info("[유저의 채팅방 조회] 유저 ID: {}", userId); From b16e0fee59ea609084ba8fadd10d90ebd9b5b754 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 14:59:26 +0900 Subject: [PATCH 0609/1002] =?UTF-8?q?fix=20:=20GlobalExceptionHandler?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20403=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/exception/GlobalExceptionHandler.java | 7 +++++++ .../inu/codin/codin/common/response/ExceptionResponse.java | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 0ea9336c..a3ae2154 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -38,4 +39,10 @@ public ResponseEntity handleValidationExceptions(MethodArgume return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST.value())); } + @ExceptionHandler(ChatRoomExistedException.class) + public ResponseEntity handleChatRoomExistedException(ChatRoomExistedException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ExceptionResponse(e.getMessage() +"/"+ e.getChatRoomId(), e.getErrorCode())); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java index afa36599..b9af8553 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java @@ -1,12 +1,10 @@ package inu.codin.codin.common.response; -import lombok.Builder; import lombok.Getter; @Getter public class ExceptionResponse extends CommonResponse { - @Builder public ExceptionResponse(String message, int code) { super(false, code, message); } From 2f5629ede116f76ee9f04ae42151d0a646d96589 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 11 Mar 2025 15:32:51 +0900 Subject: [PATCH 0610/1002] =?UTF-8?q?fix=20:=20=EC=B5=9C=EA=B7=BC=20update?= =?UTF-8?q?=EB=90=9C=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=EC=88=9C=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/service/ChatRoomService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 3ee36127..75528c7b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.service; +import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; @@ -20,10 +21,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; @Service @RequiredArgsConstructor @@ -86,7 +84,9 @@ public List getAllChatRoomByUser() { return chatRooms.stream() .filter(chatRoom -> chatRoom.getParticipants().getInfo().keySet().stream() .noneMatch(blockedUsersId::contains)) + .sorted(Comparator.comparing(BaseTimeEntity::getUpdatedAt,Comparator.reverseOrder())) .map(chatRoom -> ChatRoomListResponseDto.of(chatRoom, userId)).toList(); + } public void leaveChatRoom(String chatRoomId) { From 5cb1f04f9c47d1eaf002147a63c13d2f2c06ee19 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 01:39:30 +0900 Subject: [PATCH 0611/1002] =?UTF-8?q?fix=20::=20comment=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20UserId=20:=20Null=20=EB=A1=9C=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/comment/dto/response/CommentResponseDTO.java | 1 + .../domain/post/domain/comment/service/CommentService.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index c9b94602..aea7d9dc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -109,6 +109,7 @@ public CommentResponseDTO repliesFrom(List upd return CommentResponseDTO.builder() ._id(this._id) + .userId(this.userId) .content(this.content) .nickname(this.nickname) .userImageUrl(this.userImageUrl) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 079cfa49..a297384e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -197,15 +197,16 @@ public List getReportedCommentsByPostId(String return comments.stream() .map(comment -> { + log.info("commentUserId: {}", comment.getUserId()); ObjectId ReportTargetId = new ObjectId(reportedEntityId); boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); boolean isCommentReported = existsInReportDB && comment.get_id().equals(reportedEntityId); log.info("🔸 댓글 ID: {}, 신고 여부: {}", comment.get_id(), isCommentReported); - // ✅ 대댓글 리스트 변환 (신고 여부 반영) + // 대댓글 리스트 변환 (신고 여부 반영) List reportedReplies = replyCommentService.getReportedRepliesByCommentId(comment.get_id(), reportedEntityId); - // ✅ `CommentResponseDTO`에서 `ReportedCommentResponseDTO`로 변환하여 신고 여부 추가 + // `CommentResponseDTO`에서 `ReportedCommentResponseDTO`로 변환하여 신고 여부 추가 return ReportedCommentDetailResponseDTO.from(comment.repliesFrom(reportedReplies), isCommentReported); }) .toList(); From 69ba4c3568af26bdae1294294e15515778b6be99 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 02:52:21 +0900 Subject: [PATCH 0612/1002] =?UTF-8?q?refactor=20::=20post=20->=20Report=20?= =?UTF-8?q?=20=EC=8B=A0=EA=B3=A0=EC=A1=B0=ED=9A=8C=20api=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/service/PostService.java | 72 +-------------- .../report/controller/ReportController.java | 38 ++++++++ .../domain/report/service/ReportService.java | 91 ++++++++++++++++++- 3 files changed, 131 insertions(+), 70 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 883a3739..85ee808e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -326,75 +326,9 @@ public PostPageResponse getBestPosts(int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - - - public PostPageResponse getAllReportedPosts(int pageNumber) { - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - - List reportInfos = reportRepository.findAllReportedEntities(); - - // Page 변환 - int start = Math.min((int) pageRequest.getOffset(), reportInfos.size()); - int end = Math.min((start + pageRequest.getPageSize()), reportInfos.size()); - Page reportInfoPage = new PageImpl<>(reportInfos.subList(start, end), pageRequest, reportInfos.size()); - - - List reportedPosts = reportInfoPage.getContent().stream() - .map(this::getReportedPostDetail) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); - - long lastPage = reportInfoPage.getTotalPages() - 1; - long nextPage = reportInfoPage.hasNext() ? pageNumber + 1 : -1; - - return PostPageResponse.of(reportedPosts, lastPage, nextPage); - } - - // 신고된 개별 엔터티를 PostReportDetailResponse로 변환하는 메서드 - private Optional getReportedPostDetail(ReportInfo reportInfo) { - ObjectId entityId = new ObjectId(reportInfo.getReportedEntityId()); - - return switch (reportInfo.getEntityType()) { - case POST -> postRepository.findById(entityId) - .map(this::createPostDetailResponse) - .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)); - - case COMMENT -> commentRepository.findById(entityId) - .flatMap(comment -> postRepository.findById(comment.getPostId()) - .map(this::createPostDetailResponse) - .map(postDTO -> PostReportResponse.from(postDTO, reportInfo))); - - case REPLY -> replyCommentRepository.findById(entityId) - .flatMap(reply -> commentRepository.findById(reply.getCommentId()) - .flatMap(comment -> postRepository.findById(comment.getPostId()) - .map(this::createPostDetailResponse) - .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)))); - - default -> Optional.empty(); - }; - } - - //신고된 게시물 상세조회 - public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, String reportedEntityId) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - - ObjectId userId = SecurityUtils.getCurrentUserId(); - if (hitsService.validateHits(post.get_id(), userId)) { - hitsService.addHits(post.get_id(), userId); - log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); - } - - // 게시글이 신고된 경우 표시 추가 - ObjectId ReportTargetId = new ObjectId(reportedEntityId); - boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); - boolean isReported = existsInReportDB && post.get_id().equals(ReportTargetId); - - PostDetailResponseDTO postDetailResponse = createPostDetailResponse(post); - //여기에 isReported 추가 - - return ReportedPostDetailResponseDTO.from(isReported, postDetailResponse); + public Optional getPostDetailById(ObjectId postId) { + return postRepository.findById(postId) + .map(this::createPostDetailResponse); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 572a609f..e9397a7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,7 +1,10 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; +import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; @@ -27,6 +30,7 @@ @Tag(name = "Reoprt API", description = "사용자 신고 기능") public class ReportController { private final ReportService reportService; + private final CommentService commentService; //(User)신고 작성 /** @@ -113,4 +117,38 @@ public ResponseEntity resolveReport( .body(new SingleResponse<>(200, "(관리자) 신고 처리 - 유지하기",null)); } + + + @Operation(summary = "모든 신고 조회하기 - 오름차순 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/list") + public ResponseEntity getAllReportedPosts(@RequestParam("page") @NotNull int pageNumber){ + PostPageResponse postpages = reportService.getAllReportedPosts(pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "(관리자) 신고 전체 조회",postpages)); + } + + + @Operation(summary = "게시글 신고 상세조회 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/post/{postId}/{reportedEntityId}") + public ResponseEntity getReportedPost (@PathVariable String postId, + @PathVariable String reportedEntityId) { + ReportedPostDetailResponseDTO responseDTO = reportService.getReportedPostWithDetail(postId, reportedEntityId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "(관리자) 신고 게시글 상세 조회",responseDTO)); + } + + @Operation(summary = "댓글,대댓글 신고 상세조회 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/comments/{postId}/{reportedEntityId}") + public ResponseEntity getReportedComments(@PathVariable String postId, + @PathVariable String reportedEntityId){ + List responseDTOS = reportService.getReportedCommentsByPostId(postId, reportedEntityId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "(관리자) 신고 댓글/대댓글 상세 조회",responseDTOS)); + } + + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index baf6c0f0..0b1d3c07 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -3,12 +3,22 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.PostReportResponse; +import inu.codin.codin.domain.post.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostService; +import inu.codin.codin.domain.report.dto.ReportInfo; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; @@ -22,10 +32,15 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.Document; import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,12 +54,16 @@ @Slf4j public class ReportService { - private final BlockService blockService; + private final PostService postService; + private final CommentService commentService; + private final ReplyCommentService replyCommentService; + private final ReportRepository reportRepository; private final UserRepository userRepository; private final PostRepository postRepository; private final CommentRepository commentRepository; private final ReplyCommentRepository replyRepository; + private final ReplyCommentRepository replyCommentRepository; private final CustomReportRepository customReportRepository; @@ -303,5 +322,75 @@ public void resolveReport(String reportTargetId) { log.info("총 {}개의 신고가 유지 처리되었습니다. 대상 ID: {}", reports.size(), reportTargetId); } + + + /*** + * + * 신고 조회 로직 + * + */ + + public PostPageResponse getAllReportedPosts(int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + + List reportInfos = reportRepository.findAllReportedEntities(); + + // 페이지 변환 + int start = Math.min((int) pageRequest.getOffset(), reportInfos.size()); + int end = Math.min((start + pageRequest.getPageSize()), reportInfos.size()); + Page reportInfoPage = new PageImpl<>(reportInfos.subList(start, end), pageRequest, reportInfos.size()); + + // 신고된 엔터티 조회 및 변환 + List reportedPosts = reportInfoPage.getContent().stream() + .map(this::getReportedPostDetail) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + long lastPage = reportInfoPage.getTotalPages() - 1; + long nextPage = reportInfoPage.hasNext() ? pageNumber + 1 : -1; + + return PostPageResponse.of(reportedPosts, lastPage, nextPage); + } + + private Optional getReportedPostDetail(ReportInfo reportInfo) { + ObjectId entityId = new ObjectId(reportInfo.getReportedEntityId()); + + return switch (reportInfo.getEntityType()) { + case POST -> postService.getPostDetailById(entityId) // ✅ PostService 활용 + .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)); + + case COMMENT -> commentRepository.findById(entityId) + .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) + .map(postDTO -> PostReportResponse.from(postDTO, reportInfo))); + + case REPLY -> replyCommentRepository.findById(entityId) + .flatMap(reply -> commentRepository.findById(reply.getCommentId()) + .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) + .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)))); + + default -> Optional.empty(); + }; + } + + public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, String reportedEntityId) { + ObjectId entityId = new ObjectId(postId); + ObjectId reportTargetId = new ObjectId(reportedEntityId); + + // 게시글이 신고된 경우 표시 추가 + boolean existsInReportDB = reportRepository.existsByReportTargetId(reportTargetId); + if (!existsInReportDB) { + throw new NotFoundException("해당 신고 대상이 존재하지 않습니다. 신고 ID: " + reportedEntityId); + } + PostDetailResponseDTO postDetailResponse = postService.getPostDetailById(entityId) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + boolean isReported = entityId.equals(reportTargetId); + + + return ReportedPostDetailResponseDTO.from(isReported, postDetailResponse); + } + + + } From 472672e714fe7862afc308ee3caea919248b61a6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 02:52:47 +0900 Subject: [PATCH 0613/1002] =?UTF-8?q?refactor=20::=20comment,Reply=20->=20?= =?UTF-8?q?Report=20=20=EC=8B=A0=EA=B3=A0=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 4 +-- .../comment/service/CommentService.java | 30 ---------------- .../reply/service/ReplyCommentService.java | 16 --------- .../domain/report/service/ReportService.java | 36 ++++++++++++++++++- 4 files changed, 37 insertions(+), 49 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index d0ede6ff..a549d396 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -69,10 +69,10 @@ public ResponseEntity> updateComment(@PathVariable String comm public ResponseEntity>> getReportedCommentsByPostId( @PathVariable String postId, @RequestParam String reportedEntityId) { - + List responseDTOS = commentService.getReportedCommentsByPostId(postId, reportedEntityId); return ResponseEntity.ok(new SingleResponse<>( 200, "신고된 댓글 조회 성공", - commentService.getReportedCommentsByPostId(postId, reportedEntityId) + responseDTOS )); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index a297384e..84ac9d29 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -182,34 +182,4 @@ public UserInfo getUserInfoAboutPost(ObjectId commentId) { } -// public List getReportedCommentsByPostId(String postId, String reportedEntityId) { -// // 기존 댓글 목록 조회 -// List comments = getCommentsByPostId(postId); -// -// // 신고 여부 추가 -// return comments.stream() -// .map(comment -> ReportedCommentDetailResponseDTO.from( comment.get_id().equals(reportedEntityId), comment)) -// .toList(); -// } - - public List getReportedCommentsByPostId(String postId, String reportedEntityId) { - List comments = getCommentsByPostId(postId); - - return comments.stream() - .map(comment -> { - log.info("commentUserId: {}", comment.getUserId()); - ObjectId ReportTargetId = new ObjectId(reportedEntityId); - boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); - boolean isCommentReported = existsInReportDB && comment.get_id().equals(reportedEntityId); - log.info("🔸 댓글 ID: {}, 신고 여부: {}", comment.get_id(), isCommentReported); - - // 대댓글 리스트 변환 (신고 여부 반영) - List reportedReplies = replyCommentService.getReportedRepliesByCommentId(comment.get_id(), reportedEntityId); - - // `CommentResponseDTO`에서 `ReportedCommentResponseDTO`로 변환하여 신고 여부 추가 - return ReportedCommentDetailResponseDTO.from(comment.repliesFrom(reportedReplies), isCommentReported); - }) - .toList(); - } - } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 54e9551e..fcf9f15a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -170,20 +170,4 @@ public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { } - public List getReportedRepliesByCommentId(String id, String reportedEntityId) { - ObjectId commentId = new ObjectId(id); - List replies = getRepliesByCommentId(commentId); - - return replies.stream() - .map(reply -> { - ObjectId ReportTargetId = new ObjectId(reportedEntityId); - boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); - boolean isReplyReported = existsInReportDB && reply.get_id().equals(reportedEntityId); - - log.info("🔹 대댓글 ID: {}, 신고 여부: {}", reply.get_id(), isReplyReported); - - return ReportedCommentDetailResponseDTO.from(reply, isReplyReported); - }) - .toList(); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 0b1d3c07..a3e1281a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -391,6 +391,40 @@ public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, St } - + public List getReportedCommentsByPostId(String postId, String reportedEntityId) { + List comments = commentService.getCommentsByPostId(postId); + + return comments.stream() + .map(comment -> { + ObjectId ReportTargetId = new ObjectId(reportedEntityId); + boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); + boolean isCommentReported = existsInReportDB && comment.get_id().equals(reportedEntityId); + log.info("🔸 댓글 ID: {}, 신고 여부: {}", comment.get_id(), isCommentReported); + + // 대댓글 리스트 변환 (신고 여부 반영) + List reportedReplies = getReportedRepliesByCommentId(comment.get_id(), reportedEntityId); + + // `CommentResponseDTO`에서 `ReportedCommentResponseDTO`로 변환하여 신고 여부 추가 + return ReportedCommentDetailResponseDTO.from(comment.repliesFrom(reportedReplies), isCommentReported); + }) + .toList(); + } + + public List getReportedRepliesByCommentId(String id, String reportedEntityId) { + ObjectId commentId = new ObjectId(id); + List replies = replyCommentService.getRepliesByCommentId(commentId); + + return replies.stream() + .map(reply -> { + ObjectId ReportTargetId = new ObjectId(reportedEntityId); + boolean existsInReportDB = reportRepository.existsByReportTargetId(ReportTargetId); + boolean isReplyReported = existsInReportDB && reply.get_id().equals(reportedEntityId); + + log.info("🔹 대댓글 ID: {}, 신고 여부: {}", reply.get_id(), isReplyReported); + + return ReportedCommentDetailResponseDTO.from(reply, isReplyReported); + }) + .toList(); + } } From 83e5b3815307f695da95120c69b5f814f3ef49a6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 03:05:18 +0900 Subject: [PATCH 0614/1002] =?UTF-8?q?refactor=20::=20comment,post=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EA=B4=80=EB=A0=A8=20api=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 --- .../post/controller/PostController.java | 28 ------------------- .../comment/controller/CommentController.java | 13 --------- 2 files changed, 41 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 758d6b28..7e5a2af5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -111,34 +111,6 @@ public ResponseEntity> getPostWithDetail(@ .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } - @Operation( - summary = "신고된 게시물 전체 조회 - 관리자" - ) - @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/Allreported") - public ResponseEntity> getAllReportedPosts(@RequestParam("page") @NotNull int pageNumber) { - PostPageResponse postpages= postService.getAllReportedPosts(pageNumber); - return ResponseEntity.ok() - .body(new SingleResponse<>( - 200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); - } - - @Operation( - summary = "신고된 게시물 상세 조회 - 관리자" - ) - @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/reported/posts/{postId}") - public ResponseEntity> getPostWithDetail( - @PathVariable String postId, - @RequestParam(required = false) String reportedEntityId) { - - ReportedPostDetailResponseDTO responseDTO=postService.getReportedPostWithDetail(postId, reportedEntityId); - return ResponseEntity.ok(new SingleResponse<>( - 200, "게시글 상세 조회 성공",responseDTO - )); - } - - @Operation(summary = "게시물 이미지 삭제") @DeleteMapping("/{postId}/images") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index a549d396..610ba0de 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -62,17 +62,4 @@ public ResponseEntity> updateComment(@PathVariable String comm } - @Operation(summary = "신고된 댓글 목록 조회 - 관리자") - // 신고된 댓글 목록 조회 - @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/{postId}/reported-comments") - public ResponseEntity>> getReportedCommentsByPostId( - @PathVariable String postId, - @RequestParam String reportedEntityId) { - List responseDTOS = commentService.getReportedCommentsByPostId(postId, reportedEntityId); - return ResponseEntity.ok(new SingleResponse<>( - 200, "신고된 댓글 조회 성공", - responseDTOS - )); - } } \ No newline at end of file From 2ed46ba86d614ca4ff4c1e933c47c9f1b2bf52a9 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 03:05:42 +0900 Subject: [PATCH 0615/1002] =?UTF-8?q?refactor=20::=20post,comment=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=EC=A1=B0=ED=9A=8C=20dto=20report=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/CommentResponseDTO.java | 2 + .../report/controller/ReportController.java | 14 +- .../dto/response/ReportListResponseDto.java | 161 ++++-------------- .../dto/response/ReportPageResponse.java | 64 +++---- .../ReportedCommentDetailResponseDTO.java | 20 +++ .../ReportedPostDetailResponseDTO.java | 26 +++ .../domain/report/service/ReportService.java | 28 ++- 7 files changed, 135 insertions(+), 180 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedCommentDetailResponseDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index aea7d9dc..76b941a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -101,6 +102,7 @@ public static CommentResponseDTO replyOf(ReplyCommentEntity replyCommentEntity, .userInfo(userInfoAboutPost) .build(); } + // 기존 객체에서 replies 리스트만 변경 public CommentResponseDTO repliesFrom(List updatedReplies) { List commentReplies = updatedReplies.stream() diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index e9397a7c..e7d506b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,16 +1,14 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; + import inu.codin.codin.domain.post.domain.comment.service.CommentService; -import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.post.dto.response.ReportedPostDetailResponseDTO; + + import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; -import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; -import inu.codin.codin.domain.report.dto.response.ReportResponseDto; -import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; -import inu.codin.codin.domain.report.entity.ReportTargetType; +import inu.codin.codin.domain.report.dto.response.*; + import inu.codin.codin.domain.report.service.ReportService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -123,7 +121,7 @@ public ResponseEntity resolveReport( @PreAuthorize("hasRole('ADMIN')") @GetMapping("/list") public ResponseEntity getAllReportedPosts(@RequestParam("page") @NotNull int pageNumber){ - PostPageResponse postpages = reportService.getAllReportedPosts(pageNumber); + ReportPageResponse postpages = reportService.getAllReportedPosts(pageNumber); return ResponseEntity.ok() .body(new SingleResponse<>(200, "(관리자) 신고 전체 조회",postpages)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java index 60ed075e..92c4b5ad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java @@ -1,124 +1,37 @@ -//package inu.codin.codin.domain.report.dto.response; -// -//import com.fasterxml.jackson.annotation.JsonFormat; -//import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -//import inu.codin.codin.domain.post.entity.PostCategory; -//import inu.codin.codin.domain.post.entity.PostEntity; -//import io.swagger.v3.oas.annotations.media.Schema; -//import jakarta.validation.constraints.NotBlank; -//import jakarta.validation.constraints.NotNull; -//import lombok.Builder; -//import lombok.Getter; -// -//import java.time.LocalDateTime; -//import java.util.List; -// -//@Getter -//public class ReportListResponseDto { -// @Schema(description = "게시물 ID", example = "111111") -// @NotBlank -// private final String _id; -// -// @Schema(description = "유저 ID", example = "111111") -// @NotBlank -// private final String userId; -// -// @Schema(description = "게시물 종류", example = "구해요") -// @NotBlank -// private final PostCategory postCategory; -// -// @Schema(description = "게시물 제목", example = "Example") -// @NotBlank -// private final String title; -// -// @Schema(description = "게시물 내용", example = "example content") -// @NotBlank -// private final String content; -// -// @Schema(description = "유저 nickname 익명시 익명으로 표시됨") -// private final String nickname; -// -// @Schema(description = "유저 이미지 url", example = "https://~") -// private final String userImageUrl; -// -// @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") -// private final List postImageUrl; -// -// @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") -// @NotNull -// private final boolean isAnonymous; -// -// @Schema(description = "좋아요 count", example = "0") -// private final int likeCount; -// -// @Schema(description = "스크랩 count", example = "0") -// private final int scrapCount; -// -// @Schema(description = "댓글 및 대댓글 count", example = "0") -// private final int commentCount; -// -// @Schema(description = "조회수", example = "0") -// private final int hits; -// -// private final int reportCount; -// -// @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") -// @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") -// private final LocalDateTime createdAt; -// -// @Schema(description = "해당 게시글에 대한 유저 반응 여부") -// private final UserInfo userInfo; -// -// public ReportListResponseDto(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, -// boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, int reportCount, UserInfo userInfo){ -// this.userId = userId; -// this._id = _id; -// this.title = title; -// this.content = content; -// this.nickname = nickname; -// this.postCategory = postCategory; -// this.userImageUrl = userImageUrl; -// this.postImageUrl = postImageUrls; -// this.isAnonymous = isAnonymous; -// this.likeCount = likeCount; -// this.scrapCount = scrapCount; -// this.commentCount = commentCount; -// this.reportCount = reportCount; -// this.hits = hits; -// this.createdAt = createdAt; -// this.userInfo = userInfo; -// } -// -// @Getter -// public static class UserInfo { -// private final boolean isLike; -// private final boolean isScrap; -// -// @Builder -// public UserInfo(boolean isLike, boolean isScrap) { -// this.isLike = isLike; -// this.isScrap = isScrap; -// } -// } -// -// public static ReportListResponseDto of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount, int reportCount, UserInfo userInfo) { -// return new ReportListResponseDto( -// post.getUserId().toString(), -// post.get_id().toString(), -// post.getTitle(), -// post.getContent(), -// nickname, -// post.getPostCategory(), -// userImageUrl, -// post.getPostImageUrls(), -// post.isAnonymous(), -// likeCount, -// scrapCount, -// hitsCount, -// post.getCreatedAt(), -// commentCount, -// reportCount, -// userInfo); -// } -//} -// +package inu.codin.codin.domain.report.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.report.dto.ReportInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List;@Getter + +public class ReportListResponseDto extends PostDetailResponseDTO { + + private final ReportInfo reportInfo; + + @Builder + public ReportListResponseDto(PostDetailResponseDTO baseDTO, ReportInfo reportInfo) { + super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), + baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), + baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), + baseDTO.getCommentCount(), baseDTO.getUserInfo()); + this.reportInfo = reportInfo; + } + + + public static ReportListResponseDto from(PostDetailResponseDTO base, ReportInfo reportInfo) { + return ReportListResponseDto.builder() + .baseDTO(base) + .reportInfo(reportInfo) + .build(); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java index c9552582..b6abd04c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportPageResponse.java @@ -1,32 +1,32 @@ -//package inu.codin.codin.domain.report.dto.response; -// -//import lombok.AccessLevel; -//import lombok.Getter; -//import lombok.NoArgsConstructor; -// -//import java.util.ArrayList; -//import java.util.List; -// -//@Getter -//@NoArgsConstructor(access = AccessLevel.PROTECTED) -//public class ReportPageResponse { -// -// private List contents = new ArrayList<>(); -// private long lastPage; -// private long nextPage; -// -// private ReportPageResponse(List contents, long lastPage, long nextPage) { -// this.contents = contents; -// this.lastPage = lastPage; -// this.nextPage = nextPage; -// } -// -// public static ReportPageResponse of(List reportPaging, long totalElements, long nextPage) { -// return ReportPageResponse.newPagingHasNext(reportPaging, totalElements, nextPage); -// } -// -// private static ReportPageResponse newPagingHasNext(List reports, long totalElements, long nextPage) { -// return new ReportPageResponse(reports, totalElements, nextPage); -// } -// -//} \ No newline at end of file +package inu.codin.codin.domain.report.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReportPageResponse { + + private List contents = new ArrayList<>(); + private long lastPage; + private long nextPage; + + private ReportPageResponse(List contents, long lastPage, long nextPage) { + this.contents = contents; + this.lastPage = lastPage; + this.nextPage = nextPage; + } + + public static ReportPageResponse of(List reportPaging, long totalElements, long nextPage) { + return ReportPageResponse.newPagingHasNext(reportPaging, totalElements, nextPage); + } + + private static ReportPageResponse newPagingHasNext(List reports, long totalElements, long nextPage) { + return new ReportPageResponse(reports, totalElements, nextPage); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedCommentDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedCommentDetailResponseDTO.java new file mode 100644 index 00000000..f9fc4d7b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedCommentDetailResponseDTO.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.report.dto.response; + +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import lombok.Getter; + +@Getter +public class ReportedCommentDetailResponseDTO extends CommentResponseDTO { + private final boolean isReported; + + private ReportedCommentDetailResponseDTO(CommentResponseDTO base, boolean isReported) { + super(base.get_id(), base.getUserId(), base.getContent(), base.getNickname(), + base.getUserImageUrl(), base.isAnonymous(), base.getReplies(), + base.getLikeCount(), base.isDeleted(), base.getCreatedAt(), base.getUserInfo()); + this.isReported = isReported; + } + + public static ReportedCommentDetailResponseDTO from(CommentResponseDTO base, boolean isReported) { + return new ReportedCommentDetailResponseDTO(base, isReported); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java new file mode 100644 index 00000000..ba70583d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.report.dto.response; + +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ReportedPostDetailResponseDTO extends PostDetailResponseDTO { + private final boolean isReported; + + @Builder + public ReportedPostDetailResponseDTO(PostDetailResponseDTO baseDTO, boolean isReported) { + super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), + baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), + baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), + baseDTO.getCommentCount(), baseDTO.getUserInfo()); + this.isReported = isReported; + } + + public static ReportedPostDetailResponseDTO from(boolean isReported, PostDetailResponseDTO postDetail) { + return ReportedPostDetailResponseDTO.builder() + .isReported(isReported) + .baseDTO(postDetail) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index a3e1281a..a602c0a2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -2,28 +2,24 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.block.service.BlockService; + import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; + import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; + import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.post.dto.response.PostReportResponse; -import inu.codin.codin.domain.post.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.report.dto.ReportInfo; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; -import inu.codin.codin.domain.report.dto.response.ReportCountResponseDto; -import inu.codin.codin.domain.report.dto.response.ReportResponseDto; -import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; +import inu.codin.codin.domain.report.dto.response.*; import inu.codin.codin.domain.report.entity.*; import inu.codin.codin.domain.report.exception.ReportAlreadyExistsException; import inu.codin.codin.domain.report.exception.ReportUnsupportedTypeException; @@ -32,7 +28,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.Document; @@ -330,7 +326,7 @@ public void resolveReport(String reportTargetId) { * */ - public PostPageResponse getAllReportedPosts(int pageNumber) { + public ReportPageResponse getAllReportedPosts(int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); List reportInfos = reportRepository.findAllReportedEntities(); @@ -341,7 +337,7 @@ public PostPageResponse getAllReportedPosts(int pageNumber) { Page reportInfoPage = new PageImpl<>(reportInfos.subList(start, end), pageRequest, reportInfos.size()); // 신고된 엔터티 조회 및 변환 - List reportedPosts = reportInfoPage.getContent().stream() + List reportedPosts = reportInfoPage.getContent().stream() .map(this::getReportedPostDetail) .filter(Optional::isPresent) .map(Optional::get) @@ -350,24 +346,24 @@ public PostPageResponse getAllReportedPosts(int pageNumber) { long lastPage = reportInfoPage.getTotalPages() - 1; long nextPage = reportInfoPage.hasNext() ? pageNumber + 1 : -1; - return PostPageResponse.of(reportedPosts, lastPage, nextPage); + return ReportPageResponse.of(reportedPosts, lastPage, nextPage); } - private Optional getReportedPostDetail(ReportInfo reportInfo) { + private Optional getReportedPostDetail(ReportInfo reportInfo) { ObjectId entityId = new ObjectId(reportInfo.getReportedEntityId()); return switch (reportInfo.getEntityType()) { case POST -> postService.getPostDetailById(entityId) // ✅ PostService 활용 - .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)); + .map(postDTO -> ReportListResponseDto.from(postDTO, reportInfo)); case COMMENT -> commentRepository.findById(entityId) .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) - .map(postDTO -> PostReportResponse.from(postDTO, reportInfo))); + .map(postDTO -> ReportListResponseDto.from(postDTO, reportInfo))); case REPLY -> replyCommentRepository.findById(entityId) .flatMap(reply -> commentRepository.findById(reply.getCommentId()) .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) - .map(postDTO -> PostReportResponse.from(postDTO, reportInfo)))); + .map(postDTO -> ReportListResponseDto.from(postDTO, reportInfo)))); default -> Optional.empty(); }; From e34ec966a553a689022e1e91331219bc4f1e387b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 03:08:07 +0900 Subject: [PATCH 0616/1002] =?UTF-8?q?refactor=20::=20=EC=9D=B4=EC=A0=84=20?= =?UTF-8?q?=EB=AF=B8=EC=82=AC=EC=9A=A9=20=EC=8B=A0=EA=B3=A0=20api=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/controller/ReportController.java | 36 +------------------ .../domain/report/service/ReportService.java | 27 -------------- 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index e7d506b2..b900d17c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -42,18 +42,9 @@ public ResponseEntity createReport(@RequestBody @Valid ReportCreateRequestDto .body(new SingleResponse<>(201, "신고 생성 완료", null)); } -// @Operation( -// summary = "삭제되지 않은 신고된 모든 게시물 조회" -// ) -// @GetMapping("/post") -// public ResponseEntity> getAllPosts(@RequestParam("page") @NotNull int pageNumber) { -// PostPageResponse postpages= reportService.getReportedPosts(pageNumber); -// return ResponseEntity.ok() -// .body(new SingleResponse<>(200, "삭제되지 않은 신고된 모든 게시물 조회 성공", postpages)); -// } //(Admin) 특정 대상 신고 정보 조회 API - @Operation(summary = "특정 게시물의 신고 내역 조회(관리자)") + @Operation(summary = "특정 게시글,댓글 신고 내역 상세조회(관리자)") @PreAuthorize("hasRole('ADMIN')") @GetMapping("/summary/{reportedEntityId}") public ResponseEntity getReportSummary(@PathVariable String reportedEntityId) { @@ -63,31 +54,6 @@ public ResponseEntity getReportSummary(@PathVariable String reportedEntityId) } - // 특정 신고 타입 목록 조회 (관리자) - @Operation(summary = "Pending(신고 처리 대기) 신고 내역 오름차순 정렬 조회 (관리자)") - @PreAuthorize("hasRole('ADMIN')") - @GetMapping - public ResponseEntity getReports(){ - - List reports = reportService.getAllReports(); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "특정 신고 타입 목록 조회", reports)); - } - - - // 특정 유저 신고 내역 조회 (관리자) - @Operation(summary = "특정 유저 신고 내역 조회 (관리자)") - @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/user") - public ResponseEntity getReportsByUserId( - @RequestParam("userId") @NotNull String userId) { - - List userReports = reportService.getReportsByUserId(userId); - return ResponseEntity.ok() - .body(new SingleResponse<>(200, "특정 유저가 신고한 내역 조회", userReports)); - } - - // 신고 처리 (관리자) @Operation(summary = "신고 처리 (관리자)") @PreAuthorize("hasRole('ADMIN')") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index a602c0a2..c6878d5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -153,33 +153,6 @@ private ObjectId validateAndGetReportedUserId(ReportTargetType reportTargetType, - // 신고 목록 조회 (관리자) - - public List getAllReports() { - - List aggregationResults = customReportRepository.findPendingReportsOrderedGroupedBy(); - - return aggregationResults.stream() - .map(ReportCountResponseDto::from) // 변환 메서드 호출 - .collect(Collectors.toList()); - } - - - - // 특정 유저가 신고 내역 조회 (관리자) - public List getReportsByUserId(String userId) { - log.info("특정 유저 신고 내역 조회: userId={}", userId); - - ObjectId ObjUserId = new ObjectId(userId); - List reports = reportRepository.findByReportingUserId(ObjUserId); - - log.info("DB에서 가져온 ReportEntity 리스트:"); - reports.forEach(report -> log.info("ID: {}, ReportStatus: {}", report.get_id(), report.getReportStatus())); - - return reports.stream() - .map(ReportResponseDto::from) - .collect(Collectors.toList()); - } From 820bfff170c187b819cfa81af6fdced2578ebe8a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 12 Mar 2025 03:09:12 +0900 Subject: [PATCH 0617/1002] =?UTF-8?q?refactor=20::=20post,comment=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20api=20dto=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 2 -- .../comment/controller/CommentController.java | 2 -- .../ReportedCommentDetailResponseDTO.java | 20 ------------- .../comment/service/CommentService.java | 1 - .../reply/service/ReplyCommentService.java | 1 - .../post/dto/response/PostReportResponse.java | 29 ------------------- .../ReportedPostDetailResponseDTO.java | 27 ----------------- 7 files changed, 82 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 7e5a2af5..395830b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -8,7 +8,6 @@ import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.post.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; @@ -19,7 +18,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 610ba0de..4c08d2fb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -5,14 +5,12 @@ import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java deleted file mode 100644 index 8cf3a1a8..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/ReportedCommentDetailResponseDTO.java +++ /dev/null @@ -1,20 +0,0 @@ -package inu.codin.codin.domain.post.domain.comment.dto.response; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class ReportedCommentDetailResponseDTO extends CommentResponseDTO { - private final boolean isReported; - - private ReportedCommentDetailResponseDTO(CommentResponseDTO base, boolean isReported) { - super(base.get_id(), base.getUserId(), base.getContent(), base.getNickname(), - base.getUserImageUrl(), base.isAnonymous(), base.getReplies(), - base.getLikeCount(), base.isDeleted(), base.getCreatedAt(), base.getUserInfo()); - this.isReported = isReported; - } - - public static ReportedCommentDetailResponseDTO from(CommentResponseDTO base, boolean isReported) { - return new ReportedCommentDetailResponseDTO(base, isReported); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 84ac9d29..893a147d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -9,7 +9,6 @@ import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; -import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index fcf9f15a..207b37e8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -6,7 +6,6 @@ import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.dto.response.ReportedCommentDetailResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java deleted file mode 100644 index c36bfaa7..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostReportResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - - -import inu.codin.codin.domain.report.dto.ReportInfo; -import lombok.Builder; -import lombok.Getter; - -@Getter -public class PostReportResponse extends PostDetailResponseDTO { - - private final ReportInfo reportInfo; - - @Builder - public PostReportResponse(PostDetailResponseDTO baseDTO, ReportInfo reportInfo) { - super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), - baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), - baseDTO.getCommentCount(), baseDTO.getUserInfo()); - this.reportInfo = reportInfo; - } - - - public static PostReportResponse from(PostDetailResponseDTO base, ReportInfo reportInfo) { - return PostReportResponse.builder() - .baseDTO(base) - .reportInfo(reportInfo) - .build(); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java deleted file mode 100644 index e7843957..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/ReportedPostDetailResponseDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -import inu.codin.codin.domain.report.dto.ReportInfo; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -public class ReportedPostDetailResponseDTO extends PostDetailResponseDTO{ - private final boolean isReported; - - @Builder - public ReportedPostDetailResponseDTO(PostDetailResponseDTO baseDTO, boolean isReported) { - super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), - baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), - baseDTO.getCommentCount(), baseDTO.getUserInfo()); - this.isReported = isReported; - } - - public static ReportedPostDetailResponseDTO from(boolean isReported, PostDetailResponseDTO postDetail) { - return ReportedPostDetailResponseDTO.builder() - .isReported(isReported) - .baseDTO(postDetail) - .build(); - } -} From 71af018db40dbcfd0eabcd84581892db353e4469 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Mar 2025 02:17:12 +0900 Subject: [PATCH 0618/1002] =?UTF-8?q?docs=20:=20log=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatting/service/ChattingService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 00924594..3272d5c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -83,7 +83,6 @@ public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); return new ChatRoomNotFoundException("채팅방을 찾을 수 없습니다."); }); - log.info("[메시지 조회] 채팅방 ID: {}, 페이지: {}", id, page); Pageable pageable = PageRequest.of(page, 20, Sort.by("createdAt").descending()); chatRoomRepository.findById(new ObjectId(id)) From 1f94929c18235657816c2a3bc85bcd20ab5efeed Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Mar 2025 23:29:12 +0900 Subject: [PATCH 0619/1002] =?UTF-8?q?fix=20:=20@DBref=EB=A1=9C=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=EC=A7=93=EB=8D=98=20=EB=B6=80=EB=B6=84=EC=9D=84=20Obj?= =?UTF-8?q?ectId=EB=A7=8C=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/NotificationEntity.java | 7 ++- .../service/NotificationService.java | 2 +- .../infra/fcm/dto/FcmMessageUserDto.java | 7 +-- .../infra/fcm/entity/FcmTokenEntity.java | 9 ++-- .../fcm/repository/FcmTokenRepository.java | 5 +- .../codin/infra/fcm/service/FcmService.java | 49 ++++++++----------- 6 files changed, 35 insertions(+), 44 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index 39edcb64..66d4d8aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -23,8 +23,7 @@ public class NotificationEntity extends BaseTimeEntity { @Id @NotBlank private ObjectId id; - @DBRef(lazy = true) - private UserEntity user; + private ObjectId userId; private ObjectId targetId; @@ -43,8 +42,8 @@ public class NotificationEntity extends BaseTimeEntity { private String priority; @Builder - public NotificationEntity(UserEntity user, ObjectId targetId, String title, String message, String type, String priority) { - this.user = user; + public NotificationEntity(ObjectId userId, ObjectId targetId, String title, String message, String type, String priority) { + this.userId = userId; this.targetId = targetId; this.title = title; this.message = message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 396385bd..afa17512 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -109,7 +109,7 @@ public void sendFcmMessageToTopic(String title, String body, Map // 알림 로그를 저장하는 로직 (특정 사용자 대상) private void saveNotificationLog(FcmMessageUserDto msgDto, Map data) { NotificationEntity notificationEntity = NotificationEntity.builder() - .user(msgDto.getUser()) + .userId(msgDto.getUserId()) .title(msgDto.getTitle()) .message(msgDto.getBody()) .targetId(new ObjectId(data.get("id"))) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java index 49bab9c8..f6f7a106 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import lombok.Builder; import lombok.Data; +import org.bson.types.ObjectId; import java.util.Map; @@ -13,15 +14,15 @@ @Data public class FcmMessageUserDto { - private UserEntity user; + private ObjectId userId; private String title; private String body; private String imageUrl; private Map data; @Builder - public FcmMessageUserDto(UserEntity user, String title, String body, String imageUrl, Map data) { - this.user = user; + public FcmMessageUserDto(ObjectId userId, String title, String body, String imageUrl, Map data) { + this.userId = userId; this.title = title; this.body = body; this.imageUrl = imageUrl; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index f9d1ba7a..733765b0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -18,18 +18,17 @@ public class FcmTokenEntity extends BaseTimeEntity { @Id - private ObjectId id; + private ObjectId _id; - @DBRef - private UserEntity user; + private ObjectId userId; private List fcmTokenList; private String deviceType; @Builder - public FcmTokenEntity(UserEntity user, List fcmTokenList, String deviceType) { - this.user = user; + public FcmTokenEntity(ObjectId userId, List fcmTokenList, String deviceType) { + this.userId = userId; this.fcmTokenList = fcmTokenList; this.deviceType = deviceType; } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java index ec11c7b2..ee3b015b 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/repository/FcmTokenRepository.java @@ -1,6 +1,5 @@ package inu.codin.codin.infra.fcm.repository; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; @@ -11,6 +10,6 @@ @Repository public interface FcmTokenRepository extends MongoRepository { - @Query("{ 'user': ?0, deletedAt: null }") - Optional findByUser(UserEntity user); + @Query("{ 'userId': ?0, deletedAt: null }") + Optional findByUserId(ObjectId user); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 8a139575..2ed53662 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -36,8 +36,7 @@ public class FcmService { public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { // 유저의 FCM 토큰이 존재하는지 확인 ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = getUserEntityFromUserId(userId); - Optional fcmToken = fcmTokenRepository.findByUser(user); + Optional fcmToken = fcmTokenRepository.findByUserId(userId); if (fcmToken.isPresent()) { // 이미 존재하는 유저라면 토큰 추가 FcmTokenEntity fcmTokenEntity = fcmToken.get(); @@ -46,7 +45,7 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { } else { // 존재하지 않는 FCM 토큰이라면 저장 FcmTokenEntity newFcmTokenEntity = FcmTokenEntity.builder() - .user(user) + .userId(userId) .fcmTokenList(List.of(fcmTokenRequest.getFcmToken())) .deviceType(fcmTokenRequest.getDeviceType()) .build(); @@ -60,11 +59,12 @@ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { */ public void sendFcmMessage(FcmMessageUserDto fcmMessageUserDto) { // 유저의 알림 설정 조회 - UserEntity user = fcmMessageUserDto.getUser(); - NotificationPreference userPreference = fcmMessageUserDto.getUser().getNotificationPreference(); + ObjectId userId = fcmMessageUserDto.getUserId(); + UserEntity user = getUserEntityFromUserId(userId); + NotificationPreference userPreference = user.getNotificationPreference(); // 유저의 FCM 토큰 조회 - FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user).orElseThrow(() + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId).orElseThrow(() -> new FcmTokenNotFoundException("유저에게 FCM 토큰이 존재하지 않습니다.")); // 알림 설정에 따라 알림 전송 @@ -130,26 +130,21 @@ public void sendFcmMessageByTopic(FcmMessageTopicDto fcmMessageTopicDto) { private void handleFirebaseMessagingException(FirebaseMessagingException e, FcmTokenEntity fcmTokenEntity, String fcmToken) { MessagingErrorCode errorCode = e.getMessagingErrorCode(); switch (errorCode) { - case INVALID_ARGUMENT: // FCM 토큰이 유효하지 않을 때 - log.error("Invalid argument error for token: {}", fcmToken); - break; - case UNREGISTERED: // FCM 토큰이 등록되지 않았을 때 + case INVALID_ARGUMENT -> // FCM 토큰이 유효하지 않을 때 + log.error("Invalid argument error for token: {}", fcmToken); + case UNREGISTERED -> { // FCM 토큰이 등록되지 않았을 때 log.warn("Unregistered token: {}. Removing from database.", fcmToken); removeFcmToken(fcmTokenEntity, fcmToken); - break; - case QUOTA_EXCEEDED: // FCM 토큰의 전송량이 초과되었을 때 - log.error("Quota exceeded for token: {}", fcmToken); - // 에러관리 및 리포팅 기능 추가 - break; - case SENDER_ID_MISMATCH: // FCM 토큰의 발신자 ID가 일치하지 않을 때 - log.error("Sender ID mismatch for token: {}", fcmToken); - break; - case THIRD_PARTY_AUTH_ERROR: // FCM 토큰의 인증이 실패했을 때 - log.error("Third-party authentication error for token: {}", fcmToken); - break; - default: // 그 외의 에러 - log.error("Unknown error for token: {}", fcmToken); - break; + } + case QUOTA_EXCEEDED -> // FCM 토큰의 전송량이 초과되었을 때 + log.error("Quota exceeded for token: {}", fcmToken); + // 에러관리 및 리포팅 기능 추가 + case SENDER_ID_MISMATCH -> // FCM 토큰의 발신자 ID가 일치하지 않을 때 + log.error("Sender ID mismatch for token: {}", fcmToken); + case THIRD_PARTY_AUTH_ERROR -> // FCM 토큰의 인증이 실패했을 때 + log.error("Third-party authentication error for token: {}", fcmToken); + default -> // 그 외의 에러 + log.error("Unknown error for token: {}", fcmToken); } } @@ -165,8 +160,7 @@ private void removeFcmToken(FcmTokenEntity fcmTokenEntity, String fcmToken) { */ public void subscribeTopic(String topic) { ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = getUserEntityFromUserId(userId); - FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user) + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); for (String token : fcmTokenEntity.getFcmTokenList()) { @@ -185,8 +179,7 @@ public void subscribeTopic(String topic) { */ public void unsubscribeTopic(String topic) { ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = getUserEntityFromUserId(userId); - FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUser(user) + FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); for (String token : fcmTokenEntity.getFcmTokenList()) { From 80740547c5ab214b1e95696ebe6880e30c54bc0d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Mar 2025 23:29:39 +0900 Subject: [PATCH 0620/1002] =?UTF-8?q?fix=20:=20chatting=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=EC=9D=84=20eventListener=EB=A1=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/event/ChattingNotificationEvent.java | 19 +++++++++++++++++++ .../service/ChattingEventListener.java | 12 ++++++++++++ .../chatting/service/ChattingService.java | 13 ++++--------- 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingNotificationEvent.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingNotificationEvent.java new file mode 100644 index 00000000..ddbff691 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/dto/event/ChattingNotificationEvent.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.chat.chatting.dto.event; + +import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; +@Getter +public class ChattingNotificationEvent extends ApplicationEvent { + + private final ChatRoom chatRoom; + private final ObjectId userId; + + + public ChattingNotificationEvent(Object source, ObjectId userId, ChatRoom chatRoom) { + super(source); + this.userId = userId; + this.chatRoom = chatRoom; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index 7cc0b0eb..2660c9a5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -2,11 +2,14 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; +import inu.codin.codin.domain.chat.chatroom.entity.Participants; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; +import inu.codin.codin.domain.chat.chatting.dto.event.ChattingNotificationEvent; import inu.codin.codin.domain.chat.chatting.dto.event.UpdateUnreadCountEvent; import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; +import inu.codin.codin.domain.notification.service.NotificationService; import lombok.RequiredArgsConstructor; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.SimpMessageSendingOperations; @@ -24,6 +27,7 @@ public class ChattingEventListener { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; private final SimpMessageSendingOperations template; + private final NotificationService notificationService; @Async @EventListener @@ -44,6 +48,14 @@ public void handleChattingArrivedEvent(ChattingArrivedEvent event){ } + @Async + @EventListener + public void handleChattingNotificationEvent(ChattingNotificationEvent event){ + event.getChatRoom().getParticipants().getInfo().values().stream() + .filter(participantInfo -> !participantInfo.getUserId().equals(event.getUserId()) && participantInfo.isNotificationsEnabled()) + .peek(participantInfo -> notificationService.sendNotificationMessageByChat(participantInfo.getUserId(), event.getChatRoom().get_id())); + } + @EventListener public void updateUnreadCountEvent(UpdateUnreadCountEvent updateUnreadCountEvent){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 3272d5c5..5c3af31a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; +import inu.codin.codin.domain.chat.chatting.dto.event.ChattingNotificationEvent; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingAndUserIdResponseDto; import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto; @@ -62,16 +63,10 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq .forEach(ParticipantInfo::remain); chatRoomRepository.save(chatRoom); - eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting)); //상대 유저가 접속하지 않은 상태라면 unread 개수 업데이트 및 마지막 대화 내용 업데이트 - -// //Receiver의 알림 체크 후, 메세지 전송 -// for (Participants participant : chatRoom.getParticipants()){ -// if (participant.getUserId() != userId && participant.isNotificationsEnabled()){ -// notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom); -// } -// } - + eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting)); + //알림 보내기 + eventPublisher.publishEvent(new ChattingNotificationEvent(this, userId, chatRoom)); return ChattingResponseDto.of(chatting); } From 2c0d9a6cffa0d87f02d8ed113c1601d4bd951fa2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 13 Mar 2025 23:37:40 +0900 Subject: [PATCH 0621/1002] =?UTF-8?q?fix=20:=20UserEntity=20->=20ObjectId?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/NotificationRepository.java | 7 ++-- .../service/NotificationService.java | 42 +++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java index 8256a6d6..075b06cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.notification.repository; import inu.codin.codin.domain.notification.entity.NotificationEntity; -import inu.codin.codin.domain.user.entity.UserEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -11,8 +10,8 @@ @Repository public interface NotificationRepository extends MongoRepository { - @Query("{ 'user': ?0, 'isRead': false, deletedAt: null }") - long countUnreadNotificationsByUser(UserEntity user); + @Query("{ 'userId': ?0, 'isRead': false, deletedAt: null }") + long countUnreadNotificationsByUserId(ObjectId userId); - List findAllByUser(UserEntity user); + List findAllByUserId(ObjectId userId); } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index afa17512..07e05bd1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.notification.dto.NotificationListResponseDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; @@ -14,7 +13,6 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; @@ -48,11 +46,11 @@ public class NotificationService { /** * 특정 유저의 읽지 않은 알림 개수를 반환 - * @param user 알림 수신자 + * @param userId 알림 수신자의 _id * @return 읽지 않은 알림 개수 */ - public long getUnreadNotificationCount(UserEntity user) { - return notificationRepository.countUnreadNotificationsByUser(user); + public long getUnreadNotificationCount(ObjectId userId) { + return notificationRepository.countUnreadNotificationsByUserId(userId); } /** @@ -60,11 +58,11 @@ public long getUnreadNotificationCount(UserEntity user) { * @param title 메시지 제목 * @param body 메시지 내용 * @param data 알림 대상의 _id - * @param user 메시지를 받을 사용자 + * @param userId 메시지를 받을 사용자의 _id */ - public void sendFcmMessageToUser(String title, String body, Map data, UserEntity user) { + public void sendFcmMessageToUser(String title, String body, Map data, ObjectId userId) { FcmMessageUserDto msgDto = FcmMessageUserDto.builder() - .user(user) + .userId(userId) .title(title) .body(body) .data(data) @@ -131,21 +129,21 @@ private void saveNotificationLog(FcmMessageTopicDto msgDto) { } public void sendNotificationMessageByComment(PostCategory postCategory, ObjectId userId, String postId, String content) { - UserEntity user = userRepository.findById(userId) + userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); Map post = new HashMap<>(); post.put("id", postId); String title = postCategory.getDescription().split("_")[0]; - sendFcmMessageToUser(title, NOTI_COMMENT+content, post, user); + sendFcmMessageToUser(title, NOTI_COMMENT+content, post, userId); } public void sendNotificationMessageByReply(PostCategory postCategory, ObjectId userId, String postId, String content) { - UserEntity user = userRepository.findById(userId) + userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); Map post = new HashMap<>(); post.put("id", postId); String title = postCategory.getDescription().split("_")[0]; - sendFcmMessageToUser(title, NOTI_REPLY+content, post, user); + sendFcmMessageToUser(title, NOTI_REPLY+content, post, userId); } public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { @@ -153,11 +151,11 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { case POST -> { PostEntity postEntity = postRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - UserEntity user = userRepository.findById(postEntity.getUserId()) + userRepository.findById(postEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map post = new HashMap<>(); post.put("id", postEntity.get_id().toString()); - sendFcmMessageToUser(NOTI_LIKE, "내 게시글 보러 가기", post, user); + sendFcmMessageToUser(NOTI_LIKE, "내 게시글 보러 가기", post, postEntity.getUserId()); } case REPLY -> { ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(id) @@ -166,32 +164,32 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity postEntity = postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - UserEntity user = userRepository.findById(replyCommentEntity.getUserId()) + userRepository.findById(replyCommentEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map post = new HashMap<>(); post.put("id", postEntity.get_id().toString()); - sendFcmMessageToUser(NOTI_LIKE, "내 답글 보러 가기", post, user); + sendFcmMessageToUser(NOTI_LIKE, "내 답글 보러 가기", post, replyCommentEntity.getUserId()); } case COMMENT -> { CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity postEntity = postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - UserEntity user = userRepository.findById(commentEntity.getUserId()) + userRepository.findById(commentEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map post = new HashMap<>(); post.put("id", postEntity.get_id().toString()); - sendFcmMessageToUser(NOTI_LIKE, "내 댓글 보러 가기", post, user); + sendFcmMessageToUser(NOTI_LIKE, "내 댓글 보러 가기", post, commentEntity.getUserId()); } } } public void sendNotificationMessageByChat(ObjectId userId, ObjectId chatRoomId) { - UserEntity user = userRepository.findById(userId) + userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); Map chat = new HashMap<>(); chat.put("id", chatRoomId.toString()); - sendFcmMessageToUser("익명 채팅방", NOTI_CHAT, chat, user); + sendFcmMessageToUser("익명 채팅방", NOTI_CHAT, chat, userId); } public void readNotification(String notificationId){ @@ -204,9 +202,9 @@ public void readNotification(String notificationId){ public List getNotification() { //todo 유저에게 맞는 토픽 알림들도 반환 ObjectId userId = SecurityUtils.getCurrentUserId(); - UserEntity user = userRepository.findById(userId) + userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다")); - List notifications = notificationRepository.findAllByUser(user); + List notifications = notificationRepository.findAllByUserId(userId); return notifications.stream() .map(NotificationListResponseDto::of) .toList(); From 15d4dab8821c2e0ce1ee52d7a113176a0ff1f101 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 16:15:43 +0900 Subject: [PATCH 0622/1002] =?UTF-8?q?fix=20:=20containsKey=EB=A1=9C=20key?= =?UTF-8?q?=EC=9D=98=20=EC=9C=A0=EB=AC=B4=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/stomp/StompMessageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index 79d78d03..2111d537 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -77,7 +77,7 @@ private Result getResult(StompHeaderAccessor headerAccessor) { log.info(headerAccessor.toString()); String chatroomId; if (Objects.equals(headerAccessor.getCommand(), StompCommand.DISCONNECT)){ //UNSCRIBE 하지 않은 상태에서 DISCONNECT라면 UNSCRIBE도 같이 - if (!sessionStore.get(headerAccessor.getSessionId()).isEmpty()) { + if (sessionStore.containsKey(headerAccessor.getSessionId())) { chatroomId = sessionStore.get(headerAccessor.getSessionId()); sessionStore.remove(headerAccessor.getSessionId()); } else return null; From ec43bf4c60e0efe1cc8eef13f7a1faf8ff56d173 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 16:19:19 +0900 Subject: [PATCH 0623/1002] =?UTF-8?q?fix=20:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=EA=B0=80=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=90=9C=20=EC=8B=9C=EA=B0=81=EC=9D=84=20?= =?UTF-8?q?update=5Fat=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EB=94=B0=EB=A1=9C?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC,=20=EC=B1=84=ED=8C=85=EB=B0=A9=EB=93=A4?= =?UTF-8?q?=EC=9D=84=20=ED=95=B4=EB=8B=B9=20=EC=8B=9C=EA=B0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=A0=AC=ED=95=98=EC=97=AC=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/dto/ChatRoomListResponseDto.java | 2 +- .../codin/codin/domain/chat/chatroom/entity/ChatRoom.java | 8 +++++++- .../domain/chat/chatroom/service/ChatRoomService.java | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java index ae731165..ebdcb985 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java @@ -47,7 +47,7 @@ public static ChatRoomListResponseDto of(ChatRoom chatRoom, ObjectId userId) { .chatRoomId(chatRoom.get_id().toString()) .roomName(chatRoom.getRoomName()) .lastMessage(chatRoom.getLastMessage()==null ? null : chatRoom.getLastMessage()) - .currentMessageDate(chatRoom.getUpdatedAt()==null ? null : chatRoom.getUpdatedAt()) + .currentMessageDate(chatRoom.getCurrentMessageDate()==null ? null : chatRoom.getCurrentMessageDate()) .unread(chatRoom.getParticipants().getInfo().get(userId).getUnreadMessage()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 8eb503a6..2ad645ae 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -11,6 +11,8 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import java.time.LocalDateTime; + @Document(collection = "chatroom") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -30,13 +32,16 @@ public class ChatRoom extends BaseTimeEntity { private String lastMessage; + private LocalDateTime currentMessageDate; + @Builder - public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage) { + public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage, LocalDateTime currentMessageDate) { this.roomName = roomName; this.referenceId = referenceId; this.participants = participants; this.lastMessage = lastMessage; + this.currentMessageDate = currentMessageDate; } public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, ObjectId senderId){ @@ -52,5 +57,6 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obj public void updateLastMessage(String message){ this.lastMessage = message; + this.currentMessageDate = LocalDateTime.now(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 75528c7b..94af1ee8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -84,7 +84,7 @@ public List getAllChatRoomByUser() { return chatRooms.stream() .filter(chatRoom -> chatRoom.getParticipants().getInfo().keySet().stream() .noneMatch(blockedUsersId::contains)) - .sorted(Comparator.comparing(BaseTimeEntity::getUpdatedAt,Comparator.reverseOrder())) + .sorted(Comparator.comparing(ChatRoom::getCurrentMessageDate,Comparator.reverseOrder())) .map(chatRoom -> ChatRoomListResponseDto.of(chatRoom, userId)).toList(); } From 77db1945144f6ba0a71f59411cac9c85ee0a0096 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 18:44:43 +0900 Subject: [PATCH 0624/1002] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=95=88=EC=9D=BD=EC=9D=80=20=EB=A9=94=EC=84=B8?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatroom/service/ChatRoomService.java | 1 - .../service/ChattingEventListener.java | 51 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 94af1ee8..d027c0eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.chat.chatroom.service; -import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index 2660c9a5..56db6b3e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; -import inu.codin.codin.domain.chat.chatroom.entity.Participants; +import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent; import inu.codin.codin.domain.chat.chatting.dto.event.ChattingNotificationEvent; @@ -11,12 +11,14 @@ import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.notification.service.NotificationService; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,25 +31,51 @@ public class ChattingEventListener { private final SimpMessageSendingOperations template; private final NotificationService notificationService; + /* + 채팅을 발신했을 경우, + 1. 상대방이 접속한 상태가 아니라면 상대방의 unread 값 +1 + 2. 채팅방의 마지막 메세지 업데이트 + 3. /queue/chatroom/{userId} 를 통해 실시간으로 채팅방 목록 업데이트 + */ @Async @EventListener public void handleChattingArrivedEvent(ChattingArrivedEvent event){ Chatting chatting = event.getChatting(); ChatRoom chatRoom = chatRoomRepository.findById(chatting.getChatRoomId()) .orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다. ID: "+ chatting.getChatRoomId())); - chatRoom.getParticipants().getInfo().forEach( - (id, participantInfo) -> { - if (!participantInfo.isConnected()) { - participantInfo.plusUnread(); - } - } - ); - chatRoom.updateLastMessage(chatting.getContent()); + + updateUnread(event, chatRoom); chatRoomRepository.save(chatRoom); chattingRepository.save(chatting); } + private void updateUnread(ChattingArrivedEvent event, ChatRoom chatRoom) { + Map> result = new HashMap<>(); + ObjectId receiverId = null; + for (Map.Entry entry : chatRoom.getParticipants().getInfo().entrySet()) { + ParticipantInfo participantInfo = entry.getValue(); + + if (!participantInfo.getUserId().equals(event.getChatting().getSenderId())) { + receiverId = participantInfo.getUserId(); + if (!participantInfo.isConnected()) { + participantInfo.plusUnread(); + result.put(chatRoom.get_id().toString(), getLastMessageAndUnread(event, participantInfo)); + } + } + } + chatRoom.updateLastMessage(event.getChatting().getContent()); + if (receiverId!=null) + template.convertAndSend("/queue/chatroom/unread/"+ receiverId, result); + } + + private static Map getLastMessageAndUnread(ChattingArrivedEvent event, ParticipantInfo participantInfo) { + Map lastMessageAndUnread = new HashMap<>(); + lastMessageAndUnread.put("lastMessage", event.getChatting().getContent()); + lastMessageAndUnread.put("unread", String.valueOf(participantInfo.getUnreadMessage())); + return lastMessageAndUnread; + } + @Async @EventListener public void handleChattingNotificationEvent(ChattingNotificationEvent event){ @@ -56,7 +84,10 @@ public void handleChattingNotificationEvent(ChattingNotificationEvent event){ .peek(participantInfo -> notificationService.sendNotificationMessageByChat(participantInfo.getUserId(), event.getChatRoom().get_id())); } - + /* + 유저가 채팅방 입장 시, 읽지 않은 채팅에 대하여 새로운 unread 값 송신 + 클라이언트 : chat_id 와 일치하는 채팅값의 unread 값 업데이트 + */ @EventListener public void updateUnreadCountEvent(UpdateUnreadCountEvent updateUnreadCountEvent){ List> result = new ArrayList<>(); From bf89942fb66ecc2129573f61cfc6930907ebc812 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 20:04:11 +0900 Subject: [PATCH 0625/1002] =?UTF-8?q?release=20:=20=EB=B0=B0=ED=8F=AC=20te?= =?UTF-8?q?st=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95,=20release=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=EC=97=90=EC=84=9C=EC=9D=98=20CI/CD?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a3835af8..ea4b661d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 +Subproject commit ea4b661df47f33eba7055775c60c5d95560de7f9 From 0c0f60aa41d15b6cb8aee79ab641b0d30d6ed80c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 20:12:51 +0900 Subject: [PATCH 0626/1002] =?UTF-8?q?release=20:=20Swagger=EC=97=90=20dev?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SwaggerConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index bfd6f45a..6aa69a4d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -47,7 +47,8 @@ public OpenAPI customOpenAPI() { .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), // Local Server - new Server().url(BASEURL+"/api").description("Production Server") // Production Server + new Server().url(BASEURL+"/api").description("Production Server"), // Production Server + new Server().url(BASEURL+"/dev").description("Release Server") )); } From 4d01bea1dbae77567d722c4d501c4aa2b249bbe3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 20:13:34 +0900 Subject: [PATCH 0627/1002] =?UTF-8?q?chore=20:=20resources=20main=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index ea4b661d..a3835af8 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit ea4b661df47f33eba7055775c60c5d95560de7f9 +Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 From d3e195a42d2caf69e2f3eb1451b69066a0018039 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 14 Mar 2025 20:18:15 +0900 Subject: [PATCH 0628/1002] =?UTF-8?q?chore=20:=20resources=20release=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a3835af8..ea4b661d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 +Subproject commit ea4b661df47f33eba7055775c60c5d95560de7f9 From 6cffdd684076aa0a0f990fe25b22a105576bc7ec Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 03:54:07 +0900 Subject: [PATCH 0629/1002] feat :: publick key generator --- .../dto/apple/key/ApplePublicKey.java | 8 ++++ .../dto/apple/key/ApplePublicKeyResponse.java | 16 +++++++ .../util/ApplePublicKeyGenerator.java | 42 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKey.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKeyResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/ApplePublicKeyGenerator.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKey.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKey.java new file mode 100644 index 00000000..301eb99e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKey.java @@ -0,0 +1,8 @@ +package inu.codin.codin.common.security.dto.apple.key; + +public record ApplePublicKey(String kty, + String kid, + String alg, + String n, + String e) { +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKeyResponse.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKeyResponse.java new file mode 100644 index 00000000..05cdc3c6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/key/ApplePublicKeyResponse.java @@ -0,0 +1,16 @@ +package inu.codin.codin.common.security.dto.apple.key; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; + +import java.util.List; + +public record ApplePublicKeyResponse(List keys) { + + public ApplePublicKey getMatchedKey(String kid, String alg) throws AuthenticationException { + return keys.stream() + .filter(key -> key.kid().equals(kid) && key.alg().equals(alg)) + .findAny() + .orElseThrow(() -> new BadCredentialsException("Apple Public Key 검증 실패")); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/ApplePublicKeyGenerator.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/ApplePublicKeyGenerator.java new file mode 100644 index 00000000..e7a0db13 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/ApplePublicKeyGenerator.java @@ -0,0 +1,42 @@ +package inu.codin.codin.common.security.util; + +import inu.codin.codin.common.security.dto.apple.key.ApplePublicKey; +import inu.codin.codin.common.security.dto.apple.key.ApplePublicKeyResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class ApplePublicKeyGenerator { + public PublicKey generatePublicKey(Map tokenHeaders, + ApplePublicKeyResponse applePublicKeys) + throws AuthenticationException, NoSuchAlgorithmException, InvalidKeySpecException { + ApplePublicKey publicKey = applePublicKeys.getMatchedKey(tokenHeaders.get("kid"), + tokenHeaders.get("alg")); + + return getPublicKey(publicKey); + } + + private PublicKey getPublicKey(ApplePublicKey publicKey) + throws NoSuchAlgorithmException, InvalidKeySpecException { + byte[] nBytes = Base64.getUrlDecoder().decode(publicKey.n()); + byte[] eBytes = Base64.getUrlDecoder().decode(publicKey.e()); + + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger(1, nBytes), + new BigInteger(1, eBytes)); + + + KeyFactory keyFactory = KeyFactory.getInstance(publicKey.kty()); + return keyFactory.generatePublic(publicKeySpec); + } +} From 34ec88066572743a5c7f9a0e60de6c62a903738b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 03:54:23 +0900 Subject: [PATCH 0630/1002] =?UTF-8?q?feat=20::=20publick=20key=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20feign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/feign/AppleAuthClient.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/feign/AppleAuthClient.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/feign/AppleAuthClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/feign/AppleAuthClient.java new file mode 100644 index 00000000..f50396cb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/feign/AppleAuthClient.java @@ -0,0 +1,11 @@ +package inu.codin.codin.common.security.feign; + +import inu.codin.codin.common.security.dto.apple.key.ApplePublicKeyResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +@FeignClient(name = "appleAuthClient", url = "${apple.auth.public-key-url}") +public interface AppleAuthClient { + @GetMapping + ApplePublicKeyResponse getAppleAuthPublicKey(); +} From d0d280e14dcd519fa3be9ea8537e8ab2cee0c9f7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 03:56:40 +0900 Subject: [PATCH 0631/1002] =?UTF-8?q?feat=20::=20Apple=20=EC=A0=84?= =?UTF-8?q?=EC=9A=A9=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=ED=81=B4=EB=9E=98=EC=8A=A4=20-=201.=20OAuth2UserRe?= =?UTF-8?q?quest=EC=97=90=EC=84=9C=20=EC=B6=94=EA=B0=80=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=EB=A1=9C=20=EB=84=98=EC=96=B4?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20id=5Ftoken=EC=9D=84=20=EC=B6=94=EC=B6=9C?= =?UTF-8?q?=202.=20Apple=EC=9D=98=20=EA=B3=B5=EA=B0=9C=ED=82=A4=EB=A5=BC?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=B4=20id=5Ftoken=EC=9D=98=20?= =?UTF-8?q?=EC=84=9C=EB=AA=85=EC=9D=84=20=EA=B2=80=EC=A6=9D=203.=20JWT?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=E2=80=9Csub=E2=80=9D=20(=EA=B3=A0?= =?UTF-8?q?=EC=9C=A0=20=EC=8B=9D=EB=B3=84=EC=9E=90)=EC=99=80=20=E2=80=9Cem?= =?UTF-8?q?ail=E2=80=9D=20=EB=93=B1=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=81=B4=EB=A0=88=EC=9E=84=EC=9D=84=20=EC=B6=94=EC=B6=9C?= =?UTF-8?q?=ED=95=B4=20AppleOAuth2User=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/apple/AppleLoginResponse.java | 4 + .../security/dto/apple/AppleOAuth2User.java | 42 ++++++++ .../security/dto/apple/user/AppleUser.java | 3 + .../security/jwt/IdentityTokenValidator.java | 39 ++++++++ .../service/AppleOAuth2UserService.java | 97 +++++++++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleLoginResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleOAuth2User.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/AppleUser.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/jwt/IdentityTokenValidator.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleLoginResponse.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleLoginResponse.java new file mode 100644 index 00000000..59b02a9d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleLoginResponse.java @@ -0,0 +1,4 @@ +package inu.codin.codin.common.security.dto.apple; + + +public record AppleLoginResponse (String accountId, String message) { } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleOAuth2User.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleOAuth2User.java new file mode 100644 index 00000000..67b0fd9c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleOAuth2User.java @@ -0,0 +1,42 @@ +package inu.codin.codin.common.security.dto.apple; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +//Apple OAuth2의 경우 sub을 고유한 식별자로 사용하고, email을 별도로 가져와서 사용하도록 처리 + +public class AppleOAuth2User implements OAuth2User { + private final Map attributes; + + public AppleOAuth2User(Map attributes) { + this.attributes = attributes; + } + + @Override + public A getAttribute(String name) { + return OAuth2User.super.getAttribute(name); + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return List.of(); + } + + @Override + public String getName() { + return (String) attributes.get("sub"); // Apple의 고유 식별자 + } + + public String getEmail() { + return (String) attributes.get("email"); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/AppleUser.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/AppleUser.java new file mode 100644 index 00000000..14c8d833 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/AppleUser.java @@ -0,0 +1,3 @@ +package inu.codin.codin.common.security.dto.apple.user; + +public record AppleUser(Name name, String email) { } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/IdentityTokenValidator.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/IdentityTokenValidator.java new file mode 100644 index 00000000..e72e5ec5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/IdentityTokenValidator.java @@ -0,0 +1,39 @@ +package inu.codin.codin.common.security.jwt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.util.Base64; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class IdentityTokenValidator { + + /** + * apple Login + * identity token 에서 Header 추출 + */ + public Map parseHeaders(String token) throws JsonProcessingException { + String header = token.split("\\.")[0]; + return new ObjectMapper().readValue(decodeHeader(header), Map.class); + } + + public String decodeHeader(String token) { + return new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8); + } + + public Claims getTokenClaims(String token, PublicKey publicKey) { + return Jwts.parserBuilder() + .setSigningKey(publicKey) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java new file mode 100644 index 00000000..34c286d1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java @@ -0,0 +1,97 @@ +package inu.codin.codin.common.security.service; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; +import inu.codin.codin.common.security.dto.apple.AppleOAuth2User; +import inu.codin.codin.common.security.feign.AppleAuthClient; +import inu.codin.codin.common.security.jwt.IdentityTokenValidator; +import inu.codin.codin.common.security.util.ApplePublicKeyGenerator; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.isNull; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AppleOAuth2UserService implements OAuth2UserService { + + private final AppleAuthClient appleAuthClient; + private final ApplePublicKeyGenerator applePublicKeyGenerator; + private final IdentityTokenValidator identityTokenValidator; + + + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + + //AppleAuthRequest 생성 (id_token, authorization code, 사용자 정보)) + AppleAuthRequest appleAuthRequest = extractAppleAuthRequest(userRequest); + log.info("추출된 AppleAuthRequest: identityToken={}, authorizationCode={}, user={}", + appleAuthRequest.getIdentityToken(), appleAuthRequest.getAuthorizationCode(), appleAuthRequest.getUser()); + + + //Apple ID Token 검증 및 사용자 ID (sub) 추출 + //OAuth2AuthenticationException에는 (String, Exception) 생성자가 없으므로, OAuth2Error를 생성하여 사용 + String appleAccountId; + try { + appleAccountId = getAppleAccountId(appleAuthRequest.getIdentityToken()); + log.info("Apple id_token 검증 성공, accountId(sub): {}", appleAccountId); + } catch (Exception e) { + OAuth2Error oauth2Error = new OAuth2Error("invalid_token", "Apple id_token 검증 실패", null); + throw new OAuth2AuthenticationException(oauth2Error, e); + } + + + String email = appleAuthRequest.getEmail(); + + // Apple OAuth2 정보를 기반으로 `AppleOAuth2User` 객체 반환 + Map attributes = new HashMap<>(); + log.info("AppleOAuth2User 생성, attributes: {}", attributes); + attributes.put("sub", appleAccountId); + attributes.put("email", email); + + return new AppleOAuth2User(attributes); + } + + /** + * AppleAuthRequest 생성 (OAuth2UserRequest에서 값 추출) + */ + private AppleAuthRequest extractAppleAuthRequest(OAuth2UserRequest userRequest) { + String identityToken = userRequest.getAdditionalParameters().get("id_token").toString(); + String authorizationCode = userRequest.getAdditionalParameters().get("code").toString(); + Map user = userRequest.getAdditionalParameters().containsKey("user") ? + (Map) userRequest.getAdditionalParameters().get("user") : Map.of(); + + return new AppleAuthRequest(identityToken, authorizationCode, user); + } + + /** + * Apple ID Token 검증 및 사용자 계정 ID(sub) 추출 + */ + public String getAppleAccountId(String identityToken) + throws JsonProcessingException, AuthenticationException, NoSuchAlgorithmException, + InvalidKeySpecException { + Map headers = identityTokenValidator.parseHeaders(identityToken); + PublicKey publicKey = applePublicKeyGenerator.generatePublicKey(headers, + appleAuthClient.getAppleAuthPublicKey()); + + return identityTokenValidator.getTokenClaims(identityToken, publicKey).getSubject(); + } +} \ No newline at end of file From 83965aaecfefaf52286b44ca6a898c26b38f2b47 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 03:57:04 +0900 Subject: [PATCH 0632/1002] =?UTF-8?q?feat=20::=20Apple=20=EC=A0=84?= =?UTF-8?q?=EC=9A=A9=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=ED=81=B4=EB=9E=98=EC=8A=A4=20-=201.=20OAuth2UserRe?= =?UTF-8?q?quest=EC=97=90=EC=84=9C=20=EC=B6=94=EA=B0=80=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=EB=A1=9C=20=EB=84=98=EC=96=B4?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20id=5Ftoken=EC=9D=84=20=EC=B6=94=EC=B6=9C?= =?UTF-8?q?=202.=20Apple=EC=9D=98=20=EA=B3=B5=EA=B0=9C=ED=82=A4=EB=A5=BC?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=B4=20id=5Ftoken=EC=9D=98=20?= =?UTF-8?q?=EC=84=9C=EB=AA=85=EC=9D=84=20=EA=B2=80=EC=A6=9D=203.=20JWT?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=E2=80=9Csub=E2=80=9D=20(=EA=B3=A0?= =?UTF-8?q?=EC=9C=A0=20=EC=8B=9D=EB=B3=84=EC=9E=90)=EC=99=80=20=E2=80=9Cem?= =?UTF-8?q?ail=E2=80=9D=20=EB=93=B1=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=81=B4=EB=A0=88=EC=9E=84=EC=9D=84=20=EC=B6=94=EC=B6=9C?= =?UTF-8?q?=ED=95=B4=20AppleOAuth2User=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/dto/apple/AppleAuthRequest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java new file mode 100644 index 00000000..7b84cd52 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java @@ -0,0 +1,24 @@ +package inu.codin.codin.common.security.dto.apple; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import java.util.Map; + +@Getter +@AllArgsConstructor +public class AppleAuthRequest { + private final String identityToken; // Apple ID Token (JWT) + private final String authorizationCode; // Authorization Code (OAuth2 검증용) + private final Map user; // 최초 로그인 시 제공되는 사용자 정보 + + public String getEmail() { + return (String) user.get("email"); + } + + public String getFullName() { + Map name = (Map) user.get("name"); + return name != null ? name.get("lastName") + name.get("firstName") : null; + } + + +} \ No newline at end of file From 423331cfe1ba43807cacece133f18afb60901f02 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 03:57:34 +0900 Subject: [PATCH 0633/1002] =?UTF-8?q?feat=20::=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=B0=8F=20?= =?UTF-8?q?AuthService=20=EC=9E=AC=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/AbstractAuthService.java | 42 +++++++++ .../security/service/AppleAuthService.java | 80 ++++++++++++++++ .../security/service/AuthCommonService.java | 81 ++++++++++++++++ .../security/service/GoogleAuthService.java | 92 +++++++++++++++++++ .../security/service/Oauth2AuthService.java | 9 ++ .../util/OAuth2LoginSuccessHandler.java | 47 +++++++--- 6 files changed, 336 insertions(+), 15 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java new file mode 100644 index 00000000..d76dab54 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java @@ -0,0 +1,42 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractAuthService { + protected final UserRepository userRepository; + protected final S3Service s3Service; + protected final JwtService jwtService; + protected final UserDetailsService userDetailsService; + + protected void issueJwtToken(String identifier, HttpServletResponse response) { + jwtService.deleteToken(response); + UserDetails userDetails = userDetailsService.loadUserByUsername(identifier); + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authToken); + jwtService.createToken(response); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java new file mode 100644 index 00000000..d914b7ae --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java @@ -0,0 +1,80 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Optional; + +@Service +@Slf4j +public class AppleAuthService extends AbstractAuthService implements Oauth2AuthService { + + public AppleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + super(userRepository, s3Service, jwtService, userDetailsService); + } + + @Override + public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { + // Apple에서는 email이 없을 수 있으므로, email이 없으면 고유 식별자(sub)를 사용. + Map attributes = oAuth2User.getAttributes(); + String email = (String) attributes.get("email"); + String sub = (String) attributes.get("sub"); + String name = (String) attributes.get("name"); + if (name == null || name.isEmpty()) { + name = (String) attributes.get("family_name"); + } + // Apple은 부서 정보 제공하지 않음. + String department = ""; + InfoFromOAuth2User info = new InfoFromOAuth2User(email, sub, name, department); + + // 식별자: email이 존재하면 email, 아니면 sub 사용 + String identifier = (info.email() != null && !info.email().isEmpty()) ? info.email() : info.sub(); + + Optional optionalUser = userRepository.findByEmailAndStatusAll(identifier); + if (optionalUser.isPresent()) { + UserEntity existingUser = optionalUser.get(); + log.info("기존 Apple 회원 로그인: {}", identifier); + switch(existingUser.getStatus()){ + case ACTIVE -> { + issueJwtToken(identifier, response); + return AuthResultStatus.LOGIN_SUCCESS; + } + case DISABLED -> { + return AuthResultStatus.PROFILE_INCOMPLETE; + } + case SUSPENDED -> { + return AuthResultStatus.SUSPENDED_USER; + } + default -> { + throw new NotFoundException("유저의 상태를 알 수 없습니다. _id: " + existingUser.get_id()); + } + } + } else { + log.info("신규 Apple 회원 등록: {}", identifier); + UserEntity newUser = UserEntity.builder() + .email(identifier) + .name(info.name() != null ? info.name() : identifier) + .department(Department.OTHERS) // Apple은 부서 정보 없음,, + .profileImageUrl(s3Service.getDefaultProfileImageUrl()) + .status(UserStatus.DISABLED) + .role(UserRole.USER) + .build(); + userRepository.save(newUser); + return AuthResultStatus.NEW_USER_REGISTERED; + } + } + + private record InfoFromOAuth2User(String email, String sub, String name, String department) { } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java new file mode 100644 index 00000000..4ea8107b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -0,0 +1,81 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +public class AuthCommonService extends AbstractAuthService { + + + public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + super(userRepository, s3Service, jwtService, userDetailsService); + } + + public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); + if (nickNameDuplicate.isPresent()){ + throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); + } + Optional userOpt = userRepository.findByEmailAndDisabled(userProfileRequestDto.getEmail()); + if (userOpt.isEmpty()){ + throw new NotFoundException("사용자를 찾을 수 없습니다."); + } + UserEntity user = userOpt.get(); + log.info("[completeUserProfile] 사용자 조회 성공: {}", userProfileRequestDto.getEmail()); + + String imageUrl = null; + if (userImage != null && !userImage.isEmpty()) { + log.info("[프로필 설정] 프로필 이미지 업로드 중..."); + imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); + log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); + } + if (imageUrl == null) { + imageUrl = s3Service.getDefaultProfileImageUrl(); + } + + user.updateNickname(userProfileRequestDto.getNickname()); + user.updateProfileImageUrl(imageUrl); + user.activation(); + userRepository.save(user); + log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); + issueJwtToken(user.getEmail(), response); + } + + public LocalDateTime getSuspensionEndDate(OAuth2User oAuth2User){ + String email = (String) oAuth2User.getAttribute("email"); + Optional optionalUser = userRepository.findByEmailAndStatusAll(email); + if (optionalUser.isPresent()){ + UserEntity user = optionalUser.get(); + return user.getTotalSuspensionEndDate(); + } else { + throw new NotFoundException("유저를 찾을 수 없습니다."); + } + } + + public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { + Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); + if (user.isPresent()) { + issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); + } else { + throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java new file mode 100644 index 00000000..64e03c8a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java @@ -0,0 +1,92 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Optional; + +@Service +@Slf4j +public class GoogleAuthService extends AbstractAuthService implements Oauth2AuthService { + + + public GoogleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + super(userRepository, s3Service, jwtService, userDetailsService); + } + + @Override + public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { + // Google에서는 email, family_name, given_name 등 제공됨. + Map attributes = oAuth2User.getAttributes(); + String email = (String) attributes.get("email"); + String name = (String) attributes.get("family_name"); + if (name == null || name.isEmpty()) { + name = (String) attributes.get("name"); + } + String department = (String) attributes.get("given_name"); + if (department == null) { + department = ""; + } + + log.info("OAuth2 login: email={}, name={}, department={}", + email, name, department); + + InfoFromOAuth2User info = new InfoFromOAuth2User(email, name, department); + + // Google은 email이 식별자 + Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); + if (optionalUser.isPresent()) { + UserEntity existingUser = optionalUser.get(); + log.info("기존 Google 회원 로그인: {}", info.email()); + switch(existingUser.getStatus()){ + case ACTIVE -> { + issueJwtToken(info.email(), response); + log.info("정상 로그인 완료: {}", info.email()); + return AuthResultStatus.LOGIN_SUCCESS; + } + case DISABLED -> { + log.info("회원 프로필 설정 미완료 (userStatus={}): {}", existingUser.getStatus(), info.email()); + return AuthResultStatus.PROFILE_INCOMPLETE; + } + case SUSPENDED -> { + log.info("정지된 유저: {}", info.email()); + return AuthResultStatus.SUSPENDED_USER; + } + default -> { + log.error("알 수 없는 유저 상태: {}", existingUser.getStatus()); + throw new NotFoundException("유저의 상태를 알 수 없습니다. _id: " + existingUser.get_id()); + } + } + } else { + log.info("신규 Google 회원 등록: {}", info.email()); + String deptDesc = (info.department() != null) ? info.department().replace("/", "").trim() : ""; + Department dept = Department.fromDescription(deptDesc); + UserEntity newUser = UserEntity.builder() + .email(info.email()) + .name(info.name()) + .department(dept) + .profileImageUrl(s3Service.getDefaultProfileImageUrl()) + .status(UserStatus.DISABLED) + .role(UserRole.USER) + .build(); + userRepository.save(newUser); + log.info("신규 회원 등록 완료 (프로필 미완료): {}", newUser); + return AuthResultStatus.NEW_USER_REGISTERED; + } + } + + private record InfoFromOAuth2User(String email, String name, String department) { } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java new file mode 100644 index 00000000..bd2596f0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java @@ -0,0 +1,9 @@ +package inu.codin.codin.common.security.service; + +import inu.codin.codin.common.security.enums.AuthResultStatus; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public interface Oauth2AuthService { + AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response); +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index c013d997..aa935779 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,13 +1,18 @@ package inu.codin.codin.common.security.util; +import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.AuthService; +import inu.codin.codin.common.security.service.AppleAuthService; +import inu.codin.codin.common.security.service.GoogleAuthService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; @@ -23,43 +28,55 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan @Value("${server.domain}") private String BASEURL; - private final AuthService authService; + private final AppleAuthService appleAuthService; + private final GoogleAuthService googleAuthService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + // 이미 인증된 사용자의 정보를 담고 있는 OAuth2User 객체 획득 OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + // 인증 토큰에서 등록 ID(공급자)를 추출 [googel , apple] + String provider = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(); + // OAuth2 회원가입/로그인 처리 후 JWT 발급 - AuthResultStatus result = authService.oauthLogin(oAuth2User, response); + AuthResultStatus result; + if ("apple".equalsIgnoreCase(provider)) { + result = appleAuthService.oauthLogin(oAuth2User, response); + } else if ("google".equalsIgnoreCase(provider)) { + result = googleAuthService.oauthLogin(oAuth2User, response); + } else { + OAuth2Error error = new OAuth2Error("unsupported_provider", "지원되지 않는 공급자입니다.", null); + throw new OAuth2AuthenticationException(error, "지원되지 않는 공급자: " + provider); + } + handleLoginResult(request, response, result, oAuth2User.getAttribute("email")); + } + private void handleLoginResult(HttpServletRequest request, HttpServletResponse response, AuthResultStatus result, String email) throws IOException { response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - - // 상황에 따라 서로 다른 응답 메시지를 반환 switch (result) { case LOGIN_SUCCESS -> { getRedirectStrategy().sendRedirect(request, response, BASEURL + "/main"); - log.info("{\"code\":200, \"message\":\"정상 로그인 완료\"}"); + log.info("{\"code\":200, \"message\":\"정상 로그인 완료: {}\"}", email); } case NEW_USER_REGISTERED -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + oAuth2User.getAttribute("email")); - log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료. 프로필 설정이 필요합니다.\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); + log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료: {}\"}", email); } case PROFILE_INCOMPLETE -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + oAuth2User.getAttribute("email")); - log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료. 프로필 설정 페이지로 이동해주세요.\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); + log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료: {}\"}", email); } case SUSPENDED_USER -> { - //todo MVC 호출을 위해 api가 붙음, 이후 삭제 예정 - getRedirectStrategy().sendRedirect(request, response, BASEURL+ "/api/suspends?endDate=" + authService.getSuspensionEndDate(oAuth2User)); - log.info("{\"code\":200, \"message\":\"정지된 회원에 대하여 정지 화면 호출\"}"); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/api/suspends"); + log.info("{\"code\":200, \"message\":\"정지된 회원: {}\"}", email); } default -> { getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login"); - log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생\"}"); + log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생: {}\"}", email); } } writer.flush(); - } } From e142d06255ef1a6ec6745180308ffb4335fd197e Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 03:59:36 +0900 Subject: [PATCH 0634/1002] =?UTF-8?q?feat=20::=20Oauth2=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=EC=9D=B8=EC=A6=9D=20=EC=9A=94=EC=B2=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OAuth2 클라이언트 프로퍼티에서는 response-mode가 기본 제공되지 않기 때문에 OAuth2 인증 요청 생성 시에 해당 파라미터(registration_id")를 추가하도록 커스텀 OAuth2AuthorizationRequestResolver를 구현 --- .../CustomAuthorizationRequestResolver.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java b/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java new file mode 100644 index 00000000..6b634bee --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java @@ -0,0 +1,48 @@ +package inu.codin.codin.common.util; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; + +import java.util.HashMap; +import java.util.Map; + +public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { + + private final OAuth2AuthorizationRequestResolver defaultResolver; + + public CustomAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { + // 기본 엔드포인트가 /oauth2/authorization + this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorization"); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { + OAuth2AuthorizationRequest authorizationRequest = defaultResolver.resolve(request); + return customizeAuthorizationRequest(authorizationRequest); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { + OAuth2AuthorizationRequest authorizationRequest = defaultResolver.resolve(request, clientRegistrationId); + return customizeAuthorizationRequest(authorizationRequest); + } + + private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) { + if (authorizationRequest == null) { + return null; + } + // "registration_id"라는 문자열 리터럴을 사용하여 등록 ID를 가져오기 + String registrationId = (String) authorizationRequest.getAttribute("registration_id"); + if ("apple".equalsIgnoreCase(registrationId)) { + Map additionalParameters = new HashMap<>(authorizationRequest.getAdditionalParameters()); + additionalParameters.put("response_mode", "form_post"); + return OAuth2AuthorizationRequest.from(authorizationRequest) + .additionalParameters(additionalParameters) + .build(); + } + return authorizationRequest; + } +} From f66270a9286baa218475cd8cc6ff134f07a1975e Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:00:28 +0900 Subject: [PATCH 0635/1002] =?UTF-8?q?feat=20::=20SecurityConfig=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=201.=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=9A=94=EC=B2=AD=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?2.=20apple/google=20=EC=9D=B8=EC=A6=9D=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index fb643152..8e57a854 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -5,9 +5,11 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.common.security.service.AppleOAuth2UserService; import inu.codin.codin.common.security.service.CustomOAuth2UserService; import inu.codin.codin.common.security.util.OAuth2LoginFailureHandler; import inu.codin.codin.common.security.util.OAuth2LoginSuccessHandler; +import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -24,6 +26,12 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; @@ -46,6 +54,9 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; private final PermitAllProperties permitAllProperties; + private final AppleOAuth2UserService appleOAuth2UserService; + private final ClientRegistrationRepository clientRegistrationRepository; + @Value("${server.domain}") private String BASEURL; @@ -70,8 +81,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 + .authorizationEndpoint(authorization -> authorization + .authorizationRequestResolver( + new CustomAuthorizationRequestResolver(clientRegistrationRepository)) + ) .userInfoEndpoint(userInfo -> userInfo - .userService(customOAuth2UserService) + .userService(delegatingOAuth2UserService()) ) .successHandler(oAuth2LoginSuccessHandler) .failureHandler(oAuth2LoginFailureHandler) @@ -88,6 +103,20 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } + private OAuth2UserService delegatingOAuth2UserService() { + return userRequest -> { + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + if ("apple".equals(registrationId)) { + return appleOAuth2UserService.loadUser(userRequest); + } else if ("google".equals(registrationId)) { + return customOAuth2UserService.loadUser(userRequest); + } else { + throw new OAuth2AuthenticationException(new OAuth2Error("unsupported_provider"), + "지원되지 않는 공급자입니다: " + registrationId); + } + }; + } + @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); From af2eaad44ba8b690776d5f6078967164ab032e74 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:00:40 +0900 Subject: [PATCH 0636/1002] =?UTF-8?q?feat=20::=20apple=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 21e7f452..13ec23be 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -2,12 +2,9 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.service.AuthService; +import inu.codin.codin.common.security.service.AuthCommonService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.repository.UserRepository; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -16,15 +13,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.Optional; @RestController @RequestMapping(value = "/auth") @@ -33,22 +25,29 @@ public class AuthController { private final JwtService jwtService; - private final AuthService authService; + private final AuthCommonService authCommonService; @GetMapping("/google") public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/api/oauth2/authorization/google"); + response.sendRedirect("/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } + @GetMapping("/apple") + public ResponseEntity> appleLogin(HttpServletResponse response) throws IOException { + response.sendRedirect("/oauth2/authorization/apple"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "apple OAuth2 Login Redirect",null)); + } + @Operation(summary = "회원 정보 입력 마무리") @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> completeUserProfile( @RequestPart @Valid UserProfileRequestDto userProfileRequestDto, @RequestPart(value = "userImage", required = false) MultipartFile userImage, HttpServletResponse response) { - authService.completeUserProfile(userProfileRequestDto, userImage, response); + authCommonService.completeUserProfile(userProfileRequestDto, userImage, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); } @@ -59,7 +58,7 @@ public ResponseEntity> completeUserProfile( ) @PostMapping("/login") public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { - authService.login(signUpAndLoginRequestDto, response); + authCommonService.login(signUpAndLoginRequestDto, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "로그인 성공", "기존 유저 로그인 완료")); From 4922d2c402808f84260147d10e4e0f2476b244c8 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:00:59 +0900 Subject: [PATCH 0637/1002] =?UTF-8?q?feat=20::=20apple=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/dto/apple/user/Name.java | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/Name.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/Name.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/Name.java new file mode 100644 index 00000000..d6cdbfc9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/user/Name.java @@ -0,0 +1,3 @@ +package inu.codin.codin.common.security.dto.apple.user; + +public record Name(String firstName, String lastName) { } From 8604deb095536266db617f1967ee075b742bd704 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:03:28 +0900 Subject: [PATCH 0638/1002] =?UTF-8?q?refactor=20::=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?AuthService.java=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/AuthService.java | 376 +++++++++--------- 1 file changed, 188 insertions(+), 188 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java index 009a12da..e7ba7d92 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java @@ -1,188 +1,188 @@ -package inu.codin.codin.common.security.service; - -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.entity.UserRole; -import inu.codin.codin.domain.user.entity.UserStatus; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.s3.S3Service; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -@Service -@RequiredArgsConstructor -@Slf4j -public class AuthService { - - private final UserRepository userRepository; - private final S3Service s3Service; - private final JwtService jwtService; - private final UserDetailsService userDetailsService; - - /** - * Google OAuth2를 통해 사용자 정보를 등록하고 로그인 처리하는 메소드 - * - 기존 회원이면, userStatus가 ACTIVE인 경우에만 JWT 토큰을 발급하여 정식 로그인 처리 - * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) - * userStatus가 DISABLED이면 프로필 설정이 필요하다는 상태로 처리 - * - userStatus가 SUSPEND이면 정지된 회원 - */ - public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { - - InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); - - // 회원 존재 여부 판단: email 기준 - Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); - if (optionalUser.isPresent()) { - UserEntity existingUser = optionalUser.get(); - log.info("기존 회원 로그인: {}", info.email()); - - switch(existingUser.getStatus()){ - case ACTIVE -> { - // 프로필 설정 완료된 회원: 정상 로그인 처리 - issueJwtToken(info.email(), response); - log.info("정상 로그인 완료: {}", info.email()); - return AuthResultStatus.LOGIN_SUCCESS; - } - case DISABLED -> { - // 프로필 설정 미완료: JWT 토큰 미발급, 프론트엔드에서 프로필 설정 페이지로 유도 - log.info("회원 프로필 설정 미완료 (userStatus={}) : {}", existingUser.getStatus(), info.email()); - return AuthResultStatus.PROFILE_INCOMPLETE; - } case SUSPENDED -> { - //정지된 유저 - log.info("정지된 유저 : email - {} ", info.email()); - return AuthResultStatus.SUSPENDED_USER; - } default -> { - log.error("유저의 상태가 ACTIVE, DISABLED, SUSPENDED 외의 값을 가지고 있습니다. UserStatus : {}", existingUser.getStatus() ); - throw new NotFoundException("유저의 상태(Status)를 알 수 없습니다. _id : "+ existingUser.get_id().toString() + ", status : " + existingUser.getStatus()); - } - } - - } else { - log.info("신규 회원 등록: {}", info.email()); - String deptDesc = (info.department() != null) ? info.department().replace("/", "").trim() : ""; - Department dept = Department.fromDescription(deptDesc); - - // 신규 회원 등록 시, userStatus를 DISABLED(비활성)으로 설정하여 프로필 설정 미완료 상태로 처리 - UserEntity newUser = UserEntity.builder() - .email(info.email()) - .name(info.name()) - .department(dept) - .profileImageUrl(s3Service.getDefaultProfileImageUrl()) // 기본 프로필 이미지 사용 - .status(UserStatus.DISABLED) - .role(UserRole.USER) - .build(); - userRepository.save(newUser); - log.info("신규 회원 등록 완료 (프로필 미완료): {}", newUser); - return AuthResultStatus.NEW_USER_REGISTERED; - // 신규 회원은 프로필 설정 완료 전까지는 JWT 토큰을 발급 안 함. - } - } - - private static InfoFromOAuth2User getInfoFromOAuth2User(OAuth2User oAuth2User) { - // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) - Map attributes = oAuth2User.getAttributes(); - - // 기본 속성 추출 - String email = (String) attributes.get("email"); - String sub = (String) attributes.get("sub"); // 고유 식별자 (필요시 저장) - String name = (String) attributes.get("family_name"); // 예: "김기수" - String department = (String) attributes.get("given_name"); // 예: "/컴퓨터공학부" - - log.info("OAuth2 login: email={}, sub={}, name={}, department={}", - email, sub, name, department); - InfoFromOAuth2User info = new InfoFromOAuth2User(email, name, department); - return info; - } - - private record InfoFromOAuth2User(String email, String name, String department) { - } - - /** - * 최초 로그인 후, 사용자에게 닉네임과 프로필 이미지를 설정받아 DB를 업데이트하는 메소드. - * 프로필 설정 완료 후 userStatus를 ACTIVE로 업데이트하여 정식 회원가입을 완료. - */ - public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { - // 중복 닉네임 검사 - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); - if (nickNameDuplicate.isPresent()){ - throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); - } - - // DB에서 해당 사용자를 이메일로 조회 - Optional userOpt = userRepository.findByEmailAndDisabled(userProfileRequestDto.getEmail()); - if (userOpt.isEmpty()){ - throw new NotFoundException("사용자를 찾을 수 없습니다."); - } - UserEntity user = userOpt.get(); - log.info("[completeUserProfile] 사용자 조회 성공: {}", userProfileRequestDto.getEmail()); - - // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 - String imageUrl = null; - if (userImage != null && !userImage.isEmpty()) { - log.info("[프로필 설정] 프로필 이미지 업로드 중..."); - imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); - log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); - } - // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 - if (imageUrl == null) { - imageUrl = s3Service.getDefaultProfileImageUrl(); - } - - - // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 - user.updateNickname(userProfileRequestDto.getNickname()); - user.updateProfileImageUrl(imageUrl); - user.activation(); - userRepository.save(user); - - log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); - // 프로필 설정 완료 후 정식 회원으로 간주하여 JWT 토큰 재발급 - issueJwtToken(user.getEmail(), response); - } - - - public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { - Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); - if (user.isPresent()) { - issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); - } else throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); - } - - public void issueJwtToken(String email, HttpServletResponse response) { - jwtService.deleteToken(response); - UserDetails userDetails = userDetailsService.loadUserByUsername(email); - UsernamePasswordAuthenticationToken authenticationToken = - new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authenticationToken); - jwtService.createToken(response); - } - - public LocalDateTime getSuspensionEndDate(OAuth2User oAuth2User){ - InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); - - Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); - if (optionalUser.isPresent()){ - UserEntity user = optionalUser.get(); - return user.getTotalSuspensionEndDate(); - } else throw new NotFoundException("유저를 찾을 수 없습니다. _id: " + optionalUser.get().get_id().toString()); - } -} +//package inu.codin.codin.common.security.service; +// +//import inu.codin.codin.common.dto.Department; +//import inu.codin.codin.common.exception.NotFoundException; +//import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +//import inu.codin.codin.common.security.enums.AuthResultStatus; +//import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +//import inu.codin.codin.domain.user.entity.UserEntity; +//import inu.codin.codin.domain.user.entity.UserRole; +//import inu.codin.codin.domain.user.entity.UserStatus; +//import inu.codin.codin.domain.user.exception.UserCreateFailException; +//import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; +//import inu.codin.codin.domain.user.repository.UserRepository; +//import inu.codin.codin.infra.s3.S3Service; +//import jakarta.servlet.http.HttpServletResponse; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +//import org.springframework.security.core.context.SecurityContextHolder; +//import org.springframework.security.core.userdetails.UserDetails; +//import org.springframework.security.core.userdetails.UserDetailsService; +//import org.springframework.security.oauth2.core.user.OAuth2User; +//import org.springframework.stereotype.Service; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.time.LocalDateTime; +//import java.util.List; +//import java.util.Map; +//import java.util.Optional; +// +//@Service +//@RequiredArgsConstructor +//@Slf4j +//public class AuthService { +// +// private final UserRepository userRepository; +// private final S3Service s3Service; +// private final JwtService jwtService; +// private final UserDetailsService userDetailsService; +// +// /** +// * Google OAuth2를 통해 사용자 정보를 등록하고 로그인 처리하는 메소드 +// * - 기존 회원이면, userStatus가 ACTIVE인 경우에만 JWT 토큰을 발급하여 정식 로그인 처리 +// * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) +// * userStatus가 DISABLED이면 프로필 설정이 필요하다는 상태로 처리 +// * - userStatus가 SUSPEND이면 정지된 회원 +// */ +// public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { +// +// InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); +// +// // 회원 존재 여부 판단: email 기준 +// Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); +// if (optionalUser.isPresent()) { +// UserEntity existingUser = optionalUser.get(); +// log.info("기존 회원 로그인: {}", info.email()); +// +// switch(existingUser.getStatus()){ +// case ACTIVE -> { +// // 프로필 설정 완료된 회원: 정상 로그인 처리 +// issueJwtToken(info.email(), response); +// log.info("정상 로그인 완료: {}", info.email()); +// return AuthResultStatus.LOGIN_SUCCESS; +// } +// case DISABLED -> { +// // 프로필 설정 미완료: JWT 토큰 미발급, 프론트엔드에서 프로필 설정 페이지로 유도 +// log.info("회원 프로필 설정 미완료 (userStatus={}) : {}", existingUser.getStatus(), info.email()); +// return AuthResultStatus.PROFILE_INCOMPLETE; +// } case SUSPENDED -> { +// //정지된 유저 +// log.info("정지된 유저 : email - {} ", info.email()); +// return AuthResultStatus.SUSPENDED_USER; +// } default -> { +// log.error("유저의 상태가 ACTIVE, DISABLED, SUSPENDED 외의 값을 가지고 있습니다. UserStatus : {}", existingUser.getStatus() ); +// throw new NotFoundException("유저의 상태(Status)를 알 수 없습니다. _id : "+ existingUser.get_id().toString() + ", status : " + existingUser.getStatus()); +// } +// } +// +// } else { +// log.info("신규 회원 등록: {}", info.email()); +// String deptDesc = (info.department() != null) ? info.department().replace("/", "").trim() : ""; +// Department dept = Department.fromDescription(deptDesc); +// +// // 신규 회원 등록 시, userStatus를 DISABLED(비활성)으로 설정하여 프로필 설정 미완료 상태로 처리 +// UserEntity newUser = UserEntity.builder() +// .email(info.email()) +// .name(info.name()) +// .department(dept) +// .profileImageUrl(s3Service.getDefaultProfileImageUrl()) // 기본 프로필 이미지 사용 +// .status(UserStatus.DISABLED) +// .role(UserRole.USER) +// .build(); +// userRepository.save(newUser); +// log.info("신규 회원 등록 완료 (프로필 미완료): {}", newUser); +// return AuthResultStatus.NEW_USER_REGISTERED; +// // 신규 회원은 프로필 설정 완료 전까지는 JWT 토큰을 발급 안 함. +// } +// } +// +// private static InfoFromOAuth2User getInfoFromOAuth2User(OAuth2User oAuth2User) { +// // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) +// Map attributes = oAuth2User.getAttributes(); +// +// // 기본 속성 추출 +// String email = (String) attributes.get("email"); +// String sub = (String) attributes.get("sub"); // 고유 식별자 (필요시 저장) +// String name = (String) attributes.get("family_name"); // 예: "김기수" +// String department = (String) attributes.get("given_name"); // 예: "/컴퓨터공학부" +// +// log.info("OAuth2 login: email={}, sub={}, name={}, department={}", +// email, sub, name, department); +// InfoFromOAuth2User info = new InfoFromOAuth2User(email, name, department); +// return info; +// } +// +// private record InfoFromOAuth2User(String email, String name, String department) { +// } +// +// /** +// * 최초 로그인 후, 사용자에게 닉네임과 프로필 이미지를 설정받아 DB를 업데이트하는 메소드. +// * 프로필 설정 완료 후 userStatus를 ACTIVE로 업데이트하여 정식 회원가입을 완료. +// */ +// public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { +// // 중복 닉네임 검사 +// Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); +// if (nickNameDuplicate.isPresent()){ +// throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); +// } +// +// // DB에서 해당 사용자를 이메일로 조회 +// Optional userOpt = userRepository.findByEmailAndDisabled(userProfileRequestDto.getEmail()); +// if (userOpt.isEmpty()){ +// throw new NotFoundException("사용자를 찾을 수 없습니다."); +// } +// UserEntity user = userOpt.get(); +// log.info("[completeUserProfile] 사용자 조회 성공: {}", userProfileRequestDto.getEmail()); +// +// // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 +// String imageUrl = null; +// if (userImage != null && !userImage.isEmpty()) { +// log.info("[프로필 설정] 프로필 이미지 업로드 중..."); +// imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); +// log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); +// } +// // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 +// if (imageUrl == null) { +// imageUrl = s3Service.getDefaultProfileImageUrl(); +// } +// +// +// // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 +// user.updateNickname(userProfileRequestDto.getNickname()); +// user.updateProfileImageUrl(imageUrl); +// user.activation(); +// userRepository.save(user); +// +// log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); +// // 프로필 설정 완료 후 정식 회원으로 간주하여 JWT 토큰 재발급 +// issueJwtToken(user.getEmail(), response); +// } +// +// +// public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { +// Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); +// if (user.isPresent()) { +// issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); +// } else throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); +// } +// +// public void issueJwtToken(String email, HttpServletResponse response) { +// jwtService.deleteToken(response); +// UserDetails userDetails = userDetailsService.loadUserByUsername(email); +// UsernamePasswordAuthenticationToken authenticationToken = +// new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); +// SecurityContextHolder.getContext().setAuthentication(authenticationToken); +// jwtService.createToken(response); +// } +// +// public LocalDateTime getSuspensionEndDate(OAuth2User oAuth2User){ +// InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); +// +// Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); +// if (optionalUser.isPresent()){ +// UserEntity user = optionalUser.get(); +// return user.getTotalSuspensionEndDate(); +// } else throw new NotFoundException("유저를 찾을 수 없습니다. _id: " + optionalUser.get().get_id().toString()); +// } +//} From a2c3a53555f6266149d542f9b22cce7b685e51be Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:09:13 +0900 Subject: [PATCH 0639/1002] chore :: update submodule --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a3835af8..15bcbe00 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 +Subproject commit 15bcbe00f7f9fff4c94188d923af056267ebdd9f From 482a668f5a029a2bbe2f61e1871deb35454d1046 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:10:09 +0900 Subject: [PATCH 0640/1002] chore :: update submodule --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 15bcbe00..e8edc79f 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 15bcbe00f7f9fff4c94188d923af056267ebdd9f +Subproject commit e8edc79f87e9caecfd84b5df593c133a405702ad From 2625e5e34ff90622c7609629d775f3c4743909fd Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:12:13 +0900 Subject: [PATCH 0641/1002] =?UTF-8?q?feat=20::=20authorization=20code=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AppleEventController.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java new file mode 100644 index 00000000..e7ede441 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -0,0 +1,32 @@ +package inu.codin.codin.common.security.controller; + +import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; +import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; +import inu.codin.codin.common.security.service.AppleOAuth2UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/auth/apple") +@RequiredArgsConstructor +public class AppleEventController { + + private final AppleOAuth2UserService appleOAuth2UserService; + +// @PostMapping("/login") +// public ResponseEntity handleAppleLogin(@RequestBody AppleAuthRequest appleAuthRequest) { +// try { +// // AppleOAuth2UserService에 AppleAuthRequest를 직접 처리하는 메서드가 있다고 가정합니다. +// OAuth2User principalUser = appleOAuth2UserService.loadUser(appleAuthRequest); +// // principalUser.getName()는 Apple의 고유 식별자(sub)를 반환하도록 구현되어 있습니다. +// String accountId = principalUser.getName(); +// return ResponseEntity.ok(new AppleLoginResponse(accountId, "User processed successfully")); +// } catch (Exception e) { +// return ResponseEntity.status(HttpStatus.UNAUTHORIZED) +// .body(new AppleLoginResponse(null, "Authentication failed: " + e.getMessage())); +// } +// } +} \ No newline at end of file From 0acbb5d3eaf3af8ad967fdf00aa6cd824e54bfa7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:49:57 +0900 Subject: [PATCH 0642/1002] =?UTF-8?q?fix=20::=20swagger=20local=20port=208?= =?UTF-8?q?082=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SwaggerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index 6aa69a4d..a38319b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -46,7 +46,7 @@ public OpenAPI customOpenAPI() { .security(List.of(securityRequirement)) .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) .servers(List.of( - new Server().url("http://localhost:8080").description("Local Server"), // Local Server + new Server().url("http://localhost:8082").description("Local Server"), // Local Server new Server().url(BASEURL+"/api").description("Production Server"), // Production Server new Server().url(BASEURL+"/dev").description("Release Server") )); From a3c055fa7fd75823f8e8deed72acfab120a72dd9 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 04:51:44 +0900 Subject: [PATCH 0643/1002] =?UTF-8?q?chore=20::=20submodule=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index e8edc79f..e4ff3c62 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit e8edc79f87e9caecfd84b5df593c133a405702ad +Subproject commit e4ff3c62007ff3fd13adaca9b599d29e13b4edfb From 5c7a4e1fd685506e66024676e6e5e4183b8992d8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 15 Mar 2025 23:48:28 +0900 Subject: [PATCH 0644/1002] =?UTF-8?q?perf=20:=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EC=9D=84=20=EB=B0=9B=EB=8A=94=20=EC=9C=A0=EC=A0=80=EC=9D=98=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/WebSocketConfig.java | 2 ++ .../service/ChattingEventListener.java | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index 006bf393..ed6205a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -39,6 +39,8 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { //메세지를 브로커로 라우팅 registry.setApplicationDestinationPrefixes("/pub"); //클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐 + registry.setUserDestinationPrefix("/user"); + //convertAndSendToUser 사용할 prefix } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index 56db6b3e..d1326614 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -10,6 +10,8 @@ import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.context.event.EventListener; @@ -17,10 +19,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Service @RequiredArgsConstructor @@ -28,6 +27,7 @@ public class ChattingEventListener { private final ChatRoomRepository chatRoomRepository; private final ChattingRepository chattingRepository; + private final UserRepository userRepository; private final SimpMessageSendingOperations template; private final NotificationService notificationService; @@ -35,7 +35,7 @@ public class ChattingEventListener { 채팅을 발신했을 경우, 1. 상대방이 접속한 상태가 아니라면 상대방의 unread 값 +1 2. 채팅방의 마지막 메세지 업데이트 - 3. /queue/chatroom/{userId} 를 통해 실시간으로 채팅방 목록 업데이트 + 3. /queue/chatroom/unread 를 통해 상대방의 채팅방 목록 실시간 업데이트 */ @Async @EventListener @@ -51,29 +51,33 @@ public void handleChattingArrivedEvent(ChattingArrivedEvent event){ } private void updateUnread(ChattingArrivedEvent event, ChatRoom chatRoom) { - Map> result = new HashMap<>(); + Map result = new HashMap<>(); ObjectId receiverId = null; for (Map.Entry entry : chatRoom.getParticipants().getInfo().entrySet()) { ParticipantInfo participantInfo = entry.getValue(); if (!participantInfo.getUserId().equals(event.getChatting().getSenderId())) { - receiverId = participantInfo.getUserId(); if (!participantInfo.isConnected()) { + receiverId = participantInfo.getUserId(); participantInfo.plusUnread(); - result.put(chatRoom.get_id().toString(), getLastMessageAndUnread(event, participantInfo)); + result = getLastMessageAndUnread(event, participantInfo); } } } chatRoom.updateLastMessage(event.getChatting().getContent()); - if (receiverId!=null) - template.convertAndSend("/queue/chatroom/unread/"+ receiverId, result); + if (receiverId!=null) { //받는 사람이 없다는 것은 채팅에 연결 중인 상태, 채팅방 업데이트할 필요 X + Optional user = userRepository.findByUserId(receiverId); + if (user.isPresent()) + template.convertAndSendToUser(user.get().getEmail(), "/queue/chatroom/unread", result); + } } private static Map getLastMessageAndUnread(ChattingArrivedEvent event, ParticipantInfo participantInfo) { - Map lastMessageAndUnread = new HashMap<>(); - lastMessageAndUnread.put("lastMessage", event.getChatting().getContent()); - lastMessageAndUnread.put("unread", String.valueOf(participantInfo.getUnreadMessage())); - return lastMessageAndUnread; + return Map.of( + "chatRoomId", event.getChatting().get_id().toString(), + "lastMessage", event.getChatting().getContent(), + "unread", String.valueOf(participantInfo.getUnreadMessage()) + ); } @Async From a5bb37639522c1989cf8c011ceef362aed80c746 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 15 Mar 2025 23:48:53 +0900 Subject: [PATCH 0645/1002] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EA=B5=AC=EB=8F=85,=20=EA=B5=AC=EB=8F=85=EC=B7=A8=EC=86=8C?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=B2=98=EB=A6=AC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageProcessor.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 1b462ebf..7432f34d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -30,19 +30,24 @@ public void handleMessage(StompHeaderAccessor headerAccessor){ throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString()); } - switch (headerAccessor.getCommand()){ - case CONNECT -> { - stompMessageService.connectSession(headerAccessor); - } - case SUBSCRIBE -> { - stompMessageService.enterToChatRoom(headerAccessor); - } - case UNSUBSCRIBE -> { - stompMessageService.exitToChatRoom(headerAccessor); - } - case DISCONNECT -> { - stompMessageService.exitToChatRoom(headerAccessor); - stompMessageService.disconnectSession(headerAccessor); + /* + 채팅방 구독, 구독취소에 대한 destination만 처리 + */ + if (headerAccessor.getDestination()!=null && headerAccessor.getDestination().matches("/queue(/unread)?/[^/]+")) { + switch (headerAccessor.getCommand()) { + case CONNECT -> { + stompMessageService.connectSession(headerAccessor); + } + case SUBSCRIBE -> { + stompMessageService.enterToChatRoom(headerAccessor); + } + case UNSUBSCRIBE -> { + stompMessageService.exitToChatRoom(headerAccessor); + } + case DISCONNECT -> { + stompMessageService.exitToChatRoom(headerAccessor); + stompMessageService.disconnectSession(headerAccessor); + } } } } From 88a4ee5170b688986cf9c50bcef7db02e0e1923d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 15 Mar 2025 23:49:15 +0900 Subject: [PATCH 0646/1002] =?UTF-8?q?feat=20:=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?test=EB=A5=BC=20=EC=9C=84=ED=95=9C=20MVC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/controller/ChattingController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 89ffd05d..53f45c62 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -67,4 +67,9 @@ public String chatImageHtml(){ return "chatImage"; } + @GetMapping("/chat/room") + public String chatroomHtml(){ + return "chatroom"; + } + } From 5d9be7d9223b7b6c0e93ea9a5c524f206836d6e1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 15 Mar 2025 23:49:39 +0900 Subject: [PATCH 0647/1002] =?UTF-8?q?fix=20:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EB=8C=80=ED=99=94=20=EB=82=B4=EC=9A=A9=EC=9D=B4=20=EC=97=86?= =?UTF-8?q?=EC=9D=84=20=EA=B2=BD=EC=9A=B0(null)=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=EC=9D=B4=20=EB=A7=88=EC=A7=80?= =?UTF-8?q?=EB=A7=89=EC=9C=BC=EB=A1=9C=20=EB=B0=98=ED=99=98=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index d027c0eb..0c8dc3af 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -83,7 +83,7 @@ public List getAllChatRoomByUser() { return chatRooms.stream() .filter(chatRoom -> chatRoom.getParticipants().getInfo().keySet().stream() .noneMatch(blockedUsersId::contains)) - .sorted(Comparator.comparing(ChatRoom::getCurrentMessageDate,Comparator.reverseOrder())) + .sorted(Comparator.comparing(ChatRoom::getCurrentMessageDate,Comparator.nullsLast(Comparator.reverseOrder()))) .map(chatRoom -> ChatRoomListResponseDto.of(chatRoom, userId)).toList(); } From 896f9ba13c87522eba1f8f76a7f96f4cdde004ba Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 15 Mar 2025 23:54:19 +0900 Subject: [PATCH 0648/1002] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=EC=97=90=20=EC=A0=91=EC=86=8D=EC=9D=B4=20=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C?= =?UTF-8?q?=EB=A7=8C=20fcm=20=EC=95=8C=EB=A6=BC=20=EB=B0=9C=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/service/ChattingEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index d1326614..d83bcf17 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -84,7 +84,7 @@ private static Map getLastMessageAndUnread(ChattingArrivedEvent @EventListener public void handleChattingNotificationEvent(ChattingNotificationEvent event){ event.getChatRoom().getParticipants().getInfo().values().stream() - .filter(participantInfo -> !participantInfo.getUserId().equals(event.getUserId()) && participantInfo.isNotificationsEnabled()) + .filter(participantInfo -> !participantInfo.getUserId().equals(event.getUserId()) && participantInfo.isNotificationsEnabled() & !participantInfo.isConnected()) .peek(participantInfo -> notificationService.sendNotificationMessageByChat(participantInfo.getUserId(), event.getChatRoom().get_id())); } From 331d9a5d8f50756ee927b732b6ea2dd90cda6692 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 15 Mar 2025 23:58:03 +0900 Subject: [PATCH 0649/1002] fix :: Security Oauth2 Chaining MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Apple 클라이언트에 대해 커스텀 토큰 응답 클라이언트 적용 2. OAuth2 정보 Cookie 사용 캐싱 3. google, apple 분기 처리 --- .../codin/common/config/SecurityConfig.java | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 8e57a854..637b5213 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.config; + import inu.codin.codin.common.dto.PermitAllProperties; import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; @@ -7,10 +8,10 @@ import inu.codin.codin.common.security.jwt.JwtUtils; import inu.codin.codin.common.security.service.AppleOAuth2UserService; import inu.codin.codin.common.security.service.CustomOAuth2UserService; -import inu.codin.codin.common.security.util.OAuth2LoginFailureHandler; -import inu.codin.codin.common.security.util.OAuth2LoginSuccessHandler; +import inu.codin.codin.common.security.util.*; import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,8 +30,12 @@ import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -41,6 +46,7 @@ import java.util.List; +@Slf4j @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -56,6 +62,7 @@ public class SecurityConfig { private final AppleOAuth2UserService appleOAuth2UserService; private final ClientRegistrationRepository clientRegistrationRepository; + private final CustomOAuth2AccessTokenResponseClient customOAuth2AccessTokenResponseClient; @Value("${server.domain}") private String BASEURL; @@ -81,13 +88,22 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 - .authorizationEndpoint(authorization -> authorization - .authorizationRequestResolver( - new CustomAuthorizationRequestResolver(clientRegistrationRepository)) + // Apple 클라이언트에 대해 커스텀 토큰 응답 클라이언트 적용 + .tokenEndpoint(token -> token + .accessTokenResponseClient(customOAuth2AccessTokenResponseClient) ) + .authorizationEndpoint(authorization -> authorization + //쿠키를 사용해 OAuth의 정보를 가져오고 저장 + .authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()) + .authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository)) + ) +// .redirectionEndpoint(redirection -> redirection +// .baseUri("/callback/apple") // apple 콜백 URI를 설정추가. +// ) .userInfoEndpoint(userInfo -> userInfo .userService(delegatingOAuth2UserService()) ) + .successHandler(oAuth2LoginSuccessHandler) .failureHandler(oAuth2LoginFailureHandler) ) @@ -103,12 +119,21 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } + + @Bean + public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository() { + return new OAuth2AuthorizationRequestBasedOnCookieRepository(); + } + private OAuth2UserService delegatingOAuth2UserService() { return userRequest -> { String registrationId = userRequest.getClientRegistration().getRegistrationId(); + log.info("OAuth2 registrationId: {}", registrationId); if ("apple".equals(registrationId)) { + log.info("apple Login loadUser : {}", userRequest); return appleOAuth2UserService.loadUser(userRequest); } else if ("google".equals(registrationId)) { + log.info("google Login loadUser"); return customOAuth2UserService.loadUser(userRequest); } else { throw new OAuth2AuthenticationException(new OAuth2Error("unsupported_provider"), @@ -117,6 +142,7 @@ private OAuth2UserService delegatingOAuth2UserSer }; } + @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); @@ -133,7 +159,10 @@ public RoleHierarchy roleHierarchy() { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - + @Bean + public AuthorizationRequestRepository authorizationRequestRepository() { + return new HttpSessionOAuth2AuthorizationRequestRepository(); + } /** * CORS 설정 */ @@ -145,14 +174,18 @@ public CorsConfigurationSource corsConfigurationSource() { "http://localhost:3000", "http://localhost:8080", BASEURL, - "https://front-end-peach-two.vercel.app" + "https://front-end-peach-two.vercel.app", + "https://e876-2406-5900-1080-882f-b519-f7cf-62b3-4ba4.ngrok-free.app", + "http://e876-2406-5900-1080-882f-b519-f7cf-62b3-4ba4.ngrok-free.app", + "https://appleid.apple.com" )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); config.setAllowedHeaders(List.of( "Authorization", "Content-Type", "X-Requested-With", - "Accept" + "Accept", + "Cache-Control" )); config.setExposedHeaders(List.of("Authorization")); config.setMaxAge(3600L); From 429020477236d77d67e015ef881a4cd74ef7bd92 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:12:40 +0900 Subject: [PATCH 0650/1002] log :: logging --- .../codin/codin/common/security/service/AppleAuthService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java index d914b7ae..9df979bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java @@ -28,6 +28,7 @@ public AppleAuthService(UserRepository userRepository, S3Service s3Service, JwtS @Override public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { // Apple에서는 email이 없을 수 있으므로, email이 없으면 고유 식별자(sub)를 사용. + log.info("AppleAuthService oauthLogin"); Map attributes = oAuth2User.getAttributes(); String email = (String) attributes.get("email"); String sub = (String) attributes.get("sub"); From ea4b0a55d217fe02b4a92c03c35269ca67b817b6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 16 Mar 2025 00:13:30 +0900 Subject: [PATCH 0651/1002] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20currentMessageDate=EB=8F=84?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 2ad645ae..e7767a05 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -52,6 +52,7 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obj .roomName(chatRoomCreateRequestDto.getRoomName()) .referenceId(new ObjectId(chatRoomCreateRequestDto.getReferenceId())) .participants(participants) + .currentMessageDate(LocalDateTime.now()) .build(); } From 76a4f14b339406faea49e6868543730f22f3e188 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:13:42 +0900 Subject: [PATCH 0652/1002] =?UTF-8?q?feat=20::=20Apple=20Secret=20Key=20Ge?= =?UTF-8?q?nerator=201.=20=20.p8=20=ED=8C=8C=EC=9D=BC=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=8F=99=EC=A0=81=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/AppleClientSecretGenerator.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/AppleClientSecretGenerator.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleClientSecretGenerator.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleClientSecretGenerator.java new file mode 100644 index 00000000..f8a4d9c0 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleClientSecretGenerator.java @@ -0,0 +1,78 @@ +package inu.codin.codin.common.security.util; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.time.Instant; +import java.util.Base64; +import java.util.Date; + +@Service +@Slf4j +public class AppleClientSecretGenerator { + + @Value("${apple.team-id}") + private String teamId; // 예: 2KTK94265N + + @Value("${apple.key.id}") + private String keyId; // 예: K74A3SVBD3 + + @Value("${apple.aud}") + private String audience; // 보통 "https://appleid.apple.com" + + @Value("${spring.security.oauth2.client.registration.apple.client-id}") + private String clientId; // Apple 서비스 ID + + @Value("${apple.key.path}") + private Resource keyResource; // 예: classpath:resources/key/AuthKey_K74A3SVBD3.p8 + + /** + * Apple client-secret (JWT)을 생성합니다. + * Apple은 client-secret의 유효기간을 최대 6개월 미만으로 요구하므로, 이 예제에서는 약 6개월 유효기간으로 설정합니다. + */ + public String generateAppleClientSecret() throws Exception { + PrivateKey privateKey = getPrivateKeyFromP8(); + + Instant now = Instant.now(); + // 약 6개월 유효 (Apple은 6개월 미만이어야 함) + Instant expirationTime = now.plusSeconds(15777000L); + + return Jwts.builder() + .setHeaderParam("kid", keyId) + .setIssuer(teamId) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(expirationTime)) + .setAudience(audience) + .setSubject(clientId) + .signWith(privateKey, SignatureAlgorithm.ES256) + .compact(); + } + + /** + * p8 파일을 읽어 PrivateKey 객체로 변환합니다. + */ + private PrivateKey getPrivateKeyFromP8() throws Exception { + try (InputStream is = keyResource.getInputStream()) { + String key = new String(is.readAllBytes(), StandardCharsets.UTF_8); + // log.info("getPrivateKeyFromP8 :{}" ,key); + key = key.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + byte[] keyBytes = Base64.getDecoder().decode(key); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("EC"); // Apple은 ES256(ECDSA with P-256) 알고리즘 사용 + return kf.generatePrivate(spec); + } + } +} + + From 44cdd12fbe760efbc5e5a2ed50b17d40f8927ac9 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:14:06 +0900 Subject: [PATCH 0653/1002] =?UTF-8?q?feat=20::=20=20apple=20=EC=9D=B8?= =?UTF-8?q?=EA=B0=80=EC=BD=94=EB=93=9C=20=EC=9C=84=ED=95=9C=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AppleEventController.java | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java index e7ede441..33c6a6cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -1,32 +1,58 @@ package inu.codin.codin.common.security.controller; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; import inu.codin.codin.common.security.service.AppleOAuth2UserService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.*; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.stream.Collectors; + @RestController -@RequestMapping("/auth/apple") @RequiredArgsConstructor +@Slf4j public class AppleEventController { - private final AppleOAuth2UserService appleOAuth2UserService; - -// @PostMapping("/login") -// public ResponseEntity handleAppleLogin(@RequestBody AppleAuthRequest appleAuthRequest) { -// try { -// // AppleOAuth2UserService에 AppleAuthRequest를 직접 처리하는 메서드가 있다고 가정합니다. -// OAuth2User principalUser = appleOAuth2UserService.loadUser(appleAuthRequest); -// // principalUser.getName()는 Apple의 고유 식별자(sub)를 반환하도록 구현되어 있습니다. -// String accountId = principalUser.getName(); -// return ResponseEntity.ok(new AppleLoginResponse(accountId, "User processed successfully")); -// } catch (Exception e) { -// return ResponseEntity.status(HttpStatus.UNAUTHORIZED) -// .body(new AppleLoginResponse(null, "Authentication failed: " + e.getMessage())); -// } -// } + private final ObjectMapper objectMapper; + + @PostMapping("/login/oauth2/code/apple") + public ResponseEntity appleCallback(HttpServletRequest request, HttpServletResponse response) { + try { + // Apple OAuth2에서 전달된 인가 코드, id_token, user 데이터 가져오기 + String authorizationCode = request.getParameter("code"); + String idToken = request.getParameter("id_token"); + String userJson = request.getParameter("user"); // 최초 로그인 시에만 제공됨 + + log.info("Apple OAuth Callback - code: {}, id_token: {}", authorizationCode, idToken); + log.info("Apple OAuth Callback - user 정보: {}", userJson); + + if (userJson != null) { + // user 정보를 Base64 인코딩하여 쿠키로 저장 (보안상 HttpOnly 설정) + Cookie userCookie = new Cookie("apple_user", Base64.getEncoder().encodeToString(userJson.getBytes(StandardCharsets.UTF_8))); + userCookie.setHttpOnly(true); + userCookie.setPath("/"); + userCookie.setMaxAge(60 * 5); // 5분 유지 + response.addCookie(userCookie); + log.info("Apple 최초 로그인 - user 정보 쿠키 저장 완료"); + } + + return ResponseEntity.ok("Apple 로그인 처리 중"); + + } catch (Exception e) { + log.error("Apple OAuth Callback 처리 중 오류 발생", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Apple OAuth 처리 실패"); + } + } } \ No newline at end of file From d3927a31dada5796e33bd0afe103dda92f5ec6b0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:15:06 +0900 Subject: [PATCH 0654/1002] =?UTF-8?q?feat=20::=20=20apple,google=20Access?= =?UTF-8?q?=20Token=20=EB=B0=9C=EA=B8=89=20=EC=9C=84=ED=95=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppleOAuth2AccessTokenResponseClient.java | 87 +++++++++++++++++++ ...CustomOAuth2AccessTokenResponseClient.java | 35 ++++++++ 2 files changed, 122 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/CustomOAuth2AccessTokenResponseClient.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java new file mode 100644 index 00000000..feb74ea4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java @@ -0,0 +1,87 @@ +package inu.codin.codin.common.security.util; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.convert.converter.Converter; +import org.springframework.http.*; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Map; + +@Component +@Slf4j +public class AppleOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient { + + private final AppleClientSecretGenerator appleClientSecretGenerator; + private final RestTemplate restTemplate = new RestTemplate(); + + public AppleOAuth2AccessTokenResponseClient(AppleClientSecretGenerator appleClientSecretGenerator) { + this.appleClientSecretGenerator = appleClientSecretGenerator; + } + + + @Override + public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) { + RequestEntity> requestEntity = buildRequestEntity(authorizationGrantRequest); + ResponseEntity responseEntity = restTemplate.exchange(requestEntity, Map.class); + @SuppressWarnings("unchecked") + Map tokenResponseParameters = responseEntity.getBody(); + return convertTokenResponse(tokenResponseParameters); + } + + private RequestEntity> buildRequestEntity(OAuth2AuthorizationCodeGrantRequest request) { + // 클라이언트 등록 정보 + var clientRegistration = request.getClientRegistration(); + + // 요청 파라미터 구성 + MultiValueMap formParameters = new LinkedMultiValueMap<>(); + formParameters.add(OAuth2ParameterNames.GRANT_TYPE, "authorization_code"); + formParameters.add(OAuth2ParameterNames.CODE, request.getAuthorizationExchange().getAuthorizationResponse().getCode()); + formParameters.add(OAuth2ParameterNames.REDIRECT_URI, clientRegistration.getRedirectUri()); + formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); + + try { + String dynamicClientSecret = appleClientSecretGenerator.generateAppleClientSecret(); + log.info("dynamicClientSecret : {}", dynamicClientSecret); + formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, dynamicClientSecret); + } catch (Exception ex) { + OAuth2Error error = new OAuth2Error("client_secret_generation_error", + "Failed to generate Apple client secret", null); + throw new OAuth2AuthenticationException(error, ex); + } + + // 헤더 설정 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + URI tokenUri = URI.create(clientRegistration.getProviderDetails().getTokenUri()); + return new RequestEntity<>(formParameters, headers, HttpMethod.POST, tokenUri); + } + + private OAuth2AccessTokenResponse convertTokenResponse(Map tokenResponseParameters) { + String accessToken = (String) tokenResponseParameters.get("access_token"); + String refreshToken = (String) tokenResponseParameters.get("refresh_token"); + Integer expiresIn = tokenResponseParameters.get("expires_in") instanceof Integer + ? (Integer) tokenResponseParameters.get("expires_in") : null; + + return OAuth2AccessTokenResponse.withToken(accessToken) + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .expiresIn(expiresIn != null ? expiresIn.longValue() : 0L) + .refreshToken(refreshToken) + .additionalParameters(tokenResponseParameters) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomOAuth2AccessTokenResponseClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomOAuth2AccessTokenResponseClient.java new file mode 100644 index 00000000..6f866138 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomOAuth2AccessTokenResponseClient.java @@ -0,0 +1,35 @@ +package inu.codin.codin.common.security.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class CustomOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient { + + private final AppleOAuth2AccessTokenResponseClient appleClient; + private final DefaultAuthorizationCodeTokenResponseClient googleClient; // Google 기본 클라이언트 + + public CustomOAuth2AccessTokenResponseClient(AppleOAuth2AccessTokenResponseClient appleClient) { + this.appleClient = appleClient; + this.googleClient = new DefaultAuthorizationCodeTokenResponseClient(); // Spring Security 기본 Google 클라이언트 + } + + @Override + public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) { + String registrationId = authorizationGrantRequest.getClientRegistration().getRegistrationId(); + log.info("OAuth2AccessTokenResponseClient - registrationId: {}", registrationId); + + if ("apple".equalsIgnoreCase(registrationId)) { + log.info("Handling Apple OAuth2 token response..."); + return appleClient.getTokenResponse(authorizationGrantRequest); + } else { + log.info("Handling Google OAuth2 token response..."); + return googleClient.getTokenResponse(authorizationGrantRequest); + } + } +} From eefb6de26a884687f6d29f1fc9b9edf3069fa289 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:15:28 +0900 Subject: [PATCH 0655/1002] =?UTF-8?q?feat=20::=20=20OAuth2=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=8B=9C=20=EC=BF=A0=ED=82=A4=20=EC=BA=90=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...izationRequestBasedOnCookieRepository.java | 40 ++++++++++++++ .../codin/codin/common/util/CookieUtil.java | 55 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2AuthorizationRequestBasedOnCookieRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/CookieUtil.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2AuthorizationRequestBasedOnCookieRepository.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2AuthorizationRequestBasedOnCookieRepository.java new file mode 100644 index 00000000..3d05e261 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2AuthorizationRequestBasedOnCookieRepository.java @@ -0,0 +1,40 @@ +package inu.codin.codin.common.security.util; + +import inu.codin.codin.common.util.CookieUtil; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.web.util.WebUtils; + +public class OAuth2AuthorizationRequestBasedOnCookieRepository implements AuthorizationRequestRepository { + + public final static String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + private final static int COOKIE_EXPIRE_SECONDS = 18000; + + @Override + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { + return this.loadAuthorizationRequest(request); + } + + @Override + public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { + Cookie cookie = WebUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + return CookieUtil.deserialize(cookie, OAuth2AuthorizationRequest.class); + } + + @Override + public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { + if (authorizationRequest == null) { + removeAuthorizationRequestCookies(request, response); + return; + } + + CookieUtil.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtil.serialize(authorizationRequest), COOKIE_EXPIRE_SECONDS); + } + + public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) { + CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/CookieUtil.java b/codin-core/src/main/java/inu/codin/codin/common/util/CookieUtil.java new file mode 100644 index 00000000..e4832ede --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/CookieUtil.java @@ -0,0 +1,55 @@ +package inu.codin.codin.common.util; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.util.SerializationUtils; + +import java.util.Base64; + +public class CookieUtil { + + // 요청값(이름, 값, 만료 기간)을 바탕으로 HTTP 응답에 쿠키 추가 + public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setMaxAge(maxAge); + + response.addCookie(cookie); + } + + // 쿠키의 이름을 입력받아 쿠키 삭제 + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { + Cookie[] cookies = request.getCookies(); + + if (cookies == null) { + return; + } + + // 실제로 삭제하는 방법은 없으므로 파라미터로 넘어온 키의 쿠키를 빈 값으로 바꾸고 + // 만료 시간을 0으로 설정해 쿠키가 재생성 되자마자 만료 처리한다. + for (Cookie cookie : cookies) { + if (name.equals(cookie.getName())) { + cookie.setValue(""); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + + // 객체를 직렬화해 쿠키의 값으로 변환 + public static String serialize(Object obj) { + return Base64.getUrlEncoder() + .encodeToString(SerializationUtils.serialize(obj)); + } + + // 쿠키를 역직렬화해서 객체로 변환 + public static T deserialize(Cookie cookie, Class cls) { + return cls.cast( + SerializationUtils.deserialize( + Base64.getUrlDecoder().decode(cookie.getValue()) + ) + ); + } +} From 18a298b1e7693d465e2196ec72200db4ebc3e3fc Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:19:17 +0900 Subject: [PATCH 0656/1002] =?UTF-8?q?feat=20::=20-=20Spring=20Security=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20OAuth2=20=EC=9A=94=EC=B2=AD=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=BB=A4=EC=8A=A4=ED=85=80=20-=20Apple=20?= =?UTF-8?q?OAuth2=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=EA=B0=90=EC=A7=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20response=5Fmode=3Dform=5Fpost=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20Google=20=EB=93=B1=20=EB=8B=A4?= =?UTF-8?q?=EB=A5=B8=20OAuth2=20=EC=9A=94=EC=B2=AD=EC=9D=80=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EB=8F=99=EC=9E=91=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomAuthorizationRequestResolver.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java b/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java index 6b634bee..a4fbdf09 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java +++ b/codin-core/src/main/java/inu/codin/codin/common/util/CustomAuthorizationRequestResolver.java @@ -1,18 +1,27 @@ package inu.codin.codin.common.util; import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; +/*** + * OAuth2 인증 요청을 가로채서 커스텀 로직을 적용하는 역할 + * Apple OAuth2 요청을 감지 -> response_mode=form_post를 추가. + */ +@Component +@Slf4j public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private final OAuth2AuthorizationRequestResolver defaultResolver; + public CustomAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { // 기본 엔드포인트가 /oauth2/authorization this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorization"); @@ -21,27 +30,51 @@ public CustomAuthorizationRequestResolver(ClientRegistrationRepository clientReg @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { OAuth2AuthorizationRequest authorizationRequest = defaultResolver.resolve(request); - return customizeAuthorizationRequest(authorizationRequest); + log.debug("resolve(HttpServletRequest) 호출됨, authorizationRequest: {}", authorizationRequest); + // registrationId가 없는 경우, redirect_uri를 통해 Apple 요청 여부를 유추 + String clientRegistrationId = null; + if (authorizationRequest != null && authorizationRequest.getRedirectUri() != null + && authorizationRequest.getRedirectUri().contains("/login/oauth2/code/apple")) { + clientRegistrationId = "apple"; + } + return customizeAuthorizationRequest(authorizationRequest, clientRegistrationId); } + //특정 clientRegistrationId(Google, Apple 등)를 명시적으로 전달받아 OAuth2 인증 요청을 해석한 @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { OAuth2AuthorizationRequest authorizationRequest = defaultResolver.resolve(request, clientRegistrationId); - return customizeAuthorizationRequest(authorizationRequest); + log.debug("resolve(HttpServletRequest, {}) 호출됨, authorizationRequest: {}", clientRegistrationId, authorizationRequest); + return customizeAuthorizationRequest(authorizationRequest, clientRegistrationId); } - private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) { + + //Apple 요청 감지 및 커스텀 처리 + private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, String clientRegistrationId) { if (authorizationRequest == null) { + log.debug("authorizationRequest is null"); return null; } - // "registration_id"라는 문자열 리터럴을 사용하여 등록 ID를 가져오기 - String registrationId = (String) authorizationRequest.getAttribute("registration_id"); + + String registrationId = clientRegistrationId; + if (registrationId == null) { + log.debug("clientRegistrationId is null; attempting to deduce registrationId from redirectUri"); + String redirectUri = authorizationRequest.getRedirectUri(); + if (redirectUri != null && redirectUri.contains("/login/oauth2/code/apple")) { + registrationId = "apple"; + } + } + log.debug("Determined registrationId: {}", registrationId); + if ("apple".equalsIgnoreCase(registrationId)) { + log.info("Apple OAuth2 요청 감지, 추가 파라미터 적용: response_mode=form_post"); Map additionalParameters = new HashMap<>(authorizationRequest.getAdditionalParameters()); additionalParameters.put("response_mode", "form_post"); - return OAuth2AuthorizationRequest.from(authorizationRequest) + OAuth2AuthorizationRequest customizedRequest = OAuth2AuthorizationRequest.from(authorizationRequest) .additionalParameters(additionalParameters) .build(); + log.debug("customizedRequest: {}", customizedRequest); + return customizedRequest; } return authorizationRequest; } From 7cf99167845dbf9d8af94f4a96de472c00c74967 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 16 Mar 2025 00:26:38 +0900 Subject: [PATCH 0657/1002] Update resources-release --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a3835af8..ea4b661d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 +Subproject commit ea4b661df47f33eba7055775c60c5d95560de7f9 From c3b9743e87e411c9daeecd787c5edcbfaa8afc4e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 16 Mar 2025 00:29:37 +0900 Subject: [PATCH 0658/1002] Update resources-release --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index ea4b661d..7db35363 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit ea4b661df47f33eba7055775c60c5d95560de7f9 +Subproject commit 7db35363aaed61eb81dc888e784d42b6bf3e6ef2 From 5c87c825eb1f4806f2d48872d6bf7b21b4fe4b29 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:49:17 +0900 Subject: [PATCH 0659/1002] =?UTF-8?q?refactor=20::=20AppleAuthRequest=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=9D=B8=EA=B0=80=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/dto/apple/AppleAuthRequest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java index 7b84cd52..2c262358 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java @@ -8,7 +8,6 @@ @AllArgsConstructor public class AppleAuthRequest { private final String identityToken; // Apple ID Token (JWT) - private final String authorizationCode; // Authorization Code (OAuth2 검증용) private final Map user; // 최초 로그인 시 제공되는 사용자 정보 public String getEmail() { From c854a1bb5fd47683915e40797bc4db54aab36e97 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:49:44 +0900 Subject: [PATCH 0660/1002] =?UTF-8?q?refactor=20::=20email=20=EC=A0=91?= =?UTF-8?q?=EB=91=90=EC=82=AC=EB=A5=BC=20name=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../service/AppleOAuth2UserService.java | 72 ++++++++++++------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java index 34c286d1..71d75bad 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java @@ -41,32 +41,45 @@ public class AppleOAuth2UserService implements OAuth2UserService additionalParams = userRequest.getAdditionalParameters(); + log.info("추가 파라미터 전체: {}", additionalParams); + // AppleAuthRequest 생성 (id_token, 사용자 정보) + AppleAuthRequest appleAuthRequest = extractAppleAuthRequest(userRequest); - //Apple ID Token 검증 및 사용자 ID (sub) 추출 - //OAuth2AuthenticationException에는 (String, Exception) 생성자가 없으므로, OAuth2Error를 생성하여 사용 - String appleAccountId; + // Apple ID Token 검증 및 사용자 ID (sub) 추출 + Map tokenClaims; try { - appleAccountId = getAppleAccountId(appleAuthRequest.getIdentityToken()); - log.info("Apple id_token 검증 성공, accountId(sub): {}", appleAccountId); + tokenClaims = getAppleAccountId(appleAuthRequest.getIdentityToken()); + log.info("Apple id_token 검증 성공, tokenClaims: {}", tokenClaims); } catch (Exception e) { OAuth2Error oauth2Error = new OAuth2Error("invalid_token", "Apple id_token 검증 실패", null); throw new OAuth2AuthenticationException(oauth2Error, e); } + // OAuth2User에 담을 속성 구성 + Map attributes = new HashMap<>(); + attributes.put("sub", tokenClaims.get("sub")); + log.info("appleAuthRequest : {}" , appleAuthRequest ); String email = appleAuthRequest.getEmail(); + attributes.put("email", tokenClaims.get("email")); - // Apple OAuth2 정보를 기반으로 `AppleOAuth2User` 객체 반환 - Map attributes = new HashMap<>(); - log.info("AppleOAuth2User 생성, attributes: {}", attributes); - attributes.put("sub", appleAccountId); - attributes.put("email", email); + String name; + + String tokenEmail = tokenClaims.get("email").toString(); + // email에서 '@' 앞부분을 추출하여 name 설정 + if (tokenEmail != null && tokenEmail.contains("@")) { + name = tokenEmail.substring(0, tokenEmail.indexOf("@")); + } else { + name = "Unknown"; // 이메일이 없을 경우 기본값 설정 + } + attributes.put("name", name); // 'name' 키로 이름 정보 전달 (후에 successHandler에서 사용) + log.info(" 로그인: email={}, name={}", email, name); + + + log.info("AppleOAuth2User 생성, attributes: {}", attributes); return new AppleOAuth2User(attributes); } @@ -74,24 +87,35 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic * AppleAuthRequest 생성 (OAuth2UserRequest에서 값 추출) */ private AppleAuthRequest extractAppleAuthRequest(OAuth2UserRequest userRequest) { - String identityToken = userRequest.getAdditionalParameters().get("id_token").toString(); - String authorizationCode = userRequest.getAdditionalParameters().get("code").toString(); - Map user = userRequest.getAdditionalParameters().containsKey("user") ? - (Map) userRequest.getAdditionalParameters().get("user") : Map.of(); + Map additionalParams = userRequest.getAdditionalParameters(); - return new AppleAuthRequest(identityToken, authorizationCode, user); + // id_token과 code가 반드시 존재하는지 확인하고, 없으면 예외 처리 또는 기본값 할당 + Object idTokenObj = additionalParams.get("id_token"); + log.info("idTokenObj: {}", idTokenObj); + if (idTokenObj == null) { + throw new OAuth2AuthenticationException(new OAuth2Error("invalid_request", "id_token is missing", null)); + } + String identityToken = idTokenObj.toString(); + log.info("identityToken: {}", identityToken); + + // 'user' 파라미터는 최초 로그인 시에만 존재할 수 있음 + Map user = additionalParams.containsKey("user") && additionalParams.get("user") != null + ? (Map) additionalParams.get("user") + : Map.of(); + + return new AppleAuthRequest(identityToken, user); } /** * Apple ID Token 검증 및 사용자 계정 ID(sub) 추출 */ - public String getAppleAccountId(String identityToken) + public Map getAppleAccountId(String identityToken) throws JsonProcessingException, AuthenticationException, NoSuchAlgorithmException, InvalidKeySpecException { Map headers = identityTokenValidator.parseHeaders(identityToken); - PublicKey publicKey = applePublicKeyGenerator.generatePublicKey(headers, - appleAuthClient.getAppleAuthPublicKey()); - - return identityTokenValidator.getTokenClaims(identityToken, publicKey).getSubject(); + PublicKey publicKey = applePublicKeyGenerator.generatePublicKey(headers,appleAuthClient.getAppleAuthPublicKey()); + log.info("applePublicKey: {}", publicKey); + Map tokenClaims = identityTokenValidator.getTokenClaims(identityToken, publicKey); + return tokenClaims; } } \ No newline at end of file From db086025052530e6bf432d4ac920ec707e85170e Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:50:03 +0900 Subject: [PATCH 0661/1002] =?UTF-8?q?chore=20::=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/util/AppleOAuth2AccessTokenResponseClient.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java index feb74ea4..b729f52c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java @@ -21,6 +21,9 @@ import java.net.URI; import java.util.Map; +/*** + * Apple에서 받은 code를 사용하여 Access Token을 요청 + */ @Component @Slf4j public class AppleOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient { From c1eeacabc57879a3e47524dcdffb920b764250c6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 00:50:33 +0900 Subject: [PATCH 0662/1002] =?UTF-8?q?refactor=20::=20AuthService=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/AuthService.java | 188 ------------------ 1 file changed, 188 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java deleted file mode 100644 index e7ba7d92..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthService.java +++ /dev/null @@ -1,188 +0,0 @@ -//package inu.codin.codin.common.security.service; -// -//import inu.codin.codin.common.dto.Department; -//import inu.codin.codin.common.exception.NotFoundException; -//import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -//import inu.codin.codin.common.security.enums.AuthResultStatus; -//import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -//import inu.codin.codin.domain.user.entity.UserEntity; -//import inu.codin.codin.domain.user.entity.UserRole; -//import inu.codin.codin.domain.user.entity.UserStatus; -//import inu.codin.codin.domain.user.exception.UserCreateFailException; -//import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; -//import inu.codin.codin.domain.user.repository.UserRepository; -//import inu.codin.codin.infra.s3.S3Service; -//import jakarta.servlet.http.HttpServletResponse; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -//import org.springframework.security.core.context.SecurityContextHolder; -//import org.springframework.security.core.userdetails.UserDetails; -//import org.springframework.security.core.userdetails.UserDetailsService; -//import org.springframework.security.oauth2.core.user.OAuth2User; -//import org.springframework.stereotype.Service; -//import org.springframework.web.multipart.MultipartFile; -// -//import java.time.LocalDateTime; -//import java.util.List; -//import java.util.Map; -//import java.util.Optional; -// -//@Service -//@RequiredArgsConstructor -//@Slf4j -//public class AuthService { -// -// private final UserRepository userRepository; -// private final S3Service s3Service; -// private final JwtService jwtService; -// private final UserDetailsService userDetailsService; -// -// /** -// * Google OAuth2를 통해 사용자 정보를 등록하고 로그인 처리하는 메소드 -// * - 기존 회원이면, userStatus가 ACTIVE인 경우에만 JWT 토큰을 발급하여 정식 로그인 처리 -// * - 첫 로그인 시: 신규 회원이면 DB에 등록할 때 userStatus를 DISABLED로 설정 (프로필 미완료) -// * userStatus가 DISABLED이면 프로필 설정이 필요하다는 상태로 처리 -// * - userStatus가 SUSPEND이면 정지된 회원 -// */ -// public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { -// -// InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); -// -// // 회원 존재 여부 판단: email 기준 -// Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); -// if (optionalUser.isPresent()) { -// UserEntity existingUser = optionalUser.get(); -// log.info("기존 회원 로그인: {}", info.email()); -// -// switch(existingUser.getStatus()){ -// case ACTIVE -> { -// // 프로필 설정 완료된 회원: 정상 로그인 처리 -// issueJwtToken(info.email(), response); -// log.info("정상 로그인 완료: {}", info.email()); -// return AuthResultStatus.LOGIN_SUCCESS; -// } -// case DISABLED -> { -// // 프로필 설정 미완료: JWT 토큰 미발급, 프론트엔드에서 프로필 설정 페이지로 유도 -// log.info("회원 프로필 설정 미완료 (userStatus={}) : {}", existingUser.getStatus(), info.email()); -// return AuthResultStatus.PROFILE_INCOMPLETE; -// } case SUSPENDED -> { -// //정지된 유저 -// log.info("정지된 유저 : email - {} ", info.email()); -// return AuthResultStatus.SUSPENDED_USER; -// } default -> { -// log.error("유저의 상태가 ACTIVE, DISABLED, SUSPENDED 외의 값을 가지고 있습니다. UserStatus : {}", existingUser.getStatus() ); -// throw new NotFoundException("유저의 상태(Status)를 알 수 없습니다. _id : "+ existingUser.get_id().toString() + ", status : " + existingUser.getStatus()); -// } -// } -// -// } else { -// log.info("신규 회원 등록: {}", info.email()); -// String deptDesc = (info.department() != null) ? info.department().replace("/", "").trim() : ""; -// Department dept = Department.fromDescription(deptDesc); -// -// // 신규 회원 등록 시, userStatus를 DISABLED(비활성)으로 설정하여 프로필 설정 미완료 상태로 처리 -// UserEntity newUser = UserEntity.builder() -// .email(info.email()) -// .name(info.name()) -// .department(dept) -// .profileImageUrl(s3Service.getDefaultProfileImageUrl()) // 기본 프로필 이미지 사용 -// .status(UserStatus.DISABLED) -// .role(UserRole.USER) -// .build(); -// userRepository.save(newUser); -// log.info("신규 회원 등록 완료 (프로필 미완료): {}", newUser); -// return AuthResultStatus.NEW_USER_REGISTERED; -// // 신규 회원은 프로필 설정 완료 전까지는 JWT 토큰을 발급 안 함. -// } -// } -// -// private static InfoFromOAuth2User getInfoFromOAuth2User(OAuth2User oAuth2User) { -// // OAuth2 제공자로부터 받은 모든 속성을 로그 출력 (디버깅용) -// Map attributes = oAuth2User.getAttributes(); -// -// // 기본 속성 추출 -// String email = (String) attributes.get("email"); -// String sub = (String) attributes.get("sub"); // 고유 식별자 (필요시 저장) -// String name = (String) attributes.get("family_name"); // 예: "김기수" -// String department = (String) attributes.get("given_name"); // 예: "/컴퓨터공학부" -// -// log.info("OAuth2 login: email={}, sub={}, name={}, department={}", -// email, sub, name, department); -// InfoFromOAuth2User info = new InfoFromOAuth2User(email, name, department); -// return info; -// } -// -// private record InfoFromOAuth2User(String email, String name, String department) { -// } -// -// /** -// * 최초 로그인 후, 사용자에게 닉네임과 프로필 이미지를 설정받아 DB를 업데이트하는 메소드. -// * 프로필 설정 완료 후 userStatus를 ACTIVE로 업데이트하여 정식 회원가입을 완료. -// */ -// public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { -// // 중복 닉네임 검사 -// Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); -// if (nickNameDuplicate.isPresent()){ -// throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); -// } -// -// // DB에서 해당 사용자를 이메일로 조회 -// Optional userOpt = userRepository.findByEmailAndDisabled(userProfileRequestDto.getEmail()); -// if (userOpt.isEmpty()){ -// throw new NotFoundException("사용자를 찾을 수 없습니다."); -// } -// UserEntity user = userOpt.get(); -// log.info("[completeUserProfile] 사용자 조회 성공: {}", userProfileRequestDto.getEmail()); -// -// // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 -// String imageUrl = null; -// if (userImage != null && !userImage.isEmpty()) { -// log.info("[프로필 설정] 프로필 이미지 업로드 중..."); -// imageUrl = s3Service.handleImageUpload(List.of(userImage)).get(0); -// log.info("[프로필 설정] 프로필 이미지 업로드 완료: {}", imageUrl); -// } -// // 업로드된 이미지가 없으면 기본 프로필 이미지 URL 사용 -// if (imageUrl == null) { -// imageUrl = s3Service.getDefaultProfileImageUrl(); -// } -// -// -// // 사용자 정보 업데이트: 닉네임, 프로필 이미지 URL 업데이트 및 userStatus를 ACTIVE(활성)으로 변경 -// user.updateNickname(userProfileRequestDto.getNickname()); -// user.updateProfileImageUrl(imageUrl); -// user.activation(); -// userRepository.save(user); -// -// log.info("[completeUserProfile] 프로필 설정 완료: {}", user.getEmail()); -// // 프로필 설정 완료 후 정식 회원으로 간주하여 JWT 토큰 재발급 -// issueJwtToken(user.getEmail(), response); -// } -// -// -// public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { -// Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); -// if (user.isPresent()) { -// issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); -// } else throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); -// } -// -// public void issueJwtToken(String email, HttpServletResponse response) { -// jwtService.deleteToken(response); -// UserDetails userDetails = userDetailsService.loadUserByUsername(email); -// UsernamePasswordAuthenticationToken authenticationToken = -// new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); -// SecurityContextHolder.getContext().setAuthentication(authenticationToken); -// jwtService.createToken(response); -// } -// -// public LocalDateTime getSuspensionEndDate(OAuth2User oAuth2User){ -// InfoFromOAuth2User info = getInfoFromOAuth2User(oAuth2User); -// -// Optional optionalUser = userRepository.findByEmailAndStatusAll(info.email()); -// if (optionalUser.isPresent()){ -// UserEntity user = optionalUser.get(); -// return user.getTotalSuspensionEndDate(); -// } else throw new NotFoundException("유저를 찾을 수 없습니다. _id: " + optionalUser.get().get_id().toString()); -// } -//} From f97fa6f5c0a944d595d7720254b9b2b75c7ceee6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 01:10:19 +0900 Subject: [PATCH 0663/1002] chore : submodule --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index e4ff3c62..27eb49a4 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit e4ff3c62007ff3fd13adaca9b599d29e13b4edfb +Subproject commit 27eb49a445f8b70076d72725cfcf3d3c737cec82 From 0e0f50f247d2cb1717ff9d7005c0c07f624ee22a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 01:26:14 +0900 Subject: [PATCH 0664/1002] chore : submodule --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 27eb49a4..fe510a82 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 27eb49a445f8b70076d72725cfcf3d3c737cec82 +Subproject commit fe510a8255a4be0d06eb8970030037962e829d25 From 68fe5adc4ebeba79267a18d89435a9c59eeba2ee Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 01:51:19 +0900 Subject: [PATCH 0665/1002] chore : submodule --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index fe510a82..b8f8bd64 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit fe510a8255a4be0d06eb8970030037962e829d25 +Subproject commit b8f8bd648f38a735948c41459547ec667dedad8f From 9774081ba6966b3d0e41879c1a37920ea8b01d4d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 02:05:41 +0900 Subject: [PATCH 0666/1002] chore : submodule --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index b8f8bd64..f1199485 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit b8f8bd648f38a735948c41459547ec667dedad8f +Subproject commit f119948529887837113cd3568cadbf5b9d83d5de From d39c54a881f2355e1692c468b8ad7e06e59ddc06 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 16 Mar 2025 02:06:43 +0900 Subject: [PATCH 0667/1002] Update resources-main --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index b8f8bd64..4b56814b 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit b8f8bd648f38a735948c41459547ec667dedad8f +Subproject commit 4b56814be0e527f1d19cfc93f1c40f55dda4b1ba From 28ca107f93f83d33da61b926559abc3bb066df92 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 16 Mar 2025 02:15:30 +0900 Subject: [PATCH 0668/1002] =?UTF-8?q?fix=20::=20/api,dev=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 13ec23be..8ab3da2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -29,14 +29,28 @@ public class AuthController { @GetMapping("/google") public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/oauth2/authorization/google"); + response.sendRedirect("/api/oauth2/authorization/google"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); + } + + @GetMapping("/dev/google") + public ResponseEntity> devGoogleLogin(HttpServletResponse response) throws IOException { + response.sendRedirect("/dev/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } @GetMapping("/apple") public ResponseEntity> appleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/oauth2/authorization/apple"); + response.sendRedirect("/api/oauth2/authorization/apple"); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "apple OAuth2 Login Redirect",null)); + } + + @GetMapping("/dev/apple") + public ResponseEntity> devAppleLogin(HttpServletResponse response) throws IOException { + response.sendRedirect("/dev/oauth2/authorization/apple"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "apple OAuth2 Login Redirect",null)); } From 168f6b6bc7a9c00e54628f4ed6767c8526ba83ef Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Mar 2025 16:25:27 +0900 Subject: [PATCH 0669/1002] Update resources - release --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index b8f8bd64..06965925 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit b8f8bd648f38a735948c41459547ec667dedad8f +Subproject commit 06965925e425225a766d6d171a07370000824ab9 From a82e5bc679fc586e55ea443656cd5617add01982 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 17 Mar 2025 16:26:19 +0900 Subject: [PATCH 0670/1002] Update resources - release --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a3835af8..06965925 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a3835af8f72cbaf712f402da373a2f9c1e3836a7 +Subproject commit 06965925e425225a766d6d171a07370000824ab9 From 172e86fb8760defbacfef517702d7515fb0e088a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 18 Mar 2025 18:38:25 +0900 Subject: [PATCH 0671/1002] =?UTF-8?q?refactor=20:=20DB=EB=A5=BC=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20redis=20=EB=B3=B5?= =?UTF-8?q?=EC=9B=90,=EC=82=AD=EC=A0=9C=ED=95=98=EC=97=AC=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/repository/ReviewRepository.java | 3 + .../domain/lecture/entity/LectureEntity.java | 11 + .../like/repository/LikeRepository.java | 2 + .../codin/domain/post/entity/PostEntity.java | 3 + .../scrap/repository/ScrapRepository.java | 2 + .../infra/redis/scheduler/SyncScheduler.java | 293 ++++++++++-------- .../infra/redis/service/RedisHitsService.java | 5 + .../redis/service/RedisReviewService.java | 11 + 8 files changed, 208 insertions(+), 122 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java index a9f15e9c..709bb1f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -16,4 +17,6 @@ public interface ReviewRepository extends MongoRepository findByLectureIdAndUserIdAndDeletedAtIsNull(ObjectId lectureId, ObjectId userId); Optional findBy_idAndDeletedAtIsNull(ObjectId Id); + + List findAllByDeletedAtIsNull(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index ce9266f1..69da801b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.lecture.entity; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.lecture.dto.Emotion; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -20,4 +21,14 @@ public class LectureEntity { private Department department; //OTHERS : 교양 private int grade; //0 : 전학년 private List semester; + + private double starRating; + private long participants; + private Emotion emotion; + + public void updateReviewRating(double starRating, long participants, Emotion emotion){ + this.starRating = starRating; + this.participants = participants; + this.emotion = emotion; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java index 64ba53da..b379a028 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java @@ -23,5 +23,7 @@ public interface LikeRepository extends MongoRepository { LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); + List findByLikeTypeAndDeletedAtIsNull(LikeType likeType); + Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 1ecd7c1d..5c698050 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -31,6 +31,7 @@ public class PostEntity extends BaseTimeEntity { private int commentCount = 0; // 댓글 + 대댓글 카운트 private int likeCount = 0; // 좋아요 카운트 (redis) private int scrapCount = 0; // 스크랩 카운트 (redis) + private int hitCount = 0; private Integer reportCount = 0; // 신고 카운트 @@ -91,5 +92,7 @@ public void updateReportCount(int reportCount) { this.reportCount=reportCount; } + public void updateHitCount(int hitCount) {this.hitCount = hitCount; } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java index 24bd8660..73481aab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java @@ -19,4 +19,6 @@ public interface ScrapRepository extends MongoRepository ScrapEntity findByPostIdAndUserId(ObjectId postId, ObjectId userId); Page findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); + + List findAllByDeletedAtIsNull(); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 11af5585..9cd7a437 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.lecture.repository.LectureRepository; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -19,14 +20,13 @@ import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisHitsService; -import inu.codin.codin.infra.redis.service.RedisLikeService; -import inu.codin.codin.infra.redis.service.RedisScrapService; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -49,13 +49,16 @@ public class SyncScheduler { private final ScrapRepository scrapRepository; private final HitsRepository hitsRepository; private final BestRepository bestRepository; + private final LectureRepository lectureRepository; private final RedisService redisService; private final RedisLikeService redisLikeService; private final RedisHitsService redisHitsService; private final RedisScrapService redisScrapService; + private final RedisReviewService redisReviewService; private final RedisHealthChecker redisHealthChecker; + @Async @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 public void syncLikes() { if (!redisHealthChecker.isRedisAvailable()) { @@ -68,72 +71,127 @@ public void syncLikes() { syncEntityLikes("REPLY", replyCommentRepository); syncEntityLikes("REVIEW", reviewRepository); syncPostScraps(); - synPostHits(); + syncPostHits(); + syncReviews(); log.info(" 동기화 작업 완료"); } - private void syncEntityLikes(String entityType, MongoRepository repository) { - Set redisKeys = redisService.getKeys(entityType+ ":likes:*"); - if (redisKeys == null || redisKeys.isEmpty()) { - return; + private void syncReviews() { + List dbReviews = reviewRepository.findAllByDeletedAtIsNull(); + Set redisKeys = redisService.getKeys("review:lectures:*").stream() + .map(key -> key.replace("review:lectures:", "")) + .collect(Collectors.toSet()); + + //Map< lectureId, 해당되는 리뷰Entity 리스트 > + Map> dbReviewMap = dbReviews.stream() + .collect(Collectors.groupingBy(review -> review.getLectureId().toString())); + + // Redis에 없는 리뷰 복구 + dbReviewMap.forEach((lectureId, reviews) -> { + if (!redisKeys.contains(lectureId)) { + // Redis에 강의 자체가 없으면 모든 리뷰 추가 + reviews.forEach(review -> redisReviewService.addReview( + lectureId, review.getStarRating(), review.getUserId())); + } else { + // Redis에 강의는 있지만 특정 유저 리뷰가 없는 경우 추가 + Set redisUsers = redisReviewService.getReviewUsers(lectureId); + reviews.stream() + .filter(review -> !redisUsers.contains(review.getUserId().toString())) + .forEach(review -> { + log.info("[Redis] 리뷰 추가: UserID={}, LectureID={}", review.getUserId(), lectureId); + redisReviewService.addReview(lectureId, review.getStarRating(), review.getUserId()); + }); + } + }); + + // DB에 없는 리뷰 삭제 + for (String lectureId : redisKeys) { + Set redisUsers = redisReviewService.getReviewUsers(lectureId); + Set dbUsers = dbReviewMap.getOrDefault(lectureId, List.of()).stream() + .map(review -> review.getUserId().toString()) + .collect(Collectors.toSet()); + + redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) + .forEach(userId -> { + log.info("[Redis] 리뷰 삭제: UserID={}, LectureID={}", userId, lectureId); + redisReviewService.removeReview(lectureId, userId); + }); } + + // 강의 평점 업데이트 + dbReviewMap.keySet().forEach(lectureId -> { + lectureRepository.findById(new ObjectId(lectureId)).ifPresent(lectureEntity -> { + lectureEntity.updateReviewRating( + redisReviewService.getAveOfRating(lectureId), + redisReviewService.getParticipants(lectureId), + redisReviewService.getEmotionRating(lectureId) + ); + lectureRepository.save(lectureEntity); + }); + }); + } + + private void syncEntityLikes(String entityType, MongoRepository repository) { + Set redisKeys = redisService.getKeys(entityType+ ":likes:*") + .stream().map(redisKey -> redisKey.replace(entityType+ ":likes:", "")) + .collect(Collectors.toSet()); + if (redisKeys.isEmpty()) return; + LikeType likeType = LikeType.valueOf(entityType); - for (String redisKey : redisKeys) { - String likeTypeId = redisKey.replace(entityType + ":likes:", ""); - Set likedUsers = redisLikeService.getLikedUsers(entityType, likeTypeId); - ObjectId entityId = new ObjectId(likeTypeId); - - // (좋아요 삭제) MongoDB에서 Redis에 없는 사용자 삭제 - List dbLikes = likeRepository.findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, entityId); - for (LikeEntity dbLike : dbLikes) { - if (!likedUsers.contains(dbLike.getUserId().toString())) { - log.info("[MongoDB] 좋아요 삭제: UserID={}, EntityID={}", dbLike.getUserId(), entityId); - likeRepository.delete(dbLike); - } - } + // 좋아요 대상 _id 와 좋아요 entity 리스트 + List dbLikes = likeRepository.findByLikeTypeAndDeletedAtIsNull(likeType); + Map> dbLikeMap = dbLikes.stream() + .collect(Collectors.groupingBy(likeEntity -> likeEntity.getLikeTypeId().toString())); - // (좋아요 추가) Redis에는 있지만 MongoDB에 없는 사용자 추가 - for (String id : likedUsers) { - ObjectId userId = new ObjectId(id); - if (!likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, entityId, userId)) { - log.info("[MongoDB] 좋아요 추가: UserID={}, EntityID={}", userId, entityId); - LikeEntity dbLike = LikeEntity.builder() - .likeType(likeType) - .likeTypeId(entityId) - .userId(userId) - .build(); - likeRepository.save(dbLike); - } - } + //DB에 있지만 Redis에 없는 좋아요 복구 + dbLikeMap.forEach((entityId, likes) -> { + if (!redisKeys.contains(entityId)) + likes.forEach(like -> redisLikeService.addLike(entityType, like.getLikeTypeId(), like.getUserId())); + }); + + //Redis에 있지만 DB에 없는 좋아요 삭제 + for (String likeTypeId : redisKeys) { + Set redisUsers = redisLikeService.getLikedUsers(entityType, likeTypeId); + Set dbUsers = dbLikeMap.getOrDefault(likeTypeId, List.of()).stream() + .map(like -> like.getUserId().toString()) + .collect(Collectors.toSet()); - // (count 업데이트) Redis 사용자 수로 엔티티의 likeCount 업데이트 - int likeCount = likedUsers.size(); + ObjectId entityTypeId = new ObjectId(likeTypeId); + + redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) + .forEach(userId -> { + log.info("[Redis] 좋아요 삭제: UserID={}, LikeID={}", userId, likeTypeId); + redisLikeService.removeLike(entityType, entityTypeId, new ObjectId(userId)); + }); + + // likeCount 업데이트 + int likeCount = redisLikeService.getLikeCount(entityType, new ObjectId(likeTypeId)); if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findByIdAndNotDeleted(entityId).orElse(null); + PostEntity post = postRepo.findByIdAndNotDeleted(entityTypeId).orElse(null); if (post != null && post.getLikeCount() != likeCount) { - log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); + log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); post.updateLikeCount(likeCount); postRepo.save(post); } } else if (repository instanceof CommentRepository commentRepo) { - CommentEntity comment = commentRepo.findByIdAndNotDeleted(entityId).orElse(null); + CommentEntity comment = commentRepo.findByIdAndNotDeleted(entityTypeId).orElse(null); if (comment != null && comment.getLikeCount() != likeCount) { - log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); + log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); comment.updateLikeCount(likeCount); commentRepo.save(comment); } } else if (repository instanceof ReplyCommentRepository replyRepo) { - ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(entityId).orElse(null); + ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(entityTypeId).orElse(null); if (reply != null && reply.getLikeCount() != likeCount) { - log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); + log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); reply.updateLikeCount(likeCount); replyRepo.save(reply); } } else if (repository instanceof ReviewRepository reviewRepo) { - ReviewEntity review = reviewRepo.findBy_idAndDeletedAtIsNull(entityId).orElse(null); + ReviewEntity review = reviewRepo.findBy_idAndDeletedAtIsNull(entityTypeId).orElse(null); if (review != null && review.getLikeCount() != likeCount) { - log.info("ReviewEntity 좋아요 수 업데이트: EntityID={}, Count={}", likeTypeId, likeCount); + log.info("ReviewEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); review.updateLikeCount(likeCount); reviewRepo.save(review); } @@ -141,102 +199,93 @@ private void syncEntityLikes(String entityType, MongoRepository } } + @Scheduled(fixedRate = 43200000) public void syncPostScraps() { + Set redisKeys = redisService.getKeys("post:scraps:*") + .stream().map(redisKey -> redisKey.replace("post:scraps:", "")) + .collect(Collectors.toSet()); + if (redisKeys.isEmpty()) return; - Set redisKeys = redisService.getKeys("post:scraps:*"); - if (redisKeys == null || redisKeys.isEmpty()) { - return; - } + //Post의 _id에 해당되는 Scrap Entity 리스트 + List dbScraps = scrapRepository.findAllByDeletedAtIsNull(); + Map> dbScrapMap = dbScraps.stream() + .collect(Collectors.groupingBy(scrapEntity -> scrapEntity.getPostId().toString())); - for (String redisKey : redisKeys) { - String postId = redisKey.replace("post:scraps:", ""); - ObjectId id = new ObjectId(postId); - Set redisScrappedUsers = redisScrapService.getScrapedUsers(id); + //DB에 있지만 Redis에 없는 Scrap 복구 + dbScrapMap.forEach((postId, scraps) -> { + if (!redisKeys.contains(postId)) + scraps.forEach(scrap -> redisScrapService.addScrap(new ObjectId(postId), scrap.getUserId())); + }); - // MongoDB의 스크랩 데이터 가져오기 - List dbScraps = scrapRepository.findByPostIdAndDeletedAtIsNull(id); - Set dbScrappedUsers = dbScraps.stream() - .map(ScrapEntity::getUserId) - .map(ObjectId::toString) + //DB에 없지만 Redis에 있는 Scrap 삭제 + for (String postId : redisKeys){ + Set redisUsers = redisScrapService.getScrapedUsers(new ObjectId(postId)); + Set dbUsers = dbScrapMap.getOrDefault(postId, List.of()).stream() + .map(scrap -> scrap.getUserId().toString()) .collect(Collectors.toSet()); - // (스크랩 삭제) MongoDB에 있지만 Redis에 없는 사용자 삭제 - for (ScrapEntity dbScrap : dbScraps) { - if (!redisScrappedUsers.contains(dbScrap.getUserId().toString())) { - log.info("[MongoDB] 스크랩 삭제: UserID={}, PostID={}", dbScrap.getUserId(), id); - scrapRepository.delete(dbScrap); - } - } - - // (스크랩 추가) Redis에 있지만 MongoDB에 없는 사용자 추가 - for (String redisUser : redisScrappedUsers) { - if (!dbScrappedUsers.contains(redisUser)) { - log.info("[MongoDB] 스크랩 추가: UserID={}, PostID={}", redisUser, id); - ScrapEntity dbScrap = ScrapEntity.builder() - .postId(id) - .userId(new ObjectId(redisUser)) - .build(); - scrapRepository.save(dbScrap); - } - } + redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) + .forEach(userId -> { + log.info("[MongoDB] 스크랩 삭제: UserID={}, PostID={}", userId, postId); + redisScrapService.removeScrap(new ObjectId(postId), new ObjectId(userId)); + }); - // Redis 사용자 수로 PostEntity의 scrapCount 업데이트 - int redisScrapCount = redisScrappedUsers.size(); - PostEntity post = postRepository.findByIdAndNotDeleted(id) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - if (post.getScrapCount() != redisScrapCount) { - log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", id, redisScrapCount); - post.updateScrapCount(redisScrapCount); + //scrap 개수 업데이트 + int scarpCount = redisScrapService.getScrapCount(new ObjectId(postId)); + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElse(null); + if (post != null && post.getScrapCount() != scarpCount) { + log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, scarpCount); + post.updateScrapCount(scarpCount); postRepository.save(post); } } } @Scheduled(fixedRate = 43200000) - public void synPostHits(){ - Map> postHits = fetchAllPostHits(); //하나의 게시글에 조회한 user들 - - //Redis 데이터로 DB 저장 - postHits.forEach((postId, userIds) -> { - userIds.forEach(userId -> { - hitsRepository.findByPostIdAndUserId(postId, new ObjectId(userId)) - .orElseGet(() -> hitsRepository.save( - HitsEntity.builder() - .postId(postId) - .userId(new ObjectId(userId)) - .build() - )); - }); - }); + public void syncPostHits(){ + Set redisKeys = redisService.getKeys("post:hits:*") + .stream().map(redisKey -> redisKey.replace("post:hits:", "")) + .collect(Collectors.toSet()); + if (redisKeys.isEmpty()) return; - //DB 데이터로 Redis 저장 - postHits.keySet().forEach(postId -> { - List dbHits = hitsRepository.findAllByPostId(postId); + // Post _id에 해당하는 Hits Entity 리스트 + List dbHits = hitsRepository.findAll(); + Map> dbHitsMap = dbHits.stream() + .collect(Collectors.groupingBy(hitsEntity -> hitsEntity.getPostId().toString())); - dbHits.forEach(hitsEntity -> { - if (redisHitsService.validateHits(postId, hitsEntity.getUserId())) { - redisHitsService.addHits(postId, hitsEntity.getUserId()); - } - }); + //DB에 있지만 redis에 없는 조회수 복구 + dbHitsMap.forEach((postId, hits)-> { + if (!redisKeys.contains(postId)) + hits.forEach(hit -> redisHitsService.addHits(new ObjectId(postId), hit.getUserId())); }); - } - public Map> fetchAllPostHits(){ - Set keys = redisService.getKeys("post:hits:*"); - return keys.stream() - .collect(Collectors.toMap( - key -> { - String postId = key.replace("post:hits:", ""); - return new ObjectId(postId); - }, - key -> { - String postId = key.replace("post:hits:", ""); - return redisHitsService.getHitsUser(new ObjectId(postId)); - } - ) - ); + //DB에 없지만 redis에 있는 조회수 삭제 + for (String postId: redisKeys){ + Set redisUsers = redisHitsService.getHitsUser(new ObjectId(postId)); + Set dbUsers = dbHitsMap.getOrDefault(postId, List.of()) + .stream().map(hit -> hit.getUserId().toString()) + .collect(Collectors.toSet()); + + redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) + .forEach(userId -> { + log.info("[Redis] 조회수 삭제 : UserId = {}, PostId = {}", userId, postId); + redisHitsService.removeHits(new ObjectId(postId), userId); + }); + + //조회수 count 업데이트 + int hitCount = redisHitsService.getHitsCount(new ObjectId(postId)); + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElse(null); + if (post != null && post.getHitCount() != hitCount){ + log.info("PostEntity 조회수 업데이트: PostID={}, Count={}", postId, hitCount); + post.updateHitCount(hitCount); + postRepository.save(post); + } + } } + @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 public void getTop3BestPosts() { Map posts = redisService.getTopNPosts(3); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java index 67b9e5f8..dfe64122 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java @@ -41,4 +41,9 @@ public Set getHitsUser(ObjectId postId) { String redisKey = HITS_KEY + postId.toString(); return redisTemplate.opsForSet().members(redisKey); } + + public void removeHits(ObjectId postId, String userId) { + String redisKey = HITS_KEY + postId.toString(); + redisTemplate.opsForSet().remove(redisKey, userId); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java index eff3f87d..38e9e63b 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java @@ -53,4 +53,15 @@ public long getParticipants(String lectureId){ String redisKey = REVIEW_KEY + lectureId; return Objects.requireNonNull(redisTemplate.opsForZSet().rangeWithScores(redisKey, 0, -1)).size(); } + + public Set getReviewUsers(String lectureId) { + String redisKey = REVIEW_KEY + lectureId; + return redisTemplate.opsForZSet().range(redisKey, 0, -1); + } + + public void removeReview(String lectureId, String userId) { + String redisKey = REVIEW_KEY + lectureId; + redisTemplate.opsForZSet().remove(redisKey, userId); + } + } From eac9c4742eee4990f21b14ed98c3f6b8bf8aa981 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 18 Mar 2025 19:37:39 +0900 Subject: [PATCH 0672/1002] Update resources-main --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 4b56814b..7c46ef20 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 4b56814be0e527f1d19cfc93f1c40f55dda4b1ba +Subproject commit 7c46ef207fa09405e7728b058642b151c0aa1261 From 879a6b4be45cfba108bb3e8fc82ce0bdaa3be257 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 18 Mar 2025 19:38:49 +0900 Subject: [PATCH 0673/1002] Update resources-main --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 06965925..7c46ef20 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 06965925e425225a766d6d171a07370000824ab9 +Subproject commit 7c46ef207fa09405e7728b058642b151c0aa1261 From dfaa06fd9d680cb8ed657294db993578682c215e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 19 Mar 2025 14:29:19 +0900 Subject: [PATCH 0674/1002] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20UNSUBSCRIBE,=20DISCONNECT=20=EB=B6=80=EB=B6=84=EC=9D=84=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageProcessor.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index 1b462ebf..bedd63d5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -6,6 +6,7 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.MessageHeaderAccessor; @@ -29,21 +30,26 @@ public void handleMessage(StompHeaderAccessor headerAccessor){ if (headerAccessor == null || headerAccessor.getCommand() == null){ throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString()); } - - switch (headerAccessor.getCommand()){ - case CONNECT -> { - stompMessageService.connectSession(headerAccessor); - } - case SUBSCRIBE -> { - stompMessageService.enterToChatRoom(headerAccessor); - } - case UNSUBSCRIBE -> { + /* + 구독 취소인 경우에는 chatRoomId가 존재하면 방을 나가는 것 + 연결 끊기는 세션에서 채팅방 나가기 및 제외하는 로직 추가 + */ + if (headerAccessor.getCommand() == StompCommand.UNSUBSCRIBE || headerAccessor.getCommand() == StompCommand.DISCONNECT) { + boolean hasChatRoomId = headerAccessor.getSessionAttributes() != null + && headerAccessor.getSessionAttributes().containsKey("chatRoomId"); + + if (hasChatRoomId) { stompMessageService.exitToChatRoom(headerAccessor); } - case DISCONNECT -> { - stompMessageService.exitToChatRoom(headerAccessor); + if (headerAccessor.getCommand() == StompCommand.DISCONNECT) { stompMessageService.disconnectSession(headerAccessor); } + + } else if (headerAccessor.getDestination() != null && headerAccessor.getDestination().matches("/queue(/unread)?/[^/]+")) { + switch (headerAccessor.getCommand()) { + case CONNECT -> stompMessageService.connectSession(headerAccessor); + case SUBSCRIBE -> stompMessageService.enterToChatRoom(headerAccessor); + } } } From ad9e861c241023b040f13969f5f7facdc84b570d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 19 Mar 2025 22:17:24 +0900 Subject: [PATCH 0675/1002] =?UTF-8?q?fix=20:=20UNSUBSCRIBE,=20DISCONNECT?= =?UTF-8?q?=EC=9D=98=20=EC=97=AD=ED=95=A0=EC=9D=84=20=EC=99=84=EC=A0=84?= =?UTF-8?q?=ED=9E=88=20=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/stomp/StompMessageProcessor.java | 29 ++++------- .../common/stomp/StompMessageService.java | 52 +++++++------------ 2 files changed, 28 insertions(+), 53 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java index bedd63d5..043dd443 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageProcessor.java @@ -6,12 +6,13 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.stereotype.Component; +import java.util.Objects; + @Component @RequiredArgsConstructor @Slf4j @@ -30,26 +31,16 @@ public void handleMessage(StompHeaderAccessor headerAccessor){ if (headerAccessor == null || headerAccessor.getCommand() == null){ throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString()); } - /* - 구독 취소인 경우에는 chatRoomId가 존재하면 방을 나가는 것 - 연결 끊기는 세션에서 채팅방 나가기 및 제외하는 로직 추가 - */ - if (headerAccessor.getCommand() == StompCommand.UNSUBSCRIBE || headerAccessor.getCommand() == StompCommand.DISCONNECT) { - boolean hasChatRoomId = headerAccessor.getSessionAttributes() != null - && headerAccessor.getSessionAttributes().containsKey("chatRoomId"); - - if (hasChatRoomId) { - stompMessageService.exitToChatRoom(headerAccessor); - } - if (headerAccessor.getCommand() == StompCommand.DISCONNECT) { - stompMessageService.disconnectSession(headerAccessor); - } - } else if (headerAccessor.getDestination() != null && headerAccessor.getDestination().matches("/queue(/unread)?/[^/]+")) { - switch (headerAccessor.getCommand()) { - case CONNECT -> stompMessageService.connectSession(headerAccessor); - case SUBSCRIBE -> stompMessageService.enterToChatRoom(headerAccessor); + switch (headerAccessor.getCommand()) { + case CONNECT -> stompMessageService.connectSession(headerAccessor); + case SUBSCRIBE -> { + if (Objects.requireNonNull(headerAccessor.getDestination()).matches("/queue(/unread)?/[^/]+")) + //헤더에 chatRoomId가 필요한 destination + stompMessageService.enterToChatRoom(headerAccessor); } + case UNSUBSCRIBE -> stompMessageService.exitToChatRoom(headerAccessor); //채팅방에서 끊긴 상태 + case DISCONNECT -> stompMessageService.disconnectSession(headerAccessor); //stomp 세션 끊기 } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index 2111d537..a0b34459 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -12,14 +12,12 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @Service @@ -54,9 +52,9 @@ public void enterToChatRoom(StompHeaderAccessor headerAccessor){ public void exitToChatRoom(StompHeaderAccessor headerAccessor) { Result result = getResult(headerAccessor); + if (result == null) throw new NotFoundException("[exitToChatRoom] UNSUBSCRIBE 실패"); result.chatroom().getParticipants().exit(result.user().get_id()); chatRoomRepository.save(result.chatroom()); - sessionStore.remove(headerAccessor.getSessionId()); log.info("[STOMP UNSUBSCRIBE] session : {}, chatRoomId : {} ", headerAccessor.getSessionId(), result.chatroom().get_id().toString()); } @@ -67,41 +65,27 @@ public void disconnectSession(StompHeaderAccessor headerAccessor){ } private Result getResult(StompHeaderAccessor headerAccessor) { - String email; - if (headerAccessor.getUser() != null) { - email = headerAccessor.getUser().getName(); - } else { - log.error("헤더에서 유저를 찾을 수 없습니다. command : {}, sessionId : {}", headerAccessor.getCommand(), headerAccessor.getSessionId()); - throw new UsernameNotFoundException("헤더에서 유저를 찾을 수 없습니다."); - } - log.info(headerAccessor.toString()); - String chatroomId; - if (Objects.equals(headerAccessor.getCommand(), StompCommand.DISCONNECT)){ //UNSCRIBE 하지 않은 상태에서 DISCONNECT라면 UNSCRIBE도 같이 - if (sessionStore.containsKey(headerAccessor.getSessionId())) { - chatroomId = sessionStore.get(headerAccessor.getSessionId()); - sessionStore.remove(headerAccessor.getSessionId()); - } else return null; + String email = null; + if (headerAccessor.getUser() != null) email = headerAccessor.getUser().getName(); + else log.error("헤더에서 유저를 찾을 수 없습니다. command : {}, sessionId : {}", headerAccessor.getCommand(), headerAccessor.getSessionId()); - } - else chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); + log.info(headerAccessor.toString()); + String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { log.error("chatRoomId을 찾을 수 없습니다. command : {}, sessionId : {}, chatRoomId : {}", headerAccessor.getCommand(), headerAccessor.getSessionId(), chatroomId); - throw new NotFoundException("chatRoomId을 찾을 수 없습니다. chatroomId : " + chatroomId); + return null; + } else { + Optional chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)); + if (chatroom.isEmpty()) log.error("채팅방을 찾을 수 없습니다. command : {}, sessionId : {}, chatroomId : {}", + headerAccessor.getCommand(), headerAccessor.getSessionId(), chatroomId); + Optional user = userRepository.findByEmailAndStatusAll(email); + if (user.isEmpty()) log.error("유저를 찾을 수 없습니다. command : {}, sessionId : {}, email : {}", + headerAccessor.getCommand(), headerAccessor.getSessionId(), email); + if (chatroom.isPresent() && user.isPresent()) + return new Result(chatroom.get(), user.get()); } - ChatRoom chatroom = chatRoomRepository.findById(new ObjectId(chatroomId)) - .orElseThrow(() -> { - log.error("채팅방을 찾을 수 없습니다. command : {}, sessionId : {}, chatroomId : {}", - headerAccessor.getCommand(), headerAccessor.getSessionId(), chatroomId); - return new NotFoundException("채팅방을 찾을 수 없습니다."); - }); - UserEntity user = userRepository.findByEmailAndStatusAll(email) - .orElseThrow(() -> { - log.error("유저를 찾을 수 없습니다. command : {}, sessionId : {}, email : {}", - headerAccessor.getCommand(), headerAccessor.getSessionId(), email); - return new NotFoundException("유저를 찾을 수 없습니다."); - }); - return new Result(chatroom, user); + return null; } From c2776823adea51ca0521567e8cda4664233dec53 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 19 Mar 2025 22:17:48 +0900 Subject: [PATCH 0676/1002] =?UTF-8?q?chore=20:=20Swagger=20-=20localhost?= =?UTF-8?q?=20=ED=8F=AC=ED=8A=B8=20=EB=B2=88=ED=98=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SwaggerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index a38319b8..6aa69a4d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -46,7 +46,7 @@ public OpenAPI customOpenAPI() { .security(List.of(securityRequirement)) .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) .servers(List.of( - new Server().url("http://localhost:8082").description("Local Server"), // Local Server + new Server().url("http://localhost:8080").description("Local Server"), // Local Server new Server().url(BASEURL+"/api").description("Production Server"), // Production Server new Server().url(BASEURL+"/dev").description("Release Server") )); From 07eb2b22e950e9aed06a751ba78126793bd7ba4b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 19 Mar 2025 22:19:45 +0900 Subject: [PATCH 0677/1002] =?UTF-8?q?docs=20:=20log=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/stomp/StompMessageService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index a0b34459..410682f1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -69,7 +69,6 @@ private Result getResult(StompHeaderAccessor headerAccessor) { if (headerAccessor.getUser() != null) email = headerAccessor.getUser().getName(); else log.error("헤더에서 유저를 찾을 수 없습니다. command : {}, sessionId : {}", headerAccessor.getCommand(), headerAccessor.getSessionId()); - log.info(headerAccessor.toString()); String chatroomId = headerAccessor.getFirstNativeHeader("chatRoomId"); if (chatroomId == null || !ObjectId.isValid(chatroomId)) { log.error("chatRoomId을 찾을 수 없습니다. command : {}, sessionId : {}, chatRoomId : {}", From a9a9f077dd306b9ac066c3c6d65a42d694f33d3e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 25 Mar 2025 13:45:35 +0900 Subject: [PATCH 0678/1002] =?UTF-8?q?fix=20:=20chatRoomId=EC=97=90=20chatt?= =?UTF-8?q?ing=20=5Fid=EB=A5=BC=20=EB=B3=B4=EB=82=B4=EB=8D=98=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatting/service/ChattingEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index d83bcf17..ca6a5f2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -74,7 +74,7 @@ private void updateUnread(ChattingArrivedEvent event, ChatRoom chatRoom) { private static Map getLastMessageAndUnread(ChattingArrivedEvent event, ParticipantInfo participantInfo) { return Map.of( - "chatRoomId", event.getChatting().get_id().toString(), + "chatRoomId", event.getChatting().getChatRoomId().toString(), "lastMessage", event.getChatting().getContent(), "unread", String.valueOf(participantInfo.getUnreadMessage()) ); From 0f34c6a5a2b4d5264fb3178e5a2a52436443934e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 25 Mar 2025 15:04:47 +0900 Subject: [PATCH 0679/1002] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EB=82=98=EA=B0=80=EA=B8=B0=20=ED=9B=84=20=EA=B0=99=EC=9D=80?= =?UTF-8?q?=20=EC=B1=84=ED=8C=85=EB=B0=A9=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=EB=B3=B5=EA=B5=AC=20?= =?UTF-8?q?#185?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/entity/Participants.java | 13 ++++--------- .../chat/chatroom/service/ChatRoomService.java | 6 +++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java index c4ee06a4..9f5c5e17 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/Participants.java @@ -1,9 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; -import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.Setter; import org.bson.types.ObjectId; @@ -29,25 +26,23 @@ public boolean getMessage(ObjectId receiver) { return true; } - public boolean enter(ObjectId memberId){ + public void enter(ObjectId memberId){ ParticipantInfo participantInfo; if ((participantInfo = info.get(memberId))==null) { - return false; + return; } participantInfo.connect(); info.put(memberId, participantInfo); - return true; } - public boolean exit(ObjectId memberId) { + public void exit(ObjectId memberId) { ParticipantInfo participantInfo; if ((participantInfo = info.get(memberId))==null) { - return false; + return; } participantInfo.disconnect(); info.put(memberId, participantInfo); - return true; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 0c8dc3af..25150c0a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -31,7 +31,6 @@ public class ChatRoomService { private final UserRepository userRepository; private final BlockService blockService; - private final NotificationService notificationService; private final ApplicationEventPublisher eventPublisher; @@ -68,6 +67,11 @@ private void isValidated(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obje Optional existedChatroom = chatRoomRepository.findByReferenceIdAndParticipantsContaining(new ObjectId(chatRoomCreateRequestDto.getReferenceId()), senderId, new ObjectId(chatRoomCreateRequestDto.getReceiverId())); if (existedChatroom.isPresent()){ + ParticipantInfo participantInfo= existedChatroom.get().getParticipants().getInfo().get(senderId); + if (participantInfo.isLeaved()){ + participantInfo.remain(); + chatRoomRepository.save(existedChatroom.get()); + } throw new ChatRoomExistedException("해당 reference에서 시작된 채팅방이 존재합니다.", 403, existedChatroom.get().get_id()); } } From 0524572007835b0ff0b235545e8f776d5c8ea9e8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 00:21:17 +0900 Subject: [PATCH 0680/1002] =?UTF-8?q?refactor=20:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=ED=8C=90=EC=9D=98=20=EC=9D=B5=EB=AA=85=20=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EB=B6=80=EC=97=AC=20redis=20->=20post=20document=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Map=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/service/CommentService.java | 12 +++--- .../reply/service/ReplyCommentService.java | 16 +++----- .../domain/post/entity/PostAnonymous.java | 34 ++++++++++++++++ .../codin/domain/post/entity/PostEntity.java | 23 +++++------ .../infra/redis/service/RedisAnonService.java | 39 ------------------- 5 files changed, 55 insertions(+), 69 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 893a147d..962a060e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -18,7 +18,6 @@ import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisAnonService; import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; @@ -45,7 +44,6 @@ public class CommentService { private final NotificationService notificationService; private final RedisService redisService; private final S3Service s3Service; - private final RedisAnonService redisAnonService; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -76,9 +74,9 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { private void setAnonNumber(PostEntity post, ObjectId userId) { if (post.getUserId().equals(userId)){ //글쓴이 - redisAnonService.setWriter(post.get_id().toString(), userId.toString()); + post.getAnonymous().getAnonNumber(userId.toString()); } else { - redisAnonService.getAnonNumber(post.get_id().toString(), userId.toString()); + post.getAnonymous().setWriter(userId.toString()); } } @@ -117,7 +115,7 @@ public void softDeleteComment(String id) { // 특정 게시물의 댓글 및 대댓글 조회 public List getCommentsByPostId(String id) { ObjectId postId = new ObjectId(id); - postRepository.findByIdAndNotDeleted(postId) + PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); List comments = commentRepository.findByPostId(postId); @@ -138,7 +136,7 @@ public List getCommentsByPostId(String id) { return comments.stream() .map(comment -> { UserDto userDto = userMap.get(comment.getUserId()); - int anonNum = redisAnonService.getAnonNumber(postId.toString(), comment.getUserId().toString()); + int anonNum = post.getAnonymous().getAnonNumber(comment.getUserId().toString()); String nickname; String userImageUrl; @@ -152,7 +150,7 @@ public List getCommentsByPostId(String id) { userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); } return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, - replyCommentService.getRepliesByCommentId(comment.get_id()), + replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), getUserInfoAboutPost(comment.get_id())); }) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 207b37e8..49b98d11 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -13,12 +13,12 @@ import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.response.UserDto; +import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisAnonService; import inu.codin.codin.infra.redis.service.RedisService; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; @@ -46,7 +46,6 @@ public class ReplyCommentService { private final NotificationService notificationService; private final RedisService redisService; private final S3Service s3Service; - private final RedisAnonService redisAnonService; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -68,15 +67,12 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { replyCommentRepository.save(reply); // 댓글 수 증가 (대댓글도 댓글 수에 포함) - log.info("대댓글 추가전, commentCount: {}", post.getCommentCount()); post.updateCommentCount(post.getCommentCount() + 1); redisService.applyBestScore(1, post.get_id()); setAnonNumber(post, userId); - redisAnonService.getAnonNumber(post.get_id().toString(), userId.toString()); + post.getAnonymous().getAnonNumber(userId.toString()); postRepository.save(post); - log.info("대댓글 추가후, commentCount: {}", post.getCommentCount()); - log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); @@ -84,9 +80,9 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { private void setAnonNumber(PostEntity post, ObjectId userId) { if (post.getUserId().equals(userId)){ //글쓴이 - redisAnonService.setWriter(post.get_id().toString(), userId.toString()); + post.getAnonymous().getAnonNumber(userId.toString()); } else { - redisAnonService.getAnonNumber(post.get_id().toString(), userId.toString()); + post.getAnonymous().setWriter(userId.toString()); } } @@ -112,7 +108,7 @@ public void softDeleteReply(String replyId) { } // 특정 댓글의 대댓글 조회 - public List getRepliesByCommentId(ObjectId commentId) { + public List getRepliesByCommentId(PostAnonymous postAnonymous, ObjectId commentId) { List replies = replyCommentRepository.findByCommentId(commentId); String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); @@ -128,7 +124,7 @@ public List getRepliesByCommentId(ObjectId commentId) { return replies.stream() .map(reply -> { UserDto userDto = userMap.get(reply.getUserId()); - int anonNum = redisAnonService.getAnonNumber(commentRepository.findById(reply.getCommentId()).get().getPostId().toString(), reply.getUserId().toString()); + int anonNum = postAnonymous.getAnonNumber(reply.getUserId().toString()); String nickname; String userImageUrl; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java new file mode 100644 index 00000000..c0bfaa83 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java @@ -0,0 +1,34 @@ +package inu.codin.codin.domain.post.entity; + +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +public class PostAnonymous { + private Map userAnonymousMap = new HashMap<>(); + private int anonymousNumber = 1; + + /** + * Map을 통해 유저의 익명 번호 설정 및 반환 + * @param userId 유저 _id + * @return 게시글에서 유저의 익명 번호 + */ + public Integer getAnonNumber(String userId){ + if (userAnonymousMap.containsKey(userId)) + return userAnonymousMap.get(userId); + else { + userAnonymousMap.put(userId, anonymousNumber); + return anonymousNumber++; + } + } + + public void setWriter(String userId){ + userAnonymousMap.put(userId, 0); + } + + + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 5c698050..27f1a9b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; @Document(collection = "posts") @Getter @@ -19,25 +20,25 @@ public class PostEntity extends BaseTimeEntity { @Id @NotBlank private ObjectId _id; - private ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 - private String title; + private final ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 + private final String title; private String content; - private List postImageUrls = new ArrayList<>(); + private List postImageUrls; private boolean isAnonymous; - private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) + private final PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) private int commentCount = 0; // 댓글 + 대댓글 카운트 - private int likeCount = 0; // 좋아요 카운트 (redis) - private int scrapCount = 0; // 스크랩 카운트 (redis) + private int likeCount= 0; // 좋아요 카운트 (redis) + private int scrapCount= 0; // 스크랩 카운트 (redis) private int hitCount = 0; + private int reportCount = 0; // 신고 카운트 - private Integer reportCount = 0; // 신고 카운트 + private PostAnonymous anonymous; @Builder - public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus, - int commentCount, int likeCount, int scrapCount, Integer reportCount) { + public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus) { this._id = _id; this.userId = userId; this.title = title; @@ -46,10 +47,6 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri this.isAnonymous = isAnonymous; this.postCategory = postCategory; this.postStatus = postStatus; - this.commentCount = commentCount; - this.likeCount = likeCount; - this.scrapCount = scrapCount; - this.reportCount = (reportCount == null) ? 0 : reportCount; } public void updatePostContent(String content, List postImageUrls) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java deleted file mode 100644 index 9aca35d7..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisAnonService.java +++ /dev/null @@ -1,39 +0,0 @@ -package inu.codin.codin.infra.redis.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class RedisAnonService { - - private final RedisTemplate redisTemplate; - private static final String ANON_KEY = "anon:"; - private static final String COUNTER_KEY = "anon_counter:"; - - public Integer getAnonNumber(String postId, String userId){ - String anonKey = ANON_KEY + postId + ":" + userId; - String counterKey = COUNTER_KEY + postId; - - Object existingAnonNum = redisTemplate.opsForValue().get(anonKey); - if (existingAnonNum != null){ - return Integer.parseInt(existingAnonNum.toString()); - } - - Object counterAnonNum = redisTemplate.opsForValue().get(counterKey); - if (counterAnonNum == null){ - redisTemplate.opsForValue().set(counterKey, 0); //카운터 1부터 시작 - } - - redisTemplate.opsForValue().increment(counterKey); - int counter = Integer.parseInt(redisTemplate.opsForValue().get(counterKey).toString()); - redisTemplate.opsForValue().set(anonKey, counter); //유저에게 익명 번호 설정 - return counter; - } - - public void setWriter(String postId, String userId){ - String anonKey = ANON_KEY + postId + ":" + userId; - redisTemplate.opsForValue().set(anonKey, 0); - } -} From 34ee6e332374761889feebaa36148201e3e17933 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 00:25:33 +0900 Subject: [PATCH 0681/1002] =?UTF-8?q?refactor=20:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=ED=8C=90=EC=9D=98=20=EC=9D=B5=EB=AA=85=20=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EB=B6=80=EC=97=AC=20redis=20->=20post=20document=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Map=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/entity/PostAnonymous.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java index c0bfaa83..29007f41 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java @@ -24,6 +24,10 @@ public Integer getAnonNumber(String userId){ } } + /** + * 글쓴이는 따로 관리하기 위해 0으로 설정 + * @param userId + */ public void setWriter(String userId){ userAnonymousMap.put(userId, 0); } From 593e2d333e942bc0d7536752efa42223b7284a23 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 00:53:19 +0900 Subject: [PATCH 0682/1002] =?UTF-8?q?refactor=20:=20=EC=9D=B5=EB=AA=85=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B6=80=EC=97=AC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EC=9D=84=20PostAnonymous=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/service/CommentService.java | 34 +++---------------- .../reply/service/ReplyCommentService.java | 24 ++----------- .../domain/post/entity/PostAnonymous.java | 9 +++++ .../codin/domain/post/entity/PostEntity.java | 5 +-- .../domain/report/service/ReportService.java | 9 +++-- 5 files changed, 23 insertions(+), 58 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 962a060e..49c422dc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -47,8 +47,6 @@ public class CommentService { // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { - log.info("댓글 추가 요청. postId: {}, 사용자: {}, 내용: {}", id, SecurityUtils.getCurrentUserId(), requestDTO.getContent()); - ObjectId postId = new ObjectId(id); PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); @@ -64,50 +62,26 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 수 증가 post.updateCommentCount(post.getCommentCount() + 1); - redisService.applyBestScore(1, postId); - setAnonNumber(post, userId); + post.getAnonymous().setAnonNumber(post, userId); postRepository.save(post); - log.info("댓글 추가완료 postId: {}.", postId); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); - } + redisService.applyBestScore(1, postId); + log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); - private void setAnonNumber(PostEntity post, ObjectId userId) { - if (post.getUserId().equals(userId)){ //글쓴이 - post.getAnonymous().getAnonNumber(userId.toString()); - } else { - post.getAnonymous().setWriter(userId.toString()); - } } // 댓글 삭제 (Soft Delete) public void softDeleteComment(String id) { - log.info("댓글 삭제 요청. commentId: {}", id); ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); SecurityUtils.validateUser(comment.getUserId()); -// // 댓글의 대댓글 조회 -// List replies = replyCommentRepository.findByCommentId(commentId); -// // 대댓글 Soft Delete 처리 -// replies.forEach(reply -> { -// if (reply.getDeletedAt()!=null) { -// reply.delete(); -// replyCommentRepository.save(reply); -// } -// }); - // 댓글 Soft Delete 처리 comment.delete(); commentRepository.save(comment); -// // 댓글 수 감소 (댓글 + 대댓글 수만큼 감소) -// PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) -// .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); -// post.updateCommentCount(post.getCommentCount() - (1 + replies.size())); -// postRepository.save(post); - log.info("삭제된 commentId: {}", commentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 49b98d11..b978f32c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -68,24 +68,15 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { // 댓글 수 증가 (대댓글도 댓글 수에 포함) post.updateCommentCount(post.getCommentCount() + 1); - redisService.applyBestScore(1, post.get_id()); - setAnonNumber(post, userId); - post.getAnonymous().getAnonNumber(userId.toString()); - + post.getAnonymous().setAnonNumber(post, userId); postRepository.save(post); + + redisService.applyBestScore(1, post.get_id()); log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } - private void setAnonNumber(PostEntity post, ObjectId userId) { - if (post.getUserId().equals(userId)){ //글쓴이 - post.getAnonymous().getAnonNumber(userId.toString()); - } else { - post.getAnonymous().setWriter(userId.toString()); - } - } - // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) @@ -95,15 +86,6 @@ public void softDeleteReply(String replyId) { reply.delete(); replyCommentRepository.save(reply); -// // 댓글 수 감소 (대댓글도 댓글 수에서 감소) -// CommentEntity comment = commentRepository.findByIdAndNotDeleted(reply.getCommentId()) -// .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); -// -// PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) -// .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); -// post.updateCommentCount(post.getCommentCount() - 1); -// postRepository.save(post); - log.info("대댓글 성공적 삭제 replyId: {}", replyId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java index 29007f41..f2d8c1f8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.entity; import lombok.Getter; +import org.bson.types.ObjectId; import java.util.HashMap; import java.util.Map; @@ -33,6 +34,14 @@ public void setWriter(String userId){ } + public void setAnonNumber(PostEntity post, ObjectId userId) { + if (post.getUserId().equals(userId)){ //글쓴이 + post.getAnonymous().setWriter(userId.toString()); + } else { + post.getAnonymous().getAnonNumber(userId.toString()); + } + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 27f1a9b2..6789be60 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -5,14 +5,11 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; - import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import java.util.ArrayList; import java.util.List; -import java.util.Map; @Document(collection = "posts") @Getter @@ -35,7 +32,7 @@ public class PostEntity extends BaseTimeEntity { private int hitCount = 0; private int reportCount = 0; // 신고 카운트 - private PostAnonymous anonymous; + private PostAnonymous anonymous = new PostAnonymous(); @Builder public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index c6878d5d..ace713c0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -13,6 +13,7 @@ import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; @@ -362,6 +363,8 @@ public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, St public List getReportedCommentsByPostId(String postId, String reportedEntityId) { List comments = commentService.getCommentsByPostId(postId); + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); return comments.stream() .map(comment -> { @@ -371,7 +374,7 @@ public List getReportedCommentsByPostId(String log.info("🔸 댓글 ID: {}, 신고 여부: {}", comment.get_id(), isCommentReported); // 대댓글 리스트 변환 (신고 여부 반영) - List reportedReplies = getReportedRepliesByCommentId(comment.get_id(), reportedEntityId); + List reportedReplies = getReportedRepliesByCommentId(post.getAnonymous(), comment.get_id(), reportedEntityId); // `CommentResponseDTO`에서 `ReportedCommentResponseDTO`로 변환하여 신고 여부 추가 return ReportedCommentDetailResponseDTO.from(comment.repliesFrom(reportedReplies), isCommentReported); @@ -379,9 +382,9 @@ public List getReportedCommentsByPostId(String .toList(); } - public List getReportedRepliesByCommentId(String id, String reportedEntityId) { + public List getReportedRepliesByCommentId(PostAnonymous postAnonymous, String id, String reportedEntityId) { ObjectId commentId = new ObjectId(id); - List replies = replyCommentService.getRepliesByCommentId(commentId); + List replies = replyCommentService.getRepliesByCommentId(postAnonymous, commentId); return replies.stream() .map(reply -> { From 5da9909e873bbedc89a67f3b517272357c4497ae Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 03:06:45 +0900 Subject: [PATCH 0683/1002] =?UTF-8?q?refactor=20:=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EC=88=98(Hits)=20Cache=20=EA=B4=80=EB=A6=AC=20-=20LookAside=20?= =?UTF-8?q?+=20Write=20Through=20=EC=BA=90=EC=8B=9C=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hits/repository/HitsRepository.java | 5 +- .../post/domain/hits/service/HitsService.java | 60 ++++++++++++++++++- .../codin/domain/post/entity/PostEntity.java | 2 +- .../domain/post/service/PostService.java | 5 +- .../infra/redis/service/RedisHitsService.java | 43 +++++++------ .../infra/redis/service/RedisService.java | 5 +- 6 files changed, 92 insertions(+), 28 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java index cc5efb3f..10371d26 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java @@ -5,11 +5,12 @@ import org.springframework.data.mongodb.repository.MongoRepository; import java.util.List; -import java.util.Optional; public interface HitsRepository extends MongoRepository { - Optional findByPostIdAndUserId(ObjectId postId, ObjectId userId); + int countAllByPostId(ObjectId postId); + + boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); List findAllByPostId(ObjectId postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java index e9b406b1..cf441bda 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java @@ -1,26 +1,80 @@ package inu.codin.codin.domain.post.domain.hits.service; +import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; +import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; import inu.codin.codin.infra.redis.service.RedisHitsService; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import java.util.List; + +/** + * Look Aside + * - Cache miss -> DB 조회 및 Cache 업데이트 + * Write Through + * - Cache 및 DB 동시 업데이트 + */ @Service @RequiredArgsConstructor public class HitsService { private final RedisHitsService redisHitsService; + private final HitsRepository hitsRepository; + /** + * 게시글 조회수 추가 + * Cache 업데이트 후 DB 업데이트 + * @param postId 게시글 _id + * @param userId 유저 _id + */ public void addHits(ObjectId postId, ObjectId userId){ - redisHitsService.addHits(postId,userId); + redisHitsService.addHits(postId, userId); + HitsEntity hitsEntity = HitsEntity.builder() + .postId(postId).userId(userId).build(); + hitsRepository.save(hitsEntity); } + /** + * 게시글 조회 여부 판단 + * object == null : Cache miss로 @Async로 Cache 복구 및 DB 조회 + * @param postId 게시글 _id + * @param userId 유저 _id + * @return true : 게시글 조회 유 , false : 게시글 조회 무 + */ public boolean validateHits(ObjectId postId, ObjectId userId) { - return redisHitsService.validateHits(postId,userId); + Object object = redisHitsService.validateHits(postId, userId); + if (object == null) { //Cache miss + recoveryHits(postId); + return !hitsRepository.existsByPostIdAndUserId(postId, userId); + } else { + return object.equals(Boolean.FALSE); + } } + /** + * 게시글 조회수 반환 + * null : Cache miss로 @Async로 Cache 복구 및 DB 조회 + * @param postId 게시글 _id + * @return 게시글 조회수 + */ public int getHitsCount(ObjectId postId) { - return redisHitsService.getHitsCount(postId); + if (redisHitsService.getHitsCount(postId) == null) { + recoveryHits(postId); + return hitsRepository.countAllByPostId(postId); + } + else return (int) redisHitsService.getHitsCount(postId); + } + + /** + * Cache miss로 인한 DB로부터 Cache 복구 + * @param postId 게시글 _id + */ + @Async + protected void recoveryHits(ObjectId postId) { + List hitsEntities= hitsRepository.findAllByPostId(postId); + hitsEntities.forEach(hitsEntity -> redisHitsService.addHits(postId, hitsEntity.getUserId())); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 6789be60..115d9e0a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -86,7 +86,7 @@ public void updateReportCount(int reportCount) { this.reportCount=reportCount; } - public void updateHitCount(int hitCount) {this.hitCount = hitCount; } + public void plusHitCount() {this.hitCount++;} } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 85ee808e..75252547 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -65,9 +65,6 @@ public class PostService { private final HitsService hitsService; private final RedisService redisService; private final BlockService blockService; - private final ReportRepository reportRepository; - private final CommentRepository commentRepository; - private final ReplyCommentRepository replyCommentRepository; public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); @@ -233,6 +230,8 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { ObjectId userId = SecurityUtils.getCurrentUserId(); if (hitsService.validateHits(post.get_id(), userId)) { + post.plusHitCount(); + postRepository.save(post); hitsService.addHits(post.get_id(), userId); log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java index dfe64122..fbce1cfb 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java @@ -7,43 +7,52 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.util.Set; +import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor @Slf4j public class RedisHitsService { /** - * Redis 기반 Hits 관리 Service + * Redis 기반 Hits(조회수) 관리 Service */ private final RedisTemplate redisTemplate; private static final String HITS_KEY = "post:hits:"; - //Hits + /** + * 조회수 추가 (post:hits:{postId} - {userId}) + * @param postId 게시글 _id + * @param userId 유저 _id + */ public void addHits(ObjectId postId, ObjectId userId){ String redisKey = HITS_KEY + postId.toString(); redisTemplate.opsForSet().add(redisKey, userId.toString()); + redisTemplate.expire(redisKey, 3, TimeUnit.DAYS); } - public boolean validateHits(ObjectId postId, ObjectId userId){ + /** + * 조회수 중복 방지를 위한 유저의 게시글 조회 여부 + * @param postId 게시글 _id + * @param userId 유저 _id + * @return true : 게시글 조회 유, false : 게시글 조회 무 + */ + public Object validateHits(ObjectId postId, ObjectId userId){ String redisKey = HITS_KEY + postId.toString(); - return Boolean.FALSE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); //없어야 유효성 검증 통과 + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) + return redisTemplate.opsForSet().isMember(redisKey, userId.toString()); //없어야 유효성 검증 통과 + return null; } - public int getHitsCount(ObjectId postId){ + /** + * 게시글 조회수 반환 + * @param postId 게시글 _id + * @return int : 게시글 조회수 + */ + public Object getHitsCount(ObjectId postId){ String redisKey = HITS_KEY + postId.toString(); Long hitsCount = redisTemplate.opsForSet().size(redisKey); - return hitsCount != null ? hitsCount.intValue() : 0; - } - - public Set getHitsUser(ObjectId postId) { - String redisKey = HITS_KEY + postId.toString(); - return redisTemplate.opsForSet().members(redisKey); - } - - public void removeHits(ObjectId postId, String userId) { - String redisKey = HITS_KEY + postId.toString(); - redisTemplate.opsForSet().remove(redisKey, userId); + if (hitsCount == null) return null; + return hitsCount.intValue(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java index 036d1bd6..09c80dd1 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java @@ -1,6 +1,7 @@ package inu.codin.codin.infra.redis.service; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -26,7 +27,7 @@ public class RedisService { * 장애 복구를 대비한 보완 로직 추가 */ private final RedisTemplate redisTemplate; - private final RedisHitsService redisHitsService; + private final HitsService hitsService; //post, comment, reply 구분 @@ -69,7 +70,7 @@ public Map getTopNPosts(int N) { if (scoreComparison != 0) { return scoreComparison; } - return Integer.compare(redisHitsService.getHitsCount(new ObjectId(e2.getKey())), redisHitsService.getHitsCount(new ObjectId(e1.getKey()))); + return Integer.compare(hitsService.getHitsCount(new ObjectId(e2.getKey())), hitsService.getHitsCount(new ObjectId(e1.getKey()))); }) .limit(N).collect(Collectors.toMap( Map.Entry::getKey, From 2125b0210758e536c24305b60714df210e69f1cb Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 04:39:35 +0900 Subject: [PATCH 0684/1002] =?UTF-8?q?fix=20:=20=EC=A1=B0=ED=9A=8C=EC=88=98?= =?UTF-8?q?(Hits)=20Cache=20=EA=B4=80=EB=A6=AC=20-=20LookAside=20+=20Write?= =?UTF-8?q?=20Back=20=EC=BA=90=EC=8B=9C=20=EC=A0=84=EB=9E=B5=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=8F=20opsForValues=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hits/repository/HitsRepository.java | 4 --- .../post/domain/hits/service/HitsService.java | 28 ++++++--------- .../codin/domain/post/entity/PostEntity.java | 6 ++-- .../domain/post/service/PostService.java | 20 ++++------- .../infra/redis/scheduler/SyncScheduler.java | 32 +++-------------- .../infra/redis/service/RedisHitsService.java | 34 +++++++++---------- 6 files changed, 42 insertions(+), 82 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java index 10371d26..ffb27b58 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/repository/HitsRepository.java @@ -4,13 +4,9 @@ import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; -import java.util.List; - public interface HitsRepository extends MongoRepository { int countAllByPostId(ObjectId postId); boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); - - List findAllByPostId(ObjectId postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java index cf441bda..0b49c5f7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java @@ -8,13 +8,11 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import java.util.List; - /** * Look Aside - * - Cache miss -> DB 조회 및 Cache 업데이트 - * Write Through - * - Cache 및 DB 동시 업데이트 + * - Cache miss -> DB 조회 및 Cache 등록 + * Write Back + * - SyncScheduler 동기화 */ @Service @RequiredArgsConstructor @@ -30,7 +28,7 @@ public class HitsService { * @param userId 유저 _id */ public void addHits(ObjectId postId, ObjectId userId){ - redisHitsService.addHits(postId, userId); + redisHitsService.addHits(postId); HitsEntity hitsEntity = HitsEntity.builder() .postId(postId).userId(userId).build(); hitsRepository.save(hitsEntity); @@ -38,19 +36,12 @@ public void addHits(ObjectId postId, ObjectId userId){ /** * 게시글 조회 여부 판단 - * object == null : Cache miss로 @Async로 Cache 복구 및 DB 조회 * @param postId 게시글 _id * @param userId 유저 _id * @return true : 게시글 조회 유 , false : 게시글 조회 무 */ public boolean validateHits(ObjectId postId, ObjectId userId) { - Object object = redisHitsService.validateHits(postId, userId); - if (object == null) { //Cache miss - recoveryHits(postId); - return !hitsRepository.existsByPostIdAndUserId(postId, userId); - } else { - return object.equals(Boolean.FALSE); - } + return hitsRepository.existsByPostIdAndUserId(postId, userId); } /** @@ -60,11 +51,12 @@ public boolean validateHits(ObjectId postId, ObjectId userId) { * @return 게시글 조회수 */ public int getHitsCount(ObjectId postId) { - if (redisHitsService.getHitsCount(postId) == null) { + Object hits = redisHitsService.getHitsCount(postId); + if (hits == null) { recoveryHits(postId); return hitsRepository.countAllByPostId(postId); } - else return (int) redisHitsService.getHitsCount(postId); + else return Integer.parseInt((String)hits); } /** @@ -73,8 +65,8 @@ public int getHitsCount(ObjectId postId) { */ @Async protected void recoveryHits(ObjectId postId) { - List hitsEntities= hitsRepository.findAllByPostId(postId); - hitsEntities.forEach(hitsEntity -> redisHitsService.addHits(postId, hitsEntity.getUserId())); + int hits = hitsRepository.countAllByPostId(postId); + redisHitsService.recoveryHits(postId, hits); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 115d9e0a..2eb765f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -86,7 +86,7 @@ public void updateReportCount(int reportCount) { this.reportCount=reportCount; } - public void plusHitCount() {this.hitCount++;} - - + public void updateHitCount(int hitCount) { + this.hitCount = hitCount; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 75252547..16f40140 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -5,30 +5,28 @@ import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.post.domain.best.BestEntity; -import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.hits.service.HitsService; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.dto.response.*; -import inu.codin.codin.domain.report.dto.ReportInfo; -import inu.codin.codin.domain.report.repository.ReportRepository; -import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO.UserInfo; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.PostPollDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; @@ -39,7 +37,6 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -47,7 +44,6 @@ import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; @Slf4j @Service @@ -229,9 +225,7 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - if (hitsService.validateHits(post.get_id(), userId)) { - post.plusHitCount(); - postRepository.save(post); + if (!hitsService.validateHits(post.get_id(), userId)) { hitsService.addHits(post.get_id(), userId); log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 9cd7a437..6105e302 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -1,13 +1,11 @@ package inu.codin.codin.infra.redis.scheduler; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; import inu.codin.codin.domain.lecture.repository.LectureRepository; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -25,7 +23,6 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -243,6 +240,9 @@ public void syncPostScraps() { } } + /** + * redis에서 업데이트 되는 조회수를 postEntity에 주기적으로 저장 + */ @Scheduled(fixedRate = 43200000) public void syncPostHits(){ Set redisKeys = redisService.getKeys("post:hits:*") @@ -250,32 +250,10 @@ public void syncPostHits(){ .collect(Collectors.toSet()); if (redisKeys.isEmpty()) return; - // Post _id에 해당하는 Hits Entity 리스트 - List dbHits = hitsRepository.findAll(); - Map> dbHitsMap = dbHits.stream() - .collect(Collectors.groupingBy(hitsEntity -> hitsEntity.getPostId().toString())); - - //DB에 있지만 redis에 없는 조회수 복구 - dbHitsMap.forEach((postId, hits)-> { - if (!redisKeys.contains(postId)) - hits.forEach(hit -> redisHitsService.addHits(new ObjectId(postId), hit.getUserId())); - }); - - //DB에 없지만 redis에 있는 조회수 삭제 for (String postId: redisKeys){ - Set redisUsers = redisHitsService.getHitsUser(new ObjectId(postId)); - Set dbUsers = dbHitsMap.getOrDefault(postId, List.of()) - .stream().map(hit -> hit.getUserId().toString()) - .collect(Collectors.toSet()); - - redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) - .forEach(userId -> { - log.info("[Redis] 조회수 삭제 : UserId = {}, PostId = {}", userId, postId); - redisHitsService.removeHits(new ObjectId(postId), userId); - }); - //조회수 count 업데이트 - int hitCount = redisHitsService.getHitsCount(new ObjectId(postId)); + Object object = redisHitsService.getHitsCount(new ObjectId(postId)); + int hitCount = object != null? Integer.parseInt((String) object) : hitsRepository.countAllByPostId(new ObjectId(postId)); PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElse(null); if (post != null && post.getHitCount() != hitCount){ diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java index fbce1cfb..be89655e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java @@ -21,38 +21,38 @@ public class RedisHitsService { private static final String HITS_KEY = "post:hits:"; /** - * 조회수 추가 (post:hits:{postId} - {userId}) + * 조회수 추가 (post:hits:{postId} - 조회수) * @param postId 게시글 _id - * @param userId 유저 _id */ - public void addHits(ObjectId postId, ObjectId userId){ + public void addHits(ObjectId postId){ String redisKey = HITS_KEY + postId.toString(); - redisTemplate.opsForSet().add(redisKey, userId.toString()); - redisTemplate.expire(redisKey, 3, TimeUnit.DAYS); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) + redisTemplate.opsForValue().increment(redisKey); + else { + redisTemplate.opsForValue().set(redisKey, String.valueOf(1)); + redisTemplate.expire(redisKey, 7, TimeUnit.DAYS); + } } /** - * 조회수 중복 방지를 위한 유저의 게시글 조회 여부 + * 게시글 조회수 반환 * @param postId 게시글 _id - * @param userId 유저 _id - * @return true : 게시글 조회 유, false : 게시글 조회 무 + * @return int : 게시글 조회수 */ - public Object validateHits(ObjectId postId, ObjectId userId){ + public Object getHitsCount(ObjectId postId){ String redisKey = HITS_KEY + postId.toString(); if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) - return redisTemplate.opsForSet().isMember(redisKey, userId.toString()); //없어야 유효성 검증 통과 - return null; + return redisTemplate.opsForValue().get(redisKey); + else return null; } /** - * 게시글 조회수 반환 + * hits Collection 기반으로 Cache 복구 * @param postId 게시글 _id - * @return int : 게시글 조회수 + * @param hits 해당 게시글의 조회수 */ - public Object getHitsCount(ObjectId postId){ + public void recoveryHits(ObjectId postId, int hits){ String redisKey = HITS_KEY + postId.toString(); - Long hitsCount = redisTemplate.opsForSet().size(redisKey); - if (hitsCount == null) return null; - return hitsCount.intValue(); + redisTemplate.opsForValue().set(redisKey, String.valueOf(hits)); } } From ea08b0257f3c1e88e04b50f362bde970b3d1415f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 04:40:48 +0900 Subject: [PATCH 0685/1002] =?UTF-8?q?fix=20:=20Hits=20TTL=201=EC=9D=BC=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/infra/redis/service/RedisHitsService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java index be89655e..fb3da69e 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java @@ -30,7 +30,7 @@ public void addHits(ObjectId postId){ redisTemplate.opsForValue().increment(redisKey); else { redisTemplate.opsForValue().set(redisKey, String.valueOf(1)); - redisTemplate.expire(redisKey, 7, TimeUnit.DAYS); + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); } } From b1c65ad10dea92a2a63f8f3624ba04c0959494cf Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 15:33:17 +0900 Subject: [PATCH 0686/1002] =?UTF-8?q?refactor=20:=20Scrap=EC=9D=84=20Cache?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=20DB=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scrap/repository/ScrapRepository.java | 9 +- .../domain/scrap/service/ScrapService.java | 104 +++++++++--------- .../scheduler/RedisRecoverSyncScheduler.java | 3 - .../infra/redis/scheduler/SyncScheduler.java | 46 -------- .../redis/service/RedisScrapService.java | 62 ----------- 5 files changed, 55 insertions(+), 169 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java index 73481aab..d2b8d50b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java @@ -8,17 +8,16 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface ScrapRepository extends MongoRepository { - List findByPostIdAndDeletedAtIsNull(ObjectId postId); + boolean existsByPostIdAndUserId(ObjectId postId, ObjectId userId); - long countByPostIdAndDeletedAtIsNull(ObjectId postId); + int countByPostIdAndDeletedAtIsNull(ObjectId postId); - ScrapEntity findByPostIdAndUserId(ObjectId postId, ObjectId userId); + Optional findByPostIdAndUserId(ObjectId postId, ObjectId userId); Page findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); - - List findAllByDeletedAtIsNull(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 03a7c08f..1d9fec87 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -2,18 +2,17 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.scrap.exception.ScrapCreateFailException; import inu.codin.codin.domain.scrap.repository.ScrapRepository; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisScrapService; import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import java.util.Optional; + @Service @RequiredArgsConstructor @Slf4j @@ -22,81 +21,80 @@ public class ScrapService { private final PostRepository postRepository; private final RedisService redisService; - private final RedisScrapService redisScrapService; - private final RedisHealthChecker redisHealthChecker; + /** + * 하나의 모듈로 스크랩 추가, 삭제 toggle 동작 + * @param id 게시글 _id (postId) + * @return 스크랩 상태의 message 반환 (스크랩 추가, 복원, 삭제) + */ public String toggleScrap(String id) { log.info("스크랩 토글 요청 - postId: {}", id); - ObjectId postId = new ObjectId(id); + ObjectId userId = SecurityUtils.getCurrentUserId(); + postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> { - log.warn("스크랩 토글 실패 - 게시글을 찾을 수 없음 - postId: {}", postId); - return new NotFoundException("게시글을 찾을 수 없습니다."); + log.error("스크랩 토글 실패 - 게시글을 찾을 수 없음 - postId: {}", postId); + return new NotFoundException("[toggleScrap] 게시글을 찾을 수 없습니다."); }); - ObjectId userId = SecurityUtils.getCurrentUserId(); - // 이미 스크랩한 게시물인지 확인 - ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); + Optional scrap = scrapRepository.findByPostIdAndUserId(postId, userId); + return getResult(scrap, postId, userId); + + } - if (scrap != null && scrap.getDeletedAt() == null) { - removeScrap(scrap); - return "스크랩이 취소되었습니다. "; + /** + * 스크랩이 존재하는 경우 + * 1. 삭제된 스크랩 -> 복원 + * 2. 스크랩 중 -> 삭제 + * + * 스크랩이 존재하지 않는 경우 + * 1. 스크랩 생성 + */ + private String getResult(Optional scrap, ObjectId postId, ObjectId userId) { + if (scrap.isPresent()) { + if (scrap.get().getDeletedAt() == null){ //스크랩 존재 -> 삭제 + removeScrap(scrap.get()); + return "스크랩이 취소되었습니다."; + } else { //삭제된 스크랩 존재 -> 복구 + restoreScrap(scrap.get()); + return "스트랩이 추가(복구)되었습니다."; + } } - addScrap(postId, userId); - return "스크랩이 추가되었습니다. "; + else addScrap(postId, userId); //첫 스크랩 + return "스크랩이 추가되었습니다."; + } + + private void restoreScrap(ScrapEntity scrap) { + scrap.restore(); + scrapRepository.save(scrap); + log.info("스크랩 복원 완료 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); } private void addScrap(ObjectId postId, ObjectId userId) { - log.info("스크랩 추가 요청 - postId: {}, userId: {}", postId, userId); - - ScrapEntity scrap = scrapRepository.findByPostIdAndUserId(postId, userId); - - if (scrap != null){ - if (scrap.getDeletedAt() != null){ - scrap.recreatedAt(); - scrap.restore(); - scrapRepository.save(scrap); - } else { - log.info("스크랩 추가 실패 - 이미 스크랩된 상태 - postId: {}, userId: {}", postId, userId); - throw new ScrapCreateFailException("이미 스크랩이 된 상태입니다."); - } - } else { - if (redisHealthChecker.isRedisAvailable()) { - redisScrapService.addScrap(postId, userId); - log.info("Redis에 스크랩 추가 - postId: {}, userId: {}", postId, userId); - } - scrapRepository.save(ScrapEntity.builder() - .postId(postId) - .userId(userId) - .build()); - redisService.applyBestScore(2, postId); - log.info("Redis에 Best Score 적용 - postId: {}", postId); - } + scrapRepository.save(ScrapEntity.builder() + .postId(postId) + .userId(userId) + .build()); + redisService.applyBestScore(2, postId); //Best 게시글에 적용 + log.info("스크랩 추가 완료 - postId: {}, userId: {}", postId, userId); + log.info("Redis에 Best Score 적용 - postId: {}", postId); + } private void removeScrap(ScrapEntity scrap) { - log.info("스크랩 삭제 요청 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); - if (redisHealthChecker.isRedisAvailable()) { - redisScrapService.removeScrap(scrap.getPostId(), scrap.getUserId()); - log.info("Redis에서 스크랩 삭제 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); - } scrap.delete(); scrapRepository.save(scrap); log.info("스크랩 삭제 완료 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); } public int getScrapCount(ObjectId postId) { - if (redisHealthChecker.isRedisAvailable()) { - return redisScrapService.getScrapCount(postId); - } - long count = scrapRepository.countByPostIdAndDeletedAtIsNull(postId); - return (int) Math.max(0, count); + return scrapRepository.countByPostIdAndDeletedAtIsNull(postId); } public boolean isPostScraped(ObjectId postId, ObjectId userId){ - return redisScrapService.isPostScraped(postId, userId); + return scrapRepository.existsByPostIdAndUserId(postId, userId); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java index 45509790..b451d218 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java @@ -3,7 +3,6 @@ import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; import inu.codin.codin.infra.redis.service.RedisLikeService; -import inu.codin.codin.infra.redis.service.RedisScrapService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -18,7 +17,6 @@ public class RedisRecoverSyncScheduler { private final RedisHealthChecker redisHealthChecker; private final RedisLikeService redisLikeService; - private final RedisScrapService redisScrapService; private Instant lastRecoveryTime = Instant.MIN; // 마지막 복구 시간 @@ -57,7 +55,6 @@ private void recoverRedisData() { try { log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 복구 작업 시작..."); redisLikeService.recoverRedisFromDB(); - redisScrapService.recoverRedisFromDB(); lastRecoveryTime = Instant.now(); log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 데이터 복구 완료."); } catch (Exception e) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 6105e302..5bc1e264 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -43,7 +43,6 @@ public class SyncScheduler { private final ReviewRepository reviewRepository; private final LikeRepository likeRepository; - private final ScrapRepository scrapRepository; private final HitsRepository hitsRepository; private final BestRepository bestRepository; private final LectureRepository lectureRepository; @@ -51,7 +50,6 @@ public class SyncScheduler { private final RedisService redisService; private final RedisLikeService redisLikeService; private final RedisHitsService redisHitsService; - private final RedisScrapService redisScrapService; private final RedisReviewService redisReviewService; private final RedisHealthChecker redisHealthChecker; @@ -67,7 +65,6 @@ public void syncLikes() { syncEntityLikes("COMMENT", commentRepository); syncEntityLikes("REPLY", replyCommentRepository); syncEntityLikes("REVIEW", reviewRepository); - syncPostScraps(); syncPostHits(); syncReviews(); log.info(" 동기화 작업 완료"); @@ -197,49 +194,6 @@ private void syncEntityLikes(String entityType, MongoRepository } - @Scheduled(fixedRate = 43200000) - public void syncPostScraps() { - Set redisKeys = redisService.getKeys("post:scraps:*") - .stream().map(redisKey -> redisKey.replace("post:scraps:", "")) - .collect(Collectors.toSet()); - if (redisKeys.isEmpty()) return; - - //Post의 _id에 해당되는 Scrap Entity 리스트 - List dbScraps = scrapRepository.findAllByDeletedAtIsNull(); - Map> dbScrapMap = dbScraps.stream() - .collect(Collectors.groupingBy(scrapEntity -> scrapEntity.getPostId().toString())); - - //DB에 있지만 Redis에 없는 Scrap 복구 - dbScrapMap.forEach((postId, scraps) -> { - if (!redisKeys.contains(postId)) - scraps.forEach(scrap -> redisScrapService.addScrap(new ObjectId(postId), scrap.getUserId())); - }); - - //DB에 없지만 Redis에 있는 Scrap 삭제 - for (String postId : redisKeys){ - Set redisUsers = redisScrapService.getScrapedUsers(new ObjectId(postId)); - Set dbUsers = dbScrapMap.getOrDefault(postId, List.of()).stream() - .map(scrap -> scrap.getUserId().toString()) - .collect(Collectors.toSet()); - - redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) - .forEach(userId -> { - log.info("[MongoDB] 스크랩 삭제: UserID={}, PostID={}", userId, postId); - redisScrapService.removeScrap(new ObjectId(postId), new ObjectId(userId)); - }); - - //scrap 개수 업데이트 - int scarpCount = redisScrapService.getScrapCount(new ObjectId(postId)); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElse(null); - if (post != null && post.getScrapCount() != scarpCount) { - log.info("PostEntity 스크랩 수 업데이트: PostID={}, Count={}", postId, scarpCount); - post.updateScrapCount(scarpCount); - postRepository.save(post); - } - } - } - /** * redis에서 업데이트 되는 조회수를 postEntity에 주기적으로 저장 */ diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java deleted file mode 100644 index 651be61a..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisScrapService.java +++ /dev/null @@ -1,62 +0,0 @@ -package inu.codin.codin.infra.redis.service; - - -import inu.codin.codin.domain.scrap.repository.ScrapRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.Set; - -@Service -@RequiredArgsConstructor -@Slf4j -public class RedisScrapService { - /** - * Redis 기반 Scrap 관리 Service - **/ - private final RedisTemplate redisTemplate; - private final ScrapRepository scrapRepository; - - private static final String SCRAP_KEY = "post:scraps:"; - - //Scrap - public void addScrap(ObjectId postId, ObjectId userId) { - String redisKey = SCRAP_KEY + postId.toString(); - redisTemplate.opsForSet().add(redisKey, userId.toString()); - } - - public void removeScrap(ObjectId postId, ObjectId userId) { - String redisKey = SCRAP_KEY + postId.toString(); - redisTemplate.opsForSet().remove(redisKey, userId.toString()); - } - - public int getScrapCount(ObjectId postId) { - String redisKey = SCRAP_KEY + postId.toString(); - Long scrapCount = redisTemplate.opsForSet().size(redisKey); - return scrapCount != null ? scrapCount.intValue() : 0; - } - - public Set getScrapedUsers(ObjectId postId) { - String redisKey = SCRAP_KEY + postId.toString(); - return redisTemplate.opsForSet().members(redisKey); - } - - public boolean isPostScraped(ObjectId postId, ObjectId userId){ - String redisKey = SCRAP_KEY + postId.toString(); - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - - public void recoverRedisFromDB() { - log.info("Redis 복구 요청 - DB의 스크랩 데이터를 기반으로 복구 시작"); - - scrapRepository.findAll().forEach(scrap -> { - addScrap(scrap.getPostId(), scrap.getUserId()); - log.info("Redis에 스크랩 복구 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); - }); - - log.info("Redis 복구 완료"); - } -} From d9679b91bf387f4487a8b86ade36254b9200f1af Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 19:54:25 +0900 Subject: [PATCH 0687/1002] =?UTF-8?q?refactor=20:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0(Review)=EB=A5=BC=20=EB=AA=A8=EB=91=90=20DB?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B4=80=EB=A6=AC=20=EB=B0=8F=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EA=B7=B8=EC=97=90=20=EB=94=B0=EB=A5=B8=20Lectures?= =?UTF-8?q?=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 3 +- .../review/repository/ReviewRepository.java | 28 ++++++- .../domain/review/service/ReviewService.java | 50 ++++++++++--- .../codin/domain/lecture/dto/Emotion.java | 10 +++ .../lecture/dto/LectureDetailResponseDto.java | 10 +-- .../lecture/dto/LectureListResponseDto.java | 11 ++- .../domain/lecture/entity/LectureEntity.java | 4 +- .../lecture/service/LectureService.java | 38 +++++++--- .../infra/redis/scheduler/SyncScheduler.java | 73 ++----------------- .../redis/service/RedisReviewService.java | 67 ----------------- 10 files changed, 122 insertions(+), 172 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java index abba54b8..19e3d68b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -25,7 +26,7 @@ public class ReviewController { @PostMapping("/{lectureId}") public ResponseEntity> createReview(@PathVariable("lectureId") String lectureId, @RequestBody @Valid CreateReviewRequestDto createReviewRequestDto){ - reviewService.createReview(lectureId, createReviewRequestDto); + reviewService.createReview(new ObjectId(lectureId), createReviewRequestDto); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "수강 후기 작성 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java index 709bb1f5..2e929146 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -1,22 +1,42 @@ package inu.codin.codin.domain.lecture.domain.review.repository; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import inu.codin.codin.domain.lecture.dto.Emotion; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository public interface ReviewRepository extends MongoRepository { - Page findAllByLectureIdAndDeletedAtIsNull(ObjectId lectureId, PageRequest pageRequest); + + @Aggregation(pipeline = { + "{ '$match': { 'lectureId': ?0 } }", + "{ '$group': { '_id': null, 'avgRating': { '$avg': '$starRating' } } }" + }) + Double getAvgRatingByLectureId(ObjectId lectureId); + + @Aggregation(pipeline = { + "{ '$match': { 'lectureId': ?0 } }", + "{ '$group': { " + + " '_id': null, " + + " 'hard': { '$sum': { '$cond': [ { '$and': [ { '$gte': [ '$starRating', 0.25 ] }, { '$lte': [ '$starRating', 1.5 ] } ] }, 1, 0 ] } }, " + + " 'ok': { '$sum': { '$cond': [ { '$and': [ { '$gt': [ '$starRating', 1.75 ] }, { '$lte': [ '$starRating', 3.5 ] } ] }, 1, 0 ] } }, " + + " 'best': { '$sum': { '$cond': [ { '$and': [ { '$gt': [ '$starRating', 3.75 ] }, { '$lte': [ '$starRating', 5.0 ] } ] }, 1, 0 ] } } " + + "} }", + "{ '$project': { '_id': 0, 'hard': 1, 'ok': 1, 'best': 1 } }" + }) + Emotion getEmotionsCountByRanges(ObjectId lectureId); + + int countAllByLectureIdAndDeletedAtIsNotNull(ObjectId lectureId); + + Page getAvgRatingByLectureId(ObjectId lectureId, PageRequest pageRequest); Optional findByLectureIdAndUserIdAndDeletedAtIsNull(ObjectId lectureId, ObjectId userId); Optional findBy_idAndDeletedAtIsNull(ObjectId Id); - - List findAllByDeletedAtIsNull(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index 0ddf62e6..4591724d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.lecture.domain.review.service; +import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; import inu.codin.codin.domain.lecture.domain.review.dto.ReviewListResposneDto; @@ -8,10 +9,11 @@ import inu.codin.codin.domain.lecture.domain.review.exception.ReviewExistenceException; import inu.codin.codin.domain.lecture.domain.review.exception.WrongRatingException; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.lecture.dto.Emotion; +import inu.codin.codin.domain.lecture.entity.LectureEntity; +import inu.codin.codin.domain.lecture.repository.LectureRepository; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisReviewService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -28,33 +30,57 @@ public class ReviewService { private final ReviewRepository reviewRepository; + private final LectureRepository lectureRepository; private final LikeService likeService; - private final RedisReviewService redisReviewService; - private final RedisHealthChecker redisHealthChecker; - public void createReview(String lectureId, CreateReviewRequestDto createReviewRequestDto) { + /** + * 새로운 강의 후기 작성 + * @param lectureId 후기를 작성하는 강의의 _id + * @param createReviewRequestDto 후기 작성 시 포함되는 내용 + */ + public void createReview(ObjectId lectureId, CreateReviewRequestDto createReviewRequestDto) { if (createReviewRequestDto.getStarRating() > 5.0 || createReviewRequestDto.getStarRating() < 0.25){ log.warn("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요"); throw new WrongRatingException("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요."); } ObjectId userId = SecurityUtils.getCurrentUserId(); - Optional review = reviewRepository.findByLectureIdAndUserIdAndDeletedAtIsNull(new ObjectId(lectureId), userId); + Optional review = reviewRepository.findByLectureIdAndUserIdAndDeletedAtIsNull(lectureId, userId); if (review.isPresent()) { log.error("이미 유저가 작성한 후기가 존재합니다. userId: {}, lectureId: {}", userId, lectureId); throw new ReviewExistenceException("이미 유저가 작성한 후기가 존재합니다."); } - ReviewEntity newReview = ReviewEntity.of(createReviewRequestDto, new ObjectId(lectureId), userId); + ReviewEntity newReview = ReviewEntity.of(createReviewRequestDto, lectureId, userId); reviewRepository.save(newReview); - if (redisHealthChecker.isRedisAvailable()) { - redisReviewService.addReview(lectureId, createReviewRequestDto.getStarRating(), userId); - } + updateRating(lectureId); + } - public Object getListOfReviews(String lectureId, int page) { + /** + * 강의 후기 작성 시 해당 강의의 Rating 업데이트 + * @param lectureId 강의 _id + */ + public void updateRating(ObjectId lectureId){ + LectureEntity lectureEntity = lectureRepository.findById(lectureId) + .orElseThrow(() -> new NotFoundException("강의 정보를 찾을 수 없습니다.")); + double starRating = reviewRepository.getAvgRatingByLectureId(lectureId); + Emotion emotion = reviewRepository.getEmotionsCountByRanges(lectureId).changeToPercentage(); + int participants = reviewRepository.countAllByLectureIdAndDeletedAtIsNotNull(lectureId); + lectureEntity.updateReviewRating(starRating, participants, emotion); + lectureRepository.save(lectureEntity); + } + + + /** + * 해당 강의의 수강 후기 리스트 Page로 가져오기 + * @param lectureId 강의 _id + * @param page 페이지 번호 + * @return ReviewPageResponse 강의 후기 Page 반환 + */ + public ReviewPageResponse getListOfReviews(String lectureId, int page) { PageRequest pageRequest = PageRequest.of(page, 10, Sort.by("created_at").descending()); - Page reviewPage = reviewRepository.findAllByLectureIdAndDeletedAtIsNull(new ObjectId(lectureId), pageRequest); + Page reviewPage = reviewRepository.getAvgRatingByLectureId(new ObjectId(lectureId), pageRequest); ObjectId userId = SecurityUtils.getCurrentUserId(); return ReviewPageResponse.of(reviewPage.stream() diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java index 4d253c53..2fd8a3b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java @@ -17,4 +17,14 @@ public Emotion(double hard, double ok, double best) { this.best = best; } + public Emotion changeToPercentage(){ + double total = hard + ok + best; + if (total > 0) { + this.hard = (hard / total) * 100; + this.ok = (ok / total) * 100; + this.best = (best / total) * 100; + } + return this; + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java index 42c80181..9a59dddb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java @@ -12,21 +12,21 @@ public class LectureDetailResponseDto extends LectureListResponseDto{ @Schema(description = "후기 평점들의 범위마다 100분율 계산", example = "hard : 30, ok : 20, best : 50") private Emotion emotion; - public LectureDetailResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, List semesters, int grade, Emotion emotion) { + public LectureDetailResponseDto(String _id, String lectureNm, String professor, double starRating, int participants, List semesters, int grade, Emotion emotion) { super(_id, lectureNm, professor, starRating, participants, semesters, grade); this.emotion = emotion; } - public static LectureDetailResponseDto of(LectureEntity lectureEntity, double ave, Emotion emotion, long participants){ + public static LectureDetailResponseDto of(LectureEntity lectureEntity){ return new LectureDetailResponseDto( lectureEntity.get_id().toString(), lectureEntity.getLectureNm(), lectureEntity.getProfessor(), - ave, //평균 평점 - participants, //참여 인원 수 + lectureEntity.getStarRating(), //평균 평점 + lectureEntity.getParticipants(), //참여 인원 수 lectureEntity.getSemester(), lectureEntity.getGrade(), - emotion //평점 당 인원 평균 + lectureEntity.getEmotion() //평점 당 인원 평균 ); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java index 074d4b0c..c2d59ac7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.Setter; -import java.util.ArrayList; import java.util.List; @Setter @@ -26,7 +25,7 @@ public class LectureListResponseDto { private double starRating; @Schema(description = "수강 후기 작성자 수", example = "10") - private long participants; + private int participants; @Schema(description = "해당 강의가 열린 학기들", example = "23-1, 24-1, 25-1") private List semesters; @@ -35,7 +34,7 @@ public class LectureListResponseDto { private int grade; @Builder - public LectureListResponseDto(String _id, String lectureNm, String professor, double starRating, long participants, List semesters, int grade) { + public LectureListResponseDto(String _id, String lectureNm, String professor, double starRating, int participants, List semesters, int grade) { this._id = _id; this.lectureNm = lectureNm; this.professor = professor; @@ -45,13 +44,13 @@ public LectureListResponseDto(String _id, String lectureNm, String professor, do this.grade = grade; } - public static LectureListResponseDto of(LectureEntity lectureEntity, double starRating, long participants){ + public static LectureListResponseDto of(LectureEntity lectureEntity){ return LectureListResponseDto.builder() ._id(lectureEntity.get_id().toString()) .lectureNm(lectureEntity.getLectureNm()) .professor(lectureEntity.getProfessor()) - .starRating(starRating) - .participants(participants) + .starRating(lectureEntity.getStarRating()) + .participants(lectureEntity.getParticipants()) .semesters(lectureEntity.getSemester()) .grade(lectureEntity.getGrade()) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index 69da801b..6edb595a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -23,10 +23,10 @@ public class LectureEntity { private List semester; private double starRating; - private long participants; + private int participants; private Emotion emotion; - public void updateReviewRating(double starRating, long participants, Emotion emotion){ + public void updateReviewRating(double starRating, int participants, Emotion emotion){ this.starRating = starRating; this.participants = participants; this.emotion = emotion; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 65050719..106424a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -6,7 +6,6 @@ import inu.codin.codin.domain.lecture.entity.LectureEntity; import inu.codin.codin.domain.lecture.exception.WrongInputException; import inu.codin.codin.domain.lecture.repository.LectureRepository; -import inu.codin.codin.infra.redis.service.RedisReviewService; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; @@ -24,18 +23,28 @@ public class LectureService { private final LectureRepository lectureRepository; - private final RedisReviewService redisReviewService; + private final MongoTemplate mongoTemplate; + /** + * 강의의 상세 별점 반환 + * @param lectureId 강의 _id + * @return LectureDetailResponseDto + */ public LectureDetailResponseDto getLectureDetails(String lectureId) { LectureEntity lectureEntity = lectureRepository.findById(new ObjectId(lectureId)) .orElseThrow(() -> new NotFoundException("강의 정보를 찾을 수 없습니다.")); - double ave = redisReviewService.getAveOfRating(lectureId); - Emotion emotion = redisReviewService.getEmotionRating(lectureId); - long participants = redisReviewService.getParticipants(lectureId); - return LectureDetailResponseDto.of(lectureEntity, ave, emotion, participants); + return LectureDetailResponseDto.of(lectureEntity); } + /** + * 여러 옵션을 선택하여 강의 리스트 반환 + * @param department Department (COMPUTER_SCI, INFO_COMM, EMBEDDED, OTHERS) + * @param keyword 검색할 키워드 (선택) + * @param option 교수명, 강의명 중 내림차순 선택 + * @param page 페이지 번호 + * @return LecturePageResponse + */ public LecturePageResponse sortListOfLectures(Department department, String keyword, Option option, int page) { if (department.equals(Department.EMBEDDED) || department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.OTHERS)) { PageRequest pageRequest = PageRequest.of(page, 20, option==Option.LEC? Sort.by("lectureNm"):Sort.by("professor")); @@ -49,6 +58,13 @@ public LecturePageResponse sortListOfLectures(Department department, String keyw } else throw new WrongInputException("학과명을 잘못 입력하였습니다. department: " + department.getDescription()); } + /** + * 강의 후기를 작성할 강의 목록 검색 + * @param department Department (COMPUTER_SCI, INFO_COMM, EMBEDDED, OTHERS) + * @param grade 학년 (1,2,3,4) + * @param semester 수강 학기 (23-1, 23-2,,, 현재 학기) + * @return List 검색 결과 리스트 반환 + */ public List searchLecturesToReview(Department department, Integer grade, String semester) { List lectures = findLectures(department, grade, semester); if (semester != null) return lectures.stream() @@ -61,16 +77,20 @@ public List searchLecturesToReview(Department depa .toList(); } + /** + * 페이지로 반환된 LectureEntity -> Dto 변환 + */ private LecturePageResponse getLecturePageResponse(Page lecturePage) { return LecturePageResponse.of(lecturePage.stream() - .map(lecture -> LectureListResponseDto.of(lecture, - redisReviewService.getAveOfRating(lecture.get_id().toString()), - redisReviewService.getParticipants(lecture.get_id().toString()))) + .map(LectureListResponseDto::of) .toList(), lecturePage.getTotalPages() - 1, lecturePage.hasNext() ? lecturePage.getPageable().getPageNumber() + 1 : -1); } + /** + * 강의 검색 시 선택된 옵션에 따라 검색 진행 + */ public List findLectures(Department department, Integer grade, String semester) { Query query = new Query(); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 5bc1e264..c16acc74 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -2,23 +2,22 @@ import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; -import inu.codin.codin.domain.lecture.repository.LectureRepository; +import inu.codin.codin.domain.like.entity.LikeEntity; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.scrap.entity.ScrapEntity; -import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.*; +import inu.codin.codin.infra.redis.service.RedisHitsService; +import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -45,12 +44,10 @@ public class SyncScheduler { private final LikeRepository likeRepository; private final HitsRepository hitsRepository; private final BestRepository bestRepository; - private final LectureRepository lectureRepository; private final RedisService redisService; private final RedisLikeService redisLikeService; private final RedisHitsService redisHitsService; - private final RedisReviewService redisReviewService; private final RedisHealthChecker redisHealthChecker; @Async @@ -66,65 +63,9 @@ public void syncLikes() { syncEntityLikes("REPLY", replyCommentRepository); syncEntityLikes("REVIEW", reviewRepository); syncPostHits(); - syncReviews(); log.info(" 동기화 작업 완료"); } - private void syncReviews() { - List dbReviews = reviewRepository.findAllByDeletedAtIsNull(); - Set redisKeys = redisService.getKeys("review:lectures:*").stream() - .map(key -> key.replace("review:lectures:", "")) - .collect(Collectors.toSet()); - - //Map< lectureId, 해당되는 리뷰Entity 리스트 > - Map> dbReviewMap = dbReviews.stream() - .collect(Collectors.groupingBy(review -> review.getLectureId().toString())); - - // Redis에 없는 리뷰 복구 - dbReviewMap.forEach((lectureId, reviews) -> { - if (!redisKeys.contains(lectureId)) { - // Redis에 강의 자체가 없으면 모든 리뷰 추가 - reviews.forEach(review -> redisReviewService.addReview( - lectureId, review.getStarRating(), review.getUserId())); - } else { - // Redis에 강의는 있지만 특정 유저 리뷰가 없는 경우 추가 - Set redisUsers = redisReviewService.getReviewUsers(lectureId); - reviews.stream() - .filter(review -> !redisUsers.contains(review.getUserId().toString())) - .forEach(review -> { - log.info("[Redis] 리뷰 추가: UserID={}, LectureID={}", review.getUserId(), lectureId); - redisReviewService.addReview(lectureId, review.getStarRating(), review.getUserId()); - }); - } - }); - - // DB에 없는 리뷰 삭제 - for (String lectureId : redisKeys) { - Set redisUsers = redisReviewService.getReviewUsers(lectureId); - Set dbUsers = dbReviewMap.getOrDefault(lectureId, List.of()).stream() - .map(review -> review.getUserId().toString()) - .collect(Collectors.toSet()); - - redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) - .forEach(userId -> { - log.info("[Redis] 리뷰 삭제: UserID={}, LectureID={}", userId, lectureId); - redisReviewService.removeReview(lectureId, userId); - }); - } - - // 강의 평점 업데이트 - dbReviewMap.keySet().forEach(lectureId -> { - lectureRepository.findById(new ObjectId(lectureId)).ifPresent(lectureEntity -> { - lectureEntity.updateReviewRating( - redisReviewService.getAveOfRating(lectureId), - redisReviewService.getParticipants(lectureId), - redisReviewService.getEmotionRating(lectureId) - ); - lectureRepository.save(lectureEntity); - }); - }); - } - private void syncEntityLikes(String entityType, MongoRepository repository) { Set redisKeys = redisService.getKeys(entityType+ ":likes:*") .stream().map(redisKey -> redisKey.replace(entityType+ ":likes:", "")) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java deleted file mode 100644 index 38e9e63b..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisReviewService.java +++ /dev/null @@ -1,67 +0,0 @@ -package inu.codin.codin.infra.redis.service; - -import inu.codin.codin.domain.lecture.dto.Emotion; -import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ZSetOperations; -import org.springframework.stereotype.Service; - -import java.util.Objects; -import java.util.Set; - -@Service -@RequiredArgsConstructor -public class RedisReviewService { - - private static final String REVIEW_KEY = "review:lectures:"; - private final RedisTemplate redisTemplate; - - public void addReview(String lectureId, double starRating, ObjectId userId){ - String redisKey = REVIEW_KEY + lectureId; - redisTemplate.opsForZSet().add(redisKey, String.valueOf(userId), starRating); - } - - public double getAveOfRating(String lectureId) { - String redisKey = REVIEW_KEY + lectureId; - Set> ratingWithScores = - redisTemplate.opsForZSet().rangeWithScores(redisKey, 0, -1); - if (ratingWithScores == null || ratingWithScores.isEmpty()) return 0.0; - - double totalScore = ratingWithScores.stream() - .mapToDouble(rating -> rating.getScore() != null? rating.getScore():0.0).sum(); - long count = ratingWithScores.size(); - return count > 0 ? totalScore / (double) count : 0.0; - } - - public Emotion getEmotionRating(String lectureId){ - String redisKey = REVIEW_KEY + lectureId; - double total = getParticipants(lectureId); - if (total == 0) return Emotion.builder().hard(0).ok(0).best(0).build(); - return Emotion.builder() - .hard(getPercentOfRating(redisKey, 0.25, 2.0, total)) - .ok(getPercentOfRating(redisKey, 2.25, 4.0, total)) - .best(getPercentOfRating(redisKey, 4.25, 5.0, total)) - .build(); - } - - private double getPercentOfRating(String redisKey, double min, double max, double total){ - return (Objects.requireNonNull(redisTemplate.opsForZSet().rangeByScore(redisKey, min, max)).size() / total) * 100; - } - - public long getParticipants(String lectureId){ - String redisKey = REVIEW_KEY + lectureId; - return Objects.requireNonNull(redisTemplate.opsForZSet().rangeWithScores(redisKey, 0, -1)).size(); - } - - public Set getReviewUsers(String lectureId) { - String redisKey = REVIEW_KEY + lectureId; - return redisTemplate.opsForZSet().range(redisKey, 0, -1); - } - - public void removeReview(String lectureId, String userId) { - String redisKey = REVIEW_KEY + lectureId; - redisTemplate.opsForZSet().remove(redisKey, userId); - } - -} From 906f4784e228a9ac46ddea7fdac891d124c5670d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 19:58:21 +0900 Subject: [PATCH 0688/1002] =?UTF-8?q?docs=20:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0(Review)=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=EC=97=90=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/lecture/domain/review/service/ReviewService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index 4591724d..f91a93d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -54,7 +54,7 @@ public void createReview(ObjectId lectureId, CreateReviewRequestDto createReview ReviewEntity newReview = ReviewEntity.of(createReviewRequestDto, lectureId, userId); reviewRepository.save(newReview); updateRating(lectureId); - + log.info("새로운 강의 후기 저장 - lectureId : {} userId : {}", lectureId, userId); } /** From eee70974114155a184ddc23d9eaa5de068f09891 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 20:03:39 +0900 Subject: [PATCH 0689/1002] =?UTF-8?q?refacotr=20:=20Cache=EC=9D=98=20Hits?= =?UTF-8?q?=EB=A5=BC=20PostEntity=EC=97=90=20=EB=8F=99=EA=B8=B0=ED=99=94?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B3=BC=EC=A0=95=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/scheduler/SyncScheduler.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index c16acc74..8301a051 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -9,13 +9,11 @@ import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisHitsService; import inu.codin.codin.infra.redis.service.RedisLikeService; import inu.codin.codin.infra.redis.service.RedisService; import lombok.RequiredArgsConstructor; @@ -42,12 +40,10 @@ public class SyncScheduler { private final ReviewRepository reviewRepository; private final LikeRepository likeRepository; - private final HitsRepository hitsRepository; private final BestRepository bestRepository; private final RedisService redisService; private final RedisLikeService redisLikeService; - private final RedisHitsService redisHitsService; private final RedisHealthChecker redisHealthChecker; @Async @@ -62,7 +58,6 @@ public void syncLikes() { syncEntityLikes("COMMENT", commentRepository); syncEntityLikes("REPLY", replyCommentRepository); syncEntityLikes("REVIEW", reviewRepository); - syncPostHits(); log.info(" 동기화 작업 완료"); } @@ -134,31 +129,6 @@ private void syncEntityLikes(String entityType, MongoRepository } } - - /** - * redis에서 업데이트 되는 조회수를 postEntity에 주기적으로 저장 - */ - @Scheduled(fixedRate = 43200000) - public void syncPostHits(){ - Set redisKeys = redisService.getKeys("post:hits:*") - .stream().map(redisKey -> redisKey.replace("post:hits:", "")) - .collect(Collectors.toSet()); - if (redisKeys.isEmpty()) return; - - for (String postId: redisKeys){ - //조회수 count 업데이트 - Object object = redisHitsService.getHitsCount(new ObjectId(postId)); - int hitCount = object != null? Integer.parseInt((String) object) : hitsRepository.countAllByPostId(new ObjectId(postId)); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElse(null); - if (post != null && post.getHitCount() != hitCount){ - log.info("PostEntity 조회수 업데이트: PostID={}, Count={}", postId, hitCount); - post.updateHitCount(hitCount); - postRepository.save(post); - } - } - } - @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 public void getTop3BestPosts() { Map posts = redisService.getTopNPosts(3); From a642a5c095c4cd73870550a6d47f6045590299c1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:11:11 +0900 Subject: [PATCH 0690/1002] =?UTF-8?q?refacotr=20:=20Cache=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9D=98=20LikeCount=20=EC=A1=B0=ED=9A=8C,=20Look=20A?= =?UTF-8?q?side=20=EA=B7=9C=EC=B9=99=20=EC=84=A4=EC=A0=95=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/repository/LikeRepository.java | 14 +--- .../domain/like/service/LikeService.java | 84 +++++++++---------- .../post/domain/like/service/LikeService.java | 0 .../infra/redis/service/RedisLikeService.java | 48 ++++------- 4 files changed, 60 insertions(+), 86 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java index b379a028..2bc0fd2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java @@ -8,22 +8,16 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; -import java.util.List; +import java.util.Optional; @Repository public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - long countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); - - // 특정 엔티티의 좋아요 데이터 조회 - List findByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId id); - + int countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId likeTypeId); + int countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId likeTypeId); boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, ObjectId id, ObjectId userId); - LikeEntity findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId id, ObjectId userId); - - List findByLikeTypeAndDeletedAtIsNull(LikeType likeType); - + Optional findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId likeTypeId, ObjectId userId); Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index d1235cb4..026c0e81 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -5,7 +5,6 @@ import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.exception.LikeCreateFailException; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.like.dto.LikeRequestDto; @@ -19,6 +18,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import java.util.Optional; + @Service @RequiredArgsConstructor @Slf4j @@ -33,29 +34,35 @@ public class LikeService { private final RedisService redisService; private final RedisHealthChecker redisHealthChecker; + public String toggleLike(LikeRequestDto likeRequestDto) { ObjectId likeId = new ObjectId(likeRequestDto.getId()); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 - LikeEntity like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + Optional like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); + return getResult(likeRequestDto, like, likeId, userId); + } - if (like != null && like.getDeletedAt() == null) { //좋아요가 존재 - removeLike(like); - return "좋아요가 삭제되었습니다."; - } else if (like == null){ - createLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 추가되었습니다."; + private String getResult(LikeRequestDto likeRequestDto, Optional like, ObjectId likeId, ObjectId userId) { + if (like.isPresent()){ + if (like.get().getDeletedAt() == null) { + removeLike(like.get()); + return "좋아요가 삭제되었습니다."; + } else { + restoreLike(like.get()); //좋아요가 존재하는데 삭제된 상태 + return "좋아요가 복구되었습니다"; + } } else { - addLike(like); //좋아요가 존재하는데 삭제된 상태 - return "좋아요가 복구되었습니다"; + addLike(likeRequestDto.getLikeType(), likeId, userId); + return "좋아요가 추가되었습니다."; } } - public void createLike(LikeType likeType, ObjectId likeId, ObjectId userId){ + public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId){ if (redisHealthChecker.isRedisAvailable()) { - redisLikeService.addLike(likeType.name(), likeId, userId); + redisLikeService.addLike(likeType.name(), likeId); log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); } @@ -70,30 +77,22 @@ public void createLike(LikeType likeType, ObjectId likeId, ObjectId userId){ } } - public void addLike(LikeEntity like) { - LikeType likeType = like.getLikeType(); - ObjectId likeId = like.getLikeTypeId(); - ObjectId userId = like.getUserId(); - + public void restoreLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { - redisLikeService.addLike(likeType.name(), likeId, userId); - log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + redisLikeService.addLike(like.getLikeType().name(), like.getLikeTypeId()); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}", like.getLikeType(), like.getLikeTypeId()); } - if (like.getDeletedAt() != null) { //삭제된 상태라면 다시 좋아요 만들기 - like.recreatedAt(); - like.restore(); - likeRepository.save(like); - log.info("좋아요 복구 완료 - likeId: {}, userId: {}", like.get_id(), userId); - } else { - log.warn("좋아요 추가 실패 - 이미 좋아요가 눌려 있음 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); - throw new LikeCreateFailException("이미 좋아요가 눌러진 상태입니다."); - } + like.recreatedAt(); + like.restore(); + likeRepository.save(like); + log.info("좋아요 복구 완료 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); + } public void removeLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { - redisLikeService.removeLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); + redisLikeService.removeLike(like.getLikeType().name(), like.getLikeTypeId()); log.info("Redis에서 좋아요 삭제 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); } like.delete(); @@ -102,28 +101,23 @@ public void removeLike(LikeEntity like) { } public int getLikeCount(LikeType entityType, ObjectId entityId) { + Object redisResult = null; if (redisHealthChecker.isRedisAvailable()) { - return redisLikeService.getLikeCount(entityType.name(), entityId); + redisResult = redisLikeService.getLikeCount(entityType.name(), entityId); } - long count = likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); - return (int) Math.max(0, count); - } - - public boolean isPostLiked(ObjectId postId, ObjectId userId){ - return redisLikeService.isLiked(LikeType.POST, postId, userId); - } - - public boolean isCommentLiked(ObjectId commentId, ObjectId userId){ - return redisLikeService.isLiked(LikeType.COMMENT, commentId, userId); + if (redisResult == null){ + recoveryLike(entityType, entityId); + return likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); + } else return Integer.parseInt(String.valueOf(redisResult)); } - public boolean isReplyLiked(ObjectId replyId, ObjectId userId) { - return redisLikeService.isLiked(LikeType.REPLY, replyId, userId); - + private void recoveryLike(LikeType entityType, ObjectId entityId) { + int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); + redisLikeService.recoveryLike(entityType, entityId, likeCount); } - public boolean isReviewLiked(ObjectId reviewId, ObjectId userId){ - return redisLikeService.isLiked(LikeType.REVIEW, reviewId, userId); + public boolean isLiked(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ + return likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeTypeId, userId); } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/like/service/LikeService.java deleted file mode 100644 index e69de29b..00000000 diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java index a3514e12..e16638ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java @@ -2,14 +2,13 @@ import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.repository.LikeRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.util.Set; +import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor @@ -19,48 +18,35 @@ public class RedisLikeService { * Redis 기반 Like 관리 Service */ private final RedisTemplate redisTemplate; - private final LikeRepository likeRepository; private static final String LIKE_KEY=":likes:"; //Like - public void addLike(String entityType, ObjectId entityId, ObjectId userId) { + public void addLike(String entityType, ObjectId entityId) { String redisKey = entityType + LIKE_KEY + entityId; - redisTemplate.opsForSet().add(redisKey, String.valueOf(userId)); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) + redisTemplate.opsForValue().increment(redisKey); + else { + redisTemplate.opsForValue().set(redisKey, String.valueOf(1)); + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); + } } - public void removeLike(String entityType, ObjectId entityId, ObjectId userId) { + public void removeLike(String entityType, ObjectId entityId) { String redisKey = entityType + LIKE_KEY + entityId; - redisTemplate.opsForSet().remove(redisKey, String.valueOf(userId)); + redisTemplate.opsForValue().decrement(redisKey, 1); } - public int getLikeCount(String entityType, ObjectId entityId) { + public Object getLikeCount(String entityType, ObjectId entityId) { String redisKey = entityType + LIKE_KEY + entityId; - Long count = redisTemplate.opsForSet().size(redisKey); - return count != null ? count.intValue() : 0; - } + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))){ + return redisTemplate.opsForValue().get(redisKey); + } else return null; - public Set getLikedUsers(String entityType, String entityId) { - String redisKey = entityType + LIKE_KEY + entityId; - return redisTemplate.opsForSet().members(redisKey); } - public boolean isLiked(LikeType likeType, ObjectId id, ObjectId userId){ - String redisKey = ""; - switch(likeType){ - case POST -> redisKey = LikeType.POST + LIKE_KEY + id.toString(); - case COMMENT -> redisKey = LikeType.COMMENT + LIKE_KEY + id.toString(); - case REPLY -> redisKey = LikeType.REPLY + LIKE_KEY + id.toString(); - case REVIEW -> redisKey = LikeType.REVIEW + LIKE_KEY + id.toString(); - } - return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(redisKey, userId.toString())); - } - - public void recoverRedisFromDB() { - log.info("Redis 복구 요청 - DB의 좋아요 데이터를 기반으로 복구 시작"); - likeRepository.findAll().forEach(like -> { - addLike(like.getLikeType().name(), like.getLikeTypeId(), like.getUserId()); - log.info("Redis에 좋아요 복구 - likeType: {}, likeId: {}, userId: {}", like.getLikeType(), like.getLikeTypeId(), like.getUserId()); - }); + public void recoveryLike(LikeType entityType, ObjectId entityId, int likeCount) { + String redisKey = entityType + LIKE_KEY + entityId; + redisTemplate.opsForValue().set(redisKey, String.valueOf(likeCount)); } } From 4c9431a84784e2cffc51e90d83c99916786b4d59 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:11:54 +0900 Subject: [PATCH 0691/1002] =?UTF-8?q?refacotr=20:=20isLiked=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=ED=86=B5=EC=9D=BC=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/review/service/ReviewService.java | 2 +- .../post/domain/comment/service/CommentService.java | 10 ++++------ .../post/domain/reply/service/ReplyCommentService.java | 8 ++++---- .../codin/codin/domain/post/service/PostService.java | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index f91a93d6..6d93cd97 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -85,7 +85,7 @@ public ReviewPageResponse getListOfReviews(String lectureId, int page) { ObjectId userId = SecurityUtils.getCurrentUserId(); return ReviewPageResponse.of(reviewPage.stream() .map(review -> ReviewListResposneDto.of(review, - likeService.isReviewLiked(review.get_id(), userId), + likeService.isLiked(LikeType.REVIEW, review.get_id(), userId), likeService.getLikeCount(LikeType.REVIEW, review.get_id()))).toList(), reviewPage.getTotalPages() -1, reviewPage.hasNext()? reviewPage.getPageable().getPageNumber() + 1: -1); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 49c422dc..bab66cce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -15,7 +15,6 @@ import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisService; @@ -36,7 +35,6 @@ public class CommentService { private final PostRepository postRepository; private final CommentRepository commentRepository; - private final ReportRepository reportRepository; private final UserRepository userRepository; private final LikeService likeService; @@ -61,7 +59,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { commentRepository.save(comment); // 댓글 수 증가 - post.updateCommentCount(post.getCommentCount() + 1); + post.plusCommentCount(); post.getAnonymous().setAnonNumber(post, userId); postRepository.save(post); @@ -126,7 +124,7 @@ public List getCommentsByPostId(String id) { return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), - getUserInfoAboutPost(comment.get_id())); + getUserInfoAboutComment(comment.get_id())); }) .toList(); } @@ -145,10 +143,10 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { } - public UserInfo getUserInfoAboutPost(ObjectId commentId) { + public UserInfo getUserInfoAboutComment(ObjectId commentId) { ObjectId userId = SecurityUtils.getCurrentUserId(); return UserInfo.builder() - .isLike(likeService.isCommentLiked(commentId, userId)) + .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index b978f32c..e6764827 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -67,7 +67,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { replyCommentRepository.save(reply); // 댓글 수 증가 (대댓글도 댓글 수에 포함) - post.updateCommentCount(post.getCommentCount() + 1); + post.plusCommentCount(); post.getAnonymous().setAnonNumber(post, userId); postRepository.save(post); @@ -121,15 +121,15 @@ public List getRepliesByCommentId(PostAnonymous postAnonymou } return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.get_id()), // 대댓글 좋아요 수 - getUserInfoAboutPost(reply.get_id())); + getUserInfoAboutReply(reply.get_id())); }).toList(); } - public CommentResponseDTO.UserInfo getUserInfoAboutPost(ObjectId replyId) { + public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); //log.info("대댓글 userInfo - replyId: {}, userId: {}", replyId, userId); return CommentResponseDTO.UserInfo.builder() - .isLike(likeService.isReplyLiked(replyId, userId)) + .isLike(likeService.isLiked(LikeType.REPLY, replyId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 16f40140..617bfd26 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -274,7 +274,7 @@ public void deletePostImage(String postId, String imageUrl) { public UserInfo getUserInfoAboutPost(ObjectId userId, ObjectId postId){ return UserInfo.builder() - .isLike(likeService.isPostLiked(postId, userId)) + .isLike(likeService.isLiked(LikeType.POST, postId, userId)) .isScrap(scrapService.isPostScraped(postId, userId)) .build(); } From d5dc1da43486fe391994cf99b31e97177ecb5182 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:12:15 +0900 Subject: [PATCH 0692/1002] =?UTF-8?q?refacotr=20:=20=EB=B3=B5=EA=B5=AC?= =?UTF-8?q?=EB=90=9C=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20recreatedAt=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/scrap/service/ScrapService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 1d9fec87..bec08243 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -67,6 +67,7 @@ private String getResult(Optional scrap, ObjectId postId, ObjectId } private void restoreScrap(ScrapEntity scrap) { + scrap.recreatedAt(); scrap.restore(); scrapRepository.save(scrap); log.info("스크랩 복원 완료 - postId: {}, userId: {}", scrap.getPostId(), scrap.getUserId()); From 72745717ab4ff8fb3d196e4c957eb8721f0607dc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:12:44 +0900 Subject: [PATCH 0693/1002] =?UTF-8?q?refacotr=20:=20Cache=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=ED=9A=8C,=20=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC=20=EC=82=AD=EC=A0=9C=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/entity/PostEntity.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 2eb765f9..4c80dfc7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -27,9 +27,6 @@ public class PostEntity extends BaseTimeEntity { private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) private int commentCount = 0; // 댓글 + 대댓글 카운트 - private int likeCount= 0; // 좋아요 카운트 (redis) - private int scrapCount= 0; // 스크랩 카운트 (redis) - private int hitCount = 0; private int reportCount = 0; // 신고 카운트 private PostAnonymous anonymous = new PostAnonymous(); @@ -69,16 +66,8 @@ public void removePostImage(String imageUrl) { } //댓글+대댓글 수 업데이트 - public void updateCommentCount(int commentCount) { - this.commentCount=commentCount; - } - //좋아요 수 업데이트 - public void updateLikeCount(int likeCount) { - this.likeCount=likeCount; - } - //스크랩 수 업데이트 - public void updateScrapCount(int scrapCount) { - this.scrapCount=scrapCount; + public void plusCommentCount() { + this.commentCount++; } //신고 수 업데이트 @@ -86,7 +75,4 @@ public void updateReportCount(int reportCount) { this.reportCount=reportCount; } - public void updateHitCount(int hitCount) { - this.hitCount = hitCount; - } } From 62ab9d36d8cfcc0c9025f6ff07f918b65e2f363a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:38:15 +0900 Subject: [PATCH 0694/1002] =?UTF-8?q?refacotr=20:=20RedisHealthChecker?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EC=82=AC=EC=9A=A9=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=9C=EC=A7=80=20=ED=8C=90=EB=8B=A8=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/hits/service/HitsService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java index 0b49c5f7..fe97041f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisHitsService; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; @@ -19,6 +20,8 @@ public class HitsService { private final RedisHitsService redisHitsService; + private final RedisHealthChecker redisHealthChecker; + private final HitsRepository hitsRepository; /** @@ -28,7 +31,8 @@ public class HitsService { * @param userId 유저 _id */ public void addHits(ObjectId postId, ObjectId userId){ - redisHitsService.addHits(postId); + if (redisHealthChecker.isRedisAvailable()) + redisHitsService.addHits(postId); HitsEntity hitsEntity = HitsEntity.builder() .postId(postId).userId(userId).build(); hitsRepository.save(hitsEntity); @@ -51,7 +55,9 @@ public boolean validateHits(ObjectId postId, ObjectId userId) { * @return 게시글 조회수 */ public int getHitsCount(ObjectId postId) { - Object hits = redisHitsService.getHitsCount(postId); + Object hits = null; + if (redisHealthChecker.isRedisAvailable()) + hits = redisHitsService.getHitsCount(postId); if (hits == null) { recoveryHits(postId); return hitsRepository.countAllByPostId(postId); From 0c0fa9046c12e4db0351111ff41c22a6420d2976 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:43:47 +0900 Subject: [PATCH 0695/1002] =?UTF-8?q?refacotr=20:=20Comment,=20Reply,=20Re?= =?UTF-8?q?view=20=EB=AA=A8=EB=91=90=20likeCount=20=EC=B9=BC=EB=9F=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/review/entity/ReviewEntity.java | 9 +-------- .../post/domain/comment/entity/CommentEntity.java | 6 ------ .../post/domain/comment/service/CommentService.java | 2 +- .../post/domain/reply/entity/ReplyCommentEntity.java | 10 +--------- .../post/domain/reply/service/ReplyCommentService.java | 2 +- 5 files changed, 4 insertions(+), 25 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index 624421ea..aaae7d88 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -18,17 +18,15 @@ public class ReviewEntity extends BaseTimeEntity { private ObjectId userId; private String content; private double starRating; - private int likeCount; private String semester; @Builder - public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String content, double starRating, int likeCount, String semester) { + public ReviewEntity(ObjectId _id, ObjectId lectureId, ObjectId userId, String content, double starRating, String semester) { this._id = _id; this.lectureId = lectureId; this.userId = userId; this.content = content; this.starRating = starRating; - this.likeCount = likeCount; this.semester = semester; } @@ -39,11 +37,6 @@ public static ReviewEntity of(CreateReviewRequestDto createReviewRequestDto, Obj .lectureId(lectureId) .userId(userId) .semester(createReviewRequestDto.getSemester()) - .likeCount(0) .build(); } - - public void updateLikeCount(int likeCount) { - this.likeCount = likeCount; - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 8043ec3f..c3f6ca06 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -20,8 +20,6 @@ public class CommentEntity extends BaseTimeEntity { private boolean anonymous; - private int likeCount = 0; // 좋아요 수 (Redis에서 관리) - @Builder public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String content, Boolean anonymous) { this._id = _id; @@ -35,10 +33,6 @@ public void updateComment(String content) { this.content = content; } - //좋아요 수 업데이트 - public void updateLikeCount(int likeCount) { - this.likeCount=likeCount; - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index bab66cce..24749b4e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -123,7 +123,7 @@ public List getCommentsByPostId(String id) { } return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), - likeService.getLikeCount(LikeType.valueOf("COMMENT"), comment.get_id()), + likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), getUserInfoAboutComment(comment.get_id())); }) .toList(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 58c2ffb3..c15a5162 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -20,25 +20,17 @@ public class ReplyCommentEntity extends BaseTimeEntity { private boolean anonymous; - private int likeCount = 0; // 좋아요 카운트 - @Builder - public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, boolean anonymous, String content, int likeCount) { + public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, boolean anonymous, String content) { this._id = _id; this.commentId = commentId; this.userId = userId; this.content = content; this.anonymous = anonymous; - this.likeCount = likeCount; } public void updateReply(String content) { this.content = content; } - //좋아요 수 업데이트 - public void updateLikeCount(int likeCount) { - this.likeCount=likeCount; - } - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index e6764827..5249a5ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -120,7 +120,7 @@ public List getRepliesByCommentId(PostAnonymous postAnonymou userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); } return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), - likeService.getLikeCount(LikeType.valueOf("REPLY"), reply.get_id()), // 대댓글 좋아요 수 + likeService.getLikeCount(LikeType.REPLY, reply.get_id()), // 대댓글 좋아요 수 getUserInfoAboutReply(reply.get_id())); }).toList(); } From ee484ce6eaa6573b977423f4f5965d6641ebf79d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 21:44:10 +0900 Subject: [PATCH 0696/1002] =?UTF-8?q?refacotr=20:=20Like=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC=20=EC=9E=91=EC=97=85=20@Async=EB=A1=9C=20=EB=B9=84?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=20=EC=B2=98=EB=A6=AC=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/like/service/LikeService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 026c0e81..903891ed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -16,6 +16,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.Optional; @@ -111,7 +112,8 @@ public int getLikeCount(LikeType entityType, ObjectId entityId) { } else return Integer.parseInt(String.valueOf(redisResult)); } - private void recoveryLike(LikeType entityType, ObjectId entityId) { + @Async + protected void recoveryLike(LikeType entityType, ObjectId entityId) { int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); redisLikeService.recoveryLike(entityType, entityId, likeCount); } From 755dba973e40f618bc0afcac2409f404bbfb1880 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 27 Mar 2025 22:40:21 +0900 Subject: [PATCH 0697/1002] =?UTF-8?q?refacotr=20:=20Best=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EA=B3=84=EC=82=B0=20=EA=B3=BC=EC=A0=95=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20class=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/like/service/LikeService.java | 6 +- .../comment/service/CommentService.java | 6 +- .../reply/service/ReplyCommentService.java | 6 +- .../domain/post/service/PostService.java | 7 +- .../domain/scrap/service/ScrapService.java | 6 +- .../infra/redis/service/RedisBestService.java | 102 ++++++++++++++++++ .../infra/redis/service/RedisService.java | 99 ----------------- 7 files changed, 117 insertions(+), 115 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 903891ed..1f320f06 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -12,7 +12,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisLikeService; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.RedisBestService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -32,7 +32,7 @@ public class LikeService { private final ReviewRepository reviewRepository; private final RedisLikeService redisLikeService; - private final RedisService redisService; + private final RedisBestService redisBestService; private final RedisHealthChecker redisHealthChecker; @@ -73,7 +73,7 @@ public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId){ .userId(userId) .build()); if (likeType == LikeType.POST) { - redisService.applyBestScore(1, likeId); + redisBestService.applyBestScore(1, likeId); log.info("Redis에 Best Score 적용 - postId: {}", likeId); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 24749b4e..428b5492 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -17,7 +17,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.RedisBestService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,7 +40,7 @@ public class CommentService { private final LikeService likeService; private final ReplyCommentService replyCommentService; private final NotificationService notificationService; - private final RedisService redisService; + private final RedisBestService redisBestService; private final S3Service s3Service; // 댓글 추가 @@ -63,7 +63,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { post.getAnonymous().setAnonNumber(post, userId); postRepository.save(post); - redisService.applyBestScore(1, postId); + redisBestService.applyBestScore(1, postId); log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 5249a5ec..91adfc63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -19,7 +19,7 @@ import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.RedisBestService; import inu.codin.codin.infra.s3.S3Service; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -44,7 +44,7 @@ public class ReplyCommentService { private final LikeService likeService; private final NotificationService notificationService; - private final RedisService redisService; + private final RedisBestService redisBestService; private final S3Service s3Service; // 대댓글 추가 @@ -71,7 +71,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { post.getAnonymous().setAnonNumber(post, userId); postRepository.save(post); - redisService.applyBestScore(1, post.get_id()); + redisBestService.applyBestScore(1, post.get_id()); log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 617bfd26..88312432 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -30,7 +30,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.RedisBestService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; @@ -59,7 +59,7 @@ public class PostService { private final LikeService likeService; private final ScrapService scrapService; private final HitsService hitsService; - private final RedisService redisService; + private final RedisBestService redisBestService; private final BlockService blockService; public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { @@ -290,8 +290,7 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { } public List getTop3BestPosts() { - - Map posts = redisService.getTopNPosts(3); + Map posts = redisBestService.getTopNPosts(3); List bestPosts = posts.entrySet().stream() .map(post -> { BestEntity bestPost = bestRepository.findByPostId(new ObjectId(post.getKey())); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index bec08243..38d1d15d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -5,7 +5,7 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.RedisBestService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -20,7 +20,7 @@ public class ScrapService { private final ScrapRepository scrapRepository; private final PostRepository postRepository; - private final RedisService redisService; + private final RedisBestService redisBestService; /** * 하나의 모듈로 스크랩 추가, 삭제 toggle 동작 @@ -79,7 +79,7 @@ private void addScrap(ObjectId postId, ObjectId userId) { .postId(postId) .userId(userId) .build()); - redisService.applyBestScore(2, postId); //Best 게시글에 적용 + redisBestService.applyBestScore(2, postId); //Best 게시글에 적용 log.info("스크랩 추가 완료 - postId: {}, userId: {}", postId, userId); log.info("Redis에 Best Score 적용 - postId: {}", postId); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java new file mode 100644 index 00000000..e05d26ad --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -0,0 +1,102 @@ +package inu.codin.codin.infra.redis.service; + + +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RedisBestService { + /** + * Redis Best 게시글 관리 + * Best 게시글은 어떠한 게시글이 관심을 받았을 때부터 ~ 24시간 동안 게시글에 점수가 매겨짐 + * Score {좋아요, 댓글, 대댓글 : 1 / 스크랩 : 2} + * 24시간 실시간 반영으로 3위까지 나타냄 + */ + private final RedisTemplate redisTemplate; + private final HitsService hitsService; + + /** + * best 게시글을 반환하는 순간으로부터 24시간 동안의 가장 score가 높은 게시글 N개를 반환 + * @param N 순위 + * @return N개의 Key : postId, Value : score 의 score 기준 내림차순 Map + */ + public Map getTopNPosts(int N) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + //24시간 동안의 게시글들의 score들을 모으기 + Map result = new HashMap<>(); + for (int i = 0; i < 24; i++) { + String redisKey = now.minusHours(i).format(formatter); + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); + if (members != null) { + for (ZSetOperations.TypedTuple member :members){ + String postId = member.getValue(); + Double score = member.getScore(); + result.put(postId, score); + } + } + } + + return result.entrySet().stream() + .sorted((e1, e2) -> { + //1. 점수 기준 내림차순 + int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); + + // 2. 점수가 동일하면 조회수 기준 내림차순 정렬 + return scoreComparison != 0 ? scoreComparison : + Integer.compare(hitsService.getHitsCount(new ObjectId(e2.getKey())), + hitsService.getHitsCount(new ObjectId(e1.getKey()))); + }) + .limit(N).collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (existing, replacement) -> existing, + LinkedHashMap::new + )); + } + + /** + * 24시간 내에 해당 게시글의 score가 존재하면 점수 반영 + * 만약 새로운 게시글이라면 현재 시간대로 새로 생성하여 게시글 점수 저장 + * @param score 게시글에 더할 점수 + * @param postId 게시글 _id + */ + public void applyBestScore(int score, ObjectId postId){ + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); + + String redisKey; + //이전 24시간대에서 해당 게시글의 score를 찾기 + for (int i=0; i<24; i++){ + redisKey = now.minusHours(i).format(formatter); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { + Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, postId.toString()); + if (scoreOfBest != null) { //score가 존재하면 업데이트 + redisTemplate.opsForZSet().incrementScore(redisKey, postId.toString(), score); + return; + } + } + } + //score가 존재하지 않으면 현재 시간대로 새로 생성 + redisKey = now.format(formatter); + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); + redisTemplate.opsForZSet().add(redisKey, postId.toString(), score); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java deleted file mode 100644 index 09c80dd1..00000000 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisService.java +++ /dev/null @@ -1,99 +0,0 @@ -package inu.codin.codin.infra.redis.service; - - -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ZSetOperations; -import org.springframework.stereotype.Service; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Slf4j -public class RedisService { - /** - * Redis 기반 관리 Service - * Redis 작업 실패시 DB 기반으로 직접 처리 - * 장애 복구를 대비한 보완 로직 추가 - */ - private final RedisTemplate redisTemplate; - private final HitsService hitsService; - - - //post, comment, reply 구분 - public Set getKeys(String pattern) { - try { - Set keys = redisTemplate.keys(pattern); - if (keys == null || keys.isEmpty()) { - return Set.of(); // keys가 null이거나 빈 경우 빈 Set 반환 - } - return keys.stream() - .filter(key -> key != null && !key.isEmpty()) // key가 null 또는 빈 문자열이 아닌 경우 필터링 - .collect(Collectors.toSet()); - } catch (Exception e) { - log.warn("Redis 연결 중 오류 발생: {}", e.getMessage()); - return Set.of(); // Redis 예외 발생 시 빈 Set 반환 - } - } - - // Top N 게시물 조회 - public Map getTopNPosts(int N) { - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - - Map result = new HashMap<>(); - for (int i = 0; i < 24; i++) { - String redisKey = now.minusHours(i).format(formatter); - Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); - if (members != null) { - for (ZSetOperations.TypedTuple member :members){ - String postId = member.getValue(); - Double score = member.getScore(); - result.put(postId, score); - } - } - } - - return result.entrySet().stream() - .sorted((e1, e2) -> { - int scoreComparison = Double.compare(e2.getValue(), e1.getValue()); - if (scoreComparison != 0) { - return scoreComparison; - } - return Integer.compare(hitsService.getHitsCount(new ObjectId(e2.getKey())), hitsService.getHitsCount(new ObjectId(e1.getKey()))); - }) - .limit(N).collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue, - (existing, replacement) -> existing, // Merge function (not needed here) - LinkedHashMap::new // Use LinkedHashMap to preserve the order - )); - } - - public void applyBestScore(int score, ObjectId id){ - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); - - String redisKey; - for (int i=0; i<24; i++){ - redisKey = now.minusHours(i).format(formatter); - Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, id.toString()); - if (scoreOfBest != null){ - redisTemplate.opsForZSet().incrementScore(redisKey, id.toString(), score); - return; - } - } - redisKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd/HH")); - redisTemplate.opsForZSet().add(redisKey, id.toString(), score); - } -} From d4250d8629ae894c8dbd09972cdef519cf4dfcde Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 28 Mar 2025 00:23:22 +0900 Subject: [PATCH 0698/1002] =?UTF-8?q?fix=20::=20access=5Ftoken=20=EC=9D=B4?= =?UTF-8?q?=20=EB=B9=84=EC=96=B4=EC=9E=88=EC=9D=84=EC=8B=9C=20Spring=20Sec?= =?UTF-8?q?urity=20=EB=82=B4=EB=B6=80=EC=97=90=EC=84=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8A=94=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 6 +++ .../util/CustomAuthenticationEntryPoint.java | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 637b5213..b012256f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -64,6 +64,8 @@ public class SecurityConfig { private final ClientRegistrationRepository clientRegistrationRepository; private final CustomOAuth2AccessTokenResponseClient customOAuth2AccessTokenResponseClient; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + @Value("${server.domain}") private String BASEURL; @@ -86,6 +88,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(USER_AUTH_PATHS).hasRole("USER") .anyRequest().hasRole("USER") ) + //Security 내부 인증 실패 처리 => access_token 없는 경우 + .exceptionHandling(exception -> exception + .authenticationEntryPoint(customAuthenticationEntryPoint) + ) //oauth2 로그인 설정 추가 .oauth2Login(oauth2 -> oauth2 // Apple 클라이언트에 대해 커스텀 토큰 응답 클라이언트 적용 diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java new file mode 100644 index 00000000..342d6728 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java @@ -0,0 +1,46 @@ +package inu.codin.codin.common.security.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.codin.common.response.ExceptionResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +@Slf4j +@RequiredArgsConstructor +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) throws IOException { + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + String errorMessage = "AUTHENTICATION_REQUIRED."; + if (exception instanceof OAuth2AuthenticationException oauth2Ex) { + OAuth2Error error = oauth2Ex.getError(); + if (error != null && error.getDescription() != null) { + errorMessage = error.getDescription(); + } + } + + ExceptionResponse errorResponse = new ExceptionResponse(errorMessage, HttpServletResponse.SC_UNAUTHORIZED); + String responseBody = objectMapper.writeValueAsString(errorResponse); + + log.error("[AuthenticationEntryPoint] {}", responseBody); + response.getWriter().write(responseBody); + } +} From 8eda723bfd817852516fd89e613ebe60068ee0e6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 28 Mar 2025 00:27:56 +0900 Subject: [PATCH 0699/1002] =?UTF-8?q?refactor=20::=20ExceptionResponse=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/OAuth2LoginFailureHandler.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 75fa3a89..7712da6a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -1,7 +1,10 @@ package inu.codin.codin.common.security.util; +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.codin.common.response.ExceptionResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -14,7 +17,10 @@ @Component @Slf4j +@RequiredArgsConstructor public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { + private final ObjectMapper objectMapper; + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, @@ -22,18 +28,18 @@ public void onAuthenticationFailure(HttpServletRequest request, response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - String errorMessage = null; - if (exception instanceof OAuth2AuthenticationException) { - OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); - errorMessage = error.getDescription(); - } - if (errorMessage == null || errorMessage.isEmpty()) { - errorMessage = "인증 실패"; // 기본 오류 메시지 + String errorMessage = "인증 실패."; + if (exception instanceof OAuth2AuthenticationException oauth2Ex) { + OAuth2Error error = oauth2Ex.getError(); + if (error != null && error.getDescription() != null) { + errorMessage = error.getDescription(); + } } - PrintWriter writer = response.getWriter(); - writer.write("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); - log.error("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); - writer.flush(); + ExceptionResponse errorResponse = new ExceptionResponse(errorMessage, HttpServletResponse.SC_UNAUTHORIZED); + String responseBody = objectMapper.writeValueAsString(errorResponse); + + log.error("[OAuth2LoginFailureHandler] {}", responseBody); + response.getWriter().write(responseBody); } } From 11d95c05fbf245a18a70acfc7cda8261a276cd9c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 28 Mar 2025 02:24:48 +0900 Subject: [PATCH 0700/1002] =?UTF-8?q?perf:=20Cache=EC=97=90=20=EB=B2=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EB=86=93=EA=B3=A0=20=EC=A0=90?= =?UTF-8?q?=EC=88=98=20=EB=B0=98=EC=98=81=20=EC=8B=9C=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=9C=BC=EB=A1=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/service/RedisBestService.java | 157 +++++++++++++++--- 1 file changed, 133 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index e05d26ad..3ff83cb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -1,7 +1,10 @@ package inu.codin.codin.infra.redis.service; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -11,10 +14,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -29,14 +29,18 @@ public class RedisBestService { * 24시간 실시간 반영으로 3위까지 나타냄 */ private final RedisTemplate redisTemplate; + private final RedisHealthChecker redisHealthChecker; + private final HitsService hitsService; + private final BestRepository bestRepository; + private final String BEST_KEY = "post:top3"; /** * best 게시글을 반환하는 순간으로부터 24시간 동안의 가장 score가 높은 게시글 N개를 반환 * @param N 순위 * @return N개의 Key : postId, Value : score 의 score 기준 내림차순 Map */ - public Map getTopNPosts(int N) { + public Map delicatedBestsScheduler(int N) { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); @@ -44,12 +48,14 @@ public Map getTopNPosts(int N) { Map result = new HashMap<>(); for (int i = 0; i < 24; i++) { String redisKey = now.minusHours(i).format(formatter); - Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, - 1); - if (members != null) { - for (ZSetOperations.TypedTuple member :members){ - String postId = member.getValue(); - Double score = member.getScore(); - result.put(postId, score); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, -1); + if (members != null) { + for (ZSetOperations.TypedTuple member : members) { + String postId = member.getValue(); + Double score = member.getScore(); + result.put(postId, score); + } } } } @@ -73,7 +79,33 @@ public Map getTopNPosts(int N) { } /** - * 24시간 내에 해당 게시글의 score가 존재하면 점수 반영 + * Cache에서 저장 중인 Best 게시글 정보 정렬 및 반환 + * @return Key : PostId (게시글_id) - Value : Score (점수) + */ + public Map getBests(){ + if (redisHealthChecker.isRedisAvailable()){ + Set> members = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, - 1); + if (members!=null && !members.isEmpty()) + return members.stream() + .sorted((e1, e2) -> { + //1. 점수 기준 내림차순 + int scoreComparison = Double.compare(Double.parseDouble(String.valueOf(e2.getScore())), Double.parseDouble(String.valueOf(e1.getScore()))); + + // 2. 점수가 동일하면 조회수 기준 내림차순 정렬 + return scoreComparison != 0 ? scoreComparison : + Integer.compare(hitsService.getHitsCount(new ObjectId(e2.getValue())), + hitsService.getHitsCount(new ObjectId(e1.getValue()))); + }).collect(Collectors.toMap( + ZSetOperations.TypedTuple::getValue, + ZSetOperations.TypedTuple::getScore, + (existing, replacement) -> existing, + LinkedHashMap::new + )); + } + return null; + } + /** + * Cache에 저장된 24시간 데이터 중 해당 게시글의 score가 존재하면 점수 반영 * 만약 새로운 게시글이라면 현재 시간대로 새로 생성하여 게시글 점수 저장 * @param score 게시글에 더할 점수 * @param postId 게시글 _id @@ -83,20 +115,97 @@ public void applyBestScore(int score, ObjectId postId){ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd/HH"); String redisKey; - //이전 24시간대에서 해당 게시글의 score를 찾기 - for (int i=0; i<24; i++){ - redisKey = now.minusHours(i).format(formatter); - if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { - Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, postId.toString()); - if (scoreOfBest != null) { //score가 존재하면 업데이트 - redisTemplate.opsForZSet().incrementScore(redisKey, postId.toString(), score); - return; + if (redisHealthChecker.isRedisAvailable()) { + //이전 24시간대에서 해당 게시글의 score를 찾기 + for (int i = 0; i < 24; i++) { + redisKey = now.minusHours(i).format(formatter); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { + Double scoreOfBest = redisTemplate.opsForZSet().score(redisKey, postId.toString()); + if (scoreOfBest != null) { //score가 존재하면 업데이트 + redisTemplate.opsForZSet().incrementScore(redisKey, postId.toString(), score); + updateBests(redisKey, postId.toString()); + return; + } } } + //score가 존재하지 않으면 현재 시간대로 새로 생성 + redisKey = now.format(formatter); + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); //하루가 지나면 필요없는 데이터 + redisTemplate.opsForZSet().add(redisKey, postId.toString(), score); + updateBests(redisKey, postId.toString()); + } + } + + /** + * 게시글에 점수가 반영 될 때마다 베스트 게시글을 관리하는 ZSet에서 업데이트 + * 베스트 게시글에서 최소 점수보다 같거나 크거나, 최소 점수가 없거나, 베스트 게시글이 3개 이하거나 할 때 베스트 게시글에 포함 + * + * 만약 포함 후 3개 초과라면 + * 새로 적용된 게시글의 점수와 최소 점수와 같으면 조회수로 판단 + * 상위 3개를 제외한 나머지 삭제 후 DB 저장 + */ + private void updateBests(String redisKey, String postId){ + Double score = redisTemplate.opsForZSet().score(redisKey, postId); + Set> minEntry = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, 0); + + if (minEntry!=null && !minEntry.isEmpty()){ + ZSetOperations.TypedTuple minTuple = minEntry.iterator().next(); //최소 score의 Tuple + Double min = minTuple.getScore(); + Long totalSize = redisTemplate.opsForZSet().size(BEST_KEY); + + //최소 점수가 없거나, 최소 점수보다 같거나 큰 값이거나, 총 베스트 게시글 개수가 3개 미만 이면 포함 + if (min == null || score >= min || totalSize < 3) + redisTemplate.opsForZSet().add(BEST_KEY, postId, score); + + totalSize = redisTemplate.opsForZSet().size(BEST_KEY); + if (totalSize > 3) { //3개가 넘어가면 + if (score.equals(min)) //만약 최소값끼리 같으면 조회수로 비교 + checkHits(postId, minTuple.getValue()); + else redisTemplate.opsForZSet().removeRange(BEST_KEY, 0, totalSize-4); //상위 3개를 제외하고 삭제 + } + + Set> members = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, - 1); + members.forEach(member -> saveBests(member.getValue(), member.getScore().intValue())); + } + + + } + + /** + * 베스트 게시글에 처음 적용된 게시글과 기존에 있던 최소 점수 게시글의 조회수 비교 + * @param postId 점수가 적용된 게시글 _id + * @param minPostId 베스트 게시글 중에서 가장 최소 점수를 가진 게시글 _id + */ + private void checkHits(String postId, String minPostId) { + if (hitsService.getHitsCount(new ObjectId(minPostId)) > hitsService.getHitsCount(new ObjectId(postId))) { + redisTemplate.opsForZSet().remove(BEST_KEY, postId); + } else { + redisTemplate.opsForZSet().remove(BEST_KEY, minPostId); + } + + } + + /** + * 만약 bestEntity에 없다면 저장 + */ + public void saveBests(String postId, int score) { + boolean existedPost = bestRepository.existsByPostId(new ObjectId(postId)); + if (!existedPost) { + bestRepository.save(BestEntity.builder() + .postId(new ObjectId(postId)) + .score(score) + .build()); + } + } + + /** + * 동기화 과정에서 기존에 있던 값을 지우고 새롭게 등록 + * 스케줄러를 통해 매 시마다 전 1시간 동안의 베스트 게시글을 등록 + */ + public void resetBests(Map posts) { + if (Boolean.TRUE.equals(redisTemplate.hasKey(BEST_KEY))) { + redisTemplate.opsForZSet().removeRange(BEST_KEY, 0 ,-1); } - //score가 존재하지 않으면 현재 시간대로 새로 생성 - redisKey = now.format(formatter); - redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); - redisTemplate.opsForZSet().add(redisKey, postId.toString(), score); + posts.forEach((key, value) -> redisTemplate.opsForZSet().add(BEST_KEY, key, value)); } } From f59980f921f0d1e10d7d9ecf75a5b6a41cdcacc3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 28 Mar 2025 02:25:33 +0900 Subject: [PATCH 0701/1002] =?UTF-8?q?refactor=20:=20=EB=B2=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=9D=B4=20=EC=84=A0?= =?UTF-8?q?=EC=A0=95=EB=90=9C=20=EC=8B=9C=EA=B0=84=EC=9D=84=20=EB=82=98?= =?UTF-8?q?=ED=83=80=EB=82=B8=20selectedAt=20=EC=B9=BC=EB=9F=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C,=20createdAt=EB=A7=8C=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/domain/best/BestEntity.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java index 08fd3012..d81986ba 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java @@ -22,18 +22,14 @@ public class BestEntity { private ObjectId postId; - private LocalDateTime createdAt; + private int score; @CreatedDate - private LocalDateTime selectedAt; - - private int score; + private LocalDateTime createdAt; @Builder - public BestEntity(ObjectId postId, LocalDateTime createdAt, LocalDateTime selectedAt, int score) { + public BestEntity(ObjectId postId, int score) { this.postId = postId; - this.createdAt = createdAt; - this.selectedAt = selectedAt; this.score = score; } } From c5358d9014aeca387799063232f1c5fe2a7988ba Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 28 Mar 2025 02:25:48 +0900 Subject: [PATCH 0702/1002] =?UTF-8?q?refactor=20:=20postId=EA=B0=80=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=EC=A7=80=EB=A7=8C=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/domain/best/BestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java index 967c35df..739a8afb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java @@ -4,5 +4,5 @@ import org.springframework.data.mongodb.repository.MongoRepository; public interface BestRepository extends MongoRepository { - BestEntity findByPostId(ObjectId postId); + boolean existsByPostId(ObjectId postId); } From b40c3615bfa02920d2eac04646a3a068dcea6f8e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 28 Mar 2025 02:26:30 +0900 Subject: [PATCH 0703/1002] =?UTF-8?q?refactor=20:=20BestEntity=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=98=EB=A9=B0=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?#187?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/service/PostService.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 88312432..816cd50b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -290,21 +290,10 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { } public List getTop3BestPosts() { - Map posts = redisBestService.getTopNPosts(3); + Map posts = redisBestService.getBests(); List bestPosts = posts.entrySet().stream() - .map(post -> { - BestEntity bestPost = bestRepository.findByPostId(new ObjectId(post.getKey())); - PostEntity postEntity = postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) - .orElseThrow(() -> new NotFoundException("해당 게시글을 찾을 수 없습니다.")); - if (bestPost == null) { - bestRepository.save(BestEntity.builder() - .postId(new ObjectId(post.getKey())) - .createdAt(postEntity.getCreatedAt()) - .score(post.getValue().intValue()) - .build()); - } - return postEntity; - } + .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) + .orElseThrow(() -> new NotFoundException("해당 게시글을 찾을 수 없습니다.")) ).toList(); log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); From b1bf9662414a7b28f3a8b3484dce065ccbf60f78 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 28 Mar 2025 02:27:09 +0900 Subject: [PATCH 0704/1002] =?UTF-8?q?refactor=20:=20Redis=EC=97=90=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=ED=95=98=EB=8A=94=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B0=90=EC=86=8C=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=88=98=EC=A0=95=20#18?= =?UTF-8?q?7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/RedisRecoverSyncScheduler.java | 8 +- .../infra/redis/scheduler/SyncScheduler.java | 134 +++--------------- 2 files changed, 18 insertions(+), 124 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java index b451d218..e9279bc4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/RedisRecoverSyncScheduler.java @@ -2,21 +2,19 @@ import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.exception.RedisUnavailableException; -import inu.codin.codin.infra.redis.service.RedisLikeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.Instant; import java.time.Duration; +import java.time.Instant; @Component @RequiredArgsConstructor @Slf4j public class RedisRecoverSyncScheduler { private final RedisHealthChecker redisHealthChecker; - private final RedisLikeService redisLikeService; private Instant lastRecoveryTime = Instant.MIN; // 마지막 복구 시간 @@ -27,9 +25,6 @@ public class RedisRecoverSyncScheduler { public void monitorRedisAndRecover() { try { redisHealthChecker.checkRedisStatus(); // Redis 상태 확인 - if (shouldRecoverRedis()) { - recoverRedisData(); // Redis 복구 - } } catch (RedisUnavailableException e) { log.warn("Redis 장애 감지: {}. MongoDB 우회 처리 중...", e.getMessage()); } @@ -54,7 +49,6 @@ private void recoverRedisData() { } try { log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 복구 작업 시작..."); - redisLikeService.recoverRedisFromDB(); lastRecoveryTime = Instant.now(); log.info("[Redis 복구 작업] MongoDB 데이터를 Redis 데이터 복구 완료."); } catch (Exception e) { diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 8301a051..4ecf52e0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -1,49 +1,22 @@ package inu.codin.codin.infra.redis.scheduler; -import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; -import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; -import inu.codin.codin.domain.like.entity.LikeEntity; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.repository.LikeRepository; -import inu.codin.codin.domain.post.domain.best.BestEntity; -import inu.codin.codin.domain.post.domain.best.BestRepository; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisLikeService; -import inu.codin.codin.infra.redis.service.RedisService; +import inu.codin.codin.infra.redis.service.RedisBestService; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; @Component @RequiredArgsConstructor @Slf4j public class SyncScheduler { - private final PostRepository postRepository; - private final CommentRepository commentRepository; - private final ReplyCommentRepository replyCommentRepository; - private final ReviewRepository reviewRepository; - - private final LikeRepository likeRepository; - private final BestRepository bestRepository; - - private final RedisService redisService; - private final RedisLikeService redisLikeService; + private final RedisBestService redisBestService; private final RedisHealthChecker redisHealthChecker; @Async @@ -53,96 +26,23 @@ public void syncLikes() { log.warn("Redis 비활성화 상태, 동기화 작업 중지"); return; } - log.info(" 동기화 작업 시작"); - syncEntityLikes("POST", postRepository); - syncEntityLikes("COMMENT", commentRepository); - syncEntityLikes("REPLY", replyCommentRepository); - syncEntityLikes("REVIEW", reviewRepository); - log.info(" 동기화 작업 완료"); +// log.info(" 동기화 작업 시작"); +// syncEntityLikes("POST", postRepository); +// syncEntityLikes("COMMENT", commentRepository); +// syncEntityLikes("REPLY", replyCommentRepository); +// syncEntityLikes("REVIEW", reviewRepository); +// log.info(" 동기화 작업 완료"); } - - private void syncEntityLikes(String entityType, MongoRepository repository) { - Set redisKeys = redisService.getKeys(entityType+ ":likes:*") - .stream().map(redisKey -> redisKey.replace(entityType+ ":likes:", "")) - .collect(Collectors.toSet()); - if (redisKeys.isEmpty()) return; - - LikeType likeType = LikeType.valueOf(entityType); - - // 좋아요 대상 _id 와 좋아요 entity 리스트 - List dbLikes = likeRepository.findByLikeTypeAndDeletedAtIsNull(likeType); - Map> dbLikeMap = dbLikes.stream() - .collect(Collectors.groupingBy(likeEntity -> likeEntity.getLikeTypeId().toString())); - - //DB에 있지만 Redis에 없는 좋아요 복구 - dbLikeMap.forEach((entityId, likes) -> { - if (!redisKeys.contains(entityId)) - likes.forEach(like -> redisLikeService.addLike(entityType, like.getLikeTypeId(), like.getUserId())); - }); - - //Redis에 있지만 DB에 없는 좋아요 삭제 - for (String likeTypeId : redisKeys) { - Set redisUsers = redisLikeService.getLikedUsers(entityType, likeTypeId); - Set dbUsers = dbLikeMap.getOrDefault(likeTypeId, List.of()).stream() - .map(like -> like.getUserId().toString()) - .collect(Collectors.toSet()); - - ObjectId entityTypeId = new ObjectId(likeTypeId); - - redisUsers.stream().filter(userId -> !dbUsers.contains(userId)) - .forEach(userId -> { - log.info("[Redis] 좋아요 삭제: UserID={}, LikeID={}", userId, likeTypeId); - redisLikeService.removeLike(entityType, entityTypeId, new ObjectId(userId)); - }); - - // likeCount 업데이트 - int likeCount = redisLikeService.getLikeCount(entityType, new ObjectId(likeTypeId)); - if (repository instanceof PostRepository postRepo) { - PostEntity post = postRepo.findByIdAndNotDeleted(entityTypeId).orElse(null); - if (post != null && post.getLikeCount() != likeCount) { - log.info("PostEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); - post.updateLikeCount(likeCount); - postRepo.save(post); - } - } else if (repository instanceof CommentRepository commentRepo) { - CommentEntity comment = commentRepo.findByIdAndNotDeleted(entityTypeId).orElse(null); - if (comment != null && comment.getLikeCount() != likeCount) { - log.info("CommentEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); - comment.updateLikeCount(likeCount); - commentRepo.save(comment); - } - } else if (repository instanceof ReplyCommentRepository replyRepo) { - ReplyCommentEntity reply = replyRepo.findByIdAndNotDeleted(entityTypeId).orElse(null); - if (reply != null && reply.getLikeCount() != likeCount) { - log.info("ReplyEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); - reply.updateLikeCount(likeCount); - replyRepo.save(reply); - } - } else if (repository instanceof ReviewRepository reviewRepo) { - ReviewEntity review = reviewRepo.findBy_idAndDeletedAtIsNull(entityTypeId).orElse(null); - if (review != null && review.getLikeCount() != likeCount) { - log.info("ReviewEntity 좋아요 수 업데이트: EntityID={}, Count={}", entityTypeId, likeCount); - review.updateLikeCount(likeCount); - reviewRepo.save(review); - } - } + @Async + @PostConstruct + @Scheduled(cron = "0 0 * * * ?") // 1시간 마다 실행 + public void getTop3BestPosts() { + if (redisHealthChecker.isRedisAvailable()) { + Map posts = redisBestService.delicatedBestsScheduler(3); + redisBestService.resetBests(posts); + posts.forEach((key, value) -> redisBestService.saveBests(key, value.intValue())); } } - @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 - public void getTop3BestPosts() { - Map posts = redisService.getTopNPosts(3); - posts.entrySet().stream() - .peek(post -> { - BestEntity bestPost = bestRepository.findByPostId(new ObjectId(post.getKey())); - if (bestPost == null) { - bestRepository.save(BestEntity.builder() - .postId(new ObjectId(post.getKey())) - .score(post.getValue().intValue()) - .build()); - } - } - ); - } } \ No newline at end of file From 03f287b2e17bf9d3840b0c0f897df9945f567b6a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 28 Mar 2025 02:40:28 +0900 Subject: [PATCH 0705/1002] =?UTF-8?q?refactor=20:=20Cache=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C,=20TTL=20=EC=B4=88=EA=B8=B0=ED=99=94=20#1?= =?UTF-8?q?87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/service/RedisHitsService.java | 7 +++++-- .../codin/codin/infra/redis/service/RedisLikeService.java | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java index fb3da69e..3f7f24ac 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisHitsService.java @@ -30,8 +30,8 @@ public void addHits(ObjectId postId){ redisTemplate.opsForValue().increment(redisKey); else { redisTemplate.opsForValue().set(redisKey, String.valueOf(1)); - redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); } + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); } /** @@ -41,8 +41,10 @@ public void addHits(ObjectId postId){ */ public Object getHitsCount(ObjectId postId){ String redisKey = HITS_KEY + postId.toString(); - if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))){ + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); return redisTemplate.opsForValue().get(redisKey); + } else return null; } @@ -53,6 +55,7 @@ public Object getHitsCount(ObjectId postId){ */ public void recoveryHits(ObjectId postId, int hits){ String redisKey = HITS_KEY + postId.toString(); + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); redisTemplate.opsForValue().set(redisKey, String.valueOf(hits)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java index e16638ec..9968b92a 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java @@ -28,8 +28,8 @@ public void addLike(String entityType, ObjectId entityId) { redisTemplate.opsForValue().increment(redisKey); else { redisTemplate.opsForValue().set(redisKey, String.valueOf(1)); - redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); } + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); } public void removeLike(String entityType, ObjectId entityId) { @@ -40,6 +40,7 @@ public void removeLike(String entityType, ObjectId entityId) { public Object getLikeCount(String entityType, ObjectId entityId) { String redisKey = entityType + LIKE_KEY + entityId; if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))){ + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); return redisTemplate.opsForValue().get(redisKey); } else return null; @@ -47,6 +48,7 @@ public Object getLikeCount(String entityType, ObjectId entityId) { public void recoveryLike(LikeType entityType, ObjectId entityId, int likeCount) { String redisKey = entityType + LIKE_KEY + entityId; + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); redisTemplate.opsForValue().set(redisKey, String.valueOf(likeCount)); } } From e00903e3aaa597f6218353de821df364263ba758 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 30 Mar 2025 15:52:40 +0900 Subject: [PATCH 0706/1002] =?UTF-8?q?fix=20:=20Best=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C,=20BEST=5FKEY?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EB=B9=88?= =?UTF-8?q?=20Map=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/service/RedisBestService.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 3ff83cb0..28d1b2f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -84,25 +84,27 @@ public Map delicatedBestsScheduler(int N) { */ public Map getBests(){ if (redisHealthChecker.isRedisAvailable()){ - Set> members = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, - 1); - if (members!=null && !members.isEmpty()) - return members.stream() - .sorted((e1, e2) -> { - //1. 점수 기준 내림차순 - int scoreComparison = Double.compare(Double.parseDouble(String.valueOf(e2.getScore())), Double.parseDouble(String.valueOf(e1.getScore()))); - - // 2. 점수가 동일하면 조회수 기준 내림차순 정렬 - return scoreComparison != 0 ? scoreComparison : - Integer.compare(hitsService.getHitsCount(new ObjectId(e2.getValue())), - hitsService.getHitsCount(new ObjectId(e1.getValue()))); - }).collect(Collectors.toMap( - ZSetOperations.TypedTuple::getValue, - ZSetOperations.TypedTuple::getScore, - (existing, replacement) -> existing, - LinkedHashMap::new - )); + if (Boolean.TRUE.equals(redisTemplate.hasKey(BEST_KEY))){ + Set> members = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, - 1); + if (members!=null && !members.isEmpty()) + return members.stream() + .sorted((e1, e2) -> { + //1. 점수 기준 내림차순 + int scoreComparison = Double.compare(Double.parseDouble(String.valueOf(e2.getScore())), Double.parseDouble(String.valueOf(e1.getScore()))); + + // 2. 점수가 동일하면 조회수 기준 내림차순 정렬 + return scoreComparison != 0 ? scoreComparison : + Integer.compare(hitsService.getHitsCount(new ObjectId(e2.getValue())), + hitsService.getHitsCount(new ObjectId(e1.getValue()))); + }).collect(Collectors.toMap( + ZSetOperations.TypedTuple::getValue, + ZSetOperations.TypedTuple::getScore, + (existing, replacement) -> existing, + LinkedHashMap::new + )); + } else log.warn("[getBests] Best 게시글이 없습니다."); } - return null; + return new HashMap<>(); } /** * Cache에 저장된 24시간 데이터 중 해당 게시글의 score가 존재하면 점수 반영 From 6f6a45c5e309f9adbb7cf325ff11ef363e7c2a71 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 30 Mar 2025 16:03:17 +0900 Subject: [PATCH 0707/1002] =?UTF-8?q?fix=20:=20BEST=5FKEY=EA=B0=80=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/infra/redis/service/RedisBestService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 28d1b2f3..e291fe36 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -155,8 +155,8 @@ private void updateBests(String redisKey, String postId){ Double min = minTuple.getScore(); Long totalSize = redisTemplate.opsForZSet().size(BEST_KEY); - //최소 점수가 없거나, 최소 점수보다 같거나 큰 값이거나, 총 베스트 게시글 개수가 3개 미만 이면 포함 - if (min == null || score >= min || totalSize < 3) + //최소 점수보다 같거나 큰 값이거나, 총 베스트 게시글 개수가 3개 미만 이면 포함 + if (score >= min || totalSize < 3) redisTemplate.opsForZSet().add(BEST_KEY, postId, score); totalSize = redisTemplate.opsForZSet().size(BEST_KEY); @@ -168,9 +168,8 @@ private void updateBests(String redisKey, String postId){ Set> members = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, - 1); members.forEach(member -> saveBests(member.getValue(), member.getScore().intValue())); - } - - + } else + redisTemplate.opsForZSet().add(BEST_KEY, postId, score); } /** From bc4fc8129ba35a9bfa71222c9142c06dc8cb2288 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 30 Mar 2025 16:16:11 +0900 Subject: [PATCH 0708/1002] =?UTF-8?q?fix=20:=20DB=EC=9D=98=20ReviewEntity?= =?UTF-8?q?=EB=93=A4=EC=9D=98=20=EB=B3=84=EC=A0=90=20=EC=97=B0=EC=82=B0=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20LectureEntity=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20(=EC=B4=88=EA=B8=B0=201=EB=B2=88=EB=A7=8C?= =?UTF-8?q?=20=EC=A7=84=ED=96=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/infra/redis/scheduler/SyncScheduler.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java index 4ecf52e0..17c1a8fc 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/scheduler/SyncScheduler.java @@ -1,5 +1,8 @@ package inu.codin.codin.infra.redis.scheduler; +import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; +import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisBestService; import jakarta.annotation.PostConstruct; @@ -9,6 +12,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Map; @Component @@ -18,6 +22,8 @@ public class SyncScheduler { private final RedisBestService redisBestService; private final RedisHealthChecker redisHealthChecker; + private final ReviewRepository reviewRepository; + private final ReviewService reviewService; @Async @Scheduled(fixedRate = 43200000) // 12시간 마다 실행 @@ -44,5 +50,13 @@ public void getTop3BestPosts() { } } + @PostConstruct + public void recoverReviews(){ + List reviewEntityList = reviewRepository.findAll(); + reviewEntityList.forEach( + review -> reviewService.updateRating(review.getLectureId()) + ); + } + } \ No newline at end of file From efdc836e124d32d13abf292bb49a222fc69f089b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 30 Mar 2025 16:31:11 +0900 Subject: [PATCH 0709/1002] =?UTF-8?q?fix=20:=20=EA=B0=95=EC=9D=98=EC=9D=98?= =?UTF-8?q?=20Emotion=20=EA=B0=9D=EC=B2=B4=EA=B0=80=20null=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/lecture/dto/Emotion.java | 7 +++++++ .../codin/codin/domain/lecture/entity/LectureEntity.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java index 2fd8a3b6..577a6568 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/Emotion.java @@ -10,6 +10,13 @@ public class Emotion { private double ok; private double best; + + public Emotion() { + this.hard = 0; + this.ok = 0; + this.best = 0; + } + @Builder public Emotion(double hard, double ok, double best) { this.hard = hard; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index 6edb595a..7d88c255 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -24,7 +24,7 @@ public class LectureEntity { private double starRating; private int participants; - private Emotion emotion; + private Emotion emotion = new Emotion(); public void updateReviewRating(double starRating, int participants, Emotion emotion){ this.starRating = starRating; From 6baaeec7987fcf5b53f5577e44b53bf46d604e28 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 00:55:33 +0900 Subject: [PATCH 0710/1002] =?UTF-8?q?fix=20:=20=ED=97=88=EC=9A=A9=EB=90=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20yml=20=EA=B4=80=EB=A6=AC,=20@i?= =?UTF-8?q?nu.ac.kr=20=EC=A0=91=EA=B7=BC=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/dto/AccessEmailProperties.java | 15 ++++++++ .../service/CustomOAuth2UserService.java | 34 ++++++++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/dto/AccessEmailProperties.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/AccessEmailProperties.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/AccessEmailProperties.java new file mode 100644 index 00000000..167abd08 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/AccessEmailProperties.java @@ -0,0 +1,15 @@ +package inu.codin.codin.common.security.dto; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "spring.mail.access") +public class AccessEmailProperties { + private List domain; +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java index 4aaeba51..1b09a451 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java @@ -1,6 +1,7 @@ package inu.codin.codin.common.security.service; +import inu.codin.codin.common.security.dto.AccessEmailProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -9,6 +10,7 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; +import org.springframework.util.AntPathMatcher; /** * OAuth2 로그인 후 사용자 정보를 가로채고 이메일을 검증하는 로직 @@ -18,6 +20,8 @@ @Slf4j public class CustomOAuth2UserService extends DefaultOAuth2UserService { + private final AccessEmailProperties accessEmailProperties; + private final AntPathMatcher pathMatcher = new AntPathMatcher(); @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { @@ -27,17 +31,29 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String email = oAuth2User.getAttribute("email"); log.info("email: {}", email); -// if (email.equals("inu.codin@gmail.com")) return oAuth2User; + if (email == null) { + OAuth2Error oauth2Error = new OAuth2Error( + "email_not_found", + "이메일 정보를 찾을 수 없습니다.", + null + ); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); + } + + if (accessEmailProperties.getDomain().stream().anyMatch(url -> pathMatcher.match(url, email))) { + log.info("접근 허용 email : {}", email); + return oAuth2User; + } // Only Allow @inu.ac.kr -// if (email == null || !email.trim().endsWith("@inu.ac.kr")) { -// OAuth2Error oauth2Error = new OAuth2Error( -// "invalid_email_domain", -// "허용되지 않은 이메일 도메인입니다. @inu.ac.kr 이어야합니다", -// null -// ); -// throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); -// } + if (!email.trim().endsWith("@inu.ac.kr")) { + OAuth2Error oauth2Error = new OAuth2Error( + "invalid_email_domain", + "허용되지 않은 이메일 도메인입니다. @inu.ac.kr 이어야합니다", + null + ); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.getDescription()); + } return oAuth2User; } From 719832388662a88c33badf4bb208e6bc735c1723 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 00:55:56 +0900 Subject: [PATCH 0711/1002] =?UTF-8?q?fix=20:=20RedisSystemException,=20OAu?= =?UTF-8?q?th2AuthenticationException=20=EC=97=90=EB=9F=AC=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=9F=AC=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index a3ae2154..2845ff24 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -3,13 +3,17 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice +@Slf4j public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @@ -35,14 +39,27 @@ protected ResponseEntity handleJwtException(JwtException e) { } @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException e) { + protected ResponseEntity handleValidationExceptions(MethodArgumentNotValidException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST.value())); } @ExceptionHandler(ChatRoomExistedException.class) - public ResponseEntity handleChatRoomExistedException(ChatRoomExistedException e) { + protected ResponseEntity handleChatRoomExistedException(ChatRoomExistedException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ExceptionResponse(e.getMessage() +"/"+ e.getChatRoomId(), e.getErrorCode())); } + @ExceptionHandler(RedisSystemException.class) + public ResponseEntity handleRedisSystemException(RedisSystemException e){ + log.error(e.getMessage(), e.getStackTrace()[0]); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value())); + } + + @ExceptionHandler(OAuth2AuthenticationException.class) + protected ResponseEntity handleOAuth2AuthenticationException(OAuth2AuthenticationException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.value())); + } + } From 5205b36c27e1127dbb9f67fdba46113268c7b7ae Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 00:57:07 +0900 Subject: [PATCH 0712/1002] Update resources-main --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 7c46ef20..d6f976ae 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 7c46ef207fa09405e7728b058642b151c0aa1261 +Subproject commit d6f976ae37aac453ab5612c86a771f48fefaed56 From f6b4bdd59eb54a4d68c71b3088fe99707141a684 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 21:56:33 +0900 Subject: [PATCH 0713/1002] =?UTF-8?q?perf=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20Fail=20=EC=8B=9C=20login=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20redirect=20=EB=B0=8F=20error=20Param=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/util/OAuth2LoginFailureHandler.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 7712da6a..b236112d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -6,10 +6,12 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import java.io.IOException; @@ -18,8 +20,11 @@ @Component @Slf4j @RequiredArgsConstructor -public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { - private final ObjectMapper objectMapper; +public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Value("${server.domain}") + private String BASEURL; + private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void onAuthenticationFailure(HttpServletRequest request, @@ -29,10 +34,12 @@ public void onAuthenticationFailure(HttpServletRequest request, response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); String errorMessage = "인증 실패."; + String errorCode = null; if (exception instanceof OAuth2AuthenticationException oauth2Ex) { OAuth2Error error = oauth2Ex.getError(); if (error != null && error.getDescription() != null) { errorMessage = error.getDescription(); + errorCode = error.getErrorCode(); } } @@ -41,5 +48,10 @@ public void onAuthenticationFailure(HttpServletRequest request, log.error("[OAuth2LoginFailureHandler] {}", responseBody); response.getWriter().write(responseBody); + if (errorCode == null) + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login?"); + else + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login?error="+errorCode); + } } From dc7d5bcc4858f541377b30eca2aa44d8e330fa9c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 22:55:56 +0900 Subject: [PATCH 0714/1002] =?UTF-8?q?fix=20:=20@inu.ac.kr=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=9D=B4=20=EC=95=84=EB=8B=90=20=EC=8B=9C=20?= =?UTF-8?q?Cookie=20=ED=86=A0=ED=81=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/OAuth2LoginFailureHandler.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index b236112d..976e510b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.util.CookieUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -10,21 +12,23 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import java.io.IOException; -import java.io.PrintWriter; @Component @Slf4j @RequiredArgsConstructor public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { + private final ObjectMapper objectMapper = new ObjectMapper(); + + private final JwtService jwtService; @Value("${server.domain}") private String BASEURL; - private final ObjectMapper objectMapper = new ObjectMapper(); + public final static String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + @Override public void onAuthenticationFailure(HttpServletRequest request, @@ -47,11 +51,18 @@ public void onAuthenticationFailure(HttpServletRequest request, String responseBody = objectMapper.writeValueAsString(errorResponse); log.error("[OAuth2LoginFailureHandler] {}", responseBody); + + removeAllToken(request, response); response.getWriter().write(responseBody); if (errorCode == null) - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login?"); + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); else getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login?error="+errorCode); } + + private void removeAllToken(HttpServletRequest request, HttpServletResponse response) { + CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + jwtService.deleteToken(response); + } } From 946f21501d7ebfc8b3c4f7a289673faa6c9e1037 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 23:19:35 +0900 Subject: [PATCH 0715/1002] =?UTF-8?q?fix=20:=20@inu.ac.kr=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=9D=B4=20=EC=95=84=EB=8B=90=20=EC=8B=9C=20?= =?UTF-8?q?Session=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/service/JwtService.java | 8 +++++--- .../common/security/util/OAuth2LoginFailureHandler.java | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 01f5d491..c660b238 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -123,9 +123,11 @@ public void deleteToken(HttpServletResponse response) { // 어차피 JwtAuthenticationFilter 단에서 토큰을 검증하여 인증을 처리하므로 // SecurityContext에 Authentication 객체가 없는 경우는 없다. var authentication = SecurityContextHolder.getContext().getAuthentication(); - redisStorageService.deleteRefreshToken(authentication.getName()); - deleteCookie(response); - log.info("[deleteToken] Refresh Token 삭제 완료"); + if (authentication != null && authentication.getName()!=null){ + redisStorageService.deleteRefreshToken(authentication.getName()); + deleteCookie(response); + log.info("[deleteToken] Refresh Token 삭제 완료"); + } } private void deleteCookie(HttpServletResponse response) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 976e510b..f5bff242 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -62,6 +62,7 @@ public void onAuthenticationFailure(HttpServletRequest request, } private void removeAllToken(HttpServletRequest request, HttpServletResponse response) { + request.getSession().invalidate(); CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); jwtService.deleteToken(response); } From 2928cc2c02cde9953c9bd50073c0f33c726c8dda Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 23:37:07 +0900 Subject: [PATCH 0716/1002] fix : redirect to Google Logout url --- .../common/security/util/OAuth2LoginFailureHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index f5bff242..16fc8a66 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -56,13 +56,13 @@ public void onAuthenticationFailure(HttpServletRequest request, response.getWriter().write(responseBody); if (errorCode == null) getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); - else - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login?error="+errorCode); - + else { + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login?error=" + errorCode); + getRedirectStrategy().sendRedirect(request, response, "https://accounts.google.com/Logout"); + } } private void removeAllToken(HttpServletRequest request, HttpServletResponse response) { - request.getSession().invalidate(); CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); jwtService.deleteToken(response); } From d28ed0d4a25261bc73189239dd8af6ed54001be1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 23:55:50 +0900 Subject: [PATCH 0717/1002] fix : redirect to Google Logout url --- .../codin/common/security/util/OAuth2LoginFailureHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 16fc8a66..85c3b006 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -53,12 +53,14 @@ public void onAuthenticationFailure(HttpServletRequest request, log.error("[OAuth2LoginFailureHandler] {}", responseBody); removeAllToken(request, response); + response.getWriter().write(responseBody); if (errorCode == null) getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); else { getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login?error=" + errorCode); - getRedirectStrategy().sendRedirect(request, response, "https://accounts.google.com/Logout"); + String logoutUrl = "https://accounts.google.com/Logout"; + response.sendRedirect(logoutUrl); } } From b1faa177b9c2a27b75af20544ac6dc53f01f4c2b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 31 Mar 2025 23:56:35 +0900 Subject: [PATCH 0718/1002] =?UTF-8?q?fix=20:=20google=20authorization-uri?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index d6f976ae..2c767df2 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit d6f976ae37aac453ab5612c86a771f48fefaed56 +Subproject commit 2c767df21624e2270fbcaf3b821732a0f90db9fd From 7cd17f561cdc3d6f6c210143f9252254996a6363 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 00:18:32 +0900 Subject: [PATCH 0719/1002] fix : Google Logout test --- .../util/OAuth2LoginFailureHandler.java | 58 ++++++++++++++----- codin-core/src/main/resources | 2 +- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 75fa3a89..85c3b006 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -1,20 +1,35 @@ package inu.codin.codin.common.security.util; +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.util.CookieUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import java.io.IOException; -import java.io.PrintWriter; @Component @Slf4j -public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { +@RequiredArgsConstructor +public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { + private final ObjectMapper objectMapper = new ObjectMapper(); + + private final JwtService jwtService; + + @Value("${server.domain}") + private String BASEURL; + public final static String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, @@ -22,18 +37,35 @@ public void onAuthenticationFailure(HttpServletRequest request, response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - String errorMessage = null; - if (exception instanceof OAuth2AuthenticationException) { - OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); - errorMessage = error.getDescription(); + String errorMessage = "인증 실패."; + String errorCode = null; + if (exception instanceof OAuth2AuthenticationException oauth2Ex) { + OAuth2Error error = oauth2Ex.getError(); + if (error != null && error.getDescription() != null) { + errorMessage = error.getDescription(); + errorCode = error.getErrorCode(); + } } - if (errorMessage == null || errorMessage.isEmpty()) { - errorMessage = "인증 실패"; // 기본 오류 메시지 + + ExceptionResponse errorResponse = new ExceptionResponse(errorMessage, HttpServletResponse.SC_UNAUTHORIZED); + String responseBody = objectMapper.writeValueAsString(errorResponse); + + log.error("[OAuth2LoginFailureHandler] {}", responseBody); + + removeAllToken(request, response); + + response.getWriter().write(responseBody); + if (errorCode == null) + getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); + else { + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login?error=" + errorCode); + String logoutUrl = "https://accounts.google.com/Logout"; + response.sendRedirect(logoutUrl); } + } - PrintWriter writer = response.getWriter(); - writer.write("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); - log.error("{\"code\":401, \"message\":\"" + errorMessage + "\"}"); - writer.flush(); + private void removeAllToken(HttpServletRequest request, HttpServletResponse response) { + CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + jwtService.deleteToken(response); } } diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 7c46ef20..e1dc21cc 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 7c46ef207fa09405e7728b058642b151c0aa1261 +Subproject commit e1dc21cc33b305a37f6227a7f93d034c36b076c4 From 0435db79461a88c95169dc69b664e8ec5eeb9c6d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 00:35:31 +0900 Subject: [PATCH 0720/1002] fix : Google Logout test --- .../security/util/OAuth2LoginFailureHandler.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 85c3b006..1cc2a2af 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -54,14 +54,10 @@ public void onAuthenticationFailure(HttpServletRequest request, removeAllToken(request, response); - response.getWriter().write(responseBody); - if (errorCode == null) - getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); - else { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login?error=" + errorCode); - String logoutUrl = "https://accounts.google.com/Logout"; - response.sendRedirect(logoutUrl); - } + String logoutUrl = "https://accounts.google.com/Logout"; + response.sendRedirect(logoutUrl); + + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login" + (errorCode != null ? "?error=" + errorCode : "")); } private void removeAllToken(HttpServletRequest request, HttpServletResponse response) { From 3b67d7b30239129a3627fce3eb9705161b937813 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 00:35:45 +0900 Subject: [PATCH 0721/1002] fix : Google Logout test --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 2c767df2..1f58640a 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 2c767df21624e2270fbcaf3b821732a0f90db9fd +Subproject commit 1f58640ab3cf396ac7891f382b3390be950beaa7 From 66f5fc4026bf76c5b271b12117bd8af6e2367959 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 01:02:06 +0900 Subject: [PATCH 0722/1002] fix : Google Logout test --- .../codin/common/security/util/OAuth2LoginFailureHandler.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 1cc2a2af..7c84796e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -54,9 +54,6 @@ public void onAuthenticationFailure(HttpServletRequest request, removeAllToken(request, response); - String logoutUrl = "https://accounts.google.com/Logout"; - response.sendRedirect(logoutUrl); - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login" + (errorCode != null ? "?error=" + errorCode : "")); } From dc230e624753e69efb3ae483b2ca57d65fee96ff Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 01:07:41 +0900 Subject: [PATCH 0723/1002] fix : Google Logout test --- .../util/OAuth2LoginFailureHandler.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 85c3b006..eb376e18 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -9,11 +9,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; import java.io.IOException; @@ -21,6 +23,7 @@ @Slf4j @RequiredArgsConstructor public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { + private final RestTemplate restTemplate = new RestTemplate(); private final ObjectMapper objectMapper = new ObjectMapper(); private final JwtService jwtService; @@ -52,15 +55,27 @@ public void onAuthenticationFailure(HttpServletRequest request, log.error("[OAuth2LoginFailureHandler] {}", responseBody); - removeAllToken(request, response); + // 🔹 Access Token 가져오기 (요청 파라미터에서 추출) + String accessToken = request.getParameter("access_token"); + + if (accessToken != null && !accessToken.isEmpty()) { + String revokeUrl = "https://accounts.google.com/o/oauth2/revoke?token=" + accessToken; + try { + restTemplate.getForObject(revokeUrl, String.class); + log.info("[OAuth2LoginFailureHandler] Google Access Token revoked successfully."); + } catch (Exception e) { + log.error("[OAuth2LoginFailureHandler] Failed to revoke Google Access Token: {}", e.getMessage()); + } + } else { + log.warn("[OAuth2LoginFailureHandler] No access token found in request."); + } + +// removeAllToken(request, response); - response.getWriter().write(responseBody); if (errorCode == null) getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); else { getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login?error=" + errorCode); - String logoutUrl = "https://accounts.google.com/Logout"; - response.sendRedirect(logoutUrl); } } From a298a0362c4262670cba7660bcb86e4354a93e55 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 01:08:06 +0900 Subject: [PATCH 0724/1002] fix : Google Logout test --- .../codin/common/security/util/OAuth2LoginFailureHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 7c84796e..d1518dea 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -52,7 +52,7 @@ public void onAuthenticationFailure(HttpServletRequest request, log.error("[OAuth2LoginFailureHandler] {}", responseBody); - removeAllToken(request, response); +// removeAllToken(request, response); getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login" + (errorCode != null ? "?error=" + errorCode : "")); } From 52a869ef61adcdda1fef8a92fb2563e6723b075f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 02:11:13 +0900 Subject: [PATCH 0725/1002] =?UTF-8?q?fix=20:=20Google=20Access=5FToken=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EC=A0=80=EC=9E=A5=20=ED=9B=84=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 7 ++++++- .../service/CustomOAuth2UserService.java | 21 ++++++++++++++++--- .../util/OAuth2LoginFailureHandler.java | 10 ++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index b012256f..d4e5ff1a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -32,7 +32,6 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; @@ -40,6 +39,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.context.request.RequestContextListener; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -156,6 +156,11 @@ public AuthenticationManager authenticationManager(HttpSecurity http) throws Exc return authenticationManagerBuilder.build(); } + @Bean + public RequestContextListener requestContextListener() { + return new RequestContextListener(); + } + @Bean public RoleHierarchy roleHierarchy() { return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_MANAGER > ROLE_USER"); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java index 1b09a451..051558d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.security.dto.AccessEmailProperties; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -11,10 +12,9 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.util.AntPathMatcher; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; -/** - * OAuth2 로그인 후 사용자 정보를 가로채고 이메일을 검증하는 로직 - */ @Service @RequiredArgsConstructor @Slf4j @@ -23,15 +23,21 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final AccessEmailProperties accessEmailProperties; private final AntPathMatcher pathMatcher = new AntPathMatcher(); + private final String OAUTH2_ACCESS_TOKEN = "oauth2_access_token"; + @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); + // 요청에서 HttpSession 가져오기 + HttpSession httpSession = getSession(); + // Get User Email String email = oAuth2User.getAttribute("email"); log.info("email: {}", email); if (email == null) { + httpSession.setAttribute(OAUTH2_ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue()); OAuth2Error oauth2Error = new OAuth2Error( "email_not_found", "이메일 정보를 찾을 수 없습니다.", @@ -47,6 +53,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic // Only Allow @inu.ac.kr if (!email.trim().endsWith("@inu.ac.kr")) { + httpSession.setAttribute(OAUTH2_ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue()); OAuth2Error oauth2Error = new OAuth2Error( "invalid_email_domain", "허용되지 않은 이메일 도메인입니다. @inu.ac.kr 이어야합니다", @@ -57,4 +64,12 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic return oAuth2User; } + + public HttpSession getSession() { + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attr == null) { + throw new IllegalStateException("No thread-bound request found. RequestContextListener 또는 RequestContextFilter를 설정하세요."); + } + return attr.getRequest().getSession(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index eb376e18..e4f35ae2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -6,10 +6,10 @@ import inu.codin.codin.common.util.CookieUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -27,12 +27,12 @@ public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHan private final ObjectMapper objectMapper = new ObjectMapper(); private final JwtService jwtService; + private final HttpSession httpSession; @Value("${server.domain}") private String BASEURL; public final static String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; - - + private final String OAUTH2_ACCESS_TOKEN = "oauth2_access_token"; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, @@ -56,7 +56,7 @@ public void onAuthenticationFailure(HttpServletRequest request, log.error("[OAuth2LoginFailureHandler] {}", responseBody); // 🔹 Access Token 가져오기 (요청 파라미터에서 추출) - String accessToken = request.getParameter("access_token"); + String accessToken = (String) httpSession.getAttribute(OAUTH2_ACCESS_TOKEN); if (accessToken != null && !accessToken.isEmpty()) { String revokeUrl = "https://accounts.google.com/o/oauth2/revoke?token=" + accessToken; @@ -70,7 +70,7 @@ public void onAuthenticationFailure(HttpServletRequest request, log.warn("[OAuth2LoginFailureHandler] No access token found in request."); } -// removeAllToken(request, response); + removeAllToken(request, response); if (errorCode == null) getRedirectStrategy().sendRedirect(request, response, BASEURL+"/login"); From 528b728a604c07c67e1bcc9f655946c93f784461 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 02:21:26 +0900 Subject: [PATCH 0726/1002] =?UTF-8?q?fix=20:=20Google=20Access=5FToken=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EC=A0=80=EC=9E=A5=20=ED=9B=84=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index e1dc21cc..1f58640a 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit e1dc21cc33b305a37f6227a7f93d034c36b076c4 +Subproject commit 1f58640ab3cf396ac7891f382b3390be950beaa7 From 18325fa4be2270c78151dd35c926377f5c4fffd4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 02:52:15 +0900 Subject: [PATCH 0727/1002] =?UTF-8?q?fix=20:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20Participants=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lecture/domain/review/repository/ReviewRepository.java | 2 +- .../domain/lecture/domain/review/service/ReviewService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java index 2e929146..528d1c75 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/repository/ReviewRepository.java @@ -32,7 +32,7 @@ public interface ReviewRepository extends MongoRepository getAvgRatingByLectureId(ObjectId lectureId, PageRequest pageRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index 6d93cd97..e4dd991d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -66,7 +66,7 @@ public void updateRating(ObjectId lectureId){ .orElseThrow(() -> new NotFoundException("강의 정보를 찾을 수 없습니다.")); double starRating = reviewRepository.getAvgRatingByLectureId(lectureId); Emotion emotion = reviewRepository.getEmotionsCountByRanges(lectureId).changeToPercentage(); - int participants = reviewRepository.countAllByLectureIdAndDeletedAtIsNotNull(lectureId); + int participants = reviewRepository.countByLectureId(lectureId); lectureEntity.updateReviewRating(starRating, participants, emotion); lectureRepository.save(lectureEntity); } From 061e790d02e2f2ef49aafafc2c77e7cd8be27748 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 11:39:50 +0900 Subject: [PATCH 0728/1002] =?UTF-8?q?fix=20:=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=EB=B0=98=ED=99=98=20=EC=8B=9C=20=EC=9E=90=EC=8B=A0=EC=9D=98?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=9D=B8=EC=A7=80=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=ED=95=98=EB=8A=94=20isMine=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/dto/response/PostDetailResponseDTO.java | 5 +++-- .../inu/codin/codin/domain/post/service/PostService.java | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index f03413f5..34647c2d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.report.dto.ReportInfo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -91,11 +90,13 @@ public PostDetailResponseDTO(String userId, String _id, String title, String con public static class UserInfo { private final boolean isLike; private final boolean isScrap; + private final boolean isMine; @Builder - public UserInfo(boolean isLike, boolean isScrap) { + public UserInfo(boolean isLike, boolean isScrap, boolean isMine) { this.isLike = isLike; this.isScrap = isScrap; + this.isMine = isMine; } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 816cd50b..3f02ae3c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -168,7 +168,7 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfo userInfo = getUserInfoAboutPost(userId, post.get_id()); + UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); // 투표 게시물 처리 if (post.getPostCategory() == PostCategory.POLL) { @@ -272,10 +272,11 @@ public void deletePostImage(String postId, String imageUrl) { } } - public UserInfo getUserInfoAboutPost(ObjectId userId, ObjectId postId){ + public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ return UserInfo.builder() - .isLike(likeService.isLiked(LikeType.POST, postId, userId)) - .isScrap(scrapService.isPostScraped(postId, userId)) + .isLike(likeService.isLiked(LikeType.POST, postId, currentUserId)) + .isScrap(scrapService.isPostScraped(postId, currentUserId)) + .isMine(postUserId.equals(currentUserId)) .build(); } From 497b7bc5a04a04eb8181c10f3ffd9694aa205ddc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Apr 2025 19:26:00 +0900 Subject: [PATCH 0729/1002] =?UTF-8?q?fix=20:=20=EB=B2=A0=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20Scheduler=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=EC=97=90=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EB=90=9C=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=20=ED=99=95=EC=9D=B8=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/repository/PostRepository.java | 2 ++ .../domain/post/service/PostService.java | 8 +++++-- .../infra/redis/service/RedisBestService.java | 21 +++++++++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 4bc37088..0c3b5003 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -38,4 +38,6 @@ public interface PostRepository extends MongoRepository { + "{ 'title': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } } ] }") Page findAllByKeywordAndDeletedAtIsNull(String keyword, List blockedUsersId, PageRequest pageRequest); + + boolean existsBy_idAndDeletedAtIsNull(ObjectId id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 3f02ae3c..3d129c9c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -294,8 +294,12 @@ public List getTop3BestPosts() { Map posts = redisBestService.getBests(); List bestPosts = posts.entrySet().stream() .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) - .orElseThrow(() -> new NotFoundException("해당 게시글을 찾을 수 없습니다.")) - ).toList(); + .orElseGet(() -> { + redisBestService.deleteBest(post.getKey()); + return null; + })) + .filter(Objects::nonNull) // null 값 제거 + .toList(); log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index e291fe36..51cf7743 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -33,10 +34,11 @@ public class RedisBestService { private final HitsService hitsService; private final BestRepository bestRepository; + private final PostRepository postRepository; private final String BEST_KEY = "post:top3"; /** - * best 게시글을 반환하는 순간으로부터 24시간 동안의 가장 score가 높은 게시글 N개를 반환 + * 이전 1시간 동안의 가장 score가 높은 게시글 N개를 반환, DB 조회가 안된다면 삭제 * @param N 순위 * @return N개의 Key : postId, Value : score 의 score 기준 내림차순 Map */ @@ -54,6 +56,11 @@ public Map delicatedBestsScheduler(int N) { for (ZSetOperations.TypedTuple member : members) { String postId = member.getValue(); Double score = member.getScore(); + if (!postRepository.existsBy_idAndDeletedAtIsNull(new ObjectId(postId))){ + redisTemplate.opsForZSet().remove(redisKey, postId); + deleteBest(postId); + break; + } result.put(postId, score); } } @@ -140,7 +147,7 @@ public void applyBestScore(int score, ObjectId postId){ /** * 게시글에 점수가 반영 될 때마다 베스트 게시글을 관리하는 ZSet에서 업데이트 - * 베스트 게시글에서 최소 점수보다 같거나 크거나, 최소 점수가 없거나, 베스트 게시글이 3개 이하거나 할 때 베스트 게시글에 포함 + * 베스트 게시글에서 최소 점수보다 같거나 크거나, 베스트 게시글이 3개 이하거나 할 때 베스트 게시글에 포함 * * 만약 포함 후 3개 초과라면 * 새로 적용된 게시글의 점수와 최소 점수와 같으면 조회수로 판단 @@ -209,4 +216,14 @@ public void resetBests(Map posts) { } posts.forEach((key, value) -> redisTemplate.opsForZSet().add(BEST_KEY, key, value)); } + + /** + * 만약 post:top3에 포함된 게시글이 삭제되었다면 삭제 + * @param postId + */ + public void deleteBest(String postId){ + if (Boolean.TRUE.equals(redisTemplate.hasKey(BEST_KEY))){ + redisTemplate.opsForZSet().remove(BEST_KEY, postId); + } + } } From a1b892a498f48d15dca1b1f88ba3bb15cfdb9fb2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 00:28:57 +0900 Subject: [PATCH 0730/1002] =?UTF-8?q?chore=20:=20ADMIN=EC=97=90=EA=B2=8C?= =?UTF-8?q?=EB=A7=8C=20Swagger=20=EC=A0=91=EA=B7=BC=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=20=EB=B6=80=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/config/SecurityConfig.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index d4e5ff1a..75888707 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -215,6 +215,10 @@ public CorsConfigurationSource corsConfigurationSource() { // Admin 권한 URL private static final String[] ADMIN_AUTH_PATHS = { "/v3/api/test4", + "/swagger-ui/**", + "/v3/api-docs/**", + "/v3/api-docs", + "/swagger-resources/**" }; // Manager 권한 URL From 86b08ef3ca7ed055ecc7057de3925bc92090dd91 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 00:32:23 +0900 Subject: [PATCH 0731/1002] =?UTF-8?q?chore=20:=20ADMIN=EC=97=90=EA=B2=8C?= =?UTF-8?q?=EB=A7=8C=20Swagger=20=EC=A0=91=EA=B7=BC=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=20=EB=B6=80=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 1f58640a..51c30539 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 1f58640ab3cf396ac7891f382b3390be950beaa7 +Subproject commit 51c30539c96620229babf5d98ac909a989a214e6 From a7c805952fe005dc22447a341a70d0dbde904df9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 21:05:44 +0900 Subject: [PATCH 0732/1002] =?UTF-8?q?chore=20:=20localhost=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/AbstractAuthService.java | 16 +++----------- .../security/service/AppleAuthService.java | 5 +++-- .../security/service/AuthCommonService.java | 6 +++--- .../security/service/GoogleAuthService.java | 6 +++--- .../common/security/service/JwtService.java | 21 ++++++++++++------- 5 files changed, 25 insertions(+), 29 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java index d76dab54..559a9740 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java @@ -1,13 +1,8 @@ package inu.codin.codin.common.security.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,12 +10,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; @RequiredArgsConstructor @Slf4j @@ -29,6 +18,7 @@ public abstract class AbstractAuthService { protected final S3Service s3Service; protected final JwtService jwtService; protected final UserDetailsService userDetailsService; + protected final HttpServletRequest request; protected void issueJwtToken(String identifier, HttpServletResponse response) { jwtService.deleteToken(response); @@ -36,7 +26,7 @@ protected void issueJwtToken(String identifier, HttpServletResponse response) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); - jwtService.createToken(response); + jwtService.createToken(request, response); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java index 9df979bb..aaab1484 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; @@ -21,8 +22,8 @@ @Slf4j public class AppleAuthService extends AbstractAuthService implements Oauth2AuthService { - public AppleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { - super(userRepository, s3Service, jwtService, userDetailsService); + public AppleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, HttpServletRequest request) { + super(userRepository, s3Service, jwtService, userDetailsService, request); } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 4ea8107b..3ee46a79 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -25,8 +25,8 @@ public class AuthCommonService extends AbstractAuthService { - public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { - super(userRepository, s3Service, jwtService, userDetailsService); + public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, HttpServletRequest request) { + super(userRepository, s3Service, jwtService, userDetailsService, request); } public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java index 64e03c8a..7ebfff3e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -23,8 +23,8 @@ public class GoogleAuthService extends AbstractAuthService implements Oauth2AuthService { - public GoogleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { - super(userRepository, s3Service, jwtService, userDetailsService); + public GoogleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, HttpServletRequest request) { + super(userRepository, s3Service, jwtService, userDetailsService, request); } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index c660b238..31328467 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -40,8 +40,8 @@ public class JwtService { * 최초 로그인 시 Access Token, Refresh Token 발급 * @param response */ - public void createToken(HttpServletResponse response) { - createBothToken(response); + public void createToken(HttpServletRequest request, HttpServletResponse response) { + createBothToken(request, response); log.info("[createToken] Access Token, Refresh Token 발급 완료"); } @@ -68,7 +68,7 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons SecurityContextHolder.getContext().setAuthentication(authentication); } - reissueToken(refreshToken, response); + reissueToken(refreshToken, request, response); } /** @@ -76,30 +76,35 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons * @param refreshToken * @param response */ - public void reissueToken(String refreshToken, HttpServletResponse response) { + public void reissueToken(String refreshToken, HttpServletRequest request, HttpServletResponse response) { if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { log.error("[reissueToken] Refresh Token이 유효하지 않습니다. : {}", refreshToken); throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 유효하지 않습니다."); } - createBothToken(response); + createBothToken(request, response); log.info("[reissueToken] Access Token, Refresh Token 재발급 완료"); } /** * Access Token, Refresh Token 생성 */ - private void createBothToken(HttpServletResponse response) { + private void createBothToken(HttpServletRequest request, HttpServletResponse response) { // 새로운 Access Token 발급 var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); + String domain = null; + if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ + domain = BASERURL.split("//")[1]; + } + Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 - jwtCookie.setDomain(BASERURL.split("//")[1]); + jwtCookie.setDomain(domain); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); @@ -109,7 +114,7 @@ private void createBothToken(HttpServletResponse response) { refreshCookie.setSecure(true); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 - refreshCookie.setDomain(BASERURL.split("//")[1]); + refreshCookie.setDomain(domain); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); From 7826115af93ed213fcd357db185f29edd74c4cfd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 21:09:33 +0900 Subject: [PATCH 0733/1002] =?UTF-8?q?chore=20:=20localhost=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 51c30539..fc837cbc 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 51c30539c96620229babf5d98ac909a989a214e6 +Subproject commit fc837cbc28bdf8240f2f95d24e02a0cd60660c3f From a6c9073308eaa5153d8b32f8c19353d88d4771e8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 21:28:09 +0900 Subject: [PATCH 0734/1002] =?UTF-8?q?chore=20:=20localhost=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=A7=8C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/JwtService.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 31328467..af6e0eaf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -94,28 +94,28 @@ private void createBothToken(HttpServletRequest request, HttpServletResponse res var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - String domain = null; - if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ - domain = BASERURL.split("//")[1]; - } + String domain = "localhost"; +// if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ +// domain = BASERURL.split("//")[1]; +// } Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 - jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 + jwtCookie.setSecure(false); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 jwtCookie.setDomain(domain); - jwtCookie.setAttribute("SameSite", "None"); +// jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); - refreshCookie.setSecure(true); + refreshCookie.setSecure(false); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 refreshCookie.setDomain(domain); - refreshCookie.setAttribute("SameSite", "None"); +// refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); From 4aa9626641a0b5e0571da873621c79def8084996 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:16:29 +0900 Subject: [PATCH 0735/1002] =?UTF-8?q?chore=20:=20Secure=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/service/JwtService.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index af6e0eaf..b42833fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -94,28 +94,17 @@ private void createBothToken(HttpServletRequest request, HttpServletResponse res var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - String domain = "localhost"; -// if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ -// domain = BASERURL.split("//")[1]; -// } - Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 - jwtCookie.setSecure(false); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 - jwtCookie.setDomain(domain); -// jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); - refreshCookie.setSecure(false); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 - refreshCookie.setDomain(domain); -// refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); From 5205d264c31f1833ba7bf2f06ef11d4b58a37ab1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:43:43 +0900 Subject: [PATCH 0736/1002] =?UTF-8?q?Revert=20"chore=20:=20ADMIN=EC=97=90?= =?UTF-8?q?=EA=B2=8C=EB=A7=8C=20Swagger=20=EC=A0=91=EA=B7=BC=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EB=B6=80=EC=97=AC"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 86b08ef3ca7ed055ecc7057de3925bc92090dd91. --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index fc837cbc..4a5df64c 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit fc837cbc28bdf8240f2f95d24e02a0cd60660c3f +Subproject commit 4a5df64c750e3948f5be7a98d6f912886fa60ba3 From 922afc6c5f602631a0c9577999fc968a34f364d3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:46:45 +0900 Subject: [PATCH 0737/1002] =?UTF-8?q?Revert=20"chore=20:=20Secure=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=82=AD=EC=A0=9C"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4aa9626641a0b5e0571da873621c79def8084996. --- .../codin/common/security/service/JwtService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index b42833fe..af6e0eaf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -94,17 +94,28 @@ private void createBothToken(HttpServletRequest request, HttpServletResponse res var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); + String domain = "localhost"; +// if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ +// domain = BASERURL.split("//")[1]; +// } + Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 + jwtCookie.setSecure(false); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 + jwtCookie.setDomain(domain); +// jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); + refreshCookie.setSecure(false); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 + refreshCookie.setDomain(domain); +// refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); From e16f968b10535cde2973164136bdab60ebf67ee9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:46:48 +0900 Subject: [PATCH 0738/1002] =?UTF-8?q?Revert=20"chore=20:=20localhost?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=A7=8C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a6c9073308eaa5153d8b32f8c19353d88d4771e8. --- .../common/security/service/JwtService.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index af6e0eaf..31328467 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -94,28 +94,28 @@ private void createBothToken(HttpServletRequest request, HttpServletResponse res var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - String domain = "localhost"; -// if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ -// domain = BASERURL.split("//")[1]; -// } + String domain = null; + if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ + domain = BASERURL.split("//")[1]; + } Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 - jwtCookie.setSecure(false); // HTTPS 환경에서만 전송 + jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 jwtCookie.setDomain(domain); -// jwtCookie.setAttribute("SameSite", "None"); + jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); - refreshCookie.setSecure(false); + refreshCookie.setSecure(true); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 refreshCookie.setDomain(domain); -// refreshCookie.setAttribute("SameSite", "None"); + refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); From 4096cc9a72ce11aae0a5a25efb13f40ae4016b8c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:47:49 +0900 Subject: [PATCH 0739/1002] =?UTF-8?q?Revert=20"chore=20:=20localhost?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4=EC=A0=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7826115a --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 4a5df64c..200c89f8 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 4a5df64c750e3948f5be7a98d6f912886fa60ba3 +Subproject commit 200c89f8a47d692c43d8796d89709bd2a2f9b1e8 From 9eb651600f5a7ff78da50f3c344e5c6214e4d729 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:47:50 +0900 Subject: [PATCH 0740/1002] =?UTF-8?q?Revert=20"chore=20:=20localhost?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=BF=A0=ED=82=A4=20=EC=84=A4=EC=A0=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a7c805952fe005dc22447a341a70d0dbde904df9. --- .../security/service/AbstractAuthService.java | 16 +++++++++++--- .../security/service/AppleAuthService.java | 5 ++--- .../security/service/AuthCommonService.java | 6 +++--- .../security/service/GoogleAuthService.java | 6 +++--- .../common/security/service/JwtService.java | 21 +++++++------------ 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java index 559a9740..d76dab54 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java @@ -1,8 +1,13 @@ package inu.codin.codin.common.security.service; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -10,6 +15,12 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; @RequiredArgsConstructor @Slf4j @@ -18,7 +29,6 @@ public abstract class AbstractAuthService { protected final S3Service s3Service; protected final JwtService jwtService; protected final UserDetailsService userDetailsService; - protected final HttpServletRequest request; protected void issueJwtToken(String identifier, HttpServletResponse response) { jwtService.deleteToken(response); @@ -26,7 +36,7 @@ protected void issueJwtToken(String identifier, HttpServletResponse response) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); - jwtService.createToken(request, response); + jwtService.createToken(response); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java index aaab1484..9df979bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java @@ -8,7 +8,6 @@ import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; @@ -22,8 +21,8 @@ @Slf4j public class AppleAuthService extends AbstractAuthService implements Oauth2AuthService { - public AppleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, HttpServletRequest request) { - super(userRepository, s3Service, jwtService, userDetailsService, request); + public AppleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + super(userRepository, s3Service, jwtService, userDetailsService); } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 3ee46a79..4ea8107b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -25,8 +25,8 @@ public class AuthCommonService extends AbstractAuthService { - public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, HttpServletRequest request) { - super(userRepository, s3Service, jwtService, userDetailsService, request); + public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + super(userRepository, s3Service, jwtService, userDetailsService); } public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java index 7ebfff3e..64e03c8a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -23,8 +23,8 @@ public class GoogleAuthService extends AbstractAuthService implements Oauth2AuthService { - public GoogleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, HttpServletRequest request) { - super(userRepository, s3Service, jwtService, userDetailsService, request); + public GoogleAuthService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + super(userRepository, s3Service, jwtService, userDetailsService); } @Override diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 31328467..c660b238 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -40,8 +40,8 @@ public class JwtService { * 최초 로그인 시 Access Token, Refresh Token 발급 * @param response */ - public void createToken(HttpServletRequest request, HttpServletResponse response) { - createBothToken(request, response); + public void createToken(HttpServletResponse response) { + createBothToken(response); log.info("[createToken] Access Token, Refresh Token 발급 완료"); } @@ -68,7 +68,7 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons SecurityContextHolder.getContext().setAuthentication(authentication); } - reissueToken(refreshToken, request, response); + reissueToken(refreshToken, response); } /** @@ -76,35 +76,30 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons * @param refreshToken * @param response */ - public void reissueToken(String refreshToken, HttpServletRequest request, HttpServletResponse response) { + public void reissueToken(String refreshToken, HttpServletResponse response) { if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { log.error("[reissueToken] Refresh Token이 유효하지 않습니다. : {}", refreshToken); throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 유효하지 않습니다."); } - createBothToken(request, response); + createBothToken(response); log.info("[reissueToken] Access Token, Refresh Token 재발급 완료"); } /** * Access Token, Refresh Token 생성 */ - private void createBothToken(HttpServletRequest request, HttpServletResponse response) { + private void createBothToken(HttpServletResponse response) { // 새로운 Access Token 발급 var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - String domain = null; - if (!request.getHeader("Origin").split("//")[1].startsWith("localhost")){ - domain = BASERURL.split("//")[1]; - } - Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 jwtCookie.setMaxAge(60 * 60); // 1시간 유지 - jwtCookie.setDomain(domain); + jwtCookie.setDomain(BASERURL.split("//")[1]); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); @@ -114,7 +109,7 @@ private void createBothToken(HttpServletRequest request, HttpServletResponse res refreshCookie.setSecure(true); refreshCookie.setPath("/"); refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 - refreshCookie.setDomain(domain); + refreshCookie.setDomain(BASERURL.split("//")[1]); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); From 6616ef56678d15ce1e7044dd320d0658667896e0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 4 Apr 2025 22:50:48 +0900 Subject: [PATCH 0741/1002] chore : Update resources-main --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 200c89f8..51c30539 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 200c89f8a47d692c43d8796d89709bd2a2f9b1e8 +Subproject commit 51c30539c96620229babf5d98ac909a989a214e6 From 0225ece8925a77022fdc518e5142dedbb50e0fdf Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 6 Apr 2025 22:50:18 +0900 Subject: [PATCH 0742/1002] =?UTF-8?q?refactor=20:=20Entity=EC=97=90=20NoAr?= =?UTF-8?q?gsConstructor=20=EB=B0=8F=20validation=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/block/entity/BlockEntity.java | 8 +++++++ .../domain/email/entity/EmailAuthEntity.java | 6 ++++- .../codin/domain/like/entity/LikeEntity.java | 11 ++++++++- .../domain/comment/entity/CommentEntity.java | 11 ++++++++- .../domain/poll/entity/PollVoteEntity.java | 7 ++++-- .../reply/entity/ReplyCommentEntity.java | 12 +++++++++- .../codin/domain/post/entity/PostEntity.java | 17 ++++++++++---- .../domain/report/entity/ReportEntity.java | 13 +++++++++-- .../domain/scrap/entity/ScrapEntity.java | 8 ++++++- .../codin/domain/user/entity/UserEntity.java | 23 ++++++++++++------- .../infra/fcm/entity/FcmTokenEntity.java | 9 +++++--- 11 files changed, 101 insertions(+), 24 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 74aa2bb7..c86e9132 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,8 +1,12 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -10,11 +14,15 @@ @Getter @Document(collection = "blocks") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class BlockEntity extends BaseTimeEntity { @Id private ObjectId id; // MongoDB의 기본 ID + @NotNull private ObjectId blockingUserId; // 차단한 사용자 + + @NotNull private ObjectId blockedUserId; // 차단된 사용자 @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index cf94a02f..3293abb7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -2,8 +2,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,9 +15,10 @@ @Document(collection = "auth-emails") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class EmailAuthEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; @NotBlank diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java index f6beb2e9..ca396aaa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java @@ -1,19 +1,28 @@ package inu.codin.codin.domain.like.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "likes") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class LikeEntity extends BaseTimeEntity { - @Id + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId likeTypeId; // 게시글, 댓글, 대댓글의 ID + + @NotNull private LikeType likeType; // 엔티티 타입 (post, comment, reply) + + @NotNull private ObjectId userId; // 좋아요를 누른 사용자 ID @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index c3f6ca06..08d5c09b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -2,20 +2,29 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "comments") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class CommentEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId postId; //게시글 ID 참조 + + @NotNull private ObjectId userId; + + @NotBlank private String content; private boolean anonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java index 4a85aea7..3d0f0ab6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java @@ -1,18 +1,21 @@ package inu.codin.codin.domain.post.domain.poll.entity; import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; -@Document(collection = "poll_votes") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Document(collection = "poll_votes") public class PollVoteEntity { - @Id + @Id @NotNull private ObjectId _id; @NotNull diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index c15a5162..86faa31a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -2,20 +2,30 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "replies") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ReplyCommentEntity extends BaseTimeEntity { @Id - @NotBlank + @NotNull private ObjectId _id; + + @NotNull private ObjectId commentId; // 댓글 ID 참조 + + @NotNull private ObjectId userId; // 작성자 ID + + @NotBlank private String content; private boolean anonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 4c80dfc7..0577197a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -3,8 +3,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,18 +15,24 @@ import java.util.List; @Document(collection = "posts") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class PostEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; - private final ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 - private final String title; + @NotNull + private ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 + @NotBlank + private String title; + @NotBlank private String content; private List postImageUrls; private boolean isAnonymous; - private final PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) + @NotNull + private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) + @NotNull private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) private int commentCount = 0; // 댓글 + 대댓글 카운트 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index e4115baa..f30be721 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -2,9 +2,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; -import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,27 +14,34 @@ import java.time.LocalDateTime; @Document(collection = "reports") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ReportEntity extends BaseTimeEntity { @Id - @NotBlank + @NotNull private ObjectId _id; + @NotNull //신고한 유저 private ObjectId reportingUserId; + @NotNull //신고당한 유저 private ObjectId reportedUserId; + @NotNull //신고 대상 타입 ( 유저, 게시물, 댓글, 대댓글) private ReportTargetType reportTargetType; + @NotNull //신고 대상 ID ( 유저, 게시물, 댓글, 대댓글) private ObjectId reportTargetId; + @NotNull //신고 유형 ( 게시글 부적절, 스팸 ,,.) private ReportType reportType; + @NotNull //신고 처리 상태 Pending <-> Reloved private ReportStatus reportStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java index 85a1e6a8..e2887743 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java @@ -1,18 +1,24 @@ package inu.codin.codin.domain.scrap.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "scraps") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ScrapEntity extends BaseTimeEntity { - @Id + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId postId; + @NotNull private ObjectId userId; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 02fd094e..e0c9a010 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -5,8 +5,11 @@ import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.domain.notification.entity.NotificationPreference; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -17,41 +20,47 @@ @Document(collection = "users") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; + @NotBlank private String email; private String password; private String studentId; + @NotBlank private String name; + @NotBlank private String nickname; + @NotBlank private String profileImageUrl; + @NotNull private Department department; private String college; - private Boolean undergraduate; - + @NotNull private UserRole role; + @NotNull private UserStatus status; private LocalDateTime totalSuspensionEndDate; //정지 게시물이 늘어날수록 정지 종료일이 중첩 - private List blockedUsers = new ArrayList<>(); + private List blockedUsers; - private NotificationPreference notificationPreference = new NotificationPreference(); + private final NotificationPreference notificationPreference = new NotificationPreference(); @Builder - public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status, List blockedUsers) { + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, UserRole role, UserStatus status, List blockedUsers) { this.email = email; this.password = password; this.studentId = studentId; @@ -60,7 +69,6 @@ public UserEntity(String email, String password, String studentId, String name, this.profileImageUrl = profileImageUrl; this.department = department; this.college = college; - this.undergraduate = undergraduate; this.role = role; this.status = status; this.blockedUsers = (blockedUsers != null) ? blockedUsers : new ArrayList<>(); // ✅ 기본값 설정 @@ -82,7 +90,6 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .password(userPortalLoginResponseDto.getPassword()) .department(userPortalLoginResponseDto.getDepartment()) .college(userPortalLoginResponseDto.getCollege()) - .undergraduate(userPortalLoginResponseDto.getUndergraduate()) .nickname("") .profileImageUrl("") .role(UserRole.USER) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index 733765b0..d5991104 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,25 +1,28 @@ package inu.codin.codin.infra.fcm.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; @Document(collection = "fcmToken") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class FcmTokenEntity extends BaseTimeEntity { - @Id + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId userId; private List fcmTokenList; From c064ceece263d5c537f1a1599ba88112d37c4adc Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 6 Apr 2025 22:50:28 +0900 Subject: [PATCH 0743/1002] =?UTF-8?q?refactor=20:=20Unused=20import=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/AbstractAuthService.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java index d76dab54..7b57afbc 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java @@ -1,11 +1,5 @@ package inu.codin.codin.common.security.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; @@ -15,12 +9,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; @RequiredArgsConstructor @Slf4j From baf38cad9020559e7b2fd1ec60e8b1ffddb305a3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 7 Apr 2025 22:23:32 +0900 Subject: [PATCH 0744/1002] =?UTF-8?q?fix=20:=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20BestEntity=EC=9D=98=20sc?= =?UTF-8?q?ore=20=EC=A0=90=EC=88=98=EA=B0=80=20=EB=B3=80=EA=B2=BD=EB=90=98?= =?UTF-8?q?=EC=97=88=EB=8B=A4=EB=A9=B4=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/domain/best/BestEntity.java | 4 ++++ .../codin/domain/post/domain/best/BestRepository.java | 4 +++- .../codin/infra/redis/service/RedisBestService.java | 9 +++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java index d81986ba..886ccf33 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java @@ -32,4 +32,8 @@ public BestEntity(ObjectId postId, int score) { this.postId = postId; this.score = score; } + + public void updateScore(int score){ + this.score = score; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java index 739a8afb..ada1777a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java @@ -3,6 +3,8 @@ import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import java.util.Optional; + public interface BestRepository extends MongoRepository { - boolean existsByPostId(ObjectId postId); + Optional findByPostId(ObjectId postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 51cf7743..b2499276 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -197,12 +197,17 @@ private void checkHits(String postId, String minPostId) { * 만약 bestEntity에 없다면 저장 */ public void saveBests(String postId, int score) { - boolean existedPost = bestRepository.existsByPostId(new ObjectId(postId)); - if (!existedPost) { + Optional existedPost = bestRepository.findByPostId(new ObjectId(postId)); + if (existedPost.isEmpty()) { bestRepository.save(BestEntity.builder() .postId(new ObjectId(postId)) .score(score) .build()); + } else { + if (existedPost.get().getScore() != score){ + existedPost.get().updateScore(score); + bestRepository.save(existedPost.get()); + } } } From 7abd7eca22a3a1771657b3f394c5625705ec94d3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 7 Apr 2025 22:35:19 +0900 Subject: [PATCH 0745/1002] =?UTF-8?q?fix=20:=20=EC=B5=9C=EC=86=8C=204?= =?UTF-8?q?=EC=A0=90=20=EC=9D=B4=EC=83=81=EB=B6=80=ED=84=B0=20Best=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20?= =?UTF-8?q?BestEntity=EC=9D=98=20score=20=EA=B0=92=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/service/RedisBestService.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index b2499276..57263ba4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -50,18 +50,20 @@ public Map delicatedBestsScheduler(int N) { Map result = new HashMap<>(); for (int i = 0; i < 24; i++) { String redisKey = now.minusHours(i).format(formatter); - if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { - Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, -1); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { //해당 시각의 key가 존재한다면 + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, -1); //value 반환 if (members != null) { for (ZSetOperations.TypedTuple member : members) { String postId = member.getValue(); Double score = member.getScore(); + //post가 삭제되었는지 확인하고, 삭제되었다면 지우기 if (!postRepository.existsBy_idAndDeletedAtIsNull(new ObjectId(postId))){ redisTemplate.opsForZSet().remove(redisKey, postId); deleteBest(postId); - break; + continue; } - result.put(postId, score); + if (score >= 4) + result.put(postId, score); } } } @@ -141,12 +143,12 @@ public void applyBestScore(int score, ObjectId postId){ redisKey = now.format(formatter); redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); //하루가 지나면 필요없는 데이터 redisTemplate.opsForZSet().add(redisKey, postId.toString(), score); - updateBests(redisKey, postId.toString()); } } /** * 게시글에 점수가 반영 될 때마다 베스트 게시글을 관리하는 ZSet에서 업데이트 + * 4점 미만이라면 Best 게시글로 취급하지 않음 * 베스트 게시글에서 최소 점수보다 같거나 크거나, 베스트 게시글이 3개 이하거나 할 때 베스트 게시글에 포함 * * 만약 포함 후 3개 초과라면 @@ -155,12 +157,13 @@ public void applyBestScore(int score, ObjectId postId){ */ private void updateBests(String redisKey, String postId){ Double score = redisTemplate.opsForZSet().score(redisKey, postId); - Set> minEntry = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, 0); + if (score < 4) return; //총 점수가 4점 미만이면 Best 게시글로 취급하지 않음 + Set> minEntry = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, 0); if (minEntry!=null && !minEntry.isEmpty()){ ZSetOperations.TypedTuple minTuple = minEntry.iterator().next(); //최소 score의 Tuple - Double min = minTuple.getScore(); - Long totalSize = redisTemplate.opsForZSet().size(BEST_KEY); + Double min = minTuple.getScore(); //Best 게시글 중 최소 score + Long totalSize = redisTemplate.opsForZSet().size(BEST_KEY); //현재 Best 게시글 개수 //최소 점수보다 같거나 큰 값이거나, 총 베스트 게시글 개수가 3개 미만 이면 포함 if (score >= min || totalSize < 3) @@ -194,7 +197,7 @@ private void checkHits(String postId, String minPostId) { } /** - * 만약 bestEntity에 없다면 저장 + * 만약 bestEntity에 없다면 저장, 있는데 score가 변경되었다면 업데이트 */ public void saveBests(String postId, int score) { Optional existedPost = bestRepository.findByPostId(new ObjectId(postId)); @@ -203,7 +206,7 @@ public void saveBests(String postId, int score) { .postId(new ObjectId(postId)) .score(score) .build()); - } else { + } else { // if (existedPost.get().getScore() != score){ existedPost.get().updateScore(score); bestRepository.save(existedPost.get()); From 807ac3757d3df4403a3fe92cf871e6087cd72a15 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 8 Apr 2025 22:56:52 +0900 Subject: [PATCH 0746/1002] =?UTF-8?q?refactor=20:=20request,=20response=20?= =?UTF-8?q?dto=20=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/chatroom/controller/ChatRoomController.java | 4 ++-- .../dto/{ => request}/ChatRoomCreateRequestDto.java | 2 +- .../dto/{ => response}/ChatRoomListResponseDto.java | 2 +- .../codin/codin/domain/chat/chatroom/entity/ChatRoom.java | 2 +- .../domain/chat/chatroom/service/ChatRoomService.java | 5 ++--- .../codin/domain/email/controller/EmailController.java | 4 ++-- .../email/dto/{ => request}/JoinEmailCheckRequestDto.java | 2 +- .../email/dto/{ => request}/JoinEmailSendRequestDto.java | 2 +- .../codin/codin/domain/email/service/EmailAuthService.java | 4 ++-- .../lecture/domain/review/controller/ReviewController.java | 2 +- .../review/dto/{ => request}/CreateReviewRequestDto.java | 2 +- .../review/dto/{ => response}/ReviewListResposneDto.java | 2 +- .../review/dto/{ => response}/ReviewPageResponse.java | 2 +- .../domain/lecture/domain/review/entity/ReviewEntity.java | 2 +- .../lecture/domain/review/service/ReviewService.java | 6 +++--- .../room/dto/{ => response}/EmptyRoomResponseDto.java | 2 +- .../lecture/domain/room/service/LectureRoomService.java | 2 +- .../dto/{ => response}/LectureDetailResponseDto.java | 3 ++- .../lecture/dto/{ => response}/LectureListResponseDto.java | 2 +- .../lecture/dto/{ => response}/LecturePageResponse.java | 2 +- .../dto/{ => response}/LectureSearchListResponseDto.java | 2 +- .../codin/codin/domain/lecture/service/LectureService.java | 4 ++++ .../codin/codin/domain/like/controller/LikeController.java | 2 +- .../domain/like/dto/{ => request}/LikeRequestDto.java | 2 +- .../inu/codin/codin/domain/like/service/LikeService.java | 2 +- .../{ => controller}/NotificationController.java | 2 +- .../dto/{ => response}/NotificationListResponseDto.java | 2 +- .../domain/notification/service/NotificationService.java | 2 +- .../domain/post/domain/poll/controller/PollController.java | 4 ++-- .../poll/dto/{ => request}/PollCreateRequestDTO.java | 2 +- .../poll/dto/{ => request}/PollVotingRequestDTO.java | 2 +- .../codin/domain/post/domain/poll/service/PollService.java | 7 ++----- .../codin/codin/infra/fcm/controller/FcmController.java | 2 +- .../codin/infra/fcm/dto/{ => request}/FcmTokenRequest.java | 2 +- .../java/inu/codin/codin/infra/fcm/service/FcmService.java | 2 +- 35 files changed, 47 insertions(+), 46 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/{ => request}/ChatRoomCreateRequestDto.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/{ => response}/ChatRoomListResponseDto.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/email/dto/{ => request}/JoinEmailCheckRequestDto.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/email/dto/{ => request}/JoinEmailSendRequestDto.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/{ => request}/CreateReviewRequestDto.java (89%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/{ => response}/ReviewListResposneDto.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/{ => response}/ReviewPageResponse.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/{ => response}/EmptyRoomResponseDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LectureDetailResponseDto.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LectureListResponseDto.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LecturePageResponse.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LectureSearchListResponseDto.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/like/dto/{ => request}/LikeRequestDto.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/notification/{ => controller}/NotificationController.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/notification/dto/{ => response}/NotificationListResponseDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/{ => request}/PollCreateRequestDTO.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/{ => request}/PollVotingRequestDTO.java (87%) rename codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/{ => request}/FcmTokenRequest.java (88%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 73344597..3b94ba40 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/request/ChatRoomCreateRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/request/ChatRoomCreateRequestDto.java index ac998c98..d91e314a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/request/ChatRoomCreateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.chat.chatroom.dto; +package inu.codin.codin.domain.chat.chatroom.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/response/ChatRoomListResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/response/ChatRoomListResponseDto.java index ebdcb985..038e87d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/response/ChatRoomListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.chat.chatroom.dto; +package inu.codin.codin.domain.chat.chatroom.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index e7767a05..21cfbb14 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 25150c0a..793bcc07 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -3,8 +3,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.dto.event.ChatRoomNotificationEvent; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; @@ -12,7 +12,6 @@ import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 356d41eb..65f36cab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.email.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.service.EmailAuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailCheckRequestDto.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailCheckRequestDto.java index 6b396143..90503c5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailCheckRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.email.dto; +package inu.codin.codin.domain.email.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailSendRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailSendRequestDto.java index 279e6ae8..fb1a95ef 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailSendRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.email.dto; +package inu.codin.codin.domain.email.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java index 3e5fe55d..466b704d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.email.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java index 19e3d68b..75a10703 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; -import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/request/CreateReviewRequestDto.java similarity index 89% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/request/CreateReviewRequestDto.java index 7044b113..bf5cfa09 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/request/CreateReviewRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.review.dto; +package inu.codin.codin.domain.lecture.domain.review.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewListResposneDto.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewListResposneDto.java index 4d613755..2d7bdc34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewListResposneDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.review.dto; +package inu.codin.codin.domain.lecture.domain.review.dto.response; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewPageResponse.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewPageResponse.java index 95671318..f766e9d8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewPageResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.review.dto; +package inu.codin.codin.domain.lecture.domain.review.dto.response; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index aaae7d88..1459b2bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.lecture.domain.review.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index e4dd991d..5c8ec6c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -2,9 +2,9 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; -import inu.codin.codin.domain.lecture.domain.review.dto.ReviewListResposneDto; -import inu.codin.codin.domain.lecture.domain.review.dto.ReviewPageResponse; +import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewListResposneDto; +import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewPageResponse; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; import inu.codin.codin.domain.lecture.domain.review.exception.ReviewExistenceException; import inu.codin.codin.domain.lecture.domain.review.exception.WrongRatingException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/response/EmptyRoomResponseDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/response/EmptyRoomResponseDto.java index 7c294194..948bea34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/response/EmptyRoomResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.room.dto; +package inu.codin.codin.domain.lecture.domain.room.dto.response; import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java index 9a8d22e7..a12bd3ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.domain.room.service; -import inu.codin.codin.domain.lecture.domain.room.dto.EmptyRoomResponseDto; +import inu.codin.codin.domain.lecture.domain.room.dto.response.EmptyRoomResponseDto; import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; import inu.codin.codin.domain.lecture.repository.LectureRepositoryCustomImpl; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureDetailResponseDto.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureDetailResponseDto.java index 9a59dddb..4b8c73a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureDetailResponseDto.java @@ -1,5 +1,6 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; +import inu.codin.codin.domain.lecture.dto.Emotion; import inu.codin.codin.domain.lecture.entity.LectureEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureListResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureListResponseDto.java index c2d59ac7..d6484ff1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; import inu.codin.codin.domain.lecture.entity.LectureEntity; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LecturePageResponse.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LecturePageResponse.java index bfb8a75b..10501b8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LecturePageResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureSearchListResponseDto.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureSearchListResponseDto.java index 1bcd055f..ca9fa0b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureSearchListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; import inu.codin.codin.domain.lecture.entity.LectureEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 106424a4..e6d2e410 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -3,6 +3,10 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.dto.*; +import inu.codin.codin.domain.lecture.dto.response.LectureDetailResponseDto; +import inu.codin.codin.domain.lecture.dto.response.LectureListResponseDto; +import inu.codin.codin.domain.lecture.dto.response.LecturePageResponse; +import inu.codin.codin.domain.lecture.dto.response.LectureSearchListResponseDto; import inu.codin.codin.domain.lecture.entity.LectureEntity; import inu.codin.codin.domain.lecture.exception.WrongInputException; import inu.codin.codin.domain.lecture.repository.LectureRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 3d3cfe25..6b80b979 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.like.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.dto.request.LikeRequestDto; import inu.codin.codin.domain.like.service.LikeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java index b84b3186..2ba9d792 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.like.dto; +package inu.codin.codin.domain.like.dto.request; import inu.codin.codin.domain.like.entity.LikeType; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 1f320f06..3bbd4dda 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -7,7 +7,7 @@ import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.dto.request.LikeRequestDto; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java rename to codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java index b0490c88..d0baaaa0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.notification; +package inu.codin.codin.domain.notification.controller; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java index ea8ac8fb..eb72a1c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.notification.dto; +package inu.codin.codin.domain.notification.dto.response; import inu.codin.codin.domain.notification.entity.NotificationEntity; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 07e05bd1..9edee894 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.notification.dto.NotificationListResponseDto; +import inu.codin.codin.domain.notification.dto.response.NotificationListResponseDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index f1924f4a..8d70487d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.post.domain.poll.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; -import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.service.PollService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollCreateRequestDTO.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollCreateRequestDTO.java index 2721bc45..77b8c90e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.poll.dto; +package inu.codin.codin.domain.post.domain.poll.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollVotingRequestDTO.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollVotingRequestDTO.java index c06f44ed..1d36ae56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollVotingRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.poll.dto; +package inu.codin.codin.domain.post.domain.poll.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index e8b35c99..c33c4846 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -1,11 +1,9 @@ package inu.codin.codin.domain.post.domain.poll.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; -import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; @@ -16,7 +14,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.user.entity.UserRole; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java index 3d1cd4de..a7522bb3 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java @@ -1,7 +1,7 @@ package inu.codin.codin.infra.fcm.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; +import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import inu.codin.codin.infra.fcm.service.FcmService; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java rename to codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java index d873c705..97f4bead 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.infra.fcm.dto; +package inu.codin.codin.infra.fcm.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 2ed53662..eda13223 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -8,7 +8,7 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; -import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; +import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; import inu.codin.codin.infra.fcm.exception.FcmTokenNotFoundException; import inu.codin.codin.infra.fcm.repository.FcmTokenRepository; From db75c0634edf8a400e7dafd473dc50839b017f90 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 9 Apr 2025 00:39:06 +0900 Subject: [PATCH 0747/1002] =?UTF-8?q?chore=20:=20=ED=97=88=EC=9A=A9?= =?UTF-8?q?=EB=90=9C=20email=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 51c30539..50cfe48f 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 51c30539c96620229babf5d98ac909a989a214e6 +Subproject commit 50cfe48f2b60416ef75f21fc3cc58c570bc84f4b From cbef60d360b43c5444ba123a1cb2d1c3e2c3df3b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 11 Apr 2025 23:20:41 +0900 Subject: [PATCH 0748/1002] =?UTF-8?q?chore=20:=20=ED=97=88=EC=9A=A9?= =?UTF-8?q?=EB=90=9C=20email=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 51c30539..50cfe48f 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 51c30539c96620229babf5d98ac909a989a214e6 +Subproject commit 50cfe48f2b60416ef75f21fc3cc58c570bc84f4b From 1b98c1fdc88746e1454f68770c9ace5d1f62af04 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 12 Apr 2025 21:48:47 +0900 Subject: [PATCH 0749/1002] =?UTF-8?q?refactor=20:=20reply=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A5=BC=20comment=20domain=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/dto/request/LikeRequestDto.java | 2 +- .../domain}/reply/controller/ReplyCommentController.java | 8 ++++---- .../dto/request/ReplyAnonnymousUpdateRequestDTO.java | 2 +- .../domain}/reply/dto/request/ReplyCreateRequestDTO.java | 2 +- .../domain}/reply/dto/request/ReplyUpdateRequestDTO.java | 2 +- .../domain}/reply/entity/ReplyCommentEntity.java | 2 +- .../domain}/reply/repository/ReplyCommentRepository.java | 4 ++-- .../domain/comment/dto/response/CommentResponseDTO.java | 2 +- .../codin/codin/domain/report/service/ReportService.java | 7 +++---- 9 files changed, 15 insertions(+), 16 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/controller/ReplyCommentController.java (83%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java (78%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/dto/request/ReplyCreateRequestDTO.java (86%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/dto/request/ReplyUpdateRequestDTO.java (78%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/entity/ReplyCommentEntity.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/repository/ReplyCommentRepository.java (74%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java index 2ba9d792..cb8ae060 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java @@ -15,5 +15,5 @@ public class LikeRequestDto { @NotBlank @Schema(description = "좋아요를 반영할 entity 의 _id 값", example = "111111") - private String id; + private String likeTypeId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/controller/ReplyCommentController.java similarity index 83% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/controller/ReplyCommentController.java index b2529210..f0442ec1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/controller/ReplyCommentController.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.post.domain.reply.controller; +package inu.codin.codin.domain.post.domain.comment.domain.reply.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.service.ReplyCommentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java similarity index 78% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java index 0e4774f7..f6859b5a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyCreateRequestDTO.java similarity index 86% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyCreateRequestDTO.java index f00ae843..44fa5532 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyUpdateRequestDTO.java similarity index 78% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyUpdateRequestDTO.java index 3d152789..68906e50 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyUpdateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/entity/ReplyCommentEntity.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/entity/ReplyCommentEntity.java index 86faa31a..24fbad6b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/entity/ReplyCommentEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.entity; +package inu.codin.codin.domain.post.domain.comment.domain.reply.entity; import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/repository/ReplyCommentRepository.java similarity index 74% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/repository/ReplyCommentRepository.java index 7be2aeb6..209ce552 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/repository/ReplyCommentRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.domain.reply.repository; +package inu.codin.codin.domain.post.domain.comment.domain.reply.repository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 76b941a0..2012abc5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index ace713c0..9046260a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -8,9 +8,9 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; @@ -32,7 +32,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bson.Document; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; From b8fdf2ae2d3085b4ef28c6946a6a954b3bb3fc18 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 13 Apr 2025 00:24:45 +0900 Subject: [PATCH 0750/1002] =?UTF-8?q?refactor=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20eventListener=EB=A1=9C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ChattingEventListener.java | 1 - .../like/dto/event/LikeNotificationEvent.java | 19 +++++++ .../like/service/LikeEventListener.java | 22 ++++++++ .../domain/like/service/LikeService.java | 52 ++++++++++--------- .../service/NotificationService.java | 14 ++--- .../dto/event/ReplyNotificationEvent.java | 25 +++++++++ .../reply/service/ReplyCommentService.java | 16 +++--- .../reply/service/ReplyEventListener.java | 22 ++++++++ .../dto/event/CommentNotificationEvent.java | 25 +++++++++ .../comment/service/CommentEventListener.java | 22 ++++++++ .../comment/service/CommentService.java | 9 ++-- 11 files changed, 186 insertions(+), 41 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/service/ReplyCommentService.java (87%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index ca6a5f2c..4a353a8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -37,7 +37,6 @@ public class ChattingEventListener { 2. 채팅방의 마지막 메세지 업데이트 3. /queue/chatroom/unread 를 통해 상대방의 채팅방 목록 실시간 업데이트 */ - @Async @EventListener public void handleChattingArrivedEvent(ChattingArrivedEvent event){ Chatting chatting = event.getChatting(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java new file mode 100644 index 00000000..82aed5ec --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.like.dto.event; + +import inu.codin.codin.domain.like.entity.LikeType; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class LikeNotificationEvent extends ApplicationEvent { + private final LikeType likeType; + + private final ObjectId likeTypeId; + + public LikeNotificationEvent(Object source, LikeType likeType, ObjectId likeTypeId) { + super(source); + this.likeType = likeType; + this.likeTypeId = likeTypeId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java new file mode 100644 index 00000000..f9e51a24 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.like.service; + +import inu.codin.codin.domain.like.dto.event.LikeNotificationEvent; +import inu.codin.codin.domain.notification.service.NotificationService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +public class LikeEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleLikeNotificationEvent(LikeNotificationEvent event){ + notificationService.sendNotificationMessageByLike(event.getLikeType(), event.getLikeTypeId()); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 3bbd4dda..97dceaad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -3,12 +3,13 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.like.dto.event.LikeNotificationEvent; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.like.dto.request.LikeRequestDto; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisLikeService; @@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -31,22 +33,23 @@ public class LikeService { private final ReplyCommentRepository replyCommentRepository; private final ReviewRepository reviewRepository; + private final ApplicationEventPublisher eventPublisher; private final RedisLikeService redisLikeService; private final RedisBestService redisBestService; private final RedisHealthChecker redisHealthChecker; public String toggleLike(LikeRequestDto likeRequestDto) { - ObjectId likeId = new ObjectId(likeRequestDto.getId()); + ObjectId likeTypeId = new ObjectId(likeRequestDto.getLikeTypeId()); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 - Optional like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - return getResult(likeRequestDto, like, likeId, userId); + Optional like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeTypeId, userId); + return getResult(likeRequestDto, like, likeTypeId, userId); } - private String getResult(LikeRequestDto likeRequestDto, Optional like, ObjectId likeId, ObjectId userId) { + private String getResult(LikeRequestDto likeRequestDto, Optional like, ObjectId likeTypeId, ObjectId userId) { if (like.isPresent()){ if (like.get().getDeletedAt() == null) { removeLike(like.get()); @@ -56,25 +59,26 @@ private String getResult(LikeRequestDto likeRequestDto, Optional lik return "좋아요가 복구되었습니다"; } } else { - addLike(likeRequestDto.getLikeType(), likeId, userId); + addLike(likeRequestDto.getLikeType(), likeTypeId, userId); + eventPublisher.publishEvent(new LikeNotificationEvent(this, likeRequestDto.getLikeType(), likeTypeId)); return "좋아요가 추가되었습니다."; } } - public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId){ + public void addLike(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ if (redisHealthChecker.isRedisAvailable()) { - redisLikeService.addLike(likeType.name(), likeId); - log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + redisLikeService.addLike(likeType.name(), likeTypeId); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeTypeId, userId); } likeRepository.save(LikeEntity.builder() .likeType(likeType) - .likeTypeId(likeId) + .likeTypeId(likeTypeId) .userId(userId) .build()); if (likeType == LikeType.POST) { - redisBestService.applyBestScore(1, likeId); - log.info("Redis에 Best Score 적용 - postId: {}", likeId); + redisBestService.applyBestScore(1, likeTypeId); + log.info("Redis에 Best Score 적용 - postId: {}", likeTypeId); } } @@ -101,21 +105,21 @@ public void removeLike(LikeEntity like) { log.info("좋아요 삭제 완료 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); } - public int getLikeCount(LikeType entityType, ObjectId entityId) { + public int getLikeCount(LikeType likeType, ObjectId likeTypeId) { Object redisResult = null; if (redisHealthChecker.isRedisAvailable()) { - redisResult = redisLikeService.getLikeCount(entityType.name(), entityId); + redisResult = redisLikeService.getLikeCount(likeType.name(), likeTypeId); } if (redisResult == null){ - recoveryLike(entityType, entityId); - return likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); + recoveryLike(likeType, likeTypeId); + return likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeTypeId); } else return Integer.parseInt(String.valueOf(redisResult)); } @Async - protected void recoveryLike(LikeType entityType, ObjectId entityId) { - int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); - redisLikeService.recoveryLike(entityType, entityId, likeCount); + protected void recoveryLike(LikeType likeType, ObjectId likeTypeId) { + int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeTypeId); + redisLikeService.recoveryLike(likeType, likeTypeId, likeCount); } public boolean isLiked(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ @@ -123,15 +127,15 @@ public boolean isLiked(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ - ObjectId id = new ObjectId(likeRequestDto.getId()); + ObjectId likeTypeId = new ObjectId(likeRequestDto.getLikeTypeId()); switch(likeRequestDto.getLikeType()){ - case POST -> postRepository.findByIdAndNotDeleted(id) + case POST -> postRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - case REPLY -> replyCommentRepository.findByIdAndNotDeleted(id) + case REPLY -> replyCommentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); - case COMMENT -> commentRepository.findByIdAndNotDeleted(id) + case COMMENT -> commentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); - case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(id) + case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(likeTypeId) .orElseThrow(() ->new NotFoundException("수강 후기를 찾을 수 없습니다")); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 9edee894..e9d60e88 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; @@ -40,7 +40,7 @@ public class NotificationService { private final FcmService fcmService; private final String NOTI_COMMENT = "댓글이 달렸습니다: "; private final String NOTI_REPLY = "대댓글이 달렸습니다: "; - private final String NOTI_LIKE = ""; + private final String NOTI_LIKE = "좋아요가 달렸습니다"; private final String NOTI_CHAT = "새로운 채팅이 있습니다."; @@ -146,10 +146,10 @@ public void sendNotificationMessageByReply(PostCategory postCategory, ObjectId u sendFcmMessageToUser(title, NOTI_REPLY+content, post, userId); } - public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { + public void sendNotificationMessageByLike(LikeType likeType, ObjectId likeTypeId) { switch(likeType){ case POST -> { - PostEntity postEntity = postRepository.findByIdAndNotDeleted(id) + PostEntity postEntity = postRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); userRepository.findById(postEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); @@ -158,7 +158,7 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { sendFcmMessageToUser(NOTI_LIKE, "내 게시글 보러 가기", post, postEntity.getUserId()); } case REPLY -> { - ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(id) + ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(replyCommentEntity.getCommentId()) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); @@ -171,7 +171,7 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { sendFcmMessageToUser(NOTI_LIKE, "내 답글 보러 가기", post, replyCommentEntity.getUserId()); } case COMMENT -> { - CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(id) + CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity postEntity = postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java new file mode 100644 index 00000000..0226e913 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.event; + +import inu.codin.codin.domain.post.entity.PostCategory; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class ReplyNotificationEvent extends ApplicationEvent { + private final PostCategory postCategory; + + private final ObjectId userId; + + private final String postId; + + private final String content; + + public ReplyNotificationEvent(Object source, PostCategory postCategory, ObjectId userId, String postId, String content) { + super(source); + this.postCategory = postCategory; + this.userId = userId; + this.postId = postId; + this.content = content; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyCommentService.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyCommentService.java index 91adfc63..a4c2fda2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyCommentService.java @@ -1,17 +1,18 @@ -package inu.codin.codin.domain.post.domain.reply.service; +package inu.codin.codin.domain.post.domain.comment.domain.reply.service; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.event.ReplyNotificationEvent; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; @@ -25,6 +26,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import java.util.List; @@ -46,6 +48,7 @@ public class ReplyCommentService { private final NotificationService notificationService; private final RedisBestService redisBestService; private final S3Service s3Service; + private final ApplicationEventPublisher eventPublisher; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -74,7 +77,8 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { redisBestService.applyBestScore(1, post.get_id()); log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (!userId.equals(post.getUserId())) + eventPublisher.publishEvent(new ReplyNotificationEvent(this, post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent())); } // 대댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java new file mode 100644 index 00000000..2a27d3d1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.post.domain.comment.domain.reply.service; + +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.event.ReplyNotificationEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReplyEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleReplyNotificationEvent(ReplyNotificationEvent event){ + notificationService.sendNotificationMessageByReply(event.getPostCategory(), event.getUserId(), event.getPostId(), event.getContent()); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java new file mode 100644 index 00000000..987e1391 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.post.domain.comment.dto.event; + +import inu.codin.codin.domain.post.entity.PostCategory; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class CommentNotificationEvent extends ApplicationEvent { + private final PostCategory postCategory; + + private final ObjectId userId; + + private final String postId; + + private final String content; + + public CommentNotificationEvent(Object source, PostCategory postCategory, ObjectId userId, String postId, String content) { + super(source); + this.postCategory = postCategory; + this.userId = userId; + this.postId = postId; + this.content = content; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java new file mode 100644 index 00000000..c439cf96 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.post.domain.comment.service; + +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.event.CommentNotificationEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CommentEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleCommentNotificationEvent(CommentNotificationEvent event){ + notificationService.sendNotificationMessageByComment(event.getPostCategory(), event.getUserId(), event.getPostId(), event.getContent()); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 428b5492..b4776e66 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -5,13 +5,14 @@ import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.event.CommentNotificationEvent; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; @@ -22,6 +23,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import java.util.List; @@ -42,6 +44,7 @@ public class CommentService { private final NotificationService notificationService; private final RedisBestService redisBestService; private final S3Service s3Service; + private final ApplicationEventPublisher eventPublisher; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -65,8 +68,8 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { redisBestService.applyBestScore(1, postId); log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); - + if (!userId.equals(post.getUserId())) + eventPublisher.publishEvent(new CommentNotificationEvent(this, post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent())); } // 댓글 삭제 (Soft Delete) From 8916a9443d874c702f7ec7a626b56361845519aa Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 23 Jun 2025 17:04:43 +0900 Subject: [PATCH 0751/1002] =?UTF-8?q?refactor:=20Email=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailAuthService와 EmailSendService 계층 분리 - 과거 EmailAuthService을 JoinEmailAuthService와 PasswordResetEmailService로 책임 분리 - EmailSendService에서 EmailTemplateService로 변경을 통해 이메일 전송 역할 명확화 --- .../email/controller/EmailController.java | 13 +-- .../domain/email/entity/EmailAuthEntity.java | 16 ++-- .../email/repository/EmailAuthRepository.java | 5 -- .../email/service/EmailTemplateService.java | 57 ++++++++++++ .../email/service/JoinEmailAuthService.java | 89 +++++++++++++++++++ .../service/PasswordResetEmailService.java | 76 ++++++++++++++++ .../email/util/AuthNumberGenerator.java | 20 +++++ 7 files changed, 260 insertions(+), 16 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 356d41eb..22f0f632 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -3,7 +3,8 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; -import inu.codin.codin.domain.email.service.EmailAuthService; +import inu.codin.codin.domain.email.service.JoinEmailAuthService; +import inu.codin.codin.domain.email.service.PasswordResetEmailService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -19,15 +20,15 @@ @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class EmailController { - - private final EmailAuthService emailAuthService; + private final JoinEmailAuthService joinEmailAuthService; + private final PasswordResetEmailService passwordResetEmailService; @Operation(summary = "이메일 인증 코드 전송 - 학교인증 X") @PostMapping("/auth/send") public ResponseEntity> sendJoinAuthEmail( @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { - emailAuthService.sendAuthEmail(emailAuthRequestDto); + joinEmailAuthService.sendJoinAuthEmail(emailAuthRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "이메일 인증 코드 전송 성공", null)); } @@ -37,7 +38,7 @@ public ResponseEntity> sendJoinAuthEmail( public ResponseEntity> checkAuthNum( @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto ) { - emailAuthService.checkAuthNum(joinEmailCheckRequestDto); + joinEmailAuthService.checkJoinAuthEmail(joinEmailCheckRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "이메일 인증 성공", null)); } @@ -49,7 +50,7 @@ public ResponseEntity> checkAuthNum( public ResponseEntity> sendPasswordEmail( @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { - emailAuthService.sendPasswordEmail(emailAuthRequestDto); + passwordResetEmailService.sendPasswordResetEmail(emailAuthRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "이메일 비밀번호 재설정 링크 전송 성공", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index cf94a02f..6b3c4b4c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -6,6 +6,7 @@ import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; @@ -17,6 +18,7 @@ public class EmailAuthEntity extends BaseTimeEntity { @Id @NotBlank private ObjectId _id; + @Indexed @NotBlank private String email; @@ -31,8 +33,9 @@ public EmailAuthEntity(String email, String authNum) { this.authNum = authNum; } - public void changeAuthNum(String authNum) { - this.authNum = authNum; + public void renewAuthNum(String newAuthNum) { + this.authNum = newAuthNum; + this.setUpdatedAt(); } public void verifyEmail() { @@ -40,13 +43,16 @@ public void verifyEmail() { } public void unVerifyEmail(){ - this.isVerified=false; + this.isVerified = false; } /** - * 10분 이상이면 만료됌 + * 10분의 만료 시간을 가짐 + * @return 인증 번호가 만료되었는지 여부 false면 만료되지 않음, true면 만료됨 */ public boolean isExpired() { - return getUpdatedAt().plusMinutes(10).isBefore(LocalDateTime.now()); + return getUpdatedAt() + .plusMinutes(10) + .isBefore(LocalDateTime.now()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java index 4a1702b2..284d5afc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java @@ -9,11 +9,6 @@ @Repository public interface EmailAuthRepository extends MongoRepository { - Optional findByEmail(String email); - Optional findByEmailAndAuthNum(String email, String authNum); - - Optional findByAuthNum(String authNum); - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java new file mode 100644 index 00000000..a14f2e0b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -0,0 +1,57 @@ +package inu.codin.codin.domain.email.service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +/** + * 이메일 템플릿 서비스 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EmailTemplateService { + private final JavaMailSender javaMailSender; + private final SpringTemplateEngine templateEngine; + + /** + * 템플릿을 사용해 이메일을 전송합니다. + * @param email 수신자 이메일 + * @param subject 이메일 제목 + * @param templateName 템플릿 이름 + * @param authNum 인증번호 + */ + @Async + public void sendTemplateEmail(String email, String subject, String templateName, String authNum) { + try { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); + + // 템플릿 컨텍스트 설정 + Context context = new Context(); + context.setVariable("authNum", authNum); + + // HTML 내용 생성 + String htmlContent = templateEngine.process(templateName, context); + + // 이메일 설정 + helper.setTo(email); + helper.setSubject(subject); + helper.setText(htmlContent, true); + + // 이메일 전송 + javaMailSender.send(message); + log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); + } catch (MessagingException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패, email: {}, template: {}", email, templateName, e); + throw new RuntimeException("이메일 전송에 실패했습니다.", e); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java new file mode 100644 index 00000000..4dc0a5af --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java @@ -0,0 +1,89 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * 회원가입 이메일 인증 서비스 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class JoinEmailAuthService { + private final EmailAuthRepository emailAuthRepository; + private final EmailTemplateService emailTemplateService; + private final AuthNumberGenerator authNumberGenerator; + + private static final String AUTH_EMAIL_SUBJECT = "[CODIN] 회원가입 인증번호입니다."; + private static final String AUTH_EMAIL_TEMPLATE = "auth-email"; + + /** + * 회원가입용 인증 이메일을 전송합니다. + * @param request 이메일 전송 요청 정보 + */ + @Transactional + public void sendJoinAuthEmail(JoinEmailSendRequestDto request) { + String email = request.getEmail(); + log.info("[sendJoinAuthEmail] email: {}", email); + + EmailAuthEntity emailAuthEntity = getOrCreateEmailAuth(email); + emailAuthRepository.save(emailAuthEntity); + + // 트랜잭션 완료 후 비동기 이메일 전송 + emailTemplateService.sendTemplateEmail(email, AUTH_EMAIL_SUBJECT, AUTH_EMAIL_TEMPLATE, emailAuthEntity.getAuthNum()); + } + + /** + * 회원가입용 이메일 인증번호를 검증합니다. + * @param request 인증번호 검증 요청 정보 + */ + @Transactional + public void checkJoinAuthEmail(JoinEmailCheckRequestDto request) { + String email = request.getEmail(); + String authNum = request.getAuthNum(); + + // 이메일과 인증번호가 일치하는지 확인 + EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) + .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", email)); + + // 만료 시간 검증 + if (emailAuthEntity.isExpired()) { + throw new EmailAuthFailException("인증번호가 만료되었습니다.", email); + } + + emailAuthEntity.verifyEmail(); + emailAuthRepository.save(emailAuthEntity); + + log.info("[checkJoinAuthEmail] 회원가입 이메일 인증 성공, email: {}", email); + } + + /** + * 기존 이메일 인증 정보를 조회하거나 새로 생성합니다. + */ + private EmailAuthEntity getOrCreateEmailAuth(String email) { + Optional existingAuth = emailAuthRepository.findByEmail(email); + + if (existingAuth.isPresent()) { + // 기존 인증번호 갱신 + EmailAuthEntity emailAuth = existingAuth.get(); + emailAuth.renewAuthNum(authNumberGenerator.generate()); + return emailAuth; + } else { + // 새 인증 정보 생성 + return EmailAuthEntity.builder() + .email(email) + .authNum(authNumberGenerator.generate()) + .build(); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java new file mode 100644 index 00000000..1423f3b7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -0,0 +1,76 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * 비밀번호 재설정 메일링 서비스 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class PasswordResetEmailService { + private final EmailAuthRepository emailAuthRepository; + private final EmailTemplateService emailTemplateService; + private final UserRepository userRepository; + private final AuthNumberGenerator authNumberGenerator; + + private static final String PASSWORD_EMAIL_SUBJECT = "[CODIN] 비밀번호 재설정 링크입니다."; + private static final String PASSWORD_EMAIL_TEMPLATE = "password-email"; + + /** + * 비밀번호 재설정용 이메일을 전송합니다. + * @param request 이메일 전송 요청 정보 + */ + @Transactional + public void sendPasswordResetEmail(JoinEmailSendRequestDto request) { + String email = request.getEmail(); + log.info("[sendPasswordResetEmail] email: {}", email); + + // 사용자 존재 여부 확인 + userRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException("회원가입을 먼저 진행해주세요.")); + + EmailAuthEntity emailAuthEntity = getOrCreatePasswordResetAuth(email); + emailAuthRepository.save(emailAuthEntity); + + // 트랜잭션 완료 후 비동기 이메일 전송 + emailTemplateService.sendTemplateEmail( + email, + PASSWORD_EMAIL_SUBJECT, + PASSWORD_EMAIL_TEMPLATE, + emailAuthEntity.getAuthNum() + ); + } + + /** + * 비밀번호 재설정용 인증 정보를 조회하거나 새로 생성합니다. + */ + private EmailAuthEntity getOrCreatePasswordResetAuth(String email) { + Optional existingAuth = emailAuthRepository.findByEmail(email); + + if (existingAuth.isPresent()) { + // 기존 인증번호 갱신 및 인증 상태 초기화 + EmailAuthEntity emailAuth = existingAuth.get(); + emailAuth.renewAuthNum(authNumberGenerator.generate()); + emailAuth.unVerifyEmail(); + return emailAuth; + } else { + // 새 인증 정보 생성 + return EmailAuthEntity.builder() + .email(email) + .authNum(authNumberGenerator.generate()) + .build(); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java b/codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java new file mode 100644 index 00000000..d2e167fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.email.util; + +import org.springframework.stereotype.Component; + +import java.util.UUID; + +/** + * 인증번호 생성 컴포넌트 + */ +@Component +public class AuthNumberGenerator { + + /** + * UUID 기반 8자리 랜덤 인증번호를 생성합니다. + * @return 8자리 대문자 인증번호 + */ + public String generate() { + return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + } +} From b5f30bbe3a9bba186fbb0d06f36c382aa8a957ce Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 23 Jun 2025 17:05:08 +0900 Subject: [PATCH 0752/1002] =?UTF-8?q?remove:=20EmailAuthService,=20EmailSe?= =?UTF-8?q?ndService=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/service/EmailAuthService.java | 116 ------------------ .../email/service/EmailSendService.java | 72 ----------- 2 files changed, 188 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java deleted file mode 100644 index 3e5fe55d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ /dev/null @@ -1,116 +0,0 @@ -package inu.codin.codin.domain.email.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; -import inu.codin.codin.domain.email.entity.EmailAuthEntity; -import inu.codin.codin.domain.email.exception.EmailAuthFailException; -import inu.codin.codin.domain.email.repository.EmailAuthRepository; -import inu.codin.codin.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.Optional; -import java.util.UUID; - -@Service -@RequiredArgsConstructor -@Slf4j -public class EmailAuthService { - - private final EmailAuthRepository emailAuthRepository; - private final EmailSendService emailSendService; - private final UserRepository userRepository; - - public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { - - String email = joinEmailSendRequestDto.getEmail(); - log.info("[sendAuthEmail] email : {}", email); - - Optional emailAuth = emailAuthRepository.findByEmail(email); - EmailAuthEntity emailAuthEntity; - - // 재인증 로직 - if (emailAuth.isPresent()) { - emailAuthEntity = emailAuth.get(); - -// // 이미 인증된 이메일 체크 -// if (emailAuthEntity.isVerified()) { -// throw new EmailAuthFailException("이미 인증된 이메일입니다.", email); -// } - - emailAuthEntity.changeAuthNum(generateAuthNum()); - } - else { - // 인증 생성 로직 - emailAuthEntity = EmailAuthEntity.builder() - .email(email) - .authNum(generateAuthNum()) - .build(); - } - emailAuthRepository.save(emailAuthEntity); - - // 비동기 이메일 전송 로직 - emailSendService.sendAuthEmail(email, emailAuthEntity.getAuthNum()); - } - - private String generateAuthNum() { - // 8자리 인증번호 생성 - return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); - } - - public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { - - checkEmailAndAuthNum(joinEmailCheckRequestDto); - } - - public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { - - userRepository.findByEmail(joinEmailSendRequestDto.getEmail()) - .orElseThrow(() -> new NotFoundException("회원가입을 먼저 진행해주세요.")); - - String email = joinEmailSendRequestDto.getEmail(); - log.info("[sendAuthEmail] email : {}", email); - - Optional emailAuth = emailAuthRepository.findByEmail(email); - EmailAuthEntity emailAuthEntity; - - // 재인증 로직 - if (emailAuth.isPresent()) { - emailAuthEntity = emailAuth.get(); - emailAuthEntity.changeAuthNum(generateAuthNum()); - emailAuthEntity.unVerifyEmail(); - } - else { - // 인증 생성 로직 - emailAuthEntity = EmailAuthEntity.builder() - .email(email) - .authNum(generateAuthNum()) - .build(); - } - emailAuthRepository.save(emailAuthEntity); - - // 비동기 이메일 전송 로직 - emailSendService.sendPasswordEmail(email, emailAuthEntity.getAuthNum()); - } - - private void checkEmailAndAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { - String email = joinEmailCheckRequestDto.getEmail(); - String authNum = joinEmailCheckRequestDto.getAuthNum(); - log.info("[checkAuthNum] email : {}, authNum : {}", email, authNum); - - // 이메일과 인증번호가 일치하는지 확인 - EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) - .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", email)); - - // 10분 이내에 인증하지 않을 시에 인증번호 만료 - if (emailAuthEntity.isExpired()) { - throw new EmailAuthFailException("인증번호가 만료되었습니다.", email); - } - - emailAuthEntity.verifyEmail(); - emailAuthRepository.save(emailAuthEntity); - log.info("[checkAuthNum] Email AUTH SUCCESS!!, email : {}", email); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java deleted file mode 100644 index f8691f5c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java +++ /dev/null @@ -1,72 +0,0 @@ -package inu.codin.codin.domain.email.service; - -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring6.SpringTemplateEngine; - -@Service -@RequiredArgsConstructor -@Slf4j -@Transactional(readOnly = true) -public class EmailSendService { - - private final JavaMailSender javaMailSender; - private final SpringTemplateEngine templateEngine; - - // 이메일 전송 로직 - // +템플릿 엔진으로 이메일 전송 추가 필요!! - @Async - public void sendAuthEmail(String email, String authNum) { - MimeMessage message = javaMailSender.createMimeMessage(); - try { - MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); - - Context context = new Context(); - context.setVariable("authNum", authNum); - - // 템플릿 엔진을 사용하여 HTML 내용을 생성 - String htmlContent = templateEngine.process("auth-email", context); - - helper.setTo(email); - helper.setSubject("[CODIN] 회원가입 인증번호입니다."); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); - } catch (MessagingException e) { - log.error("[sendAuthEmail] 인증 이메일 전송 실패, email : {}", email); - throw new RuntimeException(e); - } - } - - public void sendPasswordEmail(String email, String authNum) { - MimeMessage message = javaMailSender.createMimeMessage(); - try { - MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); - - Context context = new Context(); - context.setVariable("authNum", authNum); - - // 템플릿 엔진을 사용하여 HTML 내용을 생성 - String htmlContent = templateEngine.process("password-email", context); - - helper.setTo(email); - helper.setSubject("[CODIN] 비밀번호 재설정 링크입니다."); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); - } catch (MessagingException e) { - log.error("[sendAuthEmail] 인증 이메일 전송 실패, email : {}", email); - throw new RuntimeException(e); - } - } -} From cfd3102ca939c8466193cc19c692c156e03a1a61 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 24 Jun 2025 21:59:24 +0900 Subject: [PATCH 0753/1002] =?UTF-8?q?test:=20Email=20Service=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=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 아래와 같은 세가지에 대해서 테스트를 작성했습니다. - EmailTemplateServiceTest - JoinEmailAuthServiceTest - PasswordResetEmailServiceTest 테스트시 객체 생성 메서드가 없기에 두개의 DTO 클래스에 Builder를 추가했습니다. - JoinEmailCheckRequestDto - JoinEmailSendRequestDto --- .../email/dto/JoinEmailCheckRequestDto.java | 2 + .../email/dto/JoinEmailSendRequestDto.java | 2 + .../service/EmailTemplateServiceTest.java | 140 ++++++++++++++ .../service/JoinEmailAuthServiceTest.java | 179 ++++++++++++++++++ .../PasswordResetEmailServiceTest.java | 142 ++++++++++++++ 5 files changed, 465 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java index 6b396143..6d33bad5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Getter; /** @@ -10,6 +11,7 @@ * 해당 이메일로 전송된 인증 코드를 확인한다. */ @Getter +@Builder public class JoinEmailCheckRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java index 279e6ae8..94a1eb50 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Getter; /** @@ -10,6 +11,7 @@ * 해당 이메일로 인증 코드를 전송한다. */ @Getter +@Builder public class JoinEmailSendRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java new file mode 100644 index 00000000..cb7b26fc --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java @@ -0,0 +1,140 @@ +package inu.codin.codin.domain.email.service; + +import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.javamail.JavaMailSender; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class EmailTemplateServiceTest { + + @InjectMocks + EmailTemplateService emailTemplateService; + + @Mock + JavaMailSender javaMailSender; + @Mock + SpringTemplateEngine templateEngine; + @Mock + MimeMessage mimeMessage; + + private final String testEmail = "test@inu.ac.kr"; + private final String testSubject = "테스트 제목"; + private final String testTemplate = "test-template"; + private final String testAuthNum = "AUTH123"; + private final String testHtmlContent = "인증번호: AUTH123"; + + @BeforeEach + void setUp() { + when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 성공") + void sendTemplateEmail_성공() throws Exception { + // given + when(templateEngine.process(eq(testTemplate), any(Context.class))) + .thenReturn(testHtmlContent); + + // when + emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum); + + // then + verify(javaMailSender).createMimeMessage(); + verify(javaMailSender).send(mimeMessage); + verify(templateEngine).process(eq(testTemplate), any(Context.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 템플릿 엔진 컨텍스트 확인") + void sendTemplateEmail_템플릿엔진컨텍스트확인() throws Exception { + // given + ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(Context.class); + when(templateEngine.process(eq(testTemplate), contextCaptor.capture())) + .thenReturn(testHtmlContent); + + // when + emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum); + + // then + Context capturedContext = contextCaptor.getValue(); + assertNotNull(capturedContext); + verify(templateEngine).process(eq(testTemplate), any(Context.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - MimeMessage 생성 실패") + void sendTemplateEmail_MimeMessage생성실패() { + // given + when(javaMailSender.createMimeMessage()).thenThrow(new RuntimeException("MimeMessage 생성 실패")); + + // when & then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); + + assertEquals("MimeMessage 생성 실패", exception.getMessage()); + verify(javaMailSender, never()).send(any(MimeMessage.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 템플릿 처리 실패") + void sendTemplateEmail_템플릿처리실패() { + // given + when(templateEngine.process(eq(testTemplate), any(Context.class))) + .thenThrow(new RuntimeException("템플릿 처리 실패")); + + // when & then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); + + assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || + exception.getMessage().contains("템플릿 처리 실패")); + verify(javaMailSender, never()).send(any(MimeMessage.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 이메일 전송 실패") + void sendTemplateEmail_이메일전송실패() throws Exception { + // given + when(templateEngine.process(eq(testTemplate), any(Context.class))) + .thenReturn(testHtmlContent); + doThrow(new RuntimeException("이메일 전송 실패")) + .when(javaMailSender).send(mimeMessage); + + // when & then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); + + assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || + exception.getMessage().contains("이메일 전송 실패")); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 파라미터 검증") + void sendTemplateEmail_파라미터검증() throws Exception { + // given + when(templateEngine.process(anyString(), any(Context.class))) + .thenReturn(testHtmlContent); + + // when + emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum); + + // then + verify(templateEngine).process(eq(testTemplate), any(Context.class)); + verify(javaMailSender).send(mimeMessage); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java new file mode 100644 index 00000000..476b2ef6 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java @@ -0,0 +1,179 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class JoinEmailAuthServiceTest { + + @InjectMocks + JoinEmailAuthService joinEmailAuthService; + + @Mock + EmailAuthRepository emailAuthRepository; + + @Mock + EmailTemplateService emailTemplateService; + + @Mock + AuthNumberGenerator authNumberGenerator; + + private JoinEmailSendRequestDto sendRequestDto; + private JoinEmailCheckRequestDto checkRequestDto; + private EmailAuthEntity mockEmailAuth; + + @BeforeEach + void setUp() { + sendRequestDto = JoinEmailSendRequestDto.builder() + .email("test@inu.ac.kr") + .build(); + checkRequestDto = JoinEmailCheckRequestDto.builder() + .email("test@inu.ac.kr") + .authNum("AUTH123") + .build(); + mockEmailAuth = EmailAuthEntity.builder() + .email("test@inu.ac.kr") + .authNum("AUTH123") + .build(); + + // BaseTimeEntity의 기본 생성 객체가 없기 때문에 null pointer 에러 발생 + mockEmailAuth.setUpdatedAt(); + } + + @Test + @DisplayName("회원가입 인증 이메일 전송 - 성공 (신규 사용자)") + void sendJoinAuthEmail_성공_신규사용자() { + // given + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); + + // when + joinEmailAuthService.sendJoinAuthEmail(sendRequestDto); + + // then + ArgumentCaptor emailAuthCaptor = ArgumentCaptor.forClass(EmailAuthEntity.class); + verify(emailAuthRepository).save(emailAuthCaptor.capture()); + + EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); + assertEquals("test@inu.ac.kr", savedAuth.getEmail()); + assertEquals("NEWAUTH", savedAuth.getAuthNum()); + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 회원가입 인증번호입니다."), + eq("auth-email"), + eq("NEWAUTH") + ); + } + + @Test + @DisplayName("회원가입 인증 이메일 전송 - 성공 (기존 인증 정보 갱신)") + void sendJoinAuthEmail_성공_기존인증정보갱신() { + // given + EmailAuthEntity existingAuth = EmailAuthEntity.builder() + .email("test@inu.ac.kr") + .authNum("OLDAUTH") + .build(); + + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingAuth)); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); + + // when + joinEmailAuthService.sendJoinAuthEmail(sendRequestDto); + + // then + verify(emailAuthRepository).save(existingAuth); + assertEquals("NEWAUTH", existingAuth.getAuthNum()); + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 회원가입 인증번호입니다."), + eq("auth-email"), + eq("NEWAUTH") + ); + } + + @Test + @DisplayName("회원가입 이메일 인증 확인 - 성공") + void checkJoinAuthEmail_성공() { + // given + when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + .thenReturn(Optional.of(mockEmailAuth)); + + // when + joinEmailAuthService.checkJoinAuthEmail(checkRequestDto); + + // then + verify(emailAuthRepository).save(mockEmailAuth); + assertTrue(mockEmailAuth.isVerified()); + } + + @Test + @DisplayName("회원가입 이메일 인증 확인 - 실패 (인증번호 불일치)") + void checkJoinAuthEmail_실패_인증번호불일치() { + // given + when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + .thenReturn(Optional.empty()); + + // when & then + EmailAuthFailException exception = assertThrows(EmailAuthFailException.class, + () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); + + assertEquals("인증번호가 일치하지 않습니다.", exception.getMessage()); + assertEquals("test@inu.ac.kr", exception.getEmail()); + + verify(emailAuthRepository, never()).save(any()); + } + + @Test + @DisplayName("회원가입 이메일 인증 확인 - 실패 (인증번호 만료)") + void checkJoinAuthEmail_실패_인증번호만료() { + // given + EmailAuthEntity expiredAuth = spy(mockEmailAuth); + when(expiredAuth.isExpired()).thenReturn(true); + + when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + .thenReturn(Optional.of(expiredAuth)); + + // when & then + EmailAuthFailException exception = assertThrows(EmailAuthFailException.class, + () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); + + assertEquals("인증번호가 만료되었습니다.", exception.getMessage()); + assertEquals("test@inu.ac.kr", exception.getEmail()); + + verify(emailAuthRepository, never()).save(any()); + } + + @Test + @DisplayName("인증번호 생성기 호출 확인") + void authNumberGenerator_호출확인() { + // given + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("GENERATED"); + + // when + joinEmailAuthService.sendJoinAuthEmail(sendRequestDto); + + // then + verify(authNumberGenerator, times(1)).generate(); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java new file mode 100644 index 00000000..db8b1075 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java @@ -0,0 +1,142 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PasswordResetEmailServiceTest { + + @InjectMocks + PasswordResetEmailService passwordResetEmailService; + + @Mock + EmailAuthRepository emailAuthRepository; + + @Mock + EmailTemplateService emailTemplateService; + + @Mock + UserRepository userRepository; + + @Mock + AuthNumberGenerator authNumberGenerator; + + private JoinEmailSendRequestDto requestDto; + private UserEntity mockUser; + private EmailAuthEntity existingEmailAuth; + + @BeforeEach + void setUp() { + mockUser = mock(UserEntity.class); + requestDto = JoinEmailSendRequestDto.builder() + .email("test@inu.ac.kr") + .build(); + existingEmailAuth = EmailAuthEntity.builder() + .email("test@inu.ac.kr") + .authNum("OLDAUTH1") + .build(); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (신규 사용자)") + void sendPasswordResetEmail_성공_신규사용자() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH1"); + + // when + passwordResetEmailService.sendPasswordResetEmail(requestDto); + + // then + ArgumentCaptor emailAuthCaptor = ArgumentCaptor.forClass(EmailAuthEntity.class); + verify(emailAuthRepository).save(emailAuthCaptor.capture()); + + EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); + assertEquals("test@inu.ac.kr", savedAuth.getEmail()); + assertEquals("NEWAUTH1", savedAuth.getAuthNum()); + assertFalse(savedAuth.isVerified()); + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 비밀번호 재설정 링크입니다."), + eq("password-email"), + eq("NEWAUTH1") + ); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (기존 인증 정보 갱신)") + void sendPasswordResetEmail_성공_기존인증정보갱신() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingEmailAuth)); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH2"); + + // when + passwordResetEmailService.sendPasswordResetEmail(requestDto); + + // then + verify(emailAuthRepository).save(existingEmailAuth); + assertEquals("NEWAUTH2", existingEmailAuth.getAuthNum()); + assertFalse(existingEmailAuth.isVerified()); // 인증 상태 초기화 확인 + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 비밀번호 재설정 링크입니다."), + eq("password-email"), + eq("NEWAUTH2") + ); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 실패 (존재하지 않는 사용자)") + void sendPasswordResetEmail_실패_존재하지않는사용자() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + + // when & then + NotFoundException exception = assertThrows(NotFoundException.class, + () -> passwordResetEmailService.sendPasswordResetEmail(requestDto)); + + assertEquals("회원가입을 먼저 진행해주세요.", exception.getMessage()); + + verify(emailAuthRepository, never()).save(any()); + verify(emailTemplateService, never()).sendTemplateEmail(anyString(), anyString(), anyString(), anyString()); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 인증번호 생성기 호출 확인") + void sendPasswordResetEmail_인증번호생성기호출확인() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("GENERATED"); + + // when + passwordResetEmailService.sendPasswordResetEmail(requestDto); + + // then + verify(authNumberGenerator, times(1)).generate(); + } +} \ No newline at end of file From 9b1a1d2da66c83dac9022d229903d5fe2ce36a5f Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 12:28:38 +0900 Subject: [PATCH 0754/1002] =?UTF-8?q?refactor:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailController : 불필요한 공백 제거 - EmailTemplateService : 예외처리 추가 - EmailTemplateServiceTest : 가독성 개선 - JoinEmailAuthService : 잘못된 주석 제거와 가독성 향상 - PasswordResetEmailService : 잘못된 주석 제거 --- .../codin/domain/block/controller/BlockController.java | 1 - .../codin/domain/email/service/EmailTemplateService.java | 5 ++++- .../codin/domain/email/service/JoinEmailAuthService.java | 9 +++++++-- .../domain/email/service/PasswordResetEmailService.java | 1 - .../domain/email/service/EmailTemplateServiceTest.java | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java index 34c5bdb7..475b3471 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java @@ -12,7 +12,6 @@ @RestController @RequestMapping("/block") @RequiredArgsConstructor - @Tag(name = "Block API", description = "사용자 차단 기능") public class BlockController { private final BlockService blockService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java index a14f2e0b..b537a1a2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -50,8 +50,11 @@ public void sendTemplateEmail(String email, String subject, String templateName, javaMailSender.send(message); log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); } catch (MessagingException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패, email: {}, template: {}", email, templateName, e); + log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName, e); throw new RuntimeException("이메일 전송에 실패했습니다.", e); + } catch (RuntimeException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패 (RuntimeException), email: {}, template: {}", email, templateName, e); + throw new RuntimeException("이메일 전송 중 알 수 없는 오류가 발생했습니다.", e); } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java index 4dc0a5af..4b8f47cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java @@ -39,8 +39,12 @@ public void sendJoinAuthEmail(JoinEmailSendRequestDto request) { EmailAuthEntity emailAuthEntity = getOrCreateEmailAuth(email); emailAuthRepository.save(emailAuthEntity); - // 트랜잭션 완료 후 비동기 이메일 전송 - emailTemplateService.sendTemplateEmail(email, AUTH_EMAIL_SUBJECT, AUTH_EMAIL_TEMPLATE, emailAuthEntity.getAuthNum()); + emailTemplateService.sendTemplateEmail( + email, + AUTH_EMAIL_SUBJECT, + AUTH_EMAIL_TEMPLATE, + emailAuthEntity.getAuthNum() + ); } /** @@ -77,6 +81,7 @@ private EmailAuthEntity getOrCreateEmailAuth(String email) { // 기존 인증번호 갱신 EmailAuthEntity emailAuth = existingAuth.get(); emailAuth.renewAuthNum(authNumberGenerator.generate()); + emailAuth.unVerifyEmail(); return emailAuth; } else { // 새 인증 정보 생성 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java index 1423f3b7..bcb7a436 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -44,7 +44,6 @@ public void sendPasswordResetEmail(JoinEmailSendRequestDto request) { EmailAuthEntity emailAuthEntity = getOrCreatePasswordResetAuth(email); emailAuthRepository.save(emailAuthEntity); - // 트랜잭션 완료 후 비동기 이메일 전송 emailTemplateService.sendTemplateEmail( email, PASSWORD_EMAIL_SUBJECT, diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java index cb7b26fc..1b656412 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java @@ -36,7 +36,7 @@ class EmailTemplateServiceTest { private final String testSubject = "테스트 제목"; private final String testTemplate = "test-template"; private final String testAuthNum = "AUTH123"; - private final String testHtmlContent = "인증번호: AUTH123"; + private final String testHtmlContent = "인증번호: " + testAuthNum + ""; @BeforeEach void setUp() { From 0bcdaea3ccf96b2a035cefbb6fb000e26cbd7f07 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:02:53 +0900 Subject: [PATCH 0755/1002] =?UTF-8?q?refactor:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailTemplateFailException 으로 예외처리 추가 --- .../email/exception/EmailTemplateFailException.java | 9 +++++++++ .../domain/email/service/EmailTemplateService.java | 10 ++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java new file mode 100644 index 00000000..21187c55 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.email.exception; + +import jdk.jfr.StackTrace; + +public class EmailTemplateFailException extends RuntimeException { + public EmailTemplateFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java index b537a1a2..880e1dac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -1,9 +1,11 @@ package inu.codin.codin.domain.email.service; +import inu.codin.codin.domain.email.exception.EmailTemplateFailException; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; @@ -51,10 +53,10 @@ public void sendTemplateEmail(String email, String subject, String templateName, log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); } catch (MessagingException e) { log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName, e); - throw new RuntimeException("이메일 전송에 실패했습니다.", e); - } catch (RuntimeException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패 (RuntimeException), email: {}, template: {}", email, templateName, e); - throw new RuntimeException("이메일 전송 중 알 수 없는 오류가 발생했습니다.", e); + throw new EmailTemplateFailException("이메일 전송에 실패했습니다."); + } catch (MailException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패 (MailException), email: {}, template: {}", email, templateName, e); + throw new EmailTemplateFailException("이메일 전송 중 알 수 없는 오류가 발생했습니다."); } } } From e70c3900cdc146edb174f017b2d30ede3b79ee5a Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:06:51 +0900 Subject: [PATCH 0756/1002] =?UTF-8?q?fixup!=20refactor:=20#207=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/email/service/EmailTemplateService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java index 880e1dac..f0915166 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -11,6 +11,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.thymeleaf.context.Context; +import org.thymeleaf.exceptions.TemplateEngineException; import org.thymeleaf.spring6.SpringTemplateEngine; /** @@ -52,11 +53,14 @@ public void sendTemplateEmail(String email, String subject, String templateName, javaMailSender.send(message); log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); } catch (MessagingException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName, e); + log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName); throw new EmailTemplateFailException("이메일 전송에 실패했습니다."); } catch (MailException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패 (MailException), email: {}, template: {}", email, templateName, e); + log.error("[sendTemplateEmail] 이메일 전송 실패 (MailException), email: {}, template: {}", email, templateName); throw new EmailTemplateFailException("이메일 전송 중 알 수 없는 오류가 발생했습니다."); + } catch (TemplateEngineException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패 (TemplateEngineException), email: {}, template: {}", email, templateName); + throw new EmailTemplateFailException("템플릿 엔진 오류가 발생했습니다."); } } } From adf10d383c7e7f26b70538679b5da86a42161b83 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:12:02 +0900 Subject: [PATCH 0757/1002] =?UTF-8?q?refactor:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 비밀번호 재인증 로직 중 기존 인증정보가 존재하지 않을 때에는 예외처리 - EmailPasswordResetFailException 으로 예외처리 추가 --- .../email/exception/EmailPasswordResetFailException.java | 7 +++++++ .../domain/email/service/PasswordResetEmailService.java | 8 +++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java new file mode 100644 index 00000000..7a08982e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.email.exception; + +public class EmailPasswordResetFailException extends RuntimeException { + public EmailPasswordResetFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java index bcb7a436..82e676bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.email.util.AuthNumberGenerator; import inu.codin.codin.domain.user.repository.UserRepository; @@ -65,11 +66,8 @@ private EmailAuthEntity getOrCreatePasswordResetAuth(String email) { emailAuth.unVerifyEmail(); return emailAuth; } else { - // 새 인증 정보 생성 - return EmailAuthEntity.builder() - .email(email) - .authNum(authNumberGenerator.generate()) - .build(); + log.error("[getOrCreatePasswordResetAuth] 등록되지 않은 사용자에 대한 비밀번호 초기화 요청, email: {}", email); + throw new EmailPasswordResetFailException("등록되지 않은 사용자 이메일입니다."); } } } From 3551fa2dde0773d1074dceb049775f93f0faecab Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:35:01 +0900 Subject: [PATCH 0758/1002] =?UTF-8?q?test:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수정된 기능 상황에 맞게 테스트 수정 --- .../service/JoinEmailAuthServiceTest.java | 39 +++++------ .../PasswordResetEmailServiceTest.java | 64 ++++++++----------- 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java index 476b2ef6..cd4995c3 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java @@ -29,10 +29,8 @@ class JoinEmailAuthServiceTest { @Mock EmailAuthRepository emailAuthRepository; - @Mock EmailTemplateService emailTemplateService; - @Mock AuthNumberGenerator authNumberGenerator; @@ -40,18 +38,21 @@ class JoinEmailAuthServiceTest { private JoinEmailCheckRequestDto checkRequestDto; private EmailAuthEntity mockEmailAuth; + private final String testMail = "test@inu.ac.kr"; + private final String testAuthNum = "AUTH123"; + @BeforeEach void setUp() { sendRequestDto = JoinEmailSendRequestDto.builder() - .email("test@inu.ac.kr") + .email(testMail) .build(); checkRequestDto = JoinEmailCheckRequestDto.builder() - .email("test@inu.ac.kr") - .authNum("AUTH123") + .email(testMail) + .authNum(testAuthNum) .build(); mockEmailAuth = EmailAuthEntity.builder() - .email("test@inu.ac.kr") - .authNum("AUTH123") + .email(testMail) + .authNum(testAuthNum) .build(); // BaseTimeEntity의 기본 생성 객체가 없기 때문에 null pointer 에러 발생 @@ -62,7 +63,7 @@ void setUp() { @DisplayName("회원가입 인증 이메일 전송 - 성공 (신규 사용자)") void sendJoinAuthEmail_성공_신규사용자() { // given - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.empty()); when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); // when @@ -73,11 +74,11 @@ void setUp() { verify(emailAuthRepository).save(emailAuthCaptor.capture()); EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); - assertEquals("test@inu.ac.kr", savedAuth.getEmail()); + assertEquals(testMail, savedAuth.getEmail()); assertEquals("NEWAUTH", savedAuth.getAuthNum()); verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), + eq(testMail), eq("[CODIN] 회원가입 인증번호입니다."), eq("auth-email"), eq("NEWAUTH") @@ -89,11 +90,11 @@ void setUp() { void sendJoinAuthEmail_성공_기존인증정보갱신() { // given EmailAuthEntity existingAuth = EmailAuthEntity.builder() - .email("test@inu.ac.kr") + .email(testMail) .authNum("OLDAUTH") .build(); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingAuth)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.of(existingAuth)); when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); // when @@ -104,7 +105,7 @@ void setUp() { assertEquals("NEWAUTH", existingAuth.getAuthNum()); verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), + eq(testMail), eq("[CODIN] 회원가입 인증번호입니다."), eq("auth-email"), eq("NEWAUTH") @@ -115,7 +116,7 @@ void setUp() { @DisplayName("회원가입 이메일 인증 확인 - 성공") void checkJoinAuthEmail_성공() { // given - when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + when(emailAuthRepository.findByEmailAndAuthNum(testMail, testAuthNum)) .thenReturn(Optional.of(mockEmailAuth)); // when @@ -130,7 +131,7 @@ void setUp() { @DisplayName("회원가입 이메일 인증 확인 - 실패 (인증번호 불일치)") void checkJoinAuthEmail_실패_인증번호불일치() { // given - when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + when(emailAuthRepository.findByEmailAndAuthNum(testMail, testAuthNum)) .thenReturn(Optional.empty()); // when & then @@ -138,7 +139,7 @@ void setUp() { () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); assertEquals("인증번호가 일치하지 않습니다.", exception.getMessage()); - assertEquals("test@inu.ac.kr", exception.getEmail()); + assertEquals(testMail, exception.getEmail()); verify(emailAuthRepository, never()).save(any()); } @@ -150,7 +151,7 @@ void setUp() { EmailAuthEntity expiredAuth = spy(mockEmailAuth); when(expiredAuth.isExpired()).thenReturn(true); - when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + when(emailAuthRepository.findByEmailAndAuthNum(testMail, testAuthNum)) .thenReturn(Optional.of(expiredAuth)); // when & then @@ -158,7 +159,7 @@ void setUp() { () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); assertEquals("인증번호가 만료되었습니다.", exception.getMessage()); - assertEquals("test@inu.ac.kr", exception.getEmail()); + assertEquals(testMail, exception.getEmail()); verify(emailAuthRepository, never()).save(any()); } @@ -167,7 +168,7 @@ void setUp() { @DisplayName("인증번호 생성기 호출 확인") void authNumberGenerator_호출확인() { // given - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.empty()); when(authNumberGenerator.generate()).thenReturn("GENERATED"); // when diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java index db8b1075..ede28705 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.email.util.AuthNumberGenerator; import inu.codin.codin.domain.user.entity.UserEntity; @@ -11,7 +12,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -31,13 +31,10 @@ class PasswordResetEmailServiceTest { @Mock EmailAuthRepository emailAuthRepository; - @Mock EmailTemplateService emailTemplateService; - @Mock UserRepository userRepository; - @Mock AuthNumberGenerator authNumberGenerator; @@ -45,67 +42,58 @@ class PasswordResetEmailServiceTest { private UserEntity mockUser; private EmailAuthEntity existingEmailAuth; + private final String testMail = "test@inu.ac.kr"; + private final String testAuthNum = "AUTH123"; + @BeforeEach void setUp() { mockUser = mock(UserEntity.class); requestDto = JoinEmailSendRequestDto.builder() - .email("test@inu.ac.kr") + .email(testMail) .build(); existingEmailAuth = EmailAuthEntity.builder() - .email("test@inu.ac.kr") - .authNum("OLDAUTH1") + .email(testMail) + .authNum(testAuthNum) .build(); } @Test - @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (신규 사용자)") + @DisplayName("비밀번호 재설정 이메일 전송 - 실패 (등록되지 않은 사용자)") void sendPasswordResetEmail_성공_신규사용자() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); - when(authNumberGenerator.generate()).thenReturn("NEWAUTH1"); - - // when - passwordResetEmailService.sendPasswordResetEmail(requestDto); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.empty()); - // then - ArgumentCaptor emailAuthCaptor = ArgumentCaptor.forClass(EmailAuthEntity.class); - verify(emailAuthRepository).save(emailAuthCaptor.capture()); - - EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); - assertEquals("test@inu.ac.kr", savedAuth.getEmail()); - assertEquals("NEWAUTH1", savedAuth.getAuthNum()); - assertFalse(savedAuth.isVerified()); + // when & then + EmailPasswordResetFailException exception = assertThrows(EmailPasswordResetFailException.class, + () -> passwordResetEmailService.sendPasswordResetEmail(requestDto)); - verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), - eq("[CODIN] 비밀번호 재설정 링크입니다."), - eq("password-email"), - eq("NEWAUTH1") - ); + verify(emailAuthRepository, never()).save(any()); + verify(emailTemplateService, never()).sendTemplateEmail(anyString(), anyString(), anyString(), anyString()); + verify(authNumberGenerator, never()).generate(); } @Test @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (기존 인증 정보 갱신)") void sendPasswordResetEmail_성공_기존인증정보갱신() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingEmailAuth)); - when(authNumberGenerator.generate()).thenReturn("NEWAUTH2"); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.of(existingEmailAuth)); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); // when passwordResetEmailService.sendPasswordResetEmail(requestDto); // then verify(emailAuthRepository).save(existingEmailAuth); - assertEquals("NEWAUTH2", existingEmailAuth.getAuthNum()); + assertEquals("NEWAUTH", existingEmailAuth.getAuthNum()); assertFalse(existingEmailAuth.isVerified()); // 인증 상태 초기화 확인 verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), + eq(testMail), eq("[CODIN] 비밀번호 재설정 링크입니다."), eq("password-email"), - eq("NEWAUTH2") + eq("NEWAUTH") ); } @@ -113,7 +101,7 @@ void setUp() { @DisplayName("비밀번호 재설정 이메일 전송 - 실패 (존재하지 않는 사용자)") void sendPasswordResetEmail_실패_존재하지않는사용자() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.empty()); // when & then NotFoundException exception = assertThrows(NotFoundException.class, @@ -129,9 +117,9 @@ void setUp() { @DisplayName("비밀번호 재설정 이메일 전송 - 인증번호 생성기 호출 확인") void sendPasswordResetEmail_인증번호생성기호출확인() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); - when(authNumberGenerator.generate()).thenReturn("GENERATED"); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.of(existingEmailAuth)); + when(authNumberGenerator.generate()).thenReturn(testAuthNum); // when passwordResetEmailService.sendPasswordResetEmail(requestDto); From 8c63c17a41cfa390d7a6ae62a605398323779fe3 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 19:56:24 +0900 Subject: [PATCH 0759/1002] =?UTF-8?q?test:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailTemplateFailException : import 최적화 - EmailTemplateFailException : 테스트 예외처리 수정 --- .../exception/EmailTemplateFailException.java | 2 -- .../service/EmailTemplateServiceTest.java | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java index 21187c55..41596fa1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java @@ -1,7 +1,5 @@ package inu.codin.codin.domain.email.exception; -import jdk.jfr.StackTrace; - public class EmailTemplateFailException extends RuntimeException { public EmailTemplateFailException(String message) { super(message); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java index 1b656412..38277b4f 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.email.service; +import inu.codin.codin.domain.email.exception.EmailTemplateFailException; import jakarta.mail.internet.MimeMessage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -45,7 +46,7 @@ void setUp() { @Test @DisplayName("템플릿 이메일 전송 - 성공") - void sendTemplateEmail_성공() throws Exception { + void sendTemplateEmail_성공() { // given when(templateEngine.process(eq(testTemplate), any(Context.class))) .thenReturn(testHtmlContent); @@ -61,7 +62,7 @@ void setUp() { @Test @DisplayName("템플릿 이메일 전송 - 템플릿 엔진 컨텍스트 확인") - void sendTemplateEmail_템플릿엔진컨텍스트확인() throws Exception { + void sendTemplateEmail_템플릿엔진컨텍스트확인() { // given ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(Context.class); when(templateEngine.process(eq(testTemplate), contextCaptor.capture())) @@ -80,10 +81,10 @@ void setUp() { @DisplayName("템플릿 이메일 전송 - MimeMessage 생성 실패") void sendTemplateEmail_MimeMessage생성실패() { // given - when(javaMailSender.createMimeMessage()).thenThrow(new RuntimeException("MimeMessage 생성 실패")); + when(javaMailSender.createMimeMessage()).thenThrow(new EmailTemplateFailException("MimeMessage 생성 실패")); // when & then - RuntimeException exception = assertThrows(RuntimeException.class, + RuntimeException exception = assertThrows(RuntimeException.class, () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); assertEquals("MimeMessage 생성 실패", exception.getMessage()); @@ -95,37 +96,35 @@ void setUp() { void sendTemplateEmail_템플릿처리실패() { // given when(templateEngine.process(eq(testTemplate), any(Context.class))) - .thenThrow(new RuntimeException("템플릿 처리 실패")); + .thenThrow(new EmailTemplateFailException("템플릿 처리 실패")); // when & then - RuntimeException exception = assertThrows(RuntimeException.class, + RuntimeException exception = assertThrows(RuntimeException.class, () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); - assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || - exception.getMessage().contains("템플릿 처리 실패")); + assertTrue(exception.getMessage().contains("템플릿 처리 실패")); verify(javaMailSender, never()).send(any(MimeMessage.class)); } @Test @DisplayName("템플릿 이메일 전송 - 이메일 전송 실패") - void sendTemplateEmail_이메일전송실패() throws Exception { + void sendTemplateEmail_이메일전송실패() { // given when(templateEngine.process(eq(testTemplate), any(Context.class))) .thenReturn(testHtmlContent); - doThrow(new RuntimeException("이메일 전송 실패")) + doThrow(new EmailTemplateFailException("이메일 전송 실패")) .when(javaMailSender).send(mimeMessage); // when & then - RuntimeException exception = assertThrows(RuntimeException.class, + RuntimeException exception = assertThrows(RuntimeException.class, () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); - assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || - exception.getMessage().contains("이메일 전송 실패")); + assertTrue(exception.getMessage().contains("이메일 전송 실패")); } @Test @DisplayName("템플릿 이메일 전송 - 파라미터 검증") - void sendTemplateEmail_파라미터검증() throws Exception { + void sendTemplateEmail_파라미터검증() { // given when(templateEngine.process(anyString(), any(Context.class))) .thenReturn(testHtmlContent); From 031ec49cf97ddf074d086da1b0d6a3000fabe60a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Jun 2025 23:44:23 +0900 Subject: [PATCH 0760/1002] =?UTF-8?q?fix=20:=20GlobalException,=20ErrorCod?= =?UTF-8?q?e=20interface=20=EC=84=A4=EC=A0=95.=20ChatException=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalErrorCode.java | 9 ++++++ .../common/exception/GlobalException.java | 14 ++++++++++ .../exception/GlobalExceptionHandler.java | 27 ++++++++++++++---- .../chat/exception/ChatRoomErrorCode.java | 26 +++++++++++++++++ .../chat/exception/ChatRoomException.java | 14 ++++++++++ .../chat/exception/ChattingErrorCode.java | 28 +++++++++++++++++++ .../chat/exception/ChattingException.java | 16 +++++++++++ 7 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java new file mode 100644 index 00000000..875f791f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java @@ -0,0 +1,9 @@ +package inu.codin.codin.common.exception; + +import org.springframework.http.HttpStatus; + +public interface GlobalErrorCode { + HttpStatus httpStatus(); + + String message(); +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java new file mode 100644 index 00000000..8e9bcdd5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java @@ -0,0 +1,14 @@ +package inu.codin.codin.common.exception; + +import lombok.Getter; + +@Getter +public class GlobalException extends RuntimeException { + + private final GlobalErrorCode errorCode; + public GlobalException(GlobalErrorCode errorCode){ + super(errorCode.message()); + this.errorCode = errorCode; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 2845ff24..8716ae3f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,7 +2,9 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; +import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; +import inu.codin.codin.domain.chat.exception.ChatRoomException; +import inu.codin.codin.domain.chat.exception.ChatRoomExistedException; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; @@ -22,6 +24,23 @@ protected ResponseEntity handleException(Exception e) { .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); } + @ExceptionHandler(GlobalException.class) + protected ResponseEntity handleGlobalException(GlobalException e) { + GlobalErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + + @ExceptionHandler(ChatRoomException.class) + protected ResponseEntity handleChatRoomException(ChatRoomException e) { + ChatRoomErrorCode code = e.getErrorCode(); + String message = code.message(); + if (e instanceof ChatRoomExistedException existedException) //클라이언트 측에서 받아야 하는 chatroomId를 포함해서 전달 + message = message + "/" + existedException.getChatRoomId(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(message, code.httpStatus().value())); + } + @ExceptionHandler(NotFoundException.class) protected ResponseEntity handleNotFoundException(NotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -43,11 +62,7 @@ protected ResponseEntity handleValidationExceptions(MethodArg return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST.value())); } - @ExceptionHandler(ChatRoomExistedException.class) - protected ResponseEntity handleChatRoomExistedException(ChatRoomExistedException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new ExceptionResponse(e.getMessage() +"/"+ e.getChatRoomId(), e.getErrorCode())); - } + @ExceptionHandler(RedisSystemException.class) public ResponseEntity handleRedisSystemException(RedisSystemException e){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java new file mode 100644 index 00000000..718f18fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum ChatRoomErrorCode implements GlobalErrorCode { + CHATROOM_CREATE_MYSELF(HttpStatus.BAD_REQUEST, "자기 자신과는 채팅방을 생성할 수 없습니다."), + CHATROOM_EXISTED(HttpStatus.valueOf(303), "해당 reference에서 시작된 채팅방이 존재합니다."), + CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND, "채팅방을 찾을 수 없습니다."), + PARTICIPANTS_NOT_FOUND(HttpStatus.NOT_FOUND, "채팅방 내의 참여자를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java new file mode 100644 index 00000000..325c0cbe --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class ChatRoomException extends GlobalException { + + private final ChatRoomErrorCode errorCode; + public ChatRoomException(ChatRoomErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java new file mode 100644 index 00000000..eb42d415 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ChattingErrorCode implements GlobalErrorCode { + + CHATTING_USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "헤더에서 유저(email)을 찾을 수 없습니다."), + CHATTING_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "헤더에서 채팅방 _id를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + + private final String message; + + @Override + public HttpStatus httpStatus() { + return null; + } + + @Override + public String message() { + return null; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java new file mode 100644 index 00000000..e78cc1d2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class ChattingException extends GlobalException { + + private final ChattingErrorCode errorCode; + private final String sessionId; + public ChattingException(ChattingErrorCode errorCode, String sessionId) { + super(errorCode); + this.errorCode = errorCode; + this.sessionId = sessionId; + } +} From 2ff5a043f79eace1b48ac20f686110be6f06562a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Jun 2025 23:51:59 +0900 Subject: [PATCH 0761/1002] =?UTF-8?q?fix=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Getter=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20GlobalExc?= =?UTF-8?q?eptionHandler=EC=97=90=20ChattingException=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalExceptionHandler.java | 13 +++++++++---- .../domain/chat/exception/ChattingErrorCode.java | 6 ++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 8716ae3f..2fb9dca9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,9 +2,7 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; -import inu.codin.codin.domain.chat.exception.ChatRoomException; -import inu.codin.codin.domain.chat.exception.ChatRoomExistedException; +import inu.codin.codin.domain.chat.exception.*; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; @@ -35,12 +33,19 @@ protected ResponseEntity handleGlobalException(GlobalExceptio protected ResponseEntity handleChatRoomException(ChatRoomException e) { ChatRoomErrorCode code = e.getErrorCode(); String message = code.message(); - if (e instanceof ChatRoomExistedException existedException) //클라이언트 측에서 받아야 하는 chatroomId를 포함해서 전달 + if (e instanceof ChatRoomExistedException existedException) //client 측에서 303 상태 코드 확인 후 message의 chatRoomId로 리다이렉션 message = message + "/" + existedException.getChatRoomId(); return ResponseEntity.status(code.httpStatus()) .body(new ExceptionResponse(message, code.httpStatus().value())); } + @ExceptionHandler(ChattingException.class) + protected ResponseEntity handleChattingException(ChattingException e) { + ChattingErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + @ExceptionHandler(NotFoundException.class) protected ResponseEntity handleNotFoundException(NotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java index eb42d415..3bf11950 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java @@ -1,11 +1,9 @@ package inu.codin.codin.domain.chat.exception; import inu.codin.codin.common.exception.GlobalErrorCode; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -@Getter @RequiredArgsConstructor public enum ChattingErrorCode implements GlobalErrorCode { @@ -18,11 +16,11 @@ public enum ChattingErrorCode implements GlobalErrorCode { @Override public HttpStatus httpStatus() { - return null; + return httpStatus; } @Override public String message() { - return null; + return message; } } From 67d029302593b9ca605426159dfa4fab990d82ed Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 00:39:01 +0900 Subject: [PATCH 0762/1002] =?UTF-8?q?refactor:=20ObjectIdUtil=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/util/ObjectIdUtil.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java b/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java new file mode 100644 index 00000000..869d9ad2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java @@ -0,0 +1,39 @@ +package inu.codin.codin.common.util; + +import org.bson.types.ObjectId; + +public class ObjectIdUtil { + private ObjectIdUtil() {} + + /** + * String을 ObjectId로 변환 + * @param objectIdString ObjectId 문자열 + * @return ObjectId 객체 + * @throws IllegalArgumentException 유효하지 않은 ObjectId 형식인 경우 + */ + public static ObjectId toObjectId(String objectIdString) { + try { + return new ObjectId(objectIdString); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("유효하지 않은 ObjectId 형식입니다: " + objectIdString, e); + } + } + + /** + * ObjectId를 String으로 변환 + * @param objectId ObjectId 객체 + * @return ObjectId 문자열 + */ + public static String toString(ObjectId objectId) { + return objectId != null ? objectId.toHexString() : null; + } + + /** + * ObjectId 유효성 검증 + * @param objectIdString 검증할 문자열 + * @return 유효한 ObjectId 형식이면 true + */ + public static boolean isValid(String objectIdString) { + return ObjectId.isValid(objectIdString); + } +} From 1eebbadf4c718ab5346056f9f2d4b752b0c1f424 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 00:45:59 +0900 Subject: [PATCH 0763/1002] =?UTF-8?q?style=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=9D=BC=EC=B9=98=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/exception/GlobalErrorCode.java | 1 - .../java/inu/codin/codin/common/exception/GlobalException.java | 1 - 2 files changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java index 875f791f..70c3f2f2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java @@ -4,6 +4,5 @@ public interface GlobalErrorCode { HttpStatus httpStatus(); - String message(); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java index 8e9bcdd5..6b2c2932 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java @@ -10,5 +10,4 @@ public GlobalException(GlobalErrorCode errorCode){ super(errorCode.message()); this.errorCode = errorCode; } - } From 9b4d977d10f9008064bb5ccc57be26fab130deef Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 00:46:25 +0900 Subject: [PATCH 0764/1002] =?UTF-8?q?refactor:=20Block=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20#21?= =?UTF-8?q?4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BlockController를 제외한 Block Domain 리팩토링 - User Domain에 UserValidator 추가#214 --- .../domain/block/entity/BlockEntity.java | 45 +++++++-- .../block/repository/BlockRepository.java | 11 +-- .../domain/block/service/BlockService.java | 95 ++++++++----------- .../codin/domain/user/entity/UserEntity.java | 8 +- .../domain/user/validator/UserValidator.java | 23 +++++ 5 files changed, 105 insertions(+), 77 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 74aa2bb7..a27c52eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,25 +1,56 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.ArrayList; +import java.util.List; + @Getter @Document(collection = "blocks") public class BlockEntity extends BaseTimeEntity { + @Id - private ObjectId id; // MongoDB의 기본 ID + private ObjectId id; + + @Indexed + private ObjectId userId; - private ObjectId blockingUserId; // 차단한 사용자 - private ObjectId blockedUserId; // 차단된 사용자 + private List blockedUsers = new ArrayList<>(); @Builder - public BlockEntity(ObjectId blockingUserId, ObjectId blockedUserId) { - this.blockingUserId = blockingUserId; - this.blockedUserId = blockedUserId; + public BlockEntity(ObjectId userId, ObjectId blockedUser) { + this.userId = userId; + this.blockedUsers.add(blockedUser); + } + + public static BlockEntity ofNew(ObjectId userId) { + return BlockEntity.builder() + .userId(userId) + .build(); + } + + public BlockEntity addBlockedUser(ObjectId blockedUser) { + if (this.blockedUsers.contains(blockedUser)) { + throw new AlreadyBlockedException("이미 차단한 유저입니다."); + } + this.blockedUsers.add(blockedUser); + return this; + } + + public BlockEntity removeBockedUser(ObjectId blockedUser) { + if (this.blockedUsers.contains(blockedUser)) { + this.blockedUsers.remove(blockedUser); + return this; + } + throw new NotFoundException("차단 목록에 없는 유저입니다."); } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java index 9b01adac..813af7db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -10,10 +11,6 @@ @Repository public interface BlockRepository extends MongoRepository { - - boolean existsByBlockingUserIdAndBlockedUserId(ObjectId blockingUserId, ObjectId blockedUserId); - Optional findByBlockingUserIdAndBlockedUserId(ObjectId blockingUserId, ObjectId blockedUserId); - -} - - + @Query("{'_id': ?0, 'deletedAt': null}") + Optional findByUserId(ObjectId userId); +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 3fe064b5..8402a211 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -2,13 +2,11 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; -import inu.codin.codin.domain.block.exception.AlreadyBlockedException; -import inu.codin.codin.domain.block.exception.NotBlockedException; import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.validator.UserValidator; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; @@ -19,70 +17,55 @@ @RequiredArgsConstructor public class BlockService { private final BlockRepository blockRepository; - private final UserRepository userRepository; + private final UserValidator userValidator; + /** + * 유저 차단 + * SecurityContextHolder에서 현재 유저를 가져옴 + * @param strBlockedUserId + */ public void blockUser(String strBlockedUserId) { - ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); - if (blockingUserId.equals(new ObjectId(strBlockedUserId))){ - throw new SelfBlockedException("자기 자신을 차단할 수 없습니다."); + if (userId.equals(blockedId)) { + throw new SelfBlockedException("자신을 차단할 수 없습니다."); } - // 유저 엔티티 조회 - UserEntity user = userRepository.findById(blockingUserId) - .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); - - - ObjectId blockedUserId = new ObjectId(strBlockedUserId); - userRepository.findById(blockedUserId) - .orElseThrow(() -> new NotFoundException("차단할 사용자를 찾을 수 없습니다.")); - - // 중복 차단 방지 - checkUserBlocked(blockingUserId,blockedUserId); - - - // 차단 정보 저 - BlockEntity block = BlockEntity.builder() - .blockingUserId(blockingUserId) - .blockedUserId(blockedUserId) - .build(); - - blockRepository.save(block); + userValidator.validateUserExists(userId, "차단하는 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(blockedId, "차단할 사용자를 찾을 수 없습니다."); - //유저 차단 리스트에 추가 ( 조회 필터링 목적) - user.getBlockedUsers().add(blockedUserId); - userRepository.save(user); + blockRepository.save(blockRepository.findByUserId(userId) + .orElseGet(() -> BlockEntity.ofNew(userId)) + .addBlockedUser(blockedId)); } + /** + * 유저 차단해제 + * SecurityContextHolder에서 현재 유저를 가져옴 + * @param strBlockedUserId 차단 해제할 유저 + */ public void unblockUser(String strBlockedUserId) { - ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); - // 유저 엔티티 조회 - UserEntity user = userRepository.findById(blockingUserId) - .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); - ObjectId blockedUserId = new ObjectId(strBlockedUserId); - // 차단 정보 조회 - BlockEntity block = blockRepository.findByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId) - .orElseThrow(() -> new NotBlockedException("차단되지 않은 사용자입니다.")); - - // 차단 해제 - blockRepository.delete(block); - - - //유저 차단 리스트에서 삭제 ( 조회 필터링 목적) - user.getBlockedUsers().remove(blockedUserId); - userRepository.save(user); - } - - public void checkUserBlocked(ObjectId blockingUserId, ObjectId blockedUserId) { - if (blockRepository.existsByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId)) { - throw new AlreadyBlockedException("이미 상대방을 차단했습니다."); + if (userId.equals(blockedId)) { + throw new SelfBlockedException("자신을 차단 해제할 수 없습니다."); } + userValidator.validateUserExists(userId, "차단 해제하는 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(blockedId, "차단 해제할 사용자를 찾을 수 없습니다."); + + blockRepository.save(blockRepository.findByUserId(userId) + .orElseThrow(() -> new NotFoundException("차단 정보가 존재하지 않습니다.")) + .removeBockedUser(blockedId)); } + /** + * 현재 유저의 차단된 유저 목록 반환 + * @return 차단한 유저 목록 (빈 리스트가 제공될 수 있음) + */ public List getBlockedUsers() { - ObjectId userId = SecurityUtils.getCurrentUserId(); - return userRepository.findById(userId) - .map(UserEntity::getBlockedUsers) - .orElseThrow(() -> new NotFoundException("존재하지 않는 회원입니다.")); + return blockRepository.findByUserId(SecurityUtils.getCurrentUserId()) + .map(BlockEntity::getBlockedUsers) + .orElse(List.of()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 02fd094e..eed72fd9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -12,8 +12,6 @@ import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; @Document(collection = "users") @Getter @@ -46,12 +44,10 @@ public class UserEntity extends BaseTimeEntity { private LocalDateTime totalSuspensionEndDate; //정지 게시물이 늘어날수록 정지 종료일이 중첩 - private List blockedUsers = new ArrayList<>(); - private NotificationPreference notificationPreference = new NotificationPreference(); @Builder - public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status, List blockedUsers) { + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status) { this.email = email; this.password = password; this.studentId = studentId; @@ -63,7 +59,6 @@ public UserEntity(String email, String password, String studentId, String name, this.undergraduate = undergraduate; this.role = role; this.status = status; - this.blockedUsers = (blockedUsers != null) ? blockedUsers : new ArrayList<>(); // ✅ 기본값 설정 } public void updateNickname(String nickname) { @@ -87,7 +82,6 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .profileImageUrl("") .role(UserRole.USER) .status(UserStatus.ACTIVE) - .blockedUsers(new ArrayList<>()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java new file mode 100644 index 00000000..3bb7a5b3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.user.validator; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class UserValidator { + private UserRepository userRepository; + + /** + * User 존재 여부 검증 + * @param userId 존재 검증할 userId - 삭제된 유저는 검색되지 않음 + * @param exceptionMsg Exception 메세지 + */ + public void validateUserExists(ObjectId userId, String exceptionMsg) { + userRepository.findByUserId(userId) + .orElseThrow(() -> new NotFoundException(exceptionMsg)); + } +} \ No newline at end of file From c460848dc852a98ba0a1e6bb7f91a62525464313 Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 23 Jun 2025 17:04:43 +0900 Subject: [PATCH 0765/1002] =?UTF-8?q?refactor:=20Email=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailAuthService와 EmailSendService 계층 분리 - 과거 EmailAuthService을 JoinEmailAuthService와 PasswordResetEmailService로 책임 분리 - EmailSendService에서 EmailTemplateService로 변경을 통해 이메일 전송 역할 명확화 --- .../email/controller/EmailController.java | 13 +-- .../domain/email/entity/EmailAuthEntity.java | 16 ++-- .../email/repository/EmailAuthRepository.java | 5 -- .../email/service/EmailTemplateService.java | 57 ++++++++++++ .../email/service/JoinEmailAuthService.java | 89 +++++++++++++++++++ .../service/PasswordResetEmailService.java | 76 ++++++++++++++++ .../email/util/AuthNumberGenerator.java | 20 +++++ 7 files changed, 260 insertions(+), 16 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 356d41eb..22f0f632 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -3,7 +3,8 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; -import inu.codin.codin.domain.email.service.EmailAuthService; +import inu.codin.codin.domain.email.service.JoinEmailAuthService; +import inu.codin.codin.domain.email.service.PasswordResetEmailService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -19,15 +20,15 @@ @Tag(name = "User Auth API", description = "유저 회원가입, 로그인, 로그아웃, 리이슈 API") @RequiredArgsConstructor public class EmailController { - - private final EmailAuthService emailAuthService; + private final JoinEmailAuthService joinEmailAuthService; + private final PasswordResetEmailService passwordResetEmailService; @Operation(summary = "이메일 인증 코드 전송 - 학교인증 X") @PostMapping("/auth/send") public ResponseEntity> sendJoinAuthEmail( @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { - emailAuthService.sendAuthEmail(emailAuthRequestDto); + joinEmailAuthService.sendJoinAuthEmail(emailAuthRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "이메일 인증 코드 전송 성공", null)); } @@ -37,7 +38,7 @@ public ResponseEntity> sendJoinAuthEmail( public ResponseEntity> checkAuthNum( @RequestBody @Valid JoinEmailCheckRequestDto joinEmailCheckRequestDto ) { - emailAuthService.checkAuthNum(joinEmailCheckRequestDto); + joinEmailAuthService.checkJoinAuthEmail(joinEmailCheckRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "이메일 인증 성공", null)); } @@ -49,7 +50,7 @@ public ResponseEntity> checkAuthNum( public ResponseEntity> sendPasswordEmail( @RequestBody @Valid JoinEmailSendRequestDto emailAuthRequestDto ) { - emailAuthService.sendPasswordEmail(emailAuthRequestDto); + passwordResetEmailService.sendPasswordResetEmail(emailAuthRequestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "이메일 비밀번호 재설정 링크 전송 성공", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index cf94a02f..6b3c4b4c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -6,6 +6,7 @@ import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; @@ -17,6 +18,7 @@ public class EmailAuthEntity extends BaseTimeEntity { @Id @NotBlank private ObjectId _id; + @Indexed @NotBlank private String email; @@ -31,8 +33,9 @@ public EmailAuthEntity(String email, String authNum) { this.authNum = authNum; } - public void changeAuthNum(String authNum) { - this.authNum = authNum; + public void renewAuthNum(String newAuthNum) { + this.authNum = newAuthNum; + this.setUpdatedAt(); } public void verifyEmail() { @@ -40,13 +43,16 @@ public void verifyEmail() { } public void unVerifyEmail(){ - this.isVerified=false; + this.isVerified = false; } /** - * 10분 이상이면 만료됌 + * 10분의 만료 시간을 가짐 + * @return 인증 번호가 만료되었는지 여부 false면 만료되지 않음, true면 만료됨 */ public boolean isExpired() { - return getUpdatedAt().plusMinutes(10).isBefore(LocalDateTime.now()); + return getUpdatedAt() + .plusMinutes(10) + .isBefore(LocalDateTime.now()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java index 4a1702b2..284d5afc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/repository/EmailAuthRepository.java @@ -9,11 +9,6 @@ @Repository public interface EmailAuthRepository extends MongoRepository { - Optional findByEmail(String email); - Optional findByEmailAndAuthNum(String email, String authNum); - - Optional findByAuthNum(String authNum); - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java new file mode 100644 index 00000000..a14f2e0b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -0,0 +1,57 @@ +package inu.codin.codin.domain.email.service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +/** + * 이메일 템플릿 서비스 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EmailTemplateService { + private final JavaMailSender javaMailSender; + private final SpringTemplateEngine templateEngine; + + /** + * 템플릿을 사용해 이메일을 전송합니다. + * @param email 수신자 이메일 + * @param subject 이메일 제목 + * @param templateName 템플릿 이름 + * @param authNum 인증번호 + */ + @Async + public void sendTemplateEmail(String email, String subject, String templateName, String authNum) { + try { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); + + // 템플릿 컨텍스트 설정 + Context context = new Context(); + context.setVariable("authNum", authNum); + + // HTML 내용 생성 + String htmlContent = templateEngine.process(templateName, context); + + // 이메일 설정 + helper.setTo(email); + helper.setSubject(subject); + helper.setText(htmlContent, true); + + // 이메일 전송 + javaMailSender.send(message); + log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); + } catch (MessagingException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패, email: {}, template: {}", email, templateName, e); + throw new RuntimeException("이메일 전송에 실패했습니다.", e); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java new file mode 100644 index 00000000..4dc0a5af --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java @@ -0,0 +1,89 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * 회원가입 이메일 인증 서비스 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class JoinEmailAuthService { + private final EmailAuthRepository emailAuthRepository; + private final EmailTemplateService emailTemplateService; + private final AuthNumberGenerator authNumberGenerator; + + private static final String AUTH_EMAIL_SUBJECT = "[CODIN] 회원가입 인증번호입니다."; + private static final String AUTH_EMAIL_TEMPLATE = "auth-email"; + + /** + * 회원가입용 인증 이메일을 전송합니다. + * @param request 이메일 전송 요청 정보 + */ + @Transactional + public void sendJoinAuthEmail(JoinEmailSendRequestDto request) { + String email = request.getEmail(); + log.info("[sendJoinAuthEmail] email: {}", email); + + EmailAuthEntity emailAuthEntity = getOrCreateEmailAuth(email); + emailAuthRepository.save(emailAuthEntity); + + // 트랜잭션 완료 후 비동기 이메일 전송 + emailTemplateService.sendTemplateEmail(email, AUTH_EMAIL_SUBJECT, AUTH_EMAIL_TEMPLATE, emailAuthEntity.getAuthNum()); + } + + /** + * 회원가입용 이메일 인증번호를 검증합니다. + * @param request 인증번호 검증 요청 정보 + */ + @Transactional + public void checkJoinAuthEmail(JoinEmailCheckRequestDto request) { + String email = request.getEmail(); + String authNum = request.getAuthNum(); + + // 이메일과 인증번호가 일치하는지 확인 + EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) + .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", email)); + + // 만료 시간 검증 + if (emailAuthEntity.isExpired()) { + throw new EmailAuthFailException("인증번호가 만료되었습니다.", email); + } + + emailAuthEntity.verifyEmail(); + emailAuthRepository.save(emailAuthEntity); + + log.info("[checkJoinAuthEmail] 회원가입 이메일 인증 성공, email: {}", email); + } + + /** + * 기존 이메일 인증 정보를 조회하거나 새로 생성합니다. + */ + private EmailAuthEntity getOrCreateEmailAuth(String email) { + Optional existingAuth = emailAuthRepository.findByEmail(email); + + if (existingAuth.isPresent()) { + // 기존 인증번호 갱신 + EmailAuthEntity emailAuth = existingAuth.get(); + emailAuth.renewAuthNum(authNumberGenerator.generate()); + return emailAuth; + } else { + // 새 인증 정보 생성 + return EmailAuthEntity.builder() + .email(email) + .authNum(authNumberGenerator.generate()) + .build(); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java new file mode 100644 index 00000000..1423f3b7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -0,0 +1,76 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * 비밀번호 재설정 메일링 서비스 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class PasswordResetEmailService { + private final EmailAuthRepository emailAuthRepository; + private final EmailTemplateService emailTemplateService; + private final UserRepository userRepository; + private final AuthNumberGenerator authNumberGenerator; + + private static final String PASSWORD_EMAIL_SUBJECT = "[CODIN] 비밀번호 재설정 링크입니다."; + private static final String PASSWORD_EMAIL_TEMPLATE = "password-email"; + + /** + * 비밀번호 재설정용 이메일을 전송합니다. + * @param request 이메일 전송 요청 정보 + */ + @Transactional + public void sendPasswordResetEmail(JoinEmailSendRequestDto request) { + String email = request.getEmail(); + log.info("[sendPasswordResetEmail] email: {}", email); + + // 사용자 존재 여부 확인 + userRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException("회원가입을 먼저 진행해주세요.")); + + EmailAuthEntity emailAuthEntity = getOrCreatePasswordResetAuth(email); + emailAuthRepository.save(emailAuthEntity); + + // 트랜잭션 완료 후 비동기 이메일 전송 + emailTemplateService.sendTemplateEmail( + email, + PASSWORD_EMAIL_SUBJECT, + PASSWORD_EMAIL_TEMPLATE, + emailAuthEntity.getAuthNum() + ); + } + + /** + * 비밀번호 재설정용 인증 정보를 조회하거나 새로 생성합니다. + */ + private EmailAuthEntity getOrCreatePasswordResetAuth(String email) { + Optional existingAuth = emailAuthRepository.findByEmail(email); + + if (existingAuth.isPresent()) { + // 기존 인증번호 갱신 및 인증 상태 초기화 + EmailAuthEntity emailAuth = existingAuth.get(); + emailAuth.renewAuthNum(authNumberGenerator.generate()); + emailAuth.unVerifyEmail(); + return emailAuth; + } else { + // 새 인증 정보 생성 + return EmailAuthEntity.builder() + .email(email) + .authNum(authNumberGenerator.generate()) + .build(); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java b/codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java new file mode 100644 index 00000000..d2e167fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/util/AuthNumberGenerator.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.email.util; + +import org.springframework.stereotype.Component; + +import java.util.UUID; + +/** + * 인증번호 생성 컴포넌트 + */ +@Component +public class AuthNumberGenerator { + + /** + * UUID 기반 8자리 랜덤 인증번호를 생성합니다. + * @return 8자리 대문자 인증번호 + */ + public String generate() { + return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + } +} From 21a442e6ba8bfec1cdbb825c342f95414c3ac51e Mon Sep 17 00:00:00 2001 From: kbm Date: Mon, 23 Jun 2025 17:05:08 +0900 Subject: [PATCH 0766/1002] =?UTF-8?q?remove:=20EmailAuthService,=20EmailSe?= =?UTF-8?q?ndService=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/service/EmailAuthService.java | 116 ------------------ .../email/service/EmailSendService.java | 72 ----------- 2 files changed, 188 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java deleted file mode 100644 index 3e5fe55d..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailAuthService.java +++ /dev/null @@ -1,116 +0,0 @@ -package inu.codin.codin.domain.email.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; -import inu.codin.codin.domain.email.entity.EmailAuthEntity; -import inu.codin.codin.domain.email.exception.EmailAuthFailException; -import inu.codin.codin.domain.email.repository.EmailAuthRepository; -import inu.codin.codin.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.Optional; -import java.util.UUID; - -@Service -@RequiredArgsConstructor -@Slf4j -public class EmailAuthService { - - private final EmailAuthRepository emailAuthRepository; - private final EmailSendService emailSendService; - private final UserRepository userRepository; - - public void sendAuthEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { - - String email = joinEmailSendRequestDto.getEmail(); - log.info("[sendAuthEmail] email : {}", email); - - Optional emailAuth = emailAuthRepository.findByEmail(email); - EmailAuthEntity emailAuthEntity; - - // 재인증 로직 - if (emailAuth.isPresent()) { - emailAuthEntity = emailAuth.get(); - -// // 이미 인증된 이메일 체크 -// if (emailAuthEntity.isVerified()) { -// throw new EmailAuthFailException("이미 인증된 이메일입니다.", email); -// } - - emailAuthEntity.changeAuthNum(generateAuthNum()); - } - else { - // 인증 생성 로직 - emailAuthEntity = EmailAuthEntity.builder() - .email(email) - .authNum(generateAuthNum()) - .build(); - } - emailAuthRepository.save(emailAuthEntity); - - // 비동기 이메일 전송 로직 - emailSendService.sendAuthEmail(email, emailAuthEntity.getAuthNum()); - } - - private String generateAuthNum() { - // 8자리 인증번호 생성 - return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); - } - - public void checkAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { - - checkEmailAndAuthNum(joinEmailCheckRequestDto); - } - - public void sendPasswordEmail(JoinEmailSendRequestDto joinEmailSendRequestDto) { - - userRepository.findByEmail(joinEmailSendRequestDto.getEmail()) - .orElseThrow(() -> new NotFoundException("회원가입을 먼저 진행해주세요.")); - - String email = joinEmailSendRequestDto.getEmail(); - log.info("[sendAuthEmail] email : {}", email); - - Optional emailAuth = emailAuthRepository.findByEmail(email); - EmailAuthEntity emailAuthEntity; - - // 재인증 로직 - if (emailAuth.isPresent()) { - emailAuthEntity = emailAuth.get(); - emailAuthEntity.changeAuthNum(generateAuthNum()); - emailAuthEntity.unVerifyEmail(); - } - else { - // 인증 생성 로직 - emailAuthEntity = EmailAuthEntity.builder() - .email(email) - .authNum(generateAuthNum()) - .build(); - } - emailAuthRepository.save(emailAuthEntity); - - // 비동기 이메일 전송 로직 - emailSendService.sendPasswordEmail(email, emailAuthEntity.getAuthNum()); - } - - private void checkEmailAndAuthNum(JoinEmailCheckRequestDto joinEmailCheckRequestDto) { - String email = joinEmailCheckRequestDto.getEmail(); - String authNum = joinEmailCheckRequestDto.getAuthNum(); - log.info("[checkAuthNum] email : {}, authNum : {}", email, authNum); - - // 이메일과 인증번호가 일치하는지 확인 - EmailAuthEntity emailAuthEntity = emailAuthRepository.findByEmailAndAuthNum(email, authNum) - .orElseThrow(() -> new EmailAuthFailException("인증번호가 일치하지 않습니다.", email)); - - // 10분 이내에 인증하지 않을 시에 인증번호 만료 - if (emailAuthEntity.isExpired()) { - throw new EmailAuthFailException("인증번호가 만료되었습니다.", email); - } - - emailAuthEntity.verifyEmail(); - emailAuthRepository.save(emailAuthEntity); - log.info("[checkAuthNum] Email AUTH SUCCESS!!, email : {}", email); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java deleted file mode 100644 index f8691f5c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailSendService.java +++ /dev/null @@ -1,72 +0,0 @@ -package inu.codin.codin.domain.email.service; - -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring6.SpringTemplateEngine; - -@Service -@RequiredArgsConstructor -@Slf4j -@Transactional(readOnly = true) -public class EmailSendService { - - private final JavaMailSender javaMailSender; - private final SpringTemplateEngine templateEngine; - - // 이메일 전송 로직 - // +템플릿 엔진으로 이메일 전송 추가 필요!! - @Async - public void sendAuthEmail(String email, String authNum) { - MimeMessage message = javaMailSender.createMimeMessage(); - try { - MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); - - Context context = new Context(); - context.setVariable("authNum", authNum); - - // 템플릿 엔진을 사용하여 HTML 내용을 생성 - String htmlContent = templateEngine.process("auth-email", context); - - helper.setTo(email); - helper.setSubject("[CODIN] 회원가입 인증번호입니다."); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); - } catch (MessagingException e) { - log.error("[sendAuthEmail] 인증 이메일 전송 실패, email : {}", email); - throw new RuntimeException(e); - } - } - - public void sendPasswordEmail(String email, String authNum) { - MimeMessage message = javaMailSender.createMimeMessage(); - try { - MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); - - Context context = new Context(); - context.setVariable("authNum", authNum); - - // 템플릿 엔진을 사용하여 HTML 내용을 생성 - String htmlContent = templateEngine.process("password-email", context); - - helper.setTo(email); - helper.setSubject("[CODIN] 비밀번호 재설정 링크입니다."); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - log.info("[sendAuthEmail] 인증 이메일 전송 성공, email : {}", email); - } catch (MessagingException e) { - log.error("[sendAuthEmail] 인증 이메일 전송 실패, email : {}", email); - throw new RuntimeException(e); - } - } -} From f9a48325361b458504d28bf9da8e9526b00565cd Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 24 Jun 2025 21:59:24 +0900 Subject: [PATCH 0767/1002] =?UTF-8?q?test:=20Email=20Service=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=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 아래와 같은 세가지에 대해서 테스트를 작성했습니다. - EmailTemplateServiceTest - JoinEmailAuthServiceTest - PasswordResetEmailServiceTest 테스트시 객체 생성 메서드가 없기에 두개의 DTO 클래스에 Builder를 추가했습니다. - JoinEmailCheckRequestDto - JoinEmailSendRequestDto --- .../email/dto/JoinEmailCheckRequestDto.java | 2 + .../email/dto/JoinEmailSendRequestDto.java | 2 + .../service/EmailTemplateServiceTest.java | 140 ++++++++++++++ .../service/JoinEmailAuthServiceTest.java | 179 ++++++++++++++++++ .../PasswordResetEmailServiceTest.java | 142 ++++++++++++++ 5 files changed, 465 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java index 6b396143..6d33bad5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Getter; /** @@ -10,6 +11,7 @@ * 해당 이메일로 전송된 인증 코드를 확인한다. */ @Getter +@Builder public class JoinEmailCheckRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java index 279e6ae8..94a1eb50 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Getter; /** @@ -10,6 +11,7 @@ * 해당 이메일로 인증 코드를 전송한다. */ @Getter +@Builder public class JoinEmailSendRequestDto { @Schema(description = "이메일 주소", example = "example@inu.ac.kr") diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java new file mode 100644 index 00000000..cb7b26fc --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java @@ -0,0 +1,140 @@ +package inu.codin.codin.domain.email.service; + +import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.javamail.JavaMailSender; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class EmailTemplateServiceTest { + + @InjectMocks + EmailTemplateService emailTemplateService; + + @Mock + JavaMailSender javaMailSender; + @Mock + SpringTemplateEngine templateEngine; + @Mock + MimeMessage mimeMessage; + + private final String testEmail = "test@inu.ac.kr"; + private final String testSubject = "테스트 제목"; + private final String testTemplate = "test-template"; + private final String testAuthNum = "AUTH123"; + private final String testHtmlContent = "인증번호: AUTH123"; + + @BeforeEach + void setUp() { + when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 성공") + void sendTemplateEmail_성공() throws Exception { + // given + when(templateEngine.process(eq(testTemplate), any(Context.class))) + .thenReturn(testHtmlContent); + + // when + emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum); + + // then + verify(javaMailSender).createMimeMessage(); + verify(javaMailSender).send(mimeMessage); + verify(templateEngine).process(eq(testTemplate), any(Context.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 템플릿 엔진 컨텍스트 확인") + void sendTemplateEmail_템플릿엔진컨텍스트확인() throws Exception { + // given + ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(Context.class); + when(templateEngine.process(eq(testTemplate), contextCaptor.capture())) + .thenReturn(testHtmlContent); + + // when + emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum); + + // then + Context capturedContext = contextCaptor.getValue(); + assertNotNull(capturedContext); + verify(templateEngine).process(eq(testTemplate), any(Context.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - MimeMessage 생성 실패") + void sendTemplateEmail_MimeMessage생성실패() { + // given + when(javaMailSender.createMimeMessage()).thenThrow(new RuntimeException("MimeMessage 생성 실패")); + + // when & then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); + + assertEquals("MimeMessage 생성 실패", exception.getMessage()); + verify(javaMailSender, never()).send(any(MimeMessage.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 템플릿 처리 실패") + void sendTemplateEmail_템플릿처리실패() { + // given + when(templateEngine.process(eq(testTemplate), any(Context.class))) + .thenThrow(new RuntimeException("템플릿 처리 실패")); + + // when & then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); + + assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || + exception.getMessage().contains("템플릿 처리 실패")); + verify(javaMailSender, never()).send(any(MimeMessage.class)); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 이메일 전송 실패") + void sendTemplateEmail_이메일전송실패() throws Exception { + // given + when(templateEngine.process(eq(testTemplate), any(Context.class))) + .thenReturn(testHtmlContent); + doThrow(new RuntimeException("이메일 전송 실패")) + .when(javaMailSender).send(mimeMessage); + + // when & then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); + + assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || + exception.getMessage().contains("이메일 전송 실패")); + } + + @Test + @DisplayName("템플릿 이메일 전송 - 파라미터 검증") + void sendTemplateEmail_파라미터검증() throws Exception { + // given + when(templateEngine.process(anyString(), any(Context.class))) + .thenReturn(testHtmlContent); + + // when + emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum); + + // then + verify(templateEngine).process(eq(testTemplate), any(Context.class)); + verify(javaMailSender).send(mimeMessage); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java new file mode 100644 index 00000000..476b2ef6 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java @@ -0,0 +1,179 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailAuthFailException; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class JoinEmailAuthServiceTest { + + @InjectMocks + JoinEmailAuthService joinEmailAuthService; + + @Mock + EmailAuthRepository emailAuthRepository; + + @Mock + EmailTemplateService emailTemplateService; + + @Mock + AuthNumberGenerator authNumberGenerator; + + private JoinEmailSendRequestDto sendRequestDto; + private JoinEmailCheckRequestDto checkRequestDto; + private EmailAuthEntity mockEmailAuth; + + @BeforeEach + void setUp() { + sendRequestDto = JoinEmailSendRequestDto.builder() + .email("test@inu.ac.kr") + .build(); + checkRequestDto = JoinEmailCheckRequestDto.builder() + .email("test@inu.ac.kr") + .authNum("AUTH123") + .build(); + mockEmailAuth = EmailAuthEntity.builder() + .email("test@inu.ac.kr") + .authNum("AUTH123") + .build(); + + // BaseTimeEntity의 기본 생성 객체가 없기 때문에 null pointer 에러 발생 + mockEmailAuth.setUpdatedAt(); + } + + @Test + @DisplayName("회원가입 인증 이메일 전송 - 성공 (신규 사용자)") + void sendJoinAuthEmail_성공_신규사용자() { + // given + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); + + // when + joinEmailAuthService.sendJoinAuthEmail(sendRequestDto); + + // then + ArgumentCaptor emailAuthCaptor = ArgumentCaptor.forClass(EmailAuthEntity.class); + verify(emailAuthRepository).save(emailAuthCaptor.capture()); + + EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); + assertEquals("test@inu.ac.kr", savedAuth.getEmail()); + assertEquals("NEWAUTH", savedAuth.getAuthNum()); + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 회원가입 인증번호입니다."), + eq("auth-email"), + eq("NEWAUTH") + ); + } + + @Test + @DisplayName("회원가입 인증 이메일 전송 - 성공 (기존 인증 정보 갱신)") + void sendJoinAuthEmail_성공_기존인증정보갱신() { + // given + EmailAuthEntity existingAuth = EmailAuthEntity.builder() + .email("test@inu.ac.kr") + .authNum("OLDAUTH") + .build(); + + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingAuth)); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); + + // when + joinEmailAuthService.sendJoinAuthEmail(sendRequestDto); + + // then + verify(emailAuthRepository).save(existingAuth); + assertEquals("NEWAUTH", existingAuth.getAuthNum()); + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 회원가입 인증번호입니다."), + eq("auth-email"), + eq("NEWAUTH") + ); + } + + @Test + @DisplayName("회원가입 이메일 인증 확인 - 성공") + void checkJoinAuthEmail_성공() { + // given + when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + .thenReturn(Optional.of(mockEmailAuth)); + + // when + joinEmailAuthService.checkJoinAuthEmail(checkRequestDto); + + // then + verify(emailAuthRepository).save(mockEmailAuth); + assertTrue(mockEmailAuth.isVerified()); + } + + @Test + @DisplayName("회원가입 이메일 인증 확인 - 실패 (인증번호 불일치)") + void checkJoinAuthEmail_실패_인증번호불일치() { + // given + when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + .thenReturn(Optional.empty()); + + // when & then + EmailAuthFailException exception = assertThrows(EmailAuthFailException.class, + () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); + + assertEquals("인증번호가 일치하지 않습니다.", exception.getMessage()); + assertEquals("test@inu.ac.kr", exception.getEmail()); + + verify(emailAuthRepository, never()).save(any()); + } + + @Test + @DisplayName("회원가입 이메일 인증 확인 - 실패 (인증번호 만료)") + void checkJoinAuthEmail_실패_인증번호만료() { + // given + EmailAuthEntity expiredAuth = spy(mockEmailAuth); + when(expiredAuth.isExpired()).thenReturn(true); + + when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + .thenReturn(Optional.of(expiredAuth)); + + // when & then + EmailAuthFailException exception = assertThrows(EmailAuthFailException.class, + () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); + + assertEquals("인증번호가 만료되었습니다.", exception.getMessage()); + assertEquals("test@inu.ac.kr", exception.getEmail()); + + verify(emailAuthRepository, never()).save(any()); + } + + @Test + @DisplayName("인증번호 생성기 호출 확인") + void authNumberGenerator_호출확인() { + // given + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("GENERATED"); + + // when + joinEmailAuthService.sendJoinAuthEmail(sendRequestDto); + + // then + verify(authNumberGenerator, times(1)).generate(); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java new file mode 100644 index 00000000..db8b1075 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java @@ -0,0 +1,142 @@ +package inu.codin.codin.domain.email.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.repository.EmailAuthRepository; +import inu.codin.codin.domain.email.util.AuthNumberGenerator; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PasswordResetEmailServiceTest { + + @InjectMocks + PasswordResetEmailService passwordResetEmailService; + + @Mock + EmailAuthRepository emailAuthRepository; + + @Mock + EmailTemplateService emailTemplateService; + + @Mock + UserRepository userRepository; + + @Mock + AuthNumberGenerator authNumberGenerator; + + private JoinEmailSendRequestDto requestDto; + private UserEntity mockUser; + private EmailAuthEntity existingEmailAuth; + + @BeforeEach + void setUp() { + mockUser = mock(UserEntity.class); + requestDto = JoinEmailSendRequestDto.builder() + .email("test@inu.ac.kr") + .build(); + existingEmailAuth = EmailAuthEntity.builder() + .email("test@inu.ac.kr") + .authNum("OLDAUTH1") + .build(); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (신규 사용자)") + void sendPasswordResetEmail_성공_신규사용자() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH1"); + + // when + passwordResetEmailService.sendPasswordResetEmail(requestDto); + + // then + ArgumentCaptor emailAuthCaptor = ArgumentCaptor.forClass(EmailAuthEntity.class); + verify(emailAuthRepository).save(emailAuthCaptor.capture()); + + EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); + assertEquals("test@inu.ac.kr", savedAuth.getEmail()); + assertEquals("NEWAUTH1", savedAuth.getAuthNum()); + assertFalse(savedAuth.isVerified()); + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 비밀번호 재설정 링크입니다."), + eq("password-email"), + eq("NEWAUTH1") + ); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (기존 인증 정보 갱신)") + void sendPasswordResetEmail_성공_기존인증정보갱신() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingEmailAuth)); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH2"); + + // when + passwordResetEmailService.sendPasswordResetEmail(requestDto); + + // then + verify(emailAuthRepository).save(existingEmailAuth); + assertEquals("NEWAUTH2", existingEmailAuth.getAuthNum()); + assertFalse(existingEmailAuth.isVerified()); // 인증 상태 초기화 확인 + + verify(emailTemplateService).sendTemplateEmail( + eq("test@inu.ac.kr"), + eq("[CODIN] 비밀번호 재설정 링크입니다."), + eq("password-email"), + eq("NEWAUTH2") + ); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 실패 (존재하지 않는 사용자)") + void sendPasswordResetEmail_실패_존재하지않는사용자() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + + // when & then + NotFoundException exception = assertThrows(NotFoundException.class, + () -> passwordResetEmailService.sendPasswordResetEmail(requestDto)); + + assertEquals("회원가입을 먼저 진행해주세요.", exception.getMessage()); + + verify(emailAuthRepository, never()).save(any()); + verify(emailTemplateService, never()).sendTemplateEmail(anyString(), anyString(), anyString(), anyString()); + } + + @Test + @DisplayName("비밀번호 재설정 이메일 전송 - 인증번호 생성기 호출 확인") + void sendPasswordResetEmail_인증번호생성기호출확인() { + // given + when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(authNumberGenerator.generate()).thenReturn("GENERATED"); + + // when + passwordResetEmailService.sendPasswordResetEmail(requestDto); + + // then + verify(authNumberGenerator, times(1)).generate(); + } +} \ No newline at end of file From 0abba111c33886d3622c45bd051b2ced857e740b Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 12:28:38 +0900 Subject: [PATCH 0768/1002] =?UTF-8?q?refactor:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailController : 불필요한 공백 제거 - EmailTemplateService : 예외처리 추가 - EmailTemplateServiceTest : 가독성 개선 - JoinEmailAuthService : 잘못된 주석 제거와 가독성 향상 - PasswordResetEmailService : 잘못된 주석 제거 --- .../codin/domain/block/controller/BlockController.java | 1 - .../codin/domain/email/service/EmailTemplateService.java | 5 ++++- .../codin/domain/email/service/JoinEmailAuthService.java | 9 +++++++-- .../domain/email/service/PasswordResetEmailService.java | 1 - .../domain/email/service/EmailTemplateServiceTest.java | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java index 34c5bdb7..475b3471 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java @@ -12,7 +12,6 @@ @RestController @RequestMapping("/block") @RequiredArgsConstructor - @Tag(name = "Block API", description = "사용자 차단 기능") public class BlockController { private final BlockService blockService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java index a14f2e0b..b537a1a2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -50,8 +50,11 @@ public void sendTemplateEmail(String email, String subject, String templateName, javaMailSender.send(message); log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); } catch (MessagingException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패, email: {}, template: {}", email, templateName, e); + log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName, e); throw new RuntimeException("이메일 전송에 실패했습니다.", e); + } catch (RuntimeException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패 (RuntimeException), email: {}, template: {}", email, templateName, e); + throw new RuntimeException("이메일 전송 중 알 수 없는 오류가 발생했습니다.", e); } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java index 4dc0a5af..4b8f47cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java @@ -39,8 +39,12 @@ public void sendJoinAuthEmail(JoinEmailSendRequestDto request) { EmailAuthEntity emailAuthEntity = getOrCreateEmailAuth(email); emailAuthRepository.save(emailAuthEntity); - // 트랜잭션 완료 후 비동기 이메일 전송 - emailTemplateService.sendTemplateEmail(email, AUTH_EMAIL_SUBJECT, AUTH_EMAIL_TEMPLATE, emailAuthEntity.getAuthNum()); + emailTemplateService.sendTemplateEmail( + email, + AUTH_EMAIL_SUBJECT, + AUTH_EMAIL_TEMPLATE, + emailAuthEntity.getAuthNum() + ); } /** @@ -77,6 +81,7 @@ private EmailAuthEntity getOrCreateEmailAuth(String email) { // 기존 인증번호 갱신 EmailAuthEntity emailAuth = existingAuth.get(); emailAuth.renewAuthNum(authNumberGenerator.generate()); + emailAuth.unVerifyEmail(); return emailAuth; } else { // 새 인증 정보 생성 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java index 1423f3b7..bcb7a436 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -44,7 +44,6 @@ public void sendPasswordResetEmail(JoinEmailSendRequestDto request) { EmailAuthEntity emailAuthEntity = getOrCreatePasswordResetAuth(email); emailAuthRepository.save(emailAuthEntity); - // 트랜잭션 완료 후 비동기 이메일 전송 emailTemplateService.sendTemplateEmail( email, PASSWORD_EMAIL_SUBJECT, diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java index cb7b26fc..1b656412 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java @@ -36,7 +36,7 @@ class EmailTemplateServiceTest { private final String testSubject = "테스트 제목"; private final String testTemplate = "test-template"; private final String testAuthNum = "AUTH123"; - private final String testHtmlContent = "인증번호: AUTH123"; + private final String testHtmlContent = "인증번호: " + testAuthNum + ""; @BeforeEach void setUp() { From ca4471b207d3a5f92fe9cdadcaf8899fb855438a Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:02:53 +0900 Subject: [PATCH 0769/1002] =?UTF-8?q?refactor:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailTemplateFailException 으로 예외처리 추가 --- .../email/exception/EmailTemplateFailException.java | 9 +++++++++ .../domain/email/service/EmailTemplateService.java | 10 ++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java new file mode 100644 index 00000000..21187c55 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.email.exception; + +import jdk.jfr.StackTrace; + +public class EmailTemplateFailException extends RuntimeException { + public EmailTemplateFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java index b537a1a2..880e1dac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -1,9 +1,11 @@ package inu.codin.codin.domain.email.service; +import inu.codin.codin.domain.email.exception.EmailTemplateFailException; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; @@ -51,10 +53,10 @@ public void sendTemplateEmail(String email, String subject, String templateName, log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); } catch (MessagingException e) { log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName, e); - throw new RuntimeException("이메일 전송에 실패했습니다.", e); - } catch (RuntimeException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패 (RuntimeException), email: {}, template: {}", email, templateName, e); - throw new RuntimeException("이메일 전송 중 알 수 없는 오류가 발생했습니다.", e); + throw new EmailTemplateFailException("이메일 전송에 실패했습니다."); + } catch (MailException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패 (MailException), email: {}, template: {}", email, templateName, e); + throw new EmailTemplateFailException("이메일 전송 중 알 수 없는 오류가 발생했습니다."); } } } From 08dc3cd1164ad996af189ebcb93c4e99f0e0edb7 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:06:51 +0900 Subject: [PATCH 0770/1002] =?UTF-8?q?fixup!=20refactor:=20#207=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/email/service/EmailTemplateService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java index 880e1dac..f0915166 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/EmailTemplateService.java @@ -11,6 +11,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.thymeleaf.context.Context; +import org.thymeleaf.exceptions.TemplateEngineException; import org.thymeleaf.spring6.SpringTemplateEngine; /** @@ -52,11 +53,14 @@ public void sendTemplateEmail(String email, String subject, String templateName, javaMailSender.send(message); log.info("[sendTemplateEmail] 이메일 전송 성공, email: {}, template: {}", email, templateName); } catch (MessagingException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName, e); + log.error("[sendTemplateEmail] 이메일 전송 실패 (MessagingException), email: {}, template: {}", email, templateName); throw new EmailTemplateFailException("이메일 전송에 실패했습니다."); } catch (MailException e) { - log.error("[sendTemplateEmail] 이메일 전송 실패 (MailException), email: {}, template: {}", email, templateName, e); + log.error("[sendTemplateEmail] 이메일 전송 실패 (MailException), email: {}, template: {}", email, templateName); throw new EmailTemplateFailException("이메일 전송 중 알 수 없는 오류가 발생했습니다."); + } catch (TemplateEngineException e) { + log.error("[sendTemplateEmail] 이메일 전송 실패 (TemplateEngineException), email: {}, template: {}", email, templateName); + throw new EmailTemplateFailException("템플릿 엔진 오류가 발생했습니다."); } } } From 6f173751d41b5f90c53bf79dbeffc85b4c10f0bd Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:12:02 +0900 Subject: [PATCH 0771/1002] =?UTF-8?q?refactor:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 비밀번호 재인증 로직 중 기존 인증정보가 존재하지 않을 때에는 예외처리 - EmailPasswordResetFailException 으로 예외처리 추가 --- .../email/exception/EmailPasswordResetFailException.java | 7 +++++++ .../domain/email/service/PasswordResetEmailService.java | 8 +++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java new file mode 100644 index 00000000..7a08982e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailPasswordResetFailException.java @@ -0,0 +1,7 @@ +package inu.codin.codin.domain.email.exception; + +public class EmailPasswordResetFailException extends RuntimeException { + public EmailPasswordResetFailException(String message) { + super(message); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java index bcb7a436..82e676bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.email.util.AuthNumberGenerator; import inu.codin.codin.domain.user.repository.UserRepository; @@ -65,11 +66,8 @@ private EmailAuthEntity getOrCreatePasswordResetAuth(String email) { emailAuth.unVerifyEmail(); return emailAuth; } else { - // 새 인증 정보 생성 - return EmailAuthEntity.builder() - .email(email) - .authNum(authNumberGenerator.generate()) - .build(); + log.error("[getOrCreatePasswordResetAuth] 등록되지 않은 사용자에 대한 비밀번호 초기화 요청, email: {}", email); + throw new EmailPasswordResetFailException("등록되지 않은 사용자 이메일입니다."); } } } From bc85daa9debe4a67004d56b68cd6585a74b3b537 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 14:35:01 +0900 Subject: [PATCH 0772/1002] =?UTF-8?q?test:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수정된 기능 상황에 맞게 테스트 수정 --- .../service/JoinEmailAuthServiceTest.java | 39 +++++------ .../PasswordResetEmailServiceTest.java | 64 ++++++++----------- 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java index 476b2ef6..cd4995c3 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/JoinEmailAuthServiceTest.java @@ -29,10 +29,8 @@ class JoinEmailAuthServiceTest { @Mock EmailAuthRepository emailAuthRepository; - @Mock EmailTemplateService emailTemplateService; - @Mock AuthNumberGenerator authNumberGenerator; @@ -40,18 +38,21 @@ class JoinEmailAuthServiceTest { private JoinEmailCheckRequestDto checkRequestDto; private EmailAuthEntity mockEmailAuth; + private final String testMail = "test@inu.ac.kr"; + private final String testAuthNum = "AUTH123"; + @BeforeEach void setUp() { sendRequestDto = JoinEmailSendRequestDto.builder() - .email("test@inu.ac.kr") + .email(testMail) .build(); checkRequestDto = JoinEmailCheckRequestDto.builder() - .email("test@inu.ac.kr") - .authNum("AUTH123") + .email(testMail) + .authNum(testAuthNum) .build(); mockEmailAuth = EmailAuthEntity.builder() - .email("test@inu.ac.kr") - .authNum("AUTH123") + .email(testMail) + .authNum(testAuthNum) .build(); // BaseTimeEntity의 기본 생성 객체가 없기 때문에 null pointer 에러 발생 @@ -62,7 +63,7 @@ void setUp() { @DisplayName("회원가입 인증 이메일 전송 - 성공 (신규 사용자)") void sendJoinAuthEmail_성공_신규사용자() { // given - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.empty()); when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); // when @@ -73,11 +74,11 @@ void setUp() { verify(emailAuthRepository).save(emailAuthCaptor.capture()); EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); - assertEquals("test@inu.ac.kr", savedAuth.getEmail()); + assertEquals(testMail, savedAuth.getEmail()); assertEquals("NEWAUTH", savedAuth.getAuthNum()); verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), + eq(testMail), eq("[CODIN] 회원가입 인증번호입니다."), eq("auth-email"), eq("NEWAUTH") @@ -89,11 +90,11 @@ void setUp() { void sendJoinAuthEmail_성공_기존인증정보갱신() { // given EmailAuthEntity existingAuth = EmailAuthEntity.builder() - .email("test@inu.ac.kr") + .email(testMail) .authNum("OLDAUTH") .build(); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingAuth)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.of(existingAuth)); when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); // when @@ -104,7 +105,7 @@ void setUp() { assertEquals("NEWAUTH", existingAuth.getAuthNum()); verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), + eq(testMail), eq("[CODIN] 회원가입 인증번호입니다."), eq("auth-email"), eq("NEWAUTH") @@ -115,7 +116,7 @@ void setUp() { @DisplayName("회원가입 이메일 인증 확인 - 성공") void checkJoinAuthEmail_성공() { // given - when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + when(emailAuthRepository.findByEmailAndAuthNum(testMail, testAuthNum)) .thenReturn(Optional.of(mockEmailAuth)); // when @@ -130,7 +131,7 @@ void setUp() { @DisplayName("회원가입 이메일 인증 확인 - 실패 (인증번호 불일치)") void checkJoinAuthEmail_실패_인증번호불일치() { // given - when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + when(emailAuthRepository.findByEmailAndAuthNum(testMail, testAuthNum)) .thenReturn(Optional.empty()); // when & then @@ -138,7 +139,7 @@ void setUp() { () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); assertEquals("인증번호가 일치하지 않습니다.", exception.getMessage()); - assertEquals("test@inu.ac.kr", exception.getEmail()); + assertEquals(testMail, exception.getEmail()); verify(emailAuthRepository, never()).save(any()); } @@ -150,7 +151,7 @@ void setUp() { EmailAuthEntity expiredAuth = spy(mockEmailAuth); when(expiredAuth.isExpired()).thenReturn(true); - when(emailAuthRepository.findByEmailAndAuthNum("test@inu.ac.kr", "AUTH123")) + when(emailAuthRepository.findByEmailAndAuthNum(testMail, testAuthNum)) .thenReturn(Optional.of(expiredAuth)); // when & then @@ -158,7 +159,7 @@ void setUp() { () -> joinEmailAuthService.checkJoinAuthEmail(checkRequestDto)); assertEquals("인증번호가 만료되었습니다.", exception.getMessage()); - assertEquals("test@inu.ac.kr", exception.getEmail()); + assertEquals(testMail, exception.getEmail()); verify(emailAuthRepository, never()).save(any()); } @@ -167,7 +168,7 @@ void setUp() { @DisplayName("인증번호 생성기 호출 확인") void authNumberGenerator_호출확인() { // given - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.empty()); when(authNumberGenerator.generate()).thenReturn("GENERATED"); // when diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java index db8b1075..ede28705 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; +import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; import inu.codin.codin.domain.email.util.AuthNumberGenerator; import inu.codin.codin.domain.user.entity.UserEntity; @@ -11,7 +12,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -31,13 +31,10 @@ class PasswordResetEmailServiceTest { @Mock EmailAuthRepository emailAuthRepository; - @Mock EmailTemplateService emailTemplateService; - @Mock UserRepository userRepository; - @Mock AuthNumberGenerator authNumberGenerator; @@ -45,67 +42,58 @@ class PasswordResetEmailServiceTest { private UserEntity mockUser; private EmailAuthEntity existingEmailAuth; + private final String testMail = "test@inu.ac.kr"; + private final String testAuthNum = "AUTH123"; + @BeforeEach void setUp() { mockUser = mock(UserEntity.class); requestDto = JoinEmailSendRequestDto.builder() - .email("test@inu.ac.kr") + .email(testMail) .build(); existingEmailAuth = EmailAuthEntity.builder() - .email("test@inu.ac.kr") - .authNum("OLDAUTH1") + .email(testMail) + .authNum(testAuthNum) .build(); } @Test - @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (신규 사용자)") + @DisplayName("비밀번호 재설정 이메일 전송 - 실패 (등록되지 않은 사용자)") void sendPasswordResetEmail_성공_신규사용자() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); - when(authNumberGenerator.generate()).thenReturn("NEWAUTH1"); - - // when - passwordResetEmailService.sendPasswordResetEmail(requestDto); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.empty()); - // then - ArgumentCaptor emailAuthCaptor = ArgumentCaptor.forClass(EmailAuthEntity.class); - verify(emailAuthRepository).save(emailAuthCaptor.capture()); - - EmailAuthEntity savedAuth = emailAuthCaptor.getValue(); - assertEquals("test@inu.ac.kr", savedAuth.getEmail()); - assertEquals("NEWAUTH1", savedAuth.getAuthNum()); - assertFalse(savedAuth.isVerified()); + // when & then + EmailPasswordResetFailException exception = assertThrows(EmailPasswordResetFailException.class, + () -> passwordResetEmailService.sendPasswordResetEmail(requestDto)); - verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), - eq("[CODIN] 비밀번호 재설정 링크입니다."), - eq("password-email"), - eq("NEWAUTH1") - ); + verify(emailAuthRepository, never()).save(any()); + verify(emailTemplateService, never()).sendTemplateEmail(anyString(), anyString(), anyString(), anyString()); + verify(authNumberGenerator, never()).generate(); } @Test @DisplayName("비밀번호 재설정 이메일 전송 - 성공 (기존 인증 정보 갱신)") void sendPasswordResetEmail_성공_기존인증정보갱신() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(existingEmailAuth)); - when(authNumberGenerator.generate()).thenReturn("NEWAUTH2"); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.of(existingEmailAuth)); + when(authNumberGenerator.generate()).thenReturn("NEWAUTH"); // when passwordResetEmailService.sendPasswordResetEmail(requestDto); // then verify(emailAuthRepository).save(existingEmailAuth); - assertEquals("NEWAUTH2", existingEmailAuth.getAuthNum()); + assertEquals("NEWAUTH", existingEmailAuth.getAuthNum()); assertFalse(existingEmailAuth.isVerified()); // 인증 상태 초기화 확인 verify(emailTemplateService).sendTemplateEmail( - eq("test@inu.ac.kr"), + eq(testMail), eq("[CODIN] 비밀번호 재설정 링크입니다."), eq("password-email"), - eq("NEWAUTH2") + eq("NEWAUTH") ); } @@ -113,7 +101,7 @@ void setUp() { @DisplayName("비밀번호 재설정 이메일 전송 - 실패 (존재하지 않는 사용자)") void sendPasswordResetEmail_실패_존재하지않는사용자() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.empty()); // when & then NotFoundException exception = assertThrows(NotFoundException.class, @@ -129,9 +117,9 @@ void setUp() { @DisplayName("비밀번호 재설정 이메일 전송 - 인증번호 생성기 호출 확인") void sendPasswordResetEmail_인증번호생성기호출확인() { // given - when(userRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.of(mockUser)); - when(emailAuthRepository.findByEmail("test@inu.ac.kr")).thenReturn(Optional.empty()); - when(authNumberGenerator.generate()).thenReturn("GENERATED"); + when(userRepository.findByEmail(testMail)).thenReturn(Optional.of(mockUser)); + when(emailAuthRepository.findByEmail(testMail)).thenReturn(Optional.of(existingEmailAuth)); + when(authNumberGenerator.generate()).thenReturn(testAuthNum); // when passwordResetEmailService.sendPasswordResetEmail(requestDto); From 1315ff594394573785aec7de6464687582c14a4c Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 25 Jun 2025 19:56:24 +0900 Subject: [PATCH 0773/1002] =?UTF-8?q?test:=20#207=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailTemplateFailException : import 최적화 - EmailTemplateFailException : 테스트 예외처리 수정 --- .../exception/EmailTemplateFailException.java | 2 -- .../service/EmailTemplateServiceTest.java | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java index 21187c55..41596fa1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/exception/EmailTemplateFailException.java @@ -1,7 +1,5 @@ package inu.codin.codin.domain.email.exception; -import jdk.jfr.StackTrace; - public class EmailTemplateFailException extends RuntimeException { public EmailTemplateFailException(String message) { super(message); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java index 1b656412..38277b4f 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/EmailTemplateServiceTest.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.email.service; +import inu.codin.codin.domain.email.exception.EmailTemplateFailException; import jakarta.mail.internet.MimeMessage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -45,7 +46,7 @@ void setUp() { @Test @DisplayName("템플릿 이메일 전송 - 성공") - void sendTemplateEmail_성공() throws Exception { + void sendTemplateEmail_성공() { // given when(templateEngine.process(eq(testTemplate), any(Context.class))) .thenReturn(testHtmlContent); @@ -61,7 +62,7 @@ void setUp() { @Test @DisplayName("템플릿 이메일 전송 - 템플릿 엔진 컨텍스트 확인") - void sendTemplateEmail_템플릿엔진컨텍스트확인() throws Exception { + void sendTemplateEmail_템플릿엔진컨텍스트확인() { // given ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(Context.class); when(templateEngine.process(eq(testTemplate), contextCaptor.capture())) @@ -80,10 +81,10 @@ void setUp() { @DisplayName("템플릿 이메일 전송 - MimeMessage 생성 실패") void sendTemplateEmail_MimeMessage생성실패() { // given - when(javaMailSender.createMimeMessage()).thenThrow(new RuntimeException("MimeMessage 생성 실패")); + when(javaMailSender.createMimeMessage()).thenThrow(new EmailTemplateFailException("MimeMessage 생성 실패")); // when & then - RuntimeException exception = assertThrows(RuntimeException.class, + RuntimeException exception = assertThrows(RuntimeException.class, () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); assertEquals("MimeMessage 생성 실패", exception.getMessage()); @@ -95,37 +96,35 @@ void setUp() { void sendTemplateEmail_템플릿처리실패() { // given when(templateEngine.process(eq(testTemplate), any(Context.class))) - .thenThrow(new RuntimeException("템플릿 처리 실패")); + .thenThrow(new EmailTemplateFailException("템플릿 처리 실패")); // when & then - RuntimeException exception = assertThrows(RuntimeException.class, + RuntimeException exception = assertThrows(RuntimeException.class, () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); - assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || - exception.getMessage().contains("템플릿 처리 실패")); + assertTrue(exception.getMessage().contains("템플릿 처리 실패")); verify(javaMailSender, never()).send(any(MimeMessage.class)); } @Test @DisplayName("템플릿 이메일 전송 - 이메일 전송 실패") - void sendTemplateEmail_이메일전송실패() throws Exception { + void sendTemplateEmail_이메일전송실패() { // given when(templateEngine.process(eq(testTemplate), any(Context.class))) .thenReturn(testHtmlContent); - doThrow(new RuntimeException("이메일 전송 실패")) + doThrow(new EmailTemplateFailException("이메일 전송 실패")) .when(javaMailSender).send(mimeMessage); // when & then - RuntimeException exception = assertThrows(RuntimeException.class, + RuntimeException exception = assertThrows(RuntimeException.class, () -> emailTemplateService.sendTemplateEmail(testEmail, testSubject, testTemplate, testAuthNum)); - assertTrue(exception.getMessage().contains("이메일 전송에 실패했습니다.") || - exception.getMessage().contains("이메일 전송 실패")); + assertTrue(exception.getMessage().contains("이메일 전송 실패")); } @Test @DisplayName("템플릿 이메일 전송 - 파라미터 검증") - void sendTemplateEmail_파라미터검증() throws Exception { + void sendTemplateEmail_파라미터검증() { // given when(templateEngine.process(anyString(), any(Context.class))) .thenReturn(testHtmlContent); From 410905f15d50d5bf73700478145b8299aa7e1370 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 00:12:41 +0900 Subject: [PATCH 0774/1002] =?UTF-8?q?refactor=20:=20Info=20package=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20=EA=B5=AC=EC=A1=B0=20=ED=8F=89=ED=83=84?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{domain/lab => }/controller/LabController.java | 10 +++++----- .../office => }/controller/OfficeController.java | 8 ++++---- .../controller/ProfessorController.java | 10 +++++----- .../dto/request/LabCreateUpdateRequestDto.java | 2 +- .../OfficeMemberCreateUpdateRequestDto.java | 2 +- .../dto/request/OfficeUpdateRequestDto.java | 4 ++-- .../request/ProfessorCreateUpdateRequestDto.java | 2 +- .../lab => }/dto/response/LabListResponseDto.java | 5 ++--- .../dto/response/LabThumbnailResponseDto.java | 4 ++-- .../dto/response/OfficeDetailsResponseDto.java | 4 ++-- .../dto/response/OfficeMemberResponseDto.java | 4 ++-- .../dto/response/ProfessorListResponseDto.java | 4 ++-- .../response/ProfessorThumbnailResponseDto.java | 4 ++-- .../domain/info/{domain/lab => }/entity/Lab.java | 6 ++---- .../info/{domain/office => }/entity/Office.java | 5 ++--- .../{domain/office => }/entity/OfficeMember.java | 4 ++-- .../{domain/professor => }/entity/Professor.java | 6 ++---- .../lab => }/exception/LabNotFoundException.java | 2 +- .../exception/ProfessorDuplicatedException.java | 2 +- .../exception/ProfessorNotFoundException.java | 2 +- .../domain/info/repository/InfoRepository.java | 6 +++--- .../info/{domain/lab => }/service/LabService.java | 12 ++++++------ .../{domain/office => }/service/OfficeService.java | 12 ++++++------ .../professor => }/service/ProfessorService.java | 14 +++++++------- 24 files changed, 64 insertions(+), 70 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/controller/LabController.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/controller/OfficeController.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/controller/ProfessorController.java (87%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/dto/request/LabCreateUpdateRequestDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/dto/request/OfficeMemberCreateUpdateRequestDto.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/dto/request/OfficeUpdateRequestDto.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/dto/request/ProfessorCreateUpdateRequestDto.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/dto/response/LabListResponseDto.java (89%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/dto/response/LabThumbnailResponseDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/dto/response/OfficeDetailsResponseDto.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/dto/response/OfficeMemberResponseDto.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/dto/response/ProfessorListResponseDto.java (89%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/dto/response/ProfessorThumbnailResponseDto.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/entity/Lab.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/entity/Office.java (85%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/entity/OfficeMember.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/entity/Professor.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/exception/LabNotFoundException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/exception/ProfessorDuplicatedException.java (71%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/exception/ProfessorNotFoundException.java (70%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/lab => }/service/LabService.java (84%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/office => }/service/OfficeService.java (85%) rename codin-core/src/main/java/inu/codin/codin/domain/info/{domain/professor => }/service/ProfessorService.java (85%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java index 0f96274f..8875a931 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java @@ -1,11 +1,11 @@ -package inu.codin.codin.domain.info.domain.lab.controller; +package inu.codin.codin.domain.info.controller; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.lab.dto.response.LabListResponseDto; -import inu.codin.codin.domain.info.domain.lab.dto.response.LabThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.lab.service.LabService; +import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.dto.response.LabListResponseDto; +import inu.codin.codin.domain.info.dto.response.LabThumbnailResponseDto; +import inu.codin.codin.domain.info.service.LabService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java index 05a7a6d4..952eb0cc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java @@ -1,10 +1,10 @@ -package inu.codin.codin.domain.info.domain.office.controller; +package inu.codin.codin.domain.info.controller; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; -import inu.codin.codin.domain.info.domain.office.service.OfficeService; +import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.service.OfficeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java index 072c30de..b5910248 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java @@ -1,12 +1,12 @@ -package inu.codin.codin.domain.info.domain.professor.controller; +package inu.codin.codin.domain.info.controller; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorListResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.professor.service.ProfessorService; +import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.dto.response.ProfessorListResponseDto; +import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.service.ProfessorService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java index 9b75e7f3..8ca41081 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/request/LabCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.lab.dto.request; +package inu.codin.codin.domain.info.dto.request; import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/OfficeMemberCreateUpdateRequestDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/OfficeMemberCreateUpdateRequestDto.java index 13bd04bb..0b3288d0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeMemberCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/OfficeMemberCreateUpdateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.office.dto.request; +package inu.codin.codin.domain.info.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/OfficeUpdateRequestDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/OfficeUpdateRequestDto.java index a6a4bb3c..1bf5d980 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/request/OfficeUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/OfficeUpdateRequestDto.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.domain.office.dto.request; +package inu.codin.codin.domain.info.dto.request; -import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.domain.info.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java index 56103dd6..3cfd3de4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/request/ProfessorCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.professor.dto.request; +package inu.codin.codin.domain.info.dto.request; import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabListResponseDto.java similarity index 89% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabListResponseDto.java index 9c76d96c..6e4e51c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabListResponseDto.java @@ -1,12 +1,11 @@ -package inu.codin.codin.domain.info.domain.lab.dto.response; +package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import org.bson.types.ObjectId; /* 연구실 리스트 반환 DTO diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java index 606b57c8..fcd0e387 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/dto/response/LabThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.info.domain.lab.dto.response; +package inu.codin.codin.domain.info.dto.response; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.lab.entity.Lab; +import inu.codin.codin.domain.info.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java index 9e22e6c3..88516715 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.info.domain.office.dto.response; +package inu.codin.codin.domain.info.dto.response; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.domain.info.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeMemberResponseDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeMemberResponseDto.java index bf7d0dbf..ee052ea2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/dto/response/OfficeMemberResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeMemberResponseDto.java @@ -1,6 +1,6 @@ -package inu.codin.codin.domain.info.domain.office.dto.response; +package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; +import inu.codin.codin.domain.info.entity.OfficeMember; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java similarity index 89% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java index ad0e58d5..62bff212 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.info.domain.professor.dto.response; +package inu.codin.codin.domain.info.dto.response; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java index 4095f694..cf2e5eb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/dto/response/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.info.domain.professor.dto.response; +package inu.codin.codin.domain.info.dto.response; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java index ca0fc14c..0bd63eda 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java @@ -1,9 +1,7 @@ -package inu.codin.codin.domain.info.domain.lab.entity; +package inu.codin.codin.domain.info.entity; -import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; -import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.entity.InfoType; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Office.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/entity/Office.java index bb998af1..b5eac1fa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/Office.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Office.java @@ -1,7 +1,6 @@ -package inu.codin.codin.domain.info.domain.office.entity; +package inu.codin.codin.domain.info.entity; -import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; -import inu.codin.codin.domain.info.entity.Info; +import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java index 16ce3e1d..19a7120c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/entity/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.info.domain.office.entity; +package inu.codin.codin.domain.info.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java index 54c5ae04..da280ae3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java @@ -1,9 +1,7 @@ -package inu.codin.codin.domain.info.domain.professor.entity; +package inu.codin.codin.domain.info.entity; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; -import inu.codin.codin.domain.info.entity.Info; -import inu.codin.codin.domain.info.entity.InfoType; +import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java index a3003257..35227c82 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/exception/LabNotFoundException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.lab.exception; +package inu.codin.codin.domain.info.exception; public class LabNotFoundException extends RuntimeException{ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java similarity index 71% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java index d78c76a3..20f8caed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorDuplicatedException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.professor.exception; +package inu.codin.codin.domain.info.exception; public class ProfessorDuplicatedException extends RuntimeException{ public ProfessorDuplicatedException(String message){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java similarity index 70% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java index 39f3cc7d..7b770415 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/exception/ProfessorNotFoundException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.info.domain.professor.exception; +package inu.codin.codin.domain.info.exception; public class ProfessorNotFoundException extends RuntimeException{ public ProfessorNotFoundException(String message){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 416fa37e..51efec33 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.repository; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.lab.entity.Lab; -import inu.codin.codin.domain.info.domain.office.entity.Office; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; +import inu.codin.codin.domain.info.entity.Lab; +import inu.codin.codin.domain.info.entity.Office; +import inu.codin.codin.domain.info.entity.Professor; import inu.codin.codin.domain.info.entity.Info; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java index 723711d3..0b95bcaf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/lab/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java @@ -1,10 +1,10 @@ -package inu.codin.codin.domain.info.domain.lab.service; +package inu.codin.codin.domain.info.service; -import inu.codin.codin.domain.info.domain.lab.dto.request.LabCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.lab.dto.response.LabListResponseDto; -import inu.codin.codin.domain.info.domain.lab.dto.response.LabThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.lab.entity.Lab; -import inu.codin.codin.domain.info.domain.lab.exception.LabNotFoundException; +import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; +import inu.codin.codin.domain.info.dto.response.LabListResponseDto; +import inu.codin.codin.domain.info.dto.response.LabThumbnailResponseDto; +import inu.codin.codin.domain.info.entity.Lab; +import inu.codin.codin.domain.info.exception.LabNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java index 760b0eb0..42f21447 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/office/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java @@ -1,11 +1,11 @@ -package inu.codin.codin.domain.info.domain.office.service; +package inu.codin.codin.domain.info.service; -import inu.codin.codin.domain.info.domain.office.dto.request.OfficeMemberCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.office.dto.request.OfficeUpdateRequestDto; -import inu.codin.codin.domain.info.domain.office.dto.response.OfficeDetailsResponseDto; -import inu.codin.codin.domain.info.domain.office.entity.Office; +import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; +import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; +import inu.codin.codin.domain.info.dto.response.OfficeDetailsResponseDto; +import inu.codin.codin.domain.info.entity.Office; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.office.entity.OfficeMember; +import inu.codin.codin.domain.info.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java rename to codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java index 0f6b70ef..d6d170f2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/domain/professor/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java @@ -1,12 +1,12 @@ -package inu.codin.codin.domain.info.domain.professor.service; +package inu.codin.codin.domain.info.service; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorListResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.response.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.domain.professor.dto.request.ProfessorCreateUpdateRequestDto; -import inu.codin.codin.domain.info.domain.professor.entity.Professor; -import inu.codin.codin.domain.info.domain.professor.exception.ProfessorDuplicatedException; -import inu.codin.codin.domain.info.domain.professor.exception.ProfessorNotFoundException; +import inu.codin.codin.domain.info.dto.response.ProfessorListResponseDto; +import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; +import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; +import inu.codin.codin.domain.info.entity.Professor; +import inu.codin.codin.domain.info.exception.ProfessorDuplicatedException; +import inu.codin.codin.domain.info.exception.ProfessorNotFoundException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; From 0d56f482d66b959e2b7ed07c04b0c52479bc2f6f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 00:40:36 +0900 Subject: [PATCH 0775/1002] =?UTF-8?q?feat=20:=20Partner(=EC=A0=9C=ED=9C=B4?= =?UTF-8?q?=20=EC=97=85=EC=B2=B4)=20Entity=20=EC=84=A4=EA=B3=84=20?= =?UTF-8?q?=EB=B0=8F=20Read=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/controller/PartnerController.java | 46 +++++++++++++++++++ .../response/PartnerDetailsResponseDto.java | 36 +++++++++++++++ .../dto/response/PartnerListResponseDto.java | 25 ++++++++++ .../codin/domain/info/entity/Partner.java | 35 ++++++++++++++ .../codin/domain/info/entity/PartnerImg.java | 11 +++++ .../info/repository/PartnerRepository.java | 9 ++++ .../domain/info/service/PartnerService.java | 32 +++++++++++++ 7 files changed, 194 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/repository/PartnerRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java new file mode 100644 index 00000000..6334a623 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -0,0 +1,46 @@ +package inu.codin.codin.domain.info.controller; + +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; +import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; +import inu.codin.codin.domain.info.service.PartnerService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/info/partner") +@Tag(name = "제휴업체 API", description = "제휴업체 CRUD") +public class PartnerController { + + private final PartnerService partnerService; + + @Operation( + description = "제휴업체 썸네일 리스트 반환" + ) + @GetMapping + public ResponseEntity> getPartnerList(){ + return ResponseEntity.ok() + .body(new ListResponse<>(200, "Partner 썸네일 리스트 반환 성공", partnerService.getPartnerList())); + } + + @Operation( + description = "제휴업체 상세 내역 반환" + ) + @GetMapping("/{id}") + public ResponseEntity> getPartnerDetails(@PathVariable("id") String partnerId){ + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "Partner 상세 내열 반환 성공", partnerService.getPartnerDetails(partnerId))); + } + + /* + 제휴업체 추가, 내용 수정, 삭제 + */ +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java new file mode 100644 index 00000000..6e46e348 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.info.dto.response; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.entity.Partner; +import inu.codin.codin.domain.info.entity.PartnerImg; +import lombok.Builder; + +import java.time.LocalDate; +import java.util.List; + +@Builder +public class PartnerDetailsResponseDto { + + private String name; + + private List tags; + + private List benefits; + + private LocalDate startDate; + + private LocalDate endDate; + + private PartnerImg img; + + public static PartnerDetailsResponseDto from(Partner partner){ + return PartnerDetailsResponseDto.builder() + .name(partner.getName()) + .tags(partner.getTags()) + .benefits(partner.getBenefits()) + .startDate(partner.getStartDate()) + .endDate(partner.getEndDate()) + .img(partner.getImg()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java new file mode 100644 index 00000000..490f6a39 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.info.dto.response; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.entity.Partner; +import lombok.Builder; + +import java.util.List; + +@Builder +public class PartnerListResponseDto { + + private String name; + + private String mainImg; + + private List tags; + + public static PartnerListResponseDto from(Partner partner) { + return PartnerListResponseDto.builder() + .name(partner.getName()) + .mainImg(partner.getImg().getMain()) + .tags(partner.getTags()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java new file mode 100644 index 00000000..59e6595e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java @@ -0,0 +1,35 @@ +package inu.codin.codin.domain.info.entity; + +import inu.codin.codin.common.dto.Department; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +@Document(collection = "partner") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Partner { + + @Id @NotBlank + private ObjectId _id; + + private String name; + + private List tags; + + private List benefits; + + private LocalDate startDate; + + private LocalDate endDate; + + private PartnerImg img; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java new file mode 100644 index 00000000..5bee79cb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.info.entity; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class PartnerImg { + private String main; + private List sub; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/PartnerRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/PartnerRepository.java new file mode 100644 index 00000000..152c36f3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/PartnerRepository.java @@ -0,0 +1,9 @@ +package inu.codin.codin.domain.info.repository; + +import inu.codin.codin.domain.info.entity.Partner; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface PartnerRepository extends MongoRepository { + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java new file mode 100644 index 00000000..b45630fc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.info.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; +import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; +import inu.codin.codin.domain.info.entity.Partner; +import inu.codin.codin.domain.info.repository.PartnerRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PartnerService { + + private final PartnerRepository partnerRepository; + + public List getPartnerList() { + List partners = partnerRepository.findAll(); + return partners.stream() + .map(PartnerListResponseDto::from) + .toList(); + } + + public PartnerDetailsResponseDto getPartnerDetails(String partnerId) { + Partner partner = partnerRepository.findById(new ObjectId(partnerId)) + .orElseThrow(() -> new NotFoundException("제휴업체를 찾을 수 없습니다.")); + return PartnerDetailsResponseDto.from(partner); + } +} From b64016ce758c7448db71de77b2879291ba662987 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Jun 2025 23:44:23 +0900 Subject: [PATCH 0776/1002] =?UTF-8?q?fix=20:=20GlobalException,=20ErrorCod?= =?UTF-8?q?e=20interface=20=EC=84=A4=EC=A0=95.=20ChatException=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalErrorCode.java | 9 ++++++ .../common/exception/GlobalException.java | 14 ++++++++++ .../exception/GlobalExceptionHandler.java | 27 ++++++++++++++---- .../chat/exception/ChatRoomErrorCode.java | 26 +++++++++++++++++ .../chat/exception/ChatRoomException.java | 14 ++++++++++ .../chat/exception/ChattingErrorCode.java | 28 +++++++++++++++++++ .../chat/exception/ChattingException.java | 16 +++++++++++ 7 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java new file mode 100644 index 00000000..875f791f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java @@ -0,0 +1,9 @@ +package inu.codin.codin.common.exception; + +import org.springframework.http.HttpStatus; + +public interface GlobalErrorCode { + HttpStatus httpStatus(); + + String message(); +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java new file mode 100644 index 00000000..8e9bcdd5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java @@ -0,0 +1,14 @@ +package inu.codin.codin.common.exception; + +import lombok.Getter; + +@Getter +public class GlobalException extends RuntimeException { + + private final GlobalErrorCode errorCode; + public GlobalException(GlobalErrorCode errorCode){ + super(errorCode.message()); + this.errorCode = errorCode; + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 2845ff24..8716ae3f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,7 +2,9 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; +import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; +import inu.codin.codin.domain.chat.exception.ChatRoomException; +import inu.codin.codin.domain.chat.exception.ChatRoomExistedException; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; @@ -22,6 +24,23 @@ protected ResponseEntity handleException(Exception e) { .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); } + @ExceptionHandler(GlobalException.class) + protected ResponseEntity handleGlobalException(GlobalException e) { + GlobalErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + + @ExceptionHandler(ChatRoomException.class) + protected ResponseEntity handleChatRoomException(ChatRoomException e) { + ChatRoomErrorCode code = e.getErrorCode(); + String message = code.message(); + if (e instanceof ChatRoomExistedException existedException) //클라이언트 측에서 받아야 하는 chatroomId를 포함해서 전달 + message = message + "/" + existedException.getChatRoomId(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(message, code.httpStatus().value())); + } + @ExceptionHandler(NotFoundException.class) protected ResponseEntity handleNotFoundException(NotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -43,11 +62,7 @@ protected ResponseEntity handleValidationExceptions(MethodArg return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ExceptionResponse(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST.value())); } - @ExceptionHandler(ChatRoomExistedException.class) - protected ResponseEntity handleChatRoomExistedException(ChatRoomExistedException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new ExceptionResponse(e.getMessage() +"/"+ e.getChatRoomId(), e.getErrorCode())); - } + @ExceptionHandler(RedisSystemException.class) public ResponseEntity handleRedisSystemException(RedisSystemException e){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java new file mode 100644 index 00000000..718f18fa --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum ChatRoomErrorCode implements GlobalErrorCode { + CHATROOM_CREATE_MYSELF(HttpStatus.BAD_REQUEST, "자기 자신과는 채팅방을 생성할 수 없습니다."), + CHATROOM_EXISTED(HttpStatus.valueOf(303), "해당 reference에서 시작된 채팅방이 존재합니다."), + CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND, "채팅방을 찾을 수 없습니다."), + PARTICIPANTS_NOT_FOUND(HttpStatus.NOT_FOUND, "채팅방 내의 참여자를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java new file mode 100644 index 00000000..325c0cbe --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class ChatRoomException extends GlobalException { + + private final ChatRoomErrorCode errorCode; + public ChatRoomException(ChatRoomErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java new file mode 100644 index 00000000..eb42d415 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ChattingErrorCode implements GlobalErrorCode { + + CHATTING_USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "헤더에서 유저(email)을 찾을 수 없습니다."), + CHATTING_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "헤더에서 채팅방 _id를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + + private final String message; + + @Override + public HttpStatus httpStatus() { + return null; + } + + @Override + public String message() { + return null; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java new file mode 100644 index 00000000..e78cc1d2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java @@ -0,0 +1,16 @@ +package inu.codin.codin.domain.chat.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class ChattingException extends GlobalException { + + private final ChattingErrorCode errorCode; + private final String sessionId; + public ChattingException(ChattingErrorCode errorCode, String sessionId) { + super(errorCode); + this.errorCode = errorCode; + this.sessionId = sessionId; + } +} From 49fc84fcd33ec22fa8cc1833a5161c6c47f115b7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 27 Jun 2025 23:51:59 +0900 Subject: [PATCH 0777/1002] =?UTF-8?q?fix=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Getter=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20GlobalExc?= =?UTF-8?q?eptionHandler=EC=97=90=20ChattingException=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalExceptionHandler.java | 13 +++++++++---- .../domain/chat/exception/ChattingErrorCode.java | 6 ++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 8716ae3f..2fb9dca9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,9 +2,7 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; -import inu.codin.codin.domain.chat.exception.ChatRoomException; -import inu.codin.codin.domain.chat.exception.ChatRoomExistedException; +import inu.codin.codin.domain.chat.exception.*; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; @@ -35,12 +33,19 @@ protected ResponseEntity handleGlobalException(GlobalExceptio protected ResponseEntity handleChatRoomException(ChatRoomException e) { ChatRoomErrorCode code = e.getErrorCode(); String message = code.message(); - if (e instanceof ChatRoomExistedException existedException) //클라이언트 측에서 받아야 하는 chatroomId를 포함해서 전달 + if (e instanceof ChatRoomExistedException existedException) //client 측에서 303 상태 코드 확인 후 message의 chatRoomId로 리다이렉션 message = message + "/" + existedException.getChatRoomId(); return ResponseEntity.status(code.httpStatus()) .body(new ExceptionResponse(message, code.httpStatus().value())); } + @ExceptionHandler(ChattingException.class) + protected ResponseEntity handleChattingException(ChattingException e) { + ChattingErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + @ExceptionHandler(NotFoundException.class) protected ResponseEntity handleNotFoundException(NotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java index eb42d415..3bf11950 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java @@ -1,11 +1,9 @@ package inu.codin.codin.domain.chat.exception; import inu.codin.codin.common.exception.GlobalErrorCode; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -@Getter @RequiredArgsConstructor public enum ChattingErrorCode implements GlobalErrorCode { @@ -18,11 +16,11 @@ public enum ChattingErrorCode implements GlobalErrorCode { @Override public HttpStatus httpStatus() { - return null; + return httpStatus; } @Override public String message() { - return null; + return message; } } From c1daa7afffbf9f5431c2ba56890d713e5e5ff458 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 00:45:59 +0900 Subject: [PATCH 0778/1002] =?UTF-8?q?style=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=9D=BC=EC=B9=98=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/exception/GlobalErrorCode.java | 1 - .../java/inu/codin/codin/common/exception/GlobalException.java | 1 - 2 files changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java index 875f791f..70c3f2f2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java @@ -4,6 +4,5 @@ public interface GlobalErrorCode { HttpStatus httpStatus(); - String message(); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java index 8e9bcdd5..6b2c2932 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java @@ -10,5 +10,4 @@ public GlobalException(GlobalErrorCode errorCode){ super(errorCode.message()); this.errorCode = errorCode; } - } From c6a4f01801dd92cf1128a0628a2e3c54ac580d3f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 01:08:14 +0900 Subject: [PATCH 0779/1002] =?UTF-8?q?refactor=20:=20InfoException,=20InfoE?= =?UTF-8?q?rrorCode=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20GlobalExceptionHand?= =?UTF-8?q?ler=20=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 10 +++++++ .../domain/info/exception/InfoErrorCode.java | 27 +++++++++++++++++++ .../domain/info/exception/InfoException.java | 14 ++++++++++ .../info/exception/LabNotFoundException.java | 8 ------ .../ProfessorDuplicatedException.java | 7 ----- .../exception/ProfessorNotFoundException.java | 7 ----- .../codin/domain/info/service/LabService.java | 9 ++++--- .../domain/info/service/ProfessorService.java | 12 ++++----- 8 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 2fb9dca9..a138ac67 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,7 +2,10 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; import inu.codin.codin.domain.chat.exception.*; +import inu.codin.codin.domain.info.exception.InfoErrorCode; +import inu.codin.codin.domain.info.exception.InfoException; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; @@ -46,6 +49,13 @@ protected ResponseEntity handleChattingException(ChattingExce .body(new ExceptionResponse(code.message(), code.httpStatus().value())); } + @ExceptionHandler(InfoException.class) + protected ResponseEntity handleInfoException(InfoException e) { + InfoErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + @ExceptionHandler(NotFoundException.class) protected ResponseEntity handleNotFoundException(NotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java new file mode 100644 index 00000000..121db0d2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.info.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum InfoErrorCode implements GlobalErrorCode { + + LAB_NOT_FOUND(HttpStatus.NOT_FOUND, "LAB 정보를 찾을 수 없습니다."), + + PROFESSOR_NOT_FOUND(HttpStatus.NOT_FOUND, "PROFESSOR 정보를 찾을 수 없습니다."), + PROFESSOR_DUPLICATED(HttpStatus.CONFLICT, "이미 존재하는 PROFESSOR 정보 입니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java new file mode 100644 index 00000000..85a383ad --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.info.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class InfoException extends GlobalException { + + private final InfoErrorCode errorCode; + public InfoException(InfoErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java deleted file mode 100644 index 35227c82..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/LabNotFoundException.java +++ /dev/null @@ -1,8 +0,0 @@ -package inu.codin.codin.domain.info.exception; - -public class LabNotFoundException extends RuntimeException{ - - public LabNotFoundException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java deleted file mode 100644 index 20f8caed..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorDuplicatedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.info.exception; - -public class ProfessorDuplicatedException extends RuntimeException{ - public ProfessorDuplicatedException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java deleted file mode 100644 index 7b770415..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/ProfessorNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.info.exception; - -public class ProfessorNotFoundException extends RuntimeException{ - public ProfessorNotFoundException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java index 0b95bcaf..6906241c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/LabService.java @@ -4,7 +4,8 @@ import inu.codin.codin.domain.info.dto.response.LabListResponseDto; import inu.codin.codin.domain.info.dto.response.LabThumbnailResponseDto; import inu.codin.codin.domain.info.entity.Lab; -import inu.codin.codin.domain.info.exception.LabNotFoundException; +import inu.codin.codin.domain.info.exception.InfoErrorCode; +import inu.codin.codin.domain.info.exception.InfoException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,7 +25,7 @@ public LabThumbnailResponseDto getLabThumbnail(String id) { Lab lab = infoRepository.findLabById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[getLabThumbnail] 연구실 정보 조회 실패, 연구실 ID: {}", id); - return new LabNotFoundException("연구실 정보를 찾을 수 없습니다."); + return new InfoException(InfoErrorCode.LAB_NOT_FOUND); }); log.info("[getLabThumbnail] {}의 연구실 정보 열람", id); return LabThumbnailResponseDto.of(lab); @@ -47,7 +48,7 @@ public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, Strin Lab lab = infoRepository.findLabById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[updateLab] 연구실 정보 업데이트 실패, 연구실 ID: {}", id); - return new LabNotFoundException("연구실 정보를 찾을 수 없습니다."); + return new InfoException(InfoErrorCode.LAB_NOT_FOUND); }); lab.update(labCreateUpdateRequestDto); infoRepository.save(lab); @@ -56,7 +57,7 @@ public void updateLab(LabCreateUpdateRequestDto labCreateUpdateRequestDto, Strin public void deleteLab(String id) { Lab lab = infoRepository.findLabById(new ObjectId(id)) - .orElseThrow(() -> new LabNotFoundException("연구실 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> new InfoException(InfoErrorCode.LAB_NOT_FOUND)); lab.delete(); infoRepository.save(lab); log.info("[deleteLab] {}의 연구실 정보 삭제", lab.get_id().toString()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java index d6d170f2..b03bd4fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java @@ -5,8 +5,8 @@ import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Professor; -import inu.codin.codin.domain.info.exception.ProfessorDuplicatedException; -import inu.codin.codin.domain.info.exception.ProfessorNotFoundException; +import inu.codin.codin.domain.info.exception.InfoErrorCode; +import inu.codin.codin.domain.info.exception.InfoException; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -32,7 +32,7 @@ public List getProfessorByDepartment(Department depart public ProfessorThumbnailResponseDto getProfessorThumbnail(String id) { log.info("[getProfessorThumbnail] 교수 ID '{}'로 정보 조회 시도", id); Professor professor = infoRepository.findProfessorById(new ObjectId(id)) - .orElseThrow(() -> new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> new InfoException(InfoErrorCode.PROFESSOR_NOT_FOUND)); log.info("[getProfessorThumbnail] {} 교수님의 정보 열람", professor.get_id().toString()); return ProfessorThumbnailResponseDto.of(professor); } @@ -41,7 +41,7 @@ public void createProfessor(ProfessorCreateUpdateRequestDto professorCreateUpdat log.info("[createProfessor] 교수 이메일 '{}'로 정보 생성 시도", professorCreateUpdateRequestDto.getEmail()); if (infoRepository.findProfessorByEmail(professorCreateUpdateRequestDto.getEmail()).isPresent()){ log.warn("[createProfessor] 교수 이메일 '{}' 이미 존재", professorCreateUpdateRequestDto.getEmail()); - throw new ProfessorDuplicatedException("이미 존재하는 Professor 정보 입니다."); + throw new InfoException(InfoErrorCode.PROFESSOR_DUPLICATED); } Professor professor = Professor.of(professorCreateUpdateRequestDto); infoRepository.save(professor); @@ -52,7 +52,7 @@ public void updateProfessor(String id, ProfessorCreateUpdateRequestDto professor Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[updateProfessor] 교수 ID '{}' 정보가 존재하지 않음", id); - return new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다."); + return new InfoException(InfoErrorCode.PROFESSOR_NOT_FOUND); }); professor.update(professorCreateUpdateRequestDto); infoRepository.save(professor); @@ -66,7 +66,7 @@ public void deleteProfessor(String id) { Professor professor = infoRepository.findProfessorById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[deleteProfessor] 교수 ID '{}' 정보가 존재하지 않음", id); - return new ProfessorNotFoundException("교수 정보를 찾을 수 없습니다."); + return new InfoException(InfoErrorCode.PROFESSOR_NOT_FOUND); });professor.delete(); infoRepository.save(professor); log.info("[deleteProfessor] {} 교수님의 정보 삭제", professor.get_id().toString()); From a799d65d901f157af6d8d25102eafb2d5633a755 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 02:17:40 +0900 Subject: [PATCH 0780/1002] =?UTF-8?q?feat=20:=20Partner=20create=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/controller/PartnerController.java | 26 +++++++++---- .../dto/request/PartnerCreateRequestDto.java | 37 +++++++++++++++++++ .../codin/domain/info/entity/Partner.java | 24 +++++++++++- .../domain/info/service/PartnerService.java | 12 +++++- 4 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java index 6334a623..4f514d24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -2,28 +2,29 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.info.dto.request.PartnerCreateRequestDto; import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; import inu.codin.codin.domain.info.service.PartnerService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -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.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/info/partner") -@Tag(name = "제휴업체 API", description = "제휴업체 CRUD") +@Tag(name = "Partner API", description = "Partner CRUD") public class PartnerController { private final PartnerService partnerService; @Operation( - description = "제휴업체 썸네일 리스트 반환" + summary = "Partner 썸네일 리스트 반환" ) @GetMapping public ResponseEntity> getPartnerList(){ @@ -32,7 +33,7 @@ public ResponseEntity> getPartnerList(){ } @Operation( - description = "제휴업체 상세 내역 반환" + summary = "Partner 상세 내역 반환" ) @GetMapping("/{id}") public ResponseEntity> getPartnerDetails(@PathVariable("id") String partnerId){ @@ -40,6 +41,17 @@ public ResponseEntity> getPartnerDetai .body(new SingleResponse<>(200, "Partner 상세 내열 반환 성공", partnerService.getPartnerDetails(partnerId))); } + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation( + summary = "[ADMIN, MANAGER] Partner 추가" + ) + @PostMapping + public ResponseEntity> createPartner(@RequestBody @Valid PartnerCreateRequestDto partnerCreateRequestDto){ + partnerService.createPartner(partnerCreateRequestDto); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "Partner 생성 완료", null)); + } + /* 제휴업체 추가, 내용 수정, 삭제 */ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java new file mode 100644 index 00000000..ec50de03 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java @@ -0,0 +1,37 @@ +package inu.codin.codin.domain.info.dto.request; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.entity.PartnerImg; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +public class PartnerCreateRequestDto { + + @NotBlank + @Schema(description = "제휴업체 가게 이름", example = "홍콩반점 송도점") + private String name; + + @NotEmpty + @Schema(description = "제휴 학과", example = "[\"COMPUTER_SCI\", \"INFO_COMM\", \"EMBEDDED\"]") + private List tags; + + @NotEmpty + @Schema(description = "제휴 혜택", example = "[\"탕수육 주문 시, 탕수육 공짜!\", \"평일 언제나 80% 대박 할인!\"]") + private List benefits; + + @Schema(description = "제휴 시작 날짜", example = "2025-03-01") + private LocalDate startDate; + + @Schema(description = "제휴 종료 날짜", example = "2026-03-01") + private LocalDate endDate; + + @Schema(description = "제휴업체 가게 이미지") + private PartnerImg img; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java index 59e6595e..31195642 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java @@ -1,8 +1,10 @@ package inu.codin.codin.domain.info.entity; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.dto.request.PartnerCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.bson.types.ObjectId; @@ -11,7 +13,6 @@ import java.time.LocalDate; import java.util.List; -import java.util.Map; @Document(collection = "partner") @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -32,4 +33,25 @@ public class Partner { private LocalDate endDate; private PartnerImg img; + + @Builder + public Partner(String name, List tags, List benefits, LocalDate startDate, LocalDate endDate, PartnerImg img) { + this.name = name; + this.tags = tags; + this.benefits = benefits; + this.startDate = startDate; + this.endDate = endDate; + this.img = img; + } + + public static Partner of(PartnerCreateRequestDto partnerCreateRequestDto){ + return Partner.builder() + .name(partnerCreateRequestDto.getName()) + .tags(partnerCreateRequestDto.getTags()) + .benefits(partnerCreateRequestDto.getBenefits()) + .startDate(partnerCreateRequestDto.getStartDate()) + .endDate(partnerCreateRequestDto.getEndDate()) + .img(partnerCreateRequestDto.getImg()) + .build(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java index b45630fc..b10e7c37 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -1,9 +1,11 @@ package inu.codin.codin.domain.info.service; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.info.dto.request.PartnerCreateRequestDto; import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; import inu.codin.codin.domain.info.entity.Partner; +import inu.codin.codin.domain.info.exception.InfoErrorCode; +import inu.codin.codin.domain.info.exception.InfoException; import inu.codin.codin.domain.info.repository.PartnerRepository; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; @@ -26,7 +28,13 @@ public List getPartnerList() { public PartnerDetailsResponseDto getPartnerDetails(String partnerId) { Partner partner = partnerRepository.findById(new ObjectId(partnerId)) - .orElseThrow(() -> new NotFoundException("제휴업체를 찾을 수 없습니다.")); + .orElseThrow(() -> new InfoException(InfoErrorCode.PARTNER_NOT_FOUND)); return PartnerDetailsResponseDto.from(partner); } + + public void createPartner(PartnerCreateRequestDto partnerCreateRequestDto) { + // todo 이미지 업로드, url 받기 + Partner partner = Partner.of(partnerCreateRequestDto); + partnerRepository.save(partner); + } } From 8aea6ab917a5cc35a9fbce6f1768e3c69c65e4c2 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 02:18:49 +0900 Subject: [PATCH 0781/1002] =?UTF-8?q?fix=20:=20Scheme=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20Department=EC=9D=98=20JSON=20=EB=A7=A4=ED=95=91?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/dto/Department.java | 11 +++++++++++ .../info/dto/response/PartnerListResponseDto.java | 12 ++++++++++++ .../codin/codin/domain/info/entity/PartnerImg.java | 5 +++++ .../codin/domain/info/exception/InfoErrorCode.java | 4 +++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java index f49f8966..9819d2e1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java @@ -1,10 +1,14 @@ package inu.codin.codin.common.dto; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Getter @RequiredArgsConstructor +@Slf4j public enum Department { IT_COLLEGE("정보기술대학"), @@ -17,13 +21,20 @@ public enum Department { private final String description; + @JsonCreator public static Department fromDescription(String description) { for (Department department : Department.values()) { if (department.getDescription().equals(description)) { return department; } } + log.warn("정보대 내의 학과가 아닙니다. description : " + description); return OTHERS; } + @JsonValue + public String getDescription(){ + return description; + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java index 490f6a39..4596cff6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java @@ -2,21 +2,33 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Partner; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; +import lombok.Getter; import java.util.List; @Builder +@Getter public class PartnerListResponseDto { + @Schema(description = "제휴업체 pk", example = "6862c1128d0ca733396ecd5b") + @NotBlank + private String id; + + @Schema(description = "제휴업체 가게 이름", example = "홍콩반점 송도점") private String name; + @Schema(description = "제휴업체 가게 이미지", example = "https://example.com") private String mainImg; + @Schema(description = "제휴 학과", example = "[\"COMPUTER_SCI\", \"INFO_COMM\", \"EMBEDDED\"]") private List tags; public static PartnerListResponseDto from(Partner partner) { return PartnerListResponseDto.builder() + .id(partner.get_id().toString()) .name(partner.getName()) .mainImg(partner.getImg().getMain()) .tags(partner.getTags()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java index 5bee79cb..f3eae4f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java @@ -1,11 +1,16 @@ package inu.codin.codin.domain.info.entity; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import java.util.List; @Getter public class PartnerImg { + + @Schema(description = "제휴업체 메인 이미지", example = "https://example.com") private String main; + + @Schema(description = "제휴업체 서브 이미지", example = "[\"https://example.com\", \"https://example.com\"]") private List sub; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java index 121db0d2..743d7b77 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java @@ -10,7 +10,9 @@ public enum InfoErrorCode implements GlobalErrorCode { LAB_NOT_FOUND(HttpStatus.NOT_FOUND, "LAB 정보를 찾을 수 없습니다."), PROFESSOR_NOT_FOUND(HttpStatus.NOT_FOUND, "PROFESSOR 정보를 찾을 수 없습니다."), - PROFESSOR_DUPLICATED(HttpStatus.CONFLICT, "이미 존재하는 PROFESSOR 정보 입니다."); + PROFESSOR_DUPLICATED(HttpStatus.CONFLICT, "이미 존재하는 PROFESSOR 정보 입니다."), + + PARTNER_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTNER 정보를 찾을 수 없습니다."); private final HttpStatus httpStatus; private final String message; From c0f8e81012f9d9d16f589d402083ea8337eccc86 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 15:55:41 +0900 Subject: [PATCH 0782/1002] =?UTF-8?q?feat=20:=20=EC=A0=9C=ED=9C=B4?= =?UTF-8?q?=EC=97=85=EC=B2=B4=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=83=9D=EC=84=B1=20=EC=8B=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/controller/PartnerController.java | 26 +++++++++++++++---- .../dto/request/PartnerCreateRequestDto.java | 6 ++--- .../codin/domain/info/entity/Partner.java | 4 +-- .../codin/domain/info/entity/PartnerImg.java | 2 ++ .../domain/info/service/PartnerService.java | 22 +++++++++++++--- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java index 4f514d24..7913a49a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -14,6 +14,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -27,7 +30,7 @@ public class PartnerController { summary = "Partner 썸네일 리스트 반환" ) @GetMapping - public ResponseEntity> getPartnerList(){ + public ResponseEntity> getPartnerList() { return ResponseEntity.ok() .body(new ListResponse<>(200, "Partner 썸네일 리스트 반환 성공", partnerService.getPartnerList())); } @@ -36,7 +39,7 @@ public ResponseEntity> getPartnerList(){ summary = "Partner 상세 내역 반환" ) @GetMapping("/{id}") - public ResponseEntity> getPartnerDetails(@PathVariable("id") String partnerId){ + public ResponseEntity> getPartnerDetails(@PathVariable("id") String partnerId) { return ResponseEntity.ok() .body(new SingleResponse<>(200, "Partner 상세 내열 반환 성공", partnerService.getPartnerDetails(partnerId))); } @@ -46,13 +49,26 @@ public ResponseEntity> getPartnerDetai summary = "[ADMIN, MANAGER] Partner 추가" ) @PostMapping - public ResponseEntity> createPartner(@RequestBody @Valid PartnerCreateRequestDto partnerCreateRequestDto){ - partnerService.createPartner(partnerCreateRequestDto); + public ResponseEntity createPartner(@RequestPart("partnerInfo") @Valid PartnerCreateRequestDto partnerCreateRequestDto, + @RequestPart("mainImage") MultipartFile mainImage, + @RequestPart("subImages")List subImages ) { + partnerService.createPartner(partnerCreateRequestDto, mainImage, subImages); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "Partner 생성 완료", null)); } + @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @Operation( + summary = "[ADMIN, MANAGER] Partner 삭제" + ) + @DeleteMapping("/{id}") + public ResponseEntity deletePartner(@PathVariable("id") String partnerId) { + partnerService.deletePartner(partnerId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "Partner 삭제 완료", null)); + } + /* - 제휴업체 추가, 내용 수정, 삭제 + 제휴업체 내용 수정 */ } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java index ec50de03..c5ae74b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java @@ -5,12 +5,14 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; import lombok.Getter; import java.time.LocalDate; import java.util.List; @Getter +@Builder public class PartnerCreateRequestDto { @NotBlank @@ -30,8 +32,4 @@ public class PartnerCreateRequestDto { @Schema(description = "제휴 종료 날짜", example = "2026-03-01") private LocalDate endDate; - - @Schema(description = "제휴업체 가게 이미지") - private PartnerImg img; - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java index 31195642..cfc135db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java @@ -44,14 +44,14 @@ public Partner(String name, List tags, List benefits, LocalD this.img = img; } - public static Partner of(PartnerCreateRequestDto partnerCreateRequestDto){ + public static Partner of(PartnerCreateRequestDto partnerCreateRequestDto, PartnerImg partnerImg){ return Partner.builder() .name(partnerCreateRequestDto.getName()) .tags(partnerCreateRequestDto.getTags()) .benefits(partnerCreateRequestDto.getBenefits()) .startDate(partnerCreateRequestDto.getStartDate()) .endDate(partnerCreateRequestDto.getEndDate()) - .img(partnerCreateRequestDto.getImg()) + .img(partnerImg) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java index f3eae4f3..465b10eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/PartnerImg.java @@ -1,11 +1,13 @@ package inu.codin.codin.domain.info.entity; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; @Getter +@AllArgsConstructor public class PartnerImg { @Schema(description = "제휴업체 메인 이미지", example = "https://example.com") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java index b10e7c37..adf530f8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -4,12 +4,16 @@ import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; import inu.codin.codin.domain.info.entity.Partner; +import inu.codin.codin.domain.info.entity.PartnerImg; import inu.codin.codin.domain.info.exception.InfoErrorCode; import inu.codin.codin.domain.info.exception.InfoException; import inu.codin.codin.domain.info.repository.PartnerRepository; +import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -18,6 +22,7 @@ public class PartnerService { private final PartnerRepository partnerRepository; + private final S3Service s3Service; public List getPartnerList() { List partners = partnerRepository.findAll(); @@ -32,9 +37,20 @@ public PartnerDetailsResponseDto getPartnerDetails(String partnerId) { return PartnerDetailsResponseDto.from(partner); } - public void createPartner(PartnerCreateRequestDto partnerCreateRequestDto) { - // todo 이미지 업로드, url 받기 - Partner partner = Partner.of(partnerCreateRequestDto); + public void createPartner(PartnerCreateRequestDto partnerCreateRequestDto, MultipartFile mainImage, List subImages) { + PartnerImg partnerImg = getPartnerImg(mainImage, subImages); + Partner partner = Partner.of(partnerCreateRequestDto, partnerImg); partnerRepository.save(partner); } + + private PartnerImg getPartnerImg(MultipartFile mainImage, List subImages) { + String main = s3Service.handleImageUpload(List.of(mainImage)).get(0); + List subs = s3Service.handleImageUpload(subImages); + return new PartnerImg(main, subs); + } + + @Transactional + public void deletePartner(String partnerId) { + partnerRepository.deleteById(new ObjectId(partnerId)); + } } From 9c295f9385c7530f6f1146e5d2a8c9041b56e642 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 15:56:11 +0900 Subject: [PATCH 0783/1002] =?UTF-8?q?test=20:=20=EC=A0=9C=ED=9C=B4?= =?UTF-8?q?=EC=97=85=EC=B2=B4=20=EA=B8=B0=EB=8A=A5=20=EB=B0=98=ED=99=98,?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1,=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/service/PartnerServiceTest.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java new file mode 100644 index 00000000..c8fbdcf5 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java @@ -0,0 +1,122 @@ +package inu.codin.codin.domain.info.service; + +import inu.codin.codin.domain.info.dto.request.PartnerCreateRequestDto; +import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; +import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; +import inu.codin.codin.domain.info.entity.Partner; +import inu.codin.codin.domain.info.entity.PartnerImg; +import inu.codin.codin.domain.info.exception.InfoErrorCode; +import inu.codin.codin.domain.info.exception.InfoException; +import inu.codin.codin.domain.info.repository.PartnerRepository; +import inu.codin.codin.infra.s3.S3Service; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PartnerServiceTest { + + @InjectMocks + PartnerService partnerService; + + @Mock + PartnerRepository partnerRepository; + + @Mock + S3Service s3Service; + + @Test + @DisplayName("제휴업체 목록 조회") + void 제휴업체_목록_반환() { + //given + Partner partner = mock(Partner.class); + given(partner.get_id()).willReturn(new ObjectId()); + given(partner.getImg()).willReturn(new PartnerImg("img1", List.of("img2"))); + + given(partnerRepository.findAll()).willReturn(List.of(partner)); + + //when + List responseDtos = partnerService.getPartnerList(); + + //then + assertThat(responseDtos).hasSize(1); + } + + @Test + @DisplayName("제휴업체 상세 조회 - 성공") + void 제휴업체_상세_조회_성공() { + //given + String partnerId = new ObjectId().toString(); + Partner partner = mock(Partner.class); + given(partnerRepository.findById(new ObjectId(partnerId))).willReturn(Optional.ofNullable(partner)); + + //when + PartnerDetailsResponseDto responseDto = partnerService.getPartnerDetails(partnerId); + + //then + assertNotNull(responseDto); + } + + @Test + @DisplayName("제휴업체 상세 조회 - 성공") + void 제휴업체_상세_조회_실패() { + //given + String partnerId = new ObjectId().toString(); + given(partnerRepository.findById(new ObjectId(partnerId))).willReturn(Optional.empty()); + + //when & then + assertThatThrownBy(() -> partnerService.getPartnerDetails(partnerId)) + .isInstanceOf(InfoException.class) + .hasMessageContaining(InfoErrorCode.PARTNER_NOT_FOUND.message()); + } + + @Test + @DisplayName("제휴업체 생성") + void 제휴업체_생성() { + //given + PartnerCreateRequestDto requestDto = mock(PartnerCreateRequestDto.class); + MultipartFile mainImage = mock(MultipartFile.class); + List subImage = List.of(mock(MultipartFile.class)); + + given(s3Service.handleImageUpload(List.of(mainImage))).willReturn(List.of("mainImage")); + given(s3Service.handleImageUpload(subImage)).willReturn(List.of("subImage1", "subImage2")); + + //when + partnerService.createPartner(requestDto, mainImage, subImage); + + //then + ArgumentCaptor captor = ArgumentCaptor.forClass(Partner.class); + verify(partnerRepository, times(1)).save(captor.capture()); + + Partner savedPartner = captor.getValue(); + assertNotNull(savedPartner); + } + + @Test + @DisplayName("제휴업체 삭제") + void 제휴업체_삭제() { + //given + String partnerId = new ObjectId().toString(); + + //when + partnerService.deletePartner(partnerId); + + //then + verify(partnerRepository, times(1)).deleteById(new ObjectId(partnerId)); + } +} \ No newline at end of file From 11f472f9455dddee26778702df6d159bd317fbf0 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 16:56:54 +0900 Subject: [PATCH 0784/1002] =?UTF-8?q?refactor:=20UserValidator=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/service/BlockService.java | 2 +- .../domain/user/{validator => service}/UserValidator.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/user/{validator => service}/UserValidator.java (88%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 8402a211..d1dd1605 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -6,7 +6,7 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; -import inu.codin.codin.domain.user.validator.UserValidator; +import inu.codin.codin.domain.user.service.UserValidator; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java rename to codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java index 3bb7a5b3..7f34a32a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.validator; +package inu.codin.codin.domain.user.service; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.user.repository.UserRepository; @@ -9,7 +9,7 @@ @Component @RequiredArgsConstructor public class UserValidator { - private UserRepository userRepository; + private final UserRepository userRepository; /** * User 존재 여부 검증 From f67d31f3b4849f4a35832c3e614c62d4103897c7 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 16:58:07 +0900 Subject: [PATCH 0785/1002] =?UTF-8?q?test:=20TestSecurityConfig=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/config/TestSecurityConfig.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java diff --git a/codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java b/codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java new file mode 100644 index 00000000..3e6924a9 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java @@ -0,0 +1,26 @@ +package inu.codin.codin.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@TestConfiguration +@EnableWebSecurity +public class TestSecurityConfig { + + @Bean + public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll() + ) + .sessionManagement(session -> session + .sessionCreationPolicy(org.springframework.security.config.http.SessionCreationPolicy.STATELESS) + ); + return http.build(); + } +} From 5010df2b720adfe8c7958d0ae2981b7bff1d5234 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 16:58:28 +0900 Subject: [PATCH 0786/1002] =?UTF-8?q?test:=20Block=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/controller/BlockControllerTest.java | 152 ++++++++++++ .../block/service/BlockServiceTest.java | 221 ++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java new file mode 100644 index 00000000..37c60ab1 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java @@ -0,0 +1,152 @@ +package inu.codin.codin.domain.block.controller; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.block.exception.AlreadyBlockedException; +import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.service.BlockService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BlockControllerTest { + + @InjectMocks + private BlockController blockController; + + @Mock + private BlockService blockService; + + private final String testUserId = "686373fdaa87fd9618a63b49"; + private final String blockedUserId = "6863740ceeb4a94ee959f592"; + + @Test + @DisplayName("사용자 차단 성공") + void blockUser_성공() { + //given + doNothing().when(blockService).blockUser(anyString()); + + //when + ResponseEntity response = blockController.blockUser(blockedUserId); + + //then + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.getBody()).isInstanceOf(SingleResponse.class); + + SingleResponse singleResponse = (SingleResponse) response.getBody(); + Assertions.assertNotNull(singleResponse); + assertThat(singleResponse.getCode()).isEqualTo(201); + assertThat(singleResponse.getMessage()).isEqualTo("사용자 차단 완료"); + assertThat(singleResponse.getData()).isNull(); + + verify(blockService).blockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 실패 - 자기 자신 차단") + void blockUser_실패_자기자신차단() { + //given + doThrow(new SelfBlockedException("자신을 차단할 수 없습니다.")) + .when(blockService).blockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.blockUser(testUserId)) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단할 수 없습니다."); + + verify(blockService).blockUser(testUserId); + } + + @Test + @DisplayName("사용자 차단 실패 - 이미 차단된 사용자") + void blockUser_실패_이미차단된사용자() { + //given + doThrow(new AlreadyBlockedException("이미 차단한 유저입니다.")) + .when(blockService).blockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) + .isInstanceOf(AlreadyBlockedException.class) + .hasMessage("이미 차단한 유저입니다."); + + verify(blockService).blockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 실패 - 차단할 사용자를 찾을 수 없음") + void blockUser_실패_사용자없음() { + //given + doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) + .when(blockService).blockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단할 사용자를 찾을 수 없습니다."); + + verify(blockService).blockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 해제 성공") + void unblockUser_성공() { + //given + doNothing().when(blockService).unblockUser(anyString()); + + //when + ResponseEntity response = blockController.unblockUser(blockedUserId); + + //then + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isInstanceOf(SingleResponse.class); + + SingleResponse singleResponse = (SingleResponse) response.getBody(); + assertThat(singleResponse.getCode()).isEqualTo(200); + assertThat(singleResponse.getMessage()).isEqualTo("사용자 차단 해제 완료"); + assertThat(singleResponse.getData()).isNull(); + + verify(blockService).unblockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 해제 실패 - 자기 자신 차단 해제") + void unblockUser_실패_자기자신차단해제() { + //given + doThrow(new SelfBlockedException("자신을 차단 해제할 수 없습니다.")) + .when(blockService).unblockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.unblockUser(testUserId)) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단 해제할 수 없습니다."); + + verify(blockService).unblockUser(testUserId); + } + + @Test + @DisplayName("사용자 차단 해제 실패 - 차단 정보 없음") + void unblockUser_실패_차단정보없음() { + //given + doThrow(new NotFoundException("차단 정보가 존재하지 않습니다.")) + .when(blockService).unblockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.unblockUser(blockedUserId)) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단 정보가 존재하지 않습니다."); + + verify(blockService).unblockUser(blockedUserId); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java new file mode 100644 index 00000000..dca94b87 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java @@ -0,0 +1,221 @@ +package inu.codin.codin.domain.block.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.block.entity.BlockEntity; +import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.repository.BlockRepository; +import inu.codin.codin.domain.user.service.UserValidator; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BlockServiceTest { + + @InjectMocks + BlockService blockService; + + @Mock + BlockRepository blockRepository; + @Mock + UserValidator userValidator; + + private final ObjectId testUserId = ObjectIdUtil.toObjectId("686373fdaa87fd9618a63b49"); + private final ObjectId blockedUserId = ObjectIdUtil.toObjectId("6863740ceeb4a94ee959f592"); + + @Test + @DisplayName("유저 차단 성공") + void blockUser_성공() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)){ + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + when(blockRepository.save(any(BlockEntity.class))).thenReturn(existingBlock); + + //when + blockService.blockUser(blockedUserId.toString()); + + //then + verify(userValidator).validateUserExists(testUserId, "차단하는 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(blockedUserId, "차단할 사용자를 찾을 수 없습니다."); + verify(blockRepository).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단 성공 - 새로운 BlockEntity 생성") + void blockUser_성공_새로운BlockEntity생성() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); + when(blockRepository.save(any(BlockEntity.class))).thenReturn(BlockEntity.ofNew(testUserId)); + + //when + blockService.blockUser(blockedUserId.toString()); + + //then + verify(blockRepository).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단 실패 - 자기 자신 차단") + void blockUser_실패_자기자신차단() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(testUserId.toString())) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단할 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단 실패 - 차단유저 존재하지 않음") + void blockUser_실패_차단유저X() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + doNothing().when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); + doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) + .when(userValidator).validateUserExists(any(ObjectId.class), eq("차단할 사용자를 찾을 수 없습니다.")); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단할 사용자를 찾을 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단 실패 - 차단 실행 유저 존재하지 않음") + void blockUser_실패_차단실행유저X() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + doThrow(new NotFoundException("차단하는 사용자를 찾을 수 없습니다.")) + .when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단하는 사용자를 찾을 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단해제 성공") + void unblockUser_성공() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + existingBlock.addBlockedUser(blockedUserId); + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + when(blockRepository.save(any(BlockEntity.class))).thenReturn(existingBlock); + + //when + blockService.unblockUser(blockedUserId.toString()); + + //then + verify(userValidator).validateUserExists(testUserId, "차단 해제하는 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(blockedUserId, "차단 해제할 사용자를 찾을 수 없습니다."); + verify(blockRepository).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단해제 실패 - 자기 자신 차단해제") + void unblockUser_실패_자기자신차단해제() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + //when & then + assertThatThrownBy(() -> blockService.unblockUser(testUserId.toString())) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단 해제할 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단해제 실패 - 차단 정보 없음") + void unblockUser_실패_차단정보없음() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); + + //when & then + assertThatThrownBy(() -> blockService.unblockUser(blockedUserId.toString())) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단 정보가 존재하지 않습니다."); + } + } + + @Test + @DisplayName("차단된 유저 목록 조회 성공") + void getBlockedUsers_성공() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity blockEntity = spy(BlockEntity.ofNew(testUserId)); + when(blockEntity.getBlockedUsers()).thenReturn(List.of(blockedUserId)); + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(blockEntity)); + + //when + List result = blockService.getBlockedUsers(); + + //then + assertThat(result).isNotNull() + .hasSize(1) + .doesNotContainNull() + .contains(blockedUserId); + verify(blockRepository).findByUserId(testUserId); + } + } + + @Test + @DisplayName("차단된 유저 목록 조회 - 차단 정보 없음") + void getBlockedUsers_차단정보없음() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); + + //when + List result = blockService.getBlockedUsers(); + + //then + assertThat(result).isEmpty(); + verify(blockRepository).findByUserId(testUserId); + } + } +} \ No newline at end of file From 9b474a48d9db20ee18ca0f8405846ed55d0b3e6d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 16:58:55 +0900 Subject: [PATCH 0787/1002] =?UTF-8?q?fix=20:=20JsonCreator=20name=EA=B3=BC?= =?UTF-8?q?=20value=20=EB=AA=A8=EB=91=90=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,=20JsonValue=EC=97=90?= =?UTF-8?q?=EC=84=9C=20description=EC=9D=B4=20=EC=95=84=EB=8B=8C=20value?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/dto/Department.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java index 9819d2e1..2ad60cc4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java @@ -24,17 +24,17 @@ public enum Department { @JsonCreator public static Department fromDescription(String description) { for (Department department : Department.values()) { - if (department.getDescription().equals(description)) { + if (department.name().equals(description) || department.getDescription().equals(description)) { return department; } } + log.warn("정보대 내의 학과가 아닙니다. description : " + description); return OTHERS; } @JsonValue - public String getDescription(){ - return description; + public String toValue(){ + return this.name(); } - } From 91a9c055050dca2267bf9b6a878009b4c96298f9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 16:59:19 +0900 Subject: [PATCH 0788/1002] =?UTF-8?q?feat=20:=20=EC=A0=9C=ED=9C=B4?= =?UTF-8?q?=EC=97=85=EC=B2=B4=20=EC=9C=84=EC=B9=98=20location=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/PartnerCreateRequestDto.java | 3 +++ .../dto/response/PartnerDetailsResponseDto.java | 16 ++++++++++++++++ .../codin/codin/domain/info/entity/Partner.java | 11 ++++------- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java index c5ae74b6..5b006f94 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java @@ -32,4 +32,7 @@ public class PartnerCreateRequestDto { @Schema(description = "제휴 종료 날짜", example = "2026-03-01") private LocalDate endDate; + + @Schema(description = "제휴업체 가게 위치", example = "인천 연수구 송도동 3-2") + private String location; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java index 6e46e348..f9b901b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java @@ -1,26 +1,41 @@ package inu.codin.codin.domain.info.dto.response; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Partner; import inu.codin.codin.domain.info.entity.PartnerImg; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; +import lombok.Getter; import java.time.LocalDate; import java.util.List; @Builder +@Getter public class PartnerDetailsResponseDto { + @Schema(description = "제휴업체 가게 이름", example = "홍콩반점 송도점") private String name; + @Schema(description = "제휴 학과", example = "[\"COMPUTER_SCI\", \"INFO_COMM\", \"EMBEDDED\"]") private List tags; + @Schema(description = "제휴 혜택", example = "[\"탕수육 주문 시, 탕수육 공짜!\", \"평일 언제나 80% 대박 할인!\"]") private List benefits; + @Schema(description = "제휴 시작 날짜", example = "2025-03-01") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") private LocalDate startDate; + @Schema(description = "제휴 종료 날짜", example = "2026-03-01") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") private LocalDate endDate; + @Schema(description = "제휴업체 가게 위치", example = "인천 연수구 송도동 3-2") + private String location; + + @Schema(description = "제휴업체 가게 이미지") private PartnerImg img; public static PartnerDetailsResponseDto from(Partner partner){ @@ -30,6 +45,7 @@ public static PartnerDetailsResponseDto from(Partner partner){ .benefits(partner.getBenefits()) .startDate(partner.getStartDate()) .endDate(partner.getEndDate()) + .location(partner.getLocation()) .img(partner.getImg()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java index cfc135db..e55fbbed 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java @@ -21,26 +21,22 @@ public class Partner { @Id @NotBlank private ObjectId _id; - private String name; - private List tags; - private List benefits; - private LocalDate startDate; - private LocalDate endDate; - + private String location; private PartnerImg img; @Builder - public Partner(String name, List tags, List benefits, LocalDate startDate, LocalDate endDate, PartnerImg img) { + public Partner(String name, List tags, List benefits, LocalDate startDate, LocalDate endDate, String location, PartnerImg img) { this.name = name; this.tags = tags; this.benefits = benefits; this.startDate = startDate; this.endDate = endDate; + this.location = location; this.img = img; } @@ -51,6 +47,7 @@ public static Partner of(PartnerCreateRequestDto partnerCreateRequestDto, Partne .benefits(partnerCreateRequestDto.getBenefits()) .startDate(partnerCreateRequestDto.getStartDate()) .endDate(partnerCreateRequestDto.getEndDate()) + .location(partnerCreateRequestDto.getLocation()) .img(partnerImg) .build(); } From dcfaef542d9e1514a7003c43e8b3f214831284d7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 1 Jul 2025 17:01:13 +0900 Subject: [PATCH 0789/1002] =?UTF-8?q?fix=20:=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=96=B4=EB=8F=84=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info/controller/PartnerController.java | 7 ++++--- .../domain/info/service/PartnerService.java | 5 ++++- .../domain/info/service/PartnerServiceTest.java | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java index 7913a49a..c25a2ba8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -11,6 +11,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -48,10 +49,10 @@ public ResponseEntity> getPartnerDetai @Operation( summary = "[ADMIN, MANAGER] Partner 추가" ) - @PostMapping + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createPartner(@RequestPart("partnerInfo") @Valid PartnerCreateRequestDto partnerCreateRequestDto, - @RequestPart("mainImage") MultipartFile mainImage, - @RequestPart("subImages")List subImages ) { + @RequestPart(value = "mainImage", required = false) MultipartFile mainImage, + @RequestPart(value = "subImages", required = false) List subImages ) { partnerService.createPartner(partnerCreateRequestDto, mainImage, subImages); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "Partner 생성 완료", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java index adf530f8..ed6b17ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -16,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -44,7 +45,9 @@ public void createPartner(PartnerCreateRequestDto partnerCreateRequestDto, Multi } private PartnerImg getPartnerImg(MultipartFile mainImage, List subImages) { - String main = s3Service.handleImageUpload(List.of(mainImage)).get(0); + String main = Optional.ofNullable(mainImage) + .map(img -> s3Service.handleImageUpload(List.of(img)).get(0)) + .orElse(null); List subs = s3Service.handleImageUpload(subImages); return new PartnerImg(main, subs); } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java index c8fbdcf5..efc5f3a2 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/info/service/PartnerServiceTest.java @@ -107,6 +107,23 @@ class PartnerServiceTest { assertNotNull(savedPartner); } + @Test + @DisplayName("이미지 없이 제휴업체 생성 성공") + void 이미지_없이_제휴업체_생성_성공(){ + //given + PartnerCreateRequestDto requestDto = mock(PartnerCreateRequestDto.class); + + //when + partnerService.createPartner(requestDto, null, null); + + //then + ArgumentCaptor captor = ArgumentCaptor.forClass(Partner.class); + verify(partnerRepository, times(1)).save(captor.capture()); + + Partner savedPartner = captor.getValue(); + assertNotNull(savedPartner); + } + @Test @DisplayName("제휴업체 삭제") void 제휴업체_삭제() { From 952d639c5b1511803bb8c6a46d959744653195b3 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min <67214970+doma17@users.noreply.github.com> Date: Tue, 1 Jul 2025 20:26:48 +0900 Subject: [PATCH 0790/1002] =?UTF-8?q?refactor:=20BlockService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/inu/codin/codin/domain/block/service/BlockService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index d1dd1605..bb9edde2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -56,7 +56,7 @@ public void unblockUser(String strBlockedUserId) { blockRepository.save(blockRepository.findByUserId(userId) .orElseThrow(() -> new NotFoundException("차단 정보가 존재하지 않습니다.")) - .removeBockedUser(blockedId)); + .removeBlockedUser(blockedId)); } /** From e4120f5399daf8f4d831d86cbea640fc4d1bb084 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 21:17:05 +0900 Subject: [PATCH 0791/1002] =?UTF-8?q?test:=20removeBlockedUser=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20throw=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/entity/BlockEntity.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index a27c52eb..8aeaea68 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -46,11 +46,8 @@ public BlockEntity addBlockedUser(ObjectId blockedUser) { return this; } - public BlockEntity removeBockedUser(ObjectId blockedUser) { - if (this.blockedUsers.contains(blockedUser)) { - this.blockedUsers.remove(blockedUser); - return this; - } - throw new NotFoundException("차단 목록에 없는 유저입니다."); + public BlockEntity removeBlockedUser(ObjectId blockedUser) { + this.blockedUsers.remove(blockedUser); + return this; } } \ No newline at end of file From 02d02fb48943af3a8694a2f3a168d736788c5eae Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 2 Jul 2025 00:46:45 +0900 Subject: [PATCH 0792/1002] =?UTF-8?q?refactor=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9D=B8=EB=9D=BC=EC=9D=B8=ED=99=94,=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/info/service/PartnerService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java index ed6b17ea..506390b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -26,10 +26,10 @@ public class PartnerService { private final S3Service s3Service; public List getPartnerList() { - List partners = partnerRepository.findAll(); - return partners.stream() - .map(PartnerListResponseDto::from) - .toList(); + return partnerRepository.findAll() + .stream() + .map(PartnerListResponseDto::from) + .toList(); } public PartnerDetailsResponseDto getPartnerDetails(String partnerId) { @@ -52,7 +52,6 @@ private PartnerImg getPartnerImg(MultipartFile mainImage, List su return new PartnerImg(main, subs); } - @Transactional public void deletePartner(String partnerId) { partnerRepository.deleteById(new ObjectId(partnerId)); } From 79c1d1fd1ac43713b1c6f76e0d8fb0552cb8601c Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 2 Jul 2025 15:21:30 +0900 Subject: [PATCH 0793/1002] =?UTF-8?q?refactor:=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20import=EB=AC=B8=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalExceptionHandler.java | 6 ++++-- .../codin/codin/common/ratelimit/ClientIpUtil.java | 2 -- .../common/ratelimit/RateLimitInterceptor.java | 4 ++-- .../security/controller/AppleEventController.java | 10 ++-------- .../security/dto/apple/AppleAuthRequest.java | 1 + .../common/security/jwt/JwtTokenProvider.java | 5 ++++- .../security/service/AbstractAuthService.java | 12 ------------ .../security/service/AppleOAuth2UserService.java | 4 ---- .../common/security/service/AuthCommonService.java | 1 - .../common/security/service/GoogleAuthService.java | 1 - .../util/AppleOAuth2AccessTokenResponseClient.java | 3 --- .../security/util/OAuth2LoginSuccessHandler.java | 1 - .../MultipartJackson2HttpMessageConverter.java | 1 - .../codin/domain/block/entity/BlockEntity.java | 1 - .../chat/chatroom/entity/ParticipantInfo.java | 4 +++- .../chat/chatroom/service/ChatRoomService.java | 1 - .../repository/CustomChattingRepository.java | 2 -- .../info/dto/request/PartnerCreateRequestDto.java | 1 - .../inu/codin/codin/domain/info/entity/Lab.java | 2 +- .../domain/info/repository/InfoRepository.java | 2 +- .../codin/domain/info/service/OfficeService.java | 2 +- .../codin/domain/info/service/PartnerService.java | 1 - .../domain/info/service/ProfessorService.java | 2 +- .../domain/review/controller/ReviewController.java | 2 +- .../domain/review/dto/CreateReviewRequestDto.java | 4 +++- .../domain/like/controller/LikeController.java | 5 ++++- .../codin/domain/like/service/LikeService.java | 4 ++-- .../notification/entity/NotificationEntity.java | 2 -- .../post/domain/poll/dto/PollVotingRequestDTO.java | 2 -- .../domain/poll/repository/PollVoteRepository.java | 1 - .../post/domain/poll/service/PollService.java | 3 --- .../reply/dto/request/ReplyCreateRequestDTO.java | 1 - .../domain/post/schedular/PostsScheduler.java | 1 - .../domain/report/controller/ReportController.java | 9 ++++----- .../report/controller/SuspendMvcController.java | 2 +- .../report/dto/request/ReportCreateRequestDto.java | 1 - .../dto/request/ReportExecuteRequestDto.java | 4 ---- .../dto/response/ReportCountResponseDto.java | 5 ----- .../report/dto/response/ReportListResponseDto.java | 9 +-------- .../report/dto/response/ReportResponseDto.java | 14 ++++++-------- .../dto/response/ReportSummaryResponseDTO.java | 1 - .../domain/report/entity/SuspensionPeriod.java | 1 - .../report/repository/CustomReportRepository.java | 6 ++---- .../codin/domain/report/service/ReportService.java | 5 ----- .../domain/scrap/repository/ScrapRepository.java | 1 - .../user/dto/request/UserNicknameRequestDto.java | 1 - .../domain/user/repository/UserRepository.java | 1 - .../codin/infra/fcm/dto/FcmMessageUserDto.java | 1 - .../codin/infra/fcm/entity/FcmTokenEntity.java | 2 -- .../infra/redis/service/RedisBestService.java | 5 ++++- 50 files changed, 48 insertions(+), 114 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index ae643bc1..f7ed371d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,8 +2,10 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; -import inu.codin.codin.domain.chat.exception.*; +import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; +import inu.codin.codin.domain.chat.exception.ChatRoomException; +import inu.codin.codin.domain.chat.exception.ChattingErrorCode; +import inu.codin.codin.domain.chat.exception.ChattingException; import inu.codin.codin.domain.info.exception.InfoErrorCode; import inu.codin.codin.domain.info.exception.InfoException; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java index e9d30f66..5d80709e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java @@ -2,8 +2,6 @@ import jakarta.servlet.http.HttpServletRequest; -import java.util.Arrays; - public class ClientIpUtil { private static final String[] IP_HEADER_CANDIDATES = { "X-Forwarded-For", diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java index 56d22261..0ce93b5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java @@ -12,11 +12,11 @@ import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; -import static inu.codin.codin.common.ratelimit.RateLimitBucketConstants.*; - import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static inu.codin.codin.common.ratelimit.RateLimitBucketConstants.*; + /** * Reference : https://velog.io/@whcksdud8/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Rate-limit-%ED%95%B8%EB%93%A4%EB%A7%81-%EB%B0%8F-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81 */ diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java index 33c6a6cd..cb34a2eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -1,10 +1,6 @@ package inu.codin.codin.common.security.controller; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; -import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; -import inu.codin.codin.common.security.service.AppleOAuth2UserService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -12,13 +8,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.Map; -import java.util.stream.Collectors; @RestController @RequiredArgsConstructor diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java index 2c262358..547833d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java @@ -2,6 +2,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; + import java.util.Map; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index f85a437a..ae4345d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -3,7 +3,10 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.infra.redis.RedisStorageService; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import lombok.*; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java index d76dab54..7b57afbc 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java @@ -1,11 +1,5 @@ package inu.codin.codin.common.security.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; @@ -15,12 +9,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java index 71d75bad..d2a411ff 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java @@ -7,8 +7,6 @@ import inu.codin.codin.common.security.feign.AppleAuthClient; import inu.codin.codin.common.security.jwt.IdentityTokenValidator; import inu.codin.codin.common.security.util.ApplePublicKeyGenerator; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; @@ -25,8 +23,6 @@ import java.util.HashMap; import java.util.Map; -import static java.util.Objects.isNull; - @Service @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 4ea8107b..c42eeb67 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -9,7 +9,6 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java index 64e03c8a..2f9a1dd9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java @@ -9,7 +9,6 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java index b729f52c..6221c19d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java @@ -2,9 +2,7 @@ import lombok.extern.slf4j.Slf4j; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.*; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -15,7 +13,6 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import java.net.URI; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index aa935779..eebb39cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,6 +1,5 @@ package inu.codin.codin.common.security.util; -import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.security.enums.AuthResultStatus; import inu.codin.codin.common.security.service.AppleAuthService; import inu.codin.codin.common.security.service.GoogleAuthService; diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java index 5fd59d8c..be518234 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java @@ -1,7 +1,6 @@ package inu.codin.codin.common.util; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; import org.springframework.stereotype.Component; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 8aeaea68..596f5674 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index ce4dbea3..6e4cc10a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -1,7 +1,9 @@ package inu.codin.codin.domain.chat.chatroom.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import lombok.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import java.time.LocalDateTime; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 25150c0a..2c1ccbbb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -12,7 +12,6 @@ import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 0aa7e48c..a216b53b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -4,11 +4,9 @@ import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; -import reactor.core.publisher.Mono; @Repository public class CustomChattingRepository { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java index 5b006f94..b51ed77b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.info.dto.request; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.entity.PartnerImg; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java index 0bd63eda..36224b63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 51efec33..23d13c05 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.info.repository; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.domain.info.entity.Lab; import inu.codin.codin.domain.info.entity.Office; import inu.codin.codin.domain.info.entity.Professor; -import inu.codin.codin.domain.info.entity.Info; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java index 42f21447..7a2639e8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.info.service; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.OfficeDetailsResponseDto; import inu.codin.codin.domain.info.entity.Office; -import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java index 506390b3..00e12192 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -12,7 +12,6 @@ import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java index b03bd4fe..031468f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.service; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.ProfessorListResponseDto; import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Professor; import inu.codin.codin.domain.info.exception.InfoErrorCode; import inu.codin.codin.domain.info.exception.InfoException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java index 19e3d68b..4b1da7ac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.lecture.domain.review.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java index 7044b113..b219d93d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java @@ -1,7 +1,9 @@ package inu.codin.codin.domain.lecture.domain.review.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 3d3cfe25..29bc860d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -9,7 +9,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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; @RestController @RequestMapping("/likes") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 1f320f06..541fbd5c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -3,16 +3,16 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisLikeService; import inu.codin.codin.infra.redis.service.RedisBestService; +import inu.codin.codin.infra.redis.service.RedisLikeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index 66d4d8aa..c8494559 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.notification.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.user.entity.UserEntity; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; @@ -9,7 +8,6 @@ import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java index c06f44ed..c95f5db4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java @@ -1,9 +1,7 @@ package inu.codin.codin.domain.post.domain.poll.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; import lombok.Getter; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java index abc24ba5..4051c311 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.post.domain.poll.repository; -import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import jakarta.validation.constraints.NotBlank; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index e8b35c99..507d01e1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -1,8 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; @@ -16,7 +14,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.user.entity.UserRole; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java index f00ae843..0795530f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -3,7 +3,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Builder; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 1cc1bf38..a15280eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.post.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; - import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index b900d17c..146f8d7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,14 +1,13 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; - import inu.codin.codin.domain.post.domain.comment.service.CommentService; - - import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; -import inu.codin.codin.domain.report.dto.response.*; - +import inu.codin.codin.domain.report.dto.response.ReportPageResponse; +import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; +import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; +import inu.codin.codin.domain.report.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.report.service.ReportService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java index a6e5266d..8cc189d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.report.controller; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.ui.Model; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java index b26f9343..de6c9019 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; -import org.bson.types.ObjectId; @Getter public class ReportCreateRequestDto { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java index 37412852..153c60ce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java @@ -1,12 +1,8 @@ package inu.codin.codin.domain.report.dto.request; -import inu.codin.codin.domain.report.entity.ReportStatus; import inu.codin.codin.domain.report.entity.SuspensionPeriod; import lombok.Getter; import lombok.NoArgsConstructor; -import org.bson.types.ObjectId; - -import java.time.LocalDateTime; @Getter @NoArgsConstructor diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java index 40a2b430..8099d6a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java @@ -1,16 +1,11 @@ package inu.codin.codin.domain.report.dto.response; -import inu.codin.codin.domain.report.entity.ReportEntity; -import inu.codin.codin.domain.report.entity.ReportStatus; -import inu.codin.codin.domain.report.entity.ReportTargetType; -import inu.codin.codin.domain.report.entity.ReportType; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.bson.Document; -import org.bson.types.ObjectId; import java.util.List; import java.util.stream.Collectors; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java index 92c4b5ad..17d1ba2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java @@ -1,18 +1,11 @@ package inu.codin.codin.domain.report.dto.response; -import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.report.dto.ReportInfo; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; -import java.util.List;@Getter +@Getter public class ReportListResponseDto extends PostDetailResponseDTO { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java index 10e3f91e..5299f3ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java @@ -1,17 +1,15 @@ package inu.codin.codin.domain.report.dto.response; -import inu.codin.codin.domain.report.entity.*; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import org.bson.Document; -import org.bson.types.ObjectId; - +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.entity.ReportEntity.ReportActionEntity; +import inu.codin.codin.domain.report.entity.ReportStatus; import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.entity.ReportType; -import inu.codin.codin.domain.report.entity.ReportStatus; -import inu.codin.codin.domain.report.entity.ReportEntity.ReportActionEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; +import org.bson.Document; @Getter @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java index 34957f3c..76271f89 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.report.entity.ReportType; import lombok.Getter; -import org.bson.types.ObjectId; import java.util.Map; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java index 1acf3896..71debf63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.report.entity; import lombok.Getter; -import software.amazon.awssdk.services.s3.endpoints.internal.Value; @Getter public enum SuspensionPeriod { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java index b703f92f..1828b647 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java @@ -1,16 +1,14 @@ package inu.codin.codin.domain.report.repository; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.report.entity.ReportEntity; import org.bson.Document; -import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; -import org.springframework.data.mongodb.core.aggregation.Aggregation; + import java.util.List; @Repository diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index ace713c0..e4b6fe7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -2,16 +2,13 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; - import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; - import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; - import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; @@ -29,10 +26,8 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import jakarta.validation.Valid; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bson.Document; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java index d2b8d50b..f00e12d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java @@ -7,7 +7,6 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java index d6114a90..6aeac2db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java @@ -3,7 +3,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; -import lombok.Setter; import java.beans.ConstructorProperties; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index b0e9f395..e65c0b34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.user.repository; -import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.user.entity.UserEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java index f6f7a106..44d4f7a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java @@ -1,6 +1,5 @@ package inu.codin.codin.infra.fcm.dto; -import inu.codin.codin.domain.user.entity.UserEntity; import lombok.Builder; import lombok.Data; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index 733765b0..6673cceb 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,14 +1,12 @@ package inu.codin.codin.infra.fcm.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 51cf7743..9868a7a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -15,7 +15,10 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; From cc841add4f90c91c7c96811e0b3f98e9bda15ece Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 3 Jul 2025 16:32:16 +0900 Subject: [PATCH 0794/1002] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20#211=20=EC=BD=94=EB=93=9C=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #211 에서 적용한 GlobalException, GlobalErrorCode 형태로 코드를 리팩토링 했습니다. --- .../exception/GlobalExceptionHandler.java | 9 ++++ .../domain/block/entity/BlockEntity.java | 6 +-- .../block/exception/BlockErrorCode.java | 27 ++++++++++++ .../block/exception/BlockException.java | 15 +++++++ .../domain/block/service/BlockService.java | 44 +++++++++++++------ .../domain/block/service/BlockValidator.java | 11 +++++ .../domain/user/service/UserValidator.java | 8 ++-- 7 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index f7ed371d..0aea23b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,6 +2,8 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; import inu.codin.codin.domain.chat.exception.ChatRoomException; import inu.codin.codin.domain.chat.exception.ChattingErrorCode; @@ -34,6 +36,13 @@ protected ResponseEntity handleGlobalException(GlobalExceptio .body(new ExceptionResponse(code.message(), code.httpStatus().value())); } + @ExceptionHandler(BlockException.class) + protected ResponseEntity handleBlockException(BlockException e) { + BlockErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + @ExceptionHandler(ChatRoomException.class) protected ResponseEntity handleChatRoomException(ChatRoomException e) { ChatRoomErrorCode code = e.getErrorCode(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 596f5674..1c5f877d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -38,15 +38,11 @@ public static BlockEntity ofNew(ObjectId userId) { } public BlockEntity addBlockedUser(ObjectId blockedUser) { - if (this.blockedUsers.contains(blockedUser)) { - throw new AlreadyBlockedException("이미 차단한 유저입니다."); - } this.blockedUsers.add(blockedUser); return this; } - public BlockEntity removeBlockedUser(ObjectId blockedUser) { + public void removeBlockedUser(ObjectId blockedUser) { this.blockedUsers.remove(blockedUser); - return this; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java new file mode 100644 index 00000000..d99b98bd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.block.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum BlockErrorCode implements GlobalErrorCode { + SELF_BLOCKED(HttpStatus.BAD_REQUEST, "자신을 차단할 수 없습니다."), + SELF_UNBLOCKED(HttpStatus.BAD_REQUEST, "자신을 차단 해제할 수 없습니다."), + ALREADY_BLOCKED(HttpStatus.BAD_REQUEST, "이미 차단한 사용자입니다."), + BLOCKING_USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "차단하는 사용자를 찾을 수 없습니다."), + BLOCKED_USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "차단할 사용자를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java new file mode 100644 index 00000000..a7fc2d36 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.block.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class BlockException extends GlobalException { + + private final BlockErrorCode errorCode; + + public BlockException(BlockErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index bb9edde2..5abf20aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -1,19 +1,22 @@ package inu.codin.codin.domain.block.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.service.UserValidator; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @Service +@Slf4j @RequiredArgsConstructor public class BlockService { private final BlockRepository blockRepository; @@ -29,14 +32,22 @@ public void blockUser(String strBlockedUserId) { ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { - throw new SelfBlockedException("자신을 차단할 수 없습니다."); + log.error(""); + throw new BlockException(BlockErrorCode.SELF_BLOCKED); } - userValidator.validateUserExists(userId, "차단하는 사용자를 찾을 수 없습니다."); - userValidator.validateUserExists(blockedId, "차단할 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(userId, () -> new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)); + userValidator.validateUserExists(blockedId, () -> new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)); - blockRepository.save(blockRepository.findByUserId(userId) - .orElseGet(() -> BlockEntity.ofNew(userId)) - .addBlockedUser(blockedId)); + blockRepository.findByUserId(userId) + .ifPresentOrElse(blockEntity -> { + if (BlockValidator.validateBlockedUserExists(blockEntity, blockedId)) { + throw new BlockException(BlockErrorCode.ALREADY_BLOCKED); + } + blockEntity.addBlockedUser(blockedId); + blockRepository.save(blockEntity); + }, () -> blockRepository.save(BlockEntity.ofNew(userId) + .addBlockedUser(blockedId)) + ); } /** @@ -49,14 +60,21 @@ public void unblockUser(String strBlockedUserId) { ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { - throw new SelfBlockedException("자신을 차단 해제할 수 없습니다."); + throw new BlockException(BlockErrorCode.SELF_UNBLOCKED); } - userValidator.validateUserExists(userId, "차단 해제하는 사용자를 찾을 수 없습니다."); - userValidator.validateUserExists(blockedId, "차단 해제할 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(userId, () -> new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)); + userValidator.validateUserExists(blockedId, () -> new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)); - blockRepository.save(blockRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException("차단 정보가 존재하지 않습니다.")) - .removeBlockedUser(blockedId)); + blockRepository.findByUserId(userId) + .ifPresentOrElse(blockEntity -> { + if (!BlockValidator.validateBlockedUserExists(blockEntity, blockedId)) { + throw new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND); + } + blockEntity.removeBlockedUser(blockedId); + blockRepository.save(blockEntity); + }, () -> { + throw new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND); + }); } /** diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java new file mode 100644 index 00000000..96adfb0e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.block.service; + +import inu.codin.codin.domain.block.entity.BlockEntity; +import org.bson.types.ObjectId; + +public class BlockValidator { + + public static boolean validateBlockedUserExists(BlockEntity blockEntity, ObjectId blockedUser) { + return blockEntity.getBlockedUsers().contains(blockedUser); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java index 7f34a32a..9aa92121 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java @@ -6,6 +6,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Component; +import java.util.function.Supplier; + @Component @RequiredArgsConstructor public class UserValidator { @@ -14,10 +16,10 @@ public class UserValidator { /** * User 존재 여부 검증 * @param userId 존재 검증할 userId - 삭제된 유저는 검색되지 않음 - * @param exceptionMsg Exception 메세지 + * @param exceptionSupplier Exception Class 지정 */ - public void validateUserExists(ObjectId userId, String exceptionMsg) { + public void validateUserExists(ObjectId userId, Supplier exceptionSupplier) { userRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException(exceptionMsg)); + .orElseThrow(exceptionSupplier); } } \ No newline at end of file From 5da02d097b9e16497a2b9de6739926738315b073 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 3 Jul 2025 16:32:49 +0900 Subject: [PATCH 0795/1002] =?UTF-8?q?test:=20=EB=B3=80=EA=B2=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20#211=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #211 에서 적용한 GlobalException, GlobalErrorCode Block 도메인에 대한 테스트 코드를 리팩토링했습니다. --- .../block/controller/BlockControllerTest.java | 40 ++++---- .../block/service/BlockServiceTest.java | 99 +++++++++++++++---- 2 files changed, 100 insertions(+), 39 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java index 37c60ab1..6909b8b1 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java @@ -1,9 +1,8 @@ package inu.codin.codin.domain.block.controller; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.block.exception.AlreadyBlockedException; -import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.service.BlockService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -58,13 +57,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 실패 - 자기 자신 차단") void blockUser_실패_자기자신차단() { //given - doThrow(new SelfBlockedException("자신을 차단할 수 없습니다.")) + doThrow(new BlockException(BlockErrorCode.SELF_BLOCKED)) .when(blockService).blockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.blockUser(testUserId)) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_BLOCKED); verify(blockService).blockUser(testUserId); } @@ -73,13 +73,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 실패 - 이미 차단된 사용자") void blockUser_실패_이미차단된사용자() { //given - doThrow(new AlreadyBlockedException("이미 차단한 유저입니다.")) + doThrow(new BlockException(BlockErrorCode.ALREADY_BLOCKED)) .when(blockService).blockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) - .isInstanceOf(AlreadyBlockedException.class) - .hasMessage("이미 차단한 유저입니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.ALREADY_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.ALREADY_BLOCKED); verify(blockService).blockUser(blockedUserId); } @@ -88,13 +89,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 실패 - 차단할 사용자를 찾을 수 없음") void blockUser_실패_사용자없음() { //given - doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) + doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) .when(blockService).blockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단할 사용자를 찾을 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); verify(blockService).blockUser(blockedUserId); } @@ -124,13 +126,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 해제 실패 - 자기 자신 차단 해제") void unblockUser_실패_자기자신차단해제() { //given - doThrow(new SelfBlockedException("자신을 차단 해제할 수 없습니다.")) + doThrow(new BlockException(BlockErrorCode.SELF_UNBLOCKED)) .when(blockService).unblockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.unblockUser(testUserId)) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단 해제할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_UNBLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_UNBLOCKED); verify(blockService).unblockUser(testUserId); } @@ -139,13 +142,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 해제 실패 - 차단 정보 없음") void unblockUser_실패_차단정보없음() { //given - doThrow(new NotFoundException("차단 정보가 존재하지 않습니다.")) + doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) .when(blockService).unblockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.unblockUser(blockedUserId)) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단 정보가 존재하지 않습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); verify(blockService).unblockUser(blockedUserId); } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java index dca94b87..bfb5b7a3 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.block.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; -import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.service.UserValidator; import org.bson.types.ObjectId; @@ -53,8 +53,8 @@ class BlockServiceTest { blockService.blockUser(blockedUserId.toString()); //then - verify(userValidator).validateUserExists(testUserId, "차단하는 사용자를 찾을 수 없습니다."); - verify(userValidator).validateUserExists(blockedUserId, "차단할 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); verify(blockRepository).save(any(BlockEntity.class)); } } @@ -86,8 +86,9 @@ class BlockServiceTest { //when & then assertThatThrownBy(() -> blockService.blockUser(testUserId.toString())) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_BLOCKED); } } @@ -98,14 +99,15 @@ class BlockServiceTest { //given mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); - doNothing().when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); - doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) - .when(userValidator).validateUserExists(any(ObjectId.class), eq("차단할 사용자를 찾을 수 없습니다.")); + doNothing().when(userValidator).validateUserExists(eq(testUserId), any()); + doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) + .when(userValidator).validateUserExists(eq(blockedUserId), any()); //when & then assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단할 사용자를 찾을 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); } } @@ -116,13 +118,14 @@ class BlockServiceTest { //given mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); - doThrow(new NotFoundException("차단하는 사용자를 찾을 수 없습니다.")) - .when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); + doThrow(new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)) + .when(userValidator).validateUserExists(eq(testUserId), any()); //when & then assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단하는 사용자를 찾을 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKING_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKING_USER_NOT_FOUND); } } @@ -142,8 +145,8 @@ class BlockServiceTest { blockService.unblockUser(blockedUserId.toString()); //then - verify(userValidator).validateUserExists(testUserId, "차단 해제하는 사용자를 찾을 수 없습니다."); - verify(userValidator).validateUserExists(blockedUserId, "차단 해제할 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); verify(blockRepository).save(any(BlockEntity.class)); } } @@ -157,8 +160,9 @@ class BlockServiceTest { //when & then assertThatThrownBy(() -> blockService.unblockUser(testUserId.toString())) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단 해제할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_UNBLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_UNBLOCKED); } } @@ -173,8 +177,61 @@ class BlockServiceTest { //when & then assertThatThrownBy(() -> blockService.unblockUser(blockedUserId.toString())) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단 정보가 존재하지 않습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); + } + } + + @Test + @DisplayName("유저 차단 실패 - 이미 차단된 사용자") + void blockUser_실패_이미차단된사용자() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + existingBlock.addBlockedUser(blockedUserId); // 이미 차단된 상태 + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.ALREADY_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.ALREADY_BLOCKED); + + // userValidator 호출 검증 + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); + // save가 호출되지 않았는지 검증 + verify(blockRepository, never()).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단해제 실패 - 차단되지 않은 사용자") + void unblockUser_실패_차단되지않은사용자() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + // 다른 사용자는 차단되어 있지만, 요청한 사용자는 차단되지 않은 상태 + ObjectId otherBlockedUserId = ObjectIdUtil.toObjectId("507f1f77bcf86cd799439011"); + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + existingBlock.addBlockedUser(otherBlockedUserId); // 다른 사용자만 차단됨 + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + + //when & then + assertThatThrownBy(() -> blockService.unblockUser(blockedUserId.toString())) + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); + + // userValidator 호출 검증 + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); + // save가 호출되지 않았는지 검증 + verify(blockRepository, never()).save(any(BlockEntity.class)); } } From 55048a8907696e63d1be69525d647dd21ee2e5a7 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 3 Jul 2025 17:17:30 +0900 Subject: [PATCH 0796/1002] =?UTF-8?q?refactor:=20Block=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20Exception=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/entity/BlockEntity.java | 1 - .../domain/block/exception/AlreadyBlockedException.java | 7 ------- .../codin/domain/block/exception/NotBlockedException.java | 7 ------- .../codin/domain/block/exception/SelfBlockedException.java | 7 ------- .../inu/codin/codin/domain/block/service/BlockService.java | 1 - 5 files changed, 23 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 1c5f877d..4b74eb8b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java deleted file mode 100644 index 223c9844..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.block.exception; - -public class AlreadyBlockedException extends RuntimeException{ - public AlreadyBlockedException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java deleted file mode 100644 index cb91fafb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.block.exception; - -public class NotBlockedException extends RuntimeException{ - public NotBlockedException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java deleted file mode 100644 index 878b28e7..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.block.exception; - -public class SelfBlockedException extends RuntimeException{ - public SelfBlockedException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 5abf20aa..974b2eac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -5,7 +5,6 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; -import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.service.UserValidator; import lombok.RequiredArgsConstructor; From be57223e0cc8b00d2269afe7bc964fb68e90224b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 8 Jul 2025 23:49:03 +0900 Subject: [PATCH 0797/1002] =?UTF-8?q?feat=20:=20=EA=B8=B0=EC=A1=B4=20test?= =?UTF-8?q?=20Code=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/CommentServiceTest.java | 233 ++++++++++++++ .../codin/domain/post/PostServiceTest.java | 284 ++++++++++++++++++ .../domain/post/ReplyCommentServiceTest.java | 231 ++++++++++++++ .../domain/post/domain/HitsServiceTest.java | 80 +++++ .../domain/post/domain/PollServiceTest.java | 199 ++++++++++++ 5 files changed, 1027 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java new file mode 100644 index 00000000..2d555df4 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java @@ -0,0 +1,233 @@ +package inu.codin.codin.domain.post; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.service.CommentService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.dto.response.UserDto; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisBestService; +import inu.codin.codin.infra.s3.S3Service; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.MockedStatic; + +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class CommentServiceTest { + @InjectMocks + private CommentService commentService; + @Mock private PostRepository postRepository; + @Mock private CommentRepository commentRepository; + @Mock private UserRepository userRepository; + @Mock private LikeService likeService; + @Mock private NotificationService notificationService; + @Mock private RedisBestService redisBestService; + @Mock private S3Service s3Service; + @Mock private ReplyCommentService replyCommentService; + private static MockedStatic securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + @AfterEach + void tearDown() { + securityUtilsMock.close(); + } + + @Test + void 댓글_정상등록_성공() throws Exception { + // Given + String postId = new ObjectId().toString(); + CommentCreateRequestDTO dto = createCommentCreateRequestDTO("내용", true); + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(commentRepository.save(any())).willAnswer(inv -> { + CommentEntity entity = inv.getArgument(0); + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, new ObjectId()); + return entity; + }); + given(postRepository.save(any())).willReturn(post); + willDoNothing().given(redisBestService).applyBestScore(anyInt(), any()); + willDoNothing().given(notificationService).sendNotificationMessageByComment(any(), any(), any(), any()); + // When + commentService.addComment(postId, dto); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(CommentEntity.class); + verify(commentRepository).save(captor.capture()); + CommentEntity saved = captor.getValue(); + assertThat(saved.getContent()).isEqualTo("내용"); + assertThat(saved.isAnonymous()).isTrue(); + assertThat(saved.getPostId()).isNotNull(); + assertThat(saved.getUserId()).isEqualTo(userId); + } + + @Test + void 댓글_등록_게시글없음_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + CommentCreateRequestDTO dto = createCommentCreateRequestDTO("내용", true); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> commentService.addComment(postId, dto)).isInstanceOf(NotFoundException.class); + } + + @Test + void 댓글_소프트삭제_성공() throws Exception { + // Given + String commentId = new ObjectId().toString(); + CommentEntity comment = createCommentEntity(); + given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(comment)); + securityUtilsMock.when(() -> SecurityUtils.validateUser(any())).thenAnswer(invocation -> null); + given(commentRepository.save(any())).willReturn(comment); + // When + commentService.softDeleteComment(commentId); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(CommentEntity.class); + verify(commentRepository).save(captor.capture()); + CommentEntity deleted = captor.getValue(); + assertThat(deleted.getDeletedAt()).isNotNull(); + } + + @Test + void 댓글_소프트삭제_댓글없음_예외() { + // Given + String commentId = new ObjectId().toString(); + given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> commentService.softDeleteComment(commentId)).isInstanceOf(NotFoundException.class); + } + + @Test + void 게시글별_댓글목록_조회_성공() { + // Given + String postId = new ObjectId().toString(); + ObjectId userId = new ObjectId(); + PostEntity post = createPostEntity(); + CommentEntity comment = CommentEntity.builder() + ._id(new ObjectId()) + .userId(userId) + .postId(new ObjectId()) + .content("내용") + .anonymous(true) + .build(); + List comments = List.of(comment); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(commentRepository.findByPostId(any())).willReturn(comments); + given(s3Service.getDefaultProfileImageUrl()).willReturn("defaultUrl"); + UserEntity user = UserEntity.builder() + .nickname("닉네임") + .profileImageUrl("url") + .build(); + try { + setField(user, "_id", userId); + } catch (Exception e) { + throw new RuntimeException(e); + } + given(userRepository.findAllById(any())).willReturn(List.of(user)); + // When + List result = commentService.getCommentsByPostId(postId); + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + assertThat(result.get(0).getContent()).isEqualTo("내용"); + } + + @Test + void 게시글별_댓글목록_조회_게시글없음_예외() { + // Given + String postId = new ObjectId().toString(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> commentService.getCommentsByPostId(postId)).isInstanceOf(NotFoundException.class); + } + + @Test + void 댓글_수정_성공() throws Exception { + // Given + String commentId = new ObjectId().toString(); + CommentUpdateRequestDTO dto = createCommentUpdateRequestDTO("수정내용"); + CommentEntity comment = createCommentEntity(); + given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(comment)); + given(commentRepository.save(any())).willReturn(comment); + // When + commentService.updateComment(commentId, dto); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(CommentEntity.class); + verify(commentRepository).save(captor.capture()); + CommentEntity updated = captor.getValue(); + assertThat(updated.getContent()).isEqualTo("수정내용"); + } + + @Test + void 댓글_수정_댓글없음_예외() throws Exception { + // Given + String commentId = new ObjectId().toString(); + CommentUpdateRequestDTO dto = createCommentUpdateRequestDTO("수정내용"); + given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> commentService.updateComment(commentId, dto)).isInstanceOf(NotFoundException.class); + } + + @Test + void 댓글_유저정보_조회_성공() { + // Given + ObjectId commentId = new ObjectId(); + ObjectId userId = new ObjectId(); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(likeService.isLiked(any(), any(), any())).willReturn(true); + // When + CommentResponseDTO.UserInfo info = commentService.getUserInfoAboutComment(commentId); + // Then + assertThat(info).isNotNull(); + assertThat(info.isLike()).isTrue(); + } + + // --- 리플렉션 기반 DTO/Entity 생성 유틸리티 --- + private PostEntity createPostEntity() { + return PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).build(); + } + private CommentEntity createCommentEntity() { + return CommentEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postId(new ObjectId()).content("내용").anonymous(true).build(); + } + private CommentCreateRequestDTO createCommentCreateRequestDTO(String content, boolean anonymous) throws Exception { + CommentCreateRequestDTO dto = CommentCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + setField(dto, "anonymous", anonymous); + return dto; + } + private CommentUpdateRequestDTO createCommentUpdateRequestDTO(String content) throws Exception { + CommentUpdateRequestDTO dto = CommentUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + return dto; + } + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java new file mode 100644 index 00000000..884f9ab3 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java @@ -0,0 +1,284 @@ +package inu.codin.codin.domain.post; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.service.BlockService; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.dto.request.*; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostService; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisBestService; +import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.infra.s3.exception.ImageRemoveException; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class PostServiceTest { + @InjectMocks + private PostService postService; + @Mock private PostRepository postRepository; + @Mock private BestRepository bestRepository; + @Mock private UserRepository userRepository; + @Mock private S3Service s3Service; + @Mock private LikeService likeService; + @Mock private ScrapService scrapService; + @Mock private HitsService hitsService; + @Mock private RedisBestService redisBestService; + @Mock private BlockService blockService; + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void 게시글_정상등록_성공() throws Exception { + // Given + PostCreateRequestDTO dto = createPostCreateRequestDTO("제목", "내용", true, PostCategory.COMMUNICATION); + List images = new ArrayList<>(); + ObjectId userId = new ObjectId(); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); + given(s3Service.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(postRepository.save(any())).willAnswer(inv -> { + PostEntity entity = inv.getArgument(0); + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, new ObjectId()); + return entity; + }); + // When + Map result = postService.createPost(dto, images); + // Then + assertThat(result).containsKey("postId"); + } + + @Test + void 게시글_등록_권한없음_예외() throws Exception { + // Given + PostCreateRequestDTO dto = createPostCreateRequestDTO("제목", "내용", true, PostCategory.EXTRACURRICULAR); + List images = new ArrayList<>(); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); + given(s3Service.handleImageUpload(any())).willReturn(new ArrayList<>()); + // When & Then + assertThatThrownBy(() -> postService.createPost(dto, images)).isInstanceOf(JwtException.class); + } + + @Test + void updatePostContent_success() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostContentUpdateRequestDTO dto = createPostContentUpdateRequestDTO("수정내용"); + List images = new ArrayList<>(); + PostEntity post = createPostEntity(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(s3Service.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(postRepository.save(any())).willReturn(post); + + // When/Then + assertThatCode(() -> postService.updatePostContent(postId, dto, images)).doesNotThrowAnyException(); + } + + @Test + void updatePostContent_notFound() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostContentUpdateRequestDTO dto = createPostContentUpdateRequestDTO("수정내용"); + List images = List.of(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + + // When/Then + assertThatThrownBy(() -> postService.updatePostContent(postId, dto, images)) + .isInstanceOf(NotFoundException.class); + } + + @Test + void updatePostAnonymous_success() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostAnonymousUpdateRequestDTO dto = createPostAnonymousUpdateRequestDTO(true); + PostEntity post = createPostEntity(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(postRepository.save(any())).willReturn(post); + + // When/Then + assertThatCode(() -> postService.updatePostAnonymous(postId, dto)).doesNotThrowAnyException(); + } + + @Test + void updatePostStatus_success() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostStatusUpdateRequestDTO dto = createPostStatusUpdateRequestDTO(PostStatus.ACTIVE); + PostEntity post = createPostEntity(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(postRepository.save(any())).willReturn(post); + + // When/Then + assertThatCode(() -> postService.updatePostStatus(postId, dto)).doesNotThrowAnyException(); + } + + @Test + void getAllPosts_success() { + given(blockService.getBlockedUsers()).willReturn(new ArrayList<>()); + List posts = new ArrayList<>(); + Page page = new PageImpl<>(posts); + given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any())).willReturn(page); + PostPageResponse response = postService.getAllPosts(PostCategory.COMMUNICATION, 0); + assertThat(response).isNotNull(); + } + + @Test + void getPostWithDetail_success() { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(hitsService.validateHits(any(), any())).willReturn(true); + given(likeService.getLikeCount(any(), any())).willReturn(0); + given(scrapService.getScrapCount(any())).willReturn(0); + given(hitsService.getHitsCount(any())).willReturn(0); + given(userRepository.findById(any())).willReturn(Optional.of(UserEntity.builder().nickname("닉네임").profileImageUrl("url").build())); + + // When + PostDetailResponseDTO response = postService.getPostWithDetail(postId); + + // Then + assertThat(response).isNotNull(); + } + + @Test + void softDeletePost_success() { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(postRepository.save(any())).willReturn(post); + + // When/Then + assertThatCode(() -> postService.softDeletePost(postId)).doesNotThrowAnyException(); + } + + @Test + void deletePostImage_success() throws Exception { + // Given + String postId = new ObjectId().toString(); + String imageUrl = "img.jpg"; + List imageList = new ArrayList<>(List.of(imageUrl)); + PostEntity post = PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).postImageUrls(imageList).build(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + willDoNothing().given(s3Service).deleteFile(anyString()); + given(postRepository.save(any())).willReturn(post); + + // When/Then + assertThatCode(() -> postService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); + } + + @Test + void searchPosts_success() { + given(blockService.getBlockedUsers()).willReturn(new ArrayList<>()); + List posts = new ArrayList<>(); + Page page = new PageImpl<>(posts); + given(postRepository.findAllByKeywordAndDeletedAtIsNull(anyString(), anyList(), any())).willReturn(page); + PostPageResponse response = postService.searchPosts("테스트", 0); + assertThat(response).isNotNull(); + } + + @Test + void getTop3BestPosts_success() { + given(redisBestService.getBests()).willReturn(new HashMap<>()); + List result = postService.getTop3BestPosts(); + assertThat(result).isNotNull(); + } + + @Test + void getBestPosts_success() { + Page bests = new PageImpl<>(new ArrayList<>()); + given(bestRepository.findAll(any(PageRequest.class))).willReturn(bests); + PostPageResponse response = postService.getBestPosts(0); + assertThat(response).isNotNull(); + } + + // --- 리플렉션 기반 DTO 생성 유틸리티 --- + private PostCreateRequestDTO createPostCreateRequestDTO(String title, String content, boolean anonymous, PostCategory category) throws Exception { + PostCreateRequestDTO dto = PostCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "title", title); + setField(dto, "content", content); + setField(dto, "anonymous", anonymous); + setField(dto, "postCategory", category); + return dto; + } + private PostContentUpdateRequestDTO createPostContentUpdateRequestDTO(String content) throws Exception { + PostContentUpdateRequestDTO dto = PostContentUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + return dto; + } + private PostAnonymousUpdateRequestDTO createPostAnonymousUpdateRequestDTO(boolean anonymous) throws Exception { + PostAnonymousUpdateRequestDTO dto = PostAnonymousUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "anonymous", anonymous); + return dto; + } + private PostStatusUpdateRequestDTO createPostStatusUpdateRequestDTO(PostStatus status) throws Exception { + PostStatusUpdateRequestDTO dto = PostStatusUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "postStatus", status); + return dto; + } + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } + + // 모든 PostEntity fixture에 _id 세팅 + private PostEntity createPostEntity() { + return PostEntity.builder() + ._id(new ObjectId()) + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .build(); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java new file mode 100644 index 00000000..558b25c3 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java @@ -0,0 +1,231 @@ +package inu.codin.codin.domain.post; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.report.repository.ReportRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.redis.service.RedisBestService; +import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.MockedStatic; + +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class ReplyCommentServiceTest { + @InjectMocks + private ReplyCommentService replyCommentService; + @Mock private PostRepository postRepository; + @Mock private ReplyCommentRepository replyCommentRepository; + @Mock private UserRepository userRepository; + @Mock private ReportRepository reportRepository; + @Mock private LikeService likeService; + @Mock private NotificationService notificationService; + @Mock private RedisBestService redisBestService; + @Mock private S3Service s3Service; + @Mock private CommentRepository commentRepository; + private static MockedStatic securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + @AfterEach + void tearDown() { + securityUtilsMock.close(); + } + + @Test + void 대댓글_정상등록_성공() throws Exception { + // Given + String commentId = new ObjectId().toString(); + ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("내용", true); + CommentEntity comment = createCommentEntity(); + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(comment)); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(replyCommentRepository.save(any())).willAnswer(inv -> { + ReplyCommentEntity entity = inv.getArgument(0); + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, new ObjectId()); + return entity; + }); + given(postRepository.save(any())).willReturn(post); + willDoNothing().given(redisBestService).applyBestScore(anyInt(), any()); + willDoNothing().given(notificationService).sendNotificationMessageByReply(any(), any(), any(), any()); + // When + replyCommentService.addReply(commentId, dto); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(ReplyCommentEntity.class); + verify(replyCommentRepository).save(captor.capture()); + ReplyCommentEntity saved = captor.getValue(); + assertThat(saved.getContent()).isEqualTo("내용"); + assertThat(saved.isAnonymous()).isTrue(); + assertThat(saved.getCommentId()).isNotNull(); + assertThat(saved.getUserId()).isEqualTo(userId); + } + + @Test + void 대댓글_등록_댓글없음_예외() throws Exception { + // Given + String commentId = new ObjectId().toString(); + ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("내용", true); + given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> replyCommentService.addReply(commentId, dto)).isInstanceOf(NotFoundException.class); + } + + @Test + void 대댓글_소프트삭제_성공() throws Exception { + // Given + String replyId = new ObjectId().toString(); + ReplyCommentEntity reply = createReplyCommentEntity(); + given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(reply)); + securityUtilsMock.when(() -> SecurityUtils.validateUser(any())).thenAnswer(invocation -> null); + given(replyCommentRepository.save(any())).willReturn(reply); + // When + replyCommentService.softDeleteReply(replyId); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(ReplyCommentEntity.class); + verify(replyCommentRepository).save(captor.capture()); + ReplyCommentEntity deleted = captor.getValue(); + assertThat(deleted.getDeletedAt()).isNotNull(); + } + + @Test + void 대댓글_소프트삭제_댓글없음_예외() { + // Given + String replyId = new ObjectId().toString(); + given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> replyCommentService.softDeleteReply(replyId)).isInstanceOf(NotFoundException.class); + } + + @Test + void 댓글별_대댓글목록_조회_성공() { + // Given + PostAnonymous postAnonymous = Mockito.mock(PostAnonymous.class); + ObjectId userId = new ObjectId(); + ObjectId commentId = new ObjectId(); + ReplyCommentEntity reply = ReplyCommentEntity.builder() + ._id(new ObjectId()) + .userId(userId) + .commentId(commentId) + .content("내용") + .anonymous(true) + .build(); + List replies = List.of(reply); + given(replyCommentRepository.findByCommentId(any())).willReturn(replies); + given(s3Service.getDefaultProfileImageUrl()).willReturn("defaultUrl"); + UserEntity user = UserEntity.builder() + .nickname("닉네임") + .profileImageUrl("url") + .build(); + try { + setField(user, "_id", userId); + } catch (Exception e) { + throw new RuntimeException(e); + } + given(userRepository.findAllById(any())).willReturn(List.of(user)); + // When + List result = replyCommentService.getRepliesByCommentId(postAnonymous, commentId); + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + assertThat(result.get(0).getContent()).isEqualTo("내용"); + } + + @Test + void 대댓글_수정_성공() throws Exception { + // Given + String replyId = new ObjectId().toString(); + ReplyUpdateRequestDTO dto = createReplyUpdateRequestDTO("수정내용"); + ReplyCommentEntity reply = createReplyCommentEntity(); + given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(reply)); + given(replyCommentRepository.save(any())).willReturn(reply); + // When + replyCommentService.updateReply(replyId, dto); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(ReplyCommentEntity.class); + verify(replyCommentRepository).save(captor.capture()); + ReplyCommentEntity updated = captor.getValue(); + assertThat(updated.getContent()).isEqualTo("수정내용"); + } + + @Test + void 대댓글_수정_댓글없음_예외() throws Exception { + // Given + String replyId = new ObjectId().toString(); + ReplyUpdateRequestDTO dto = createReplyUpdateRequestDTO("수정내용"); + given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + // When & Then + assertThatThrownBy(() -> replyCommentService.updateReply(replyId, dto)).isInstanceOf(NotFoundException.class); + } + + @Test + void 대댓글_유저정보_조회_성공() { + // Given + ObjectId replyId = new ObjectId(); + ObjectId userId = new ObjectId(); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(likeService.isLiked(any(), any(), any())).willReturn(true); + // When + CommentResponseDTO.UserInfo info = replyCommentService.getUserInfoAboutReply(replyId); + // Then + assertThat(info).isNotNull(); + assertThat(info.isLike()).isTrue(); + } + + // --- 리플렉션 기반 DTO/Entity 생성 유틸리티 --- + private PostEntity createPostEntity() { + return PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).build(); + } + private ReplyCommentEntity createReplyCommentEntity() { + return ReplyCommentEntity.builder()._id(new ObjectId()).userId(new ObjectId()).commentId(new ObjectId()).content("내용").anonymous(true).build(); + } + private ReplyCreateRequestDTO createReplyCreateRequestDTO(String content, boolean anonymous) throws Exception { + ReplyCreateRequestDTO dto = ReplyCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + setField(dto, "anonymous", anonymous); + return dto; + } + private ReplyUpdateRequestDTO createReplyUpdateRequestDTO(String content) throws Exception { + ReplyUpdateRequestDTO dto = ReplyUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + return dto; + } + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } + private CommentEntity createCommentEntity() { + return CommentEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postId(new ObjectId()).content("내용").anonymous(true).build(); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java new file mode 100644 index 00000000..85625da3 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java @@ -0,0 +1,80 @@ +package inu.codin.codin.domain.post.domain; + +import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; +import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisHitsService; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class HitsServiceTest { + @InjectMocks + private HitsService hitsService; + @Mock private RedisHitsService redisHitsService; + @Mock private RedisHealthChecker redisHealthChecker; + @Mock private HitsRepository hitsRepository; + + @Test + void 게시글_조회수_추가_성공() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + willDoNothing().given(redisHitsService).addHits(postId); + given(hitsRepository.save(any())).willAnswer(inv -> inv.getArgument(0)); + // When + hitsService.addHits(postId, userId); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(HitsEntity.class); + verify(hitsRepository).save(captor.capture()); + HitsEntity saved = captor.getValue(); + assertThat(saved.getPostId()).isEqualTo(postId); + assertThat(saved.getUserId()).isEqualTo(userId); + } + + @Test + void 게시글_조회여부_판단_성공() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + given(hitsRepository.existsByPostIdAndUserId(postId, userId)).willReturn(true); + // When + boolean result = hitsService.validateHits(postId, userId); + // Then + assertThat(result).isTrue(); + } + + @Test + void 게시글_조회수_반환_캐시있음() { + // Given + ObjectId postId = new ObjectId(); + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn("5"); + // When + int result = hitsService.getHitsCount(postId); + // Then + assertThat(result).isEqualTo(5); + } + + @Test + void 게시글_조회수_반환_캐시없음_DB조회() { + // Given + ObjectId postId = new ObjectId(); + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn(null); + given(hitsRepository.countAllByPostId(postId)).willReturn(7); + // When + int result = hitsService.getHitsCount(postId); + // Then + assertThat(result).isEqualTo(7); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java new file mode 100644 index 00000000..4d76710b --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java @@ -0,0 +1,199 @@ +package inu.codin.codin.domain.post.domain; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; +import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; +import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.repository.PostRepository; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.MockedStatic; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class PollServiceTest { + @InjectMocks + private PollService pollService; + @Mock private PostRepository postRepository; + @Mock private PollRepository pollRepository; + @Mock private PollVoteRepository pollVoteRepository; + private static MockedStatic securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + @AfterEach + void tearDown() { + securityUtilsMock.close(); + } + + @Test + void 투표_정상생성_성공() throws Exception { + // Given + PollCreateRequestDTO dto = createPollCreateRequestDTO("제목", "내용", List.of("A", "B"), false, LocalDateTime.now().plusDays(1), true, PostCategory.POLL); + ObjectId userId = new ObjectId(); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(postRepository.save(any())).willAnswer(inv -> { + PostEntity entity = inv.getArgument(0); + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, new ObjectId()); + return entity; + }); + given(pollRepository.save(any())).willAnswer(inv -> { + PollEntity entity = inv.getArgument(0); + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, new ObjectId()); + return entity; + }); + // When + pollService.createPoll(dto); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(PollEntity.class); + verify(pollRepository).save(captor.capture()); + PollEntity saved = captor.getValue(); + assertThat(saved.getPollOptions()).containsExactly("A", "B"); + assertThat(saved.isMultipleChoice()).isFalse(); + } + + @Test + void 투표_투표하기_정상() { + // Given + String postId = new ObjectId().toString(); + ObjectId postObjId = new ObjectId(postId); + ObjectId pollId = new ObjectId(); + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); + PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0)); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(any(), any())).willReturn(false); + given(pollVoteRepository.save(any())).willReturn(PollVoteEntity.builder().pollId(pollId).userId(userId).selectedOptions(List.of(0)).build()); + given(pollRepository.save(any())).willReturn(poll); + // When/Then + assertThatCode(() -> pollService.votingPoll(postId, dto)).doesNotThrowAnyException(); + } + + @Test + void 투표_투표하기_중복투표_예외() { + // Given + String postId = new ObjectId().toString(); + ObjectId postObjId = new ObjectId(postId); + ObjectId pollId = new ObjectId(); + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); + PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0)); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(any(), any())).willReturn(true); + // When/Then + assertThatThrownBy(() -> pollService.votingPoll(postId, dto)).isInstanceOf(PollDuplicateVoteException.class); + } + + @Test + void 투표_투표하기_종료된투표_예외() { + // Given + String postId = new ObjectId().toString(); + ObjectId postObjId = new ObjectId(postId); + ObjectId pollId = new ObjectId(); + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); + PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().minusDays(1)).multipleChoice(false).build(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0)); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); + // When/Then + assertThatThrownBy(() -> pollService.votingPoll(postId, dto)).isInstanceOf(PollTimeFailException.class); + } + + @Test + void 투표_투표하기_복수선택불가_예외() { + // Given + String postId = new ObjectId().toString(); + ObjectId postObjId = new ObjectId(postId); + ObjectId pollId = new ObjectId(); + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); + PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0, 1)); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(any(), any())).willReturn(false); + // When/Then + assertThatThrownBy(() -> pollService.votingPoll(postId, dto)).isInstanceOf(PollOptionChoiceException.class); + } + + @Test + void 투표_투표취소_정상() { + // Given + String postId = new ObjectId().toString(); + ObjectId postObjId = new ObjectId(postId); + ObjectId pollId = new ObjectId(); + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); + PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); + // 투표가 이미 1회 들어간 상태로 세팅 + poll.getPollVotesCounts().set(0, 1); + PollVoteEntity pollVote = PollVoteEntity.builder().pollId(pollId).userId(userId).selectedOptions(List.of(0)).build(); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.findByPollIdAndUserId(any(), any())).willReturn(Optional.of(pollVote)); + given(pollRepository.save(any())).willReturn(poll); + // When/Then + assertThatCode(() -> pollService.deleteVoting(postId)).doesNotThrowAnyException(); + } + + // --- 리플렉션 기반 DTO 생성 유틸리티 --- + private PollCreateRequestDTO createPollCreateRequestDTO(String title, String content, List options, boolean multipleChoice, LocalDateTime endTime, boolean anonymous, PostCategory category) throws Exception { + PollCreateRequestDTO dto = PollCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "title", title); + setField(dto, "content", content); + setField(dto, "pollOptions", options); + setField(dto, "multipleChoice", multipleChoice); + setField(dto, "pollEndTime", endTime); + setField(dto, "anonymous", anonymous); + setField(dto, "postCategory", category); + return dto; + } + private PollVotingRequestDTO createPollVotingRequestDTO(List selectedOptions) { + try { + PollVotingRequestDTO dto = PollVotingRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "selectedOptions", selectedOptions); + return dto; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } +} \ No newline at end of file From a5e43a316b2a34c2720c9c73592f2ddf699023a0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 8 Jul 2025 23:59:16 +0900 Subject: [PATCH 0798/1002] =?UTF-8?q?refactor:=20postImageUrls=EB=A5=BC=20?= =?UTF-8?q?=ED=95=AD=EC=83=81=20=EA=B0=80=EB=B3=80=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EC=9C=A0=EC=A7=80=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=83=9D=EC=84=B1=EC=9E=90/=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 4c80dfc7..c38f8330 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -9,6 +9,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.ArrayList; import java.util.List; @Document(collection = "posts") @@ -37,7 +38,7 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri this.userId = userId; this.title = title; this.content = content; - this.postImageUrls = postImageUrls; + this.postImageUrls = postImageUrls != null ? new ArrayList<>(postImageUrls) : new ArrayList<>(); this.isAnonymous = isAnonymous; this.postCategory = postCategory; this.postStatus = postStatus; @@ -45,7 +46,7 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri public void updatePostContent(String content, List postImageUrls) { this.content = content; - this.postImageUrls = postImageUrls; + this.postImageUrls = postImageUrls != null ? new ArrayList<>(postImageUrls) : new ArrayList<>(); } public void updatePostAnonymous(boolean isAnonymous) { From 7da4a8113030f495b29424bb884532c141ea11e3 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 01:00:22 +0900 Subject: [PATCH 0799/1002] =?UTF-8?q?refactor:=20Post=20Service=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84/=EC=97=AD=ED=95=A0=EB=B3=84=20private=20meth?= =?UTF-8?q?od=20=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 차후 각 서비스로 이전 예정 --- .../domain/post/service/PostService.java | 280 +++++++++++------- 1 file changed, 171 insertions(+), 109 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 3d129c9c..965f2522 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -64,12 +64,12 @@ public class PostService { public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); - List imageUrls = s3Service.handleImageUpload(postImages); + List imageUrls = handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시물에 대한 접근권한 없음. UserId: {}", userId); + log.error("비교과 게시물에 대한 접근권한 없음. UserId: {}", userId); throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } @@ -91,7 +91,6 @@ public Map createPost(PostCreateRequestDTO postCreateRequestDTO, return response; } - public void updatePostContent(String postId, PostContentUpdateRequestDTO requestDTO, List postImages) { log.info("게시물 수정 시작. PostId: {}", postId); @@ -99,12 +98,11 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request .orElseThrow(()->new NotFoundException("해당 게시물 없음")); validateUserAndPost(post); - List imageUrls = s3Service.handleImageUpload(postImages); + List imageUrls = handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); log.info("게시물 수정 성공. PostId: {}", postId); - } public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { @@ -115,8 +113,8 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); log.info("게시물 익명 수정 성공. PostId: {}", postId); - } + public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()->new NotFoundException("해당 게시물 없음")); @@ -125,8 +123,8 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); - } + private void validateUserAndPost(PostEntity post) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ @@ -136,68 +134,6 @@ private void validateUserAndPost(PostEntity post) { SecurityUtils.validateUser(post.getUserId()); } - - - - // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 - private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { - String nickname; - String userImageUrl; - - UserEntity user = userRepository.findById(post.getUserId()) - .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); - if (user.getDeletedAt() == null){ - if (post.isAnonymous()){ - nickname = "익명"; - userImageUrl = s3Service.getDefaultProfileImageUrl(); // S3Service에서 기본 이미지 URL 가져오기 - } else { - nickname = user.getNickname(); - userImageUrl = user.getProfileImageUrl(); - } - } else { - nickname = user.getNickname(); - userImageUrl = user.getProfileImageUrl(); - } - - //Post 관련 인자 처리 - - int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); - int scrapCount = scrapService.getScrapCount(post.get_id()); - int hitsCount = hitsService.getHitsCount(post.get_id()); - int commentCount = post.getCommentCount(); - - ObjectId userId = SecurityUtils.getCurrentUserId(); - - UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - - // 투표 게시물 처리 - if (post.getPostCategory() == PostCategory.POLL) { - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); - - long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); - List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) - .map(PollVoteEntity::getSelectedOptions) - .orElse(Collections.emptyList()); - boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); - boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); - - //투표 DTO 생성 - PostPollDetailResponseDTO.PollInfo pollInfo = PostPollDetailResponseDTO.PollInfo.of(poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), - poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); - - log.info("게시글-투표 상세정보 생성 성공. PostId: {}", post.get_id()); - - //게시물 + 투표 DTO 생성 - return PostPollDetailResponseDTO.of( - PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), - pollInfo - ); - } - // 일반 게시물 처리 - return PostDetailResponseDTO.of(post, nickname, userImageUrl, likeCount, scrapCount, hitsCount, commentCount ,userInfo); - } - // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { @@ -225,18 +161,11 @@ public PostDetailResponseDTO getPostWithDetail(String postId) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - if (!hitsService.validateHits(post.get_id(), userId)) { - hitsService.addHits(post.get_id(), userId); - log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); - } + increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 return createPostDetailResponse(post); } - - - - public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); @@ -246,7 +175,6 @@ public void softDeletePost(String postId) { log.info("게시물 안전 삭제. PostId: {}", postId); postRepository.save(post); - } public void deletePostImage(String postId, String imageUrl) { @@ -254,24 +182,83 @@ public void deletePostImage(String postId, String imageUrl) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); validateUserAndPost(post); - if (!post.getPostImageUrls().contains(imageUrl)) { - log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", postId, imageUrl); - throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); + deletePostImageInternal(post, imageUrl); + } + + public PostPageResponse searchPosts(String keyword, int pageNumber) { + // 차단 목록 조회 + List blockedUsersId = blockService.getBlockedUsers(); + + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); + log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } + + public List getTop3BestPosts() { + List bestPosts = getTop3BestPostsInternal(); // [BestService] - 베스트 게시물 조회 위임 + log.info("Top 3 베스트 게시물 반환."); + return getPostListResponseDtos(bestPosts); + } + + public PostPageResponse getBestPosts(int pageNumber) { + Page page = getBestPostsInternal(pageNumber); // [BestService] - 베스트 게시물 페이지 조회 위임 + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } + + public Optional getPostDetailById(ObjectId postId) { + return postRepository.findById(postId) + .map(this::createPostDetailResponse); + } + + // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 + private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { + // 1. 닉네임/이미지 결정 + UserProfile userProfile = resolveUserProfile(post); + + // 2. 부가 정보 조회 + int likeCount = getLikeCount(post); + int scrapCount = getScrapCount(post); + int hitsCount = getHitsCount(post); + int commentCount = post.getCommentCount(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + + // 3. 투표 게시글 분기 + if (post.getPostCategory() == PostCategory.POLL) { + PostPollDetailResponseDTO.PollInfo pollInfo = getPollInfo(post, userId); + log.info("게시글-투표 상세정보 생성 성공. PostId: {}", post.get_id()); + return PostPollDetailResponseDTO.of( + PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), + pollInfo + ); } - try { - // S3에서 이미지 삭제 - s3Service.deleteFile(imageUrl); - // 게시물의 이미지 리스트에서 제거 - post.removePostImage(imageUrl); - postRepository.save(post); - log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", postId, imageUrl); + // 4. 일반 게시물 처리 + return PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + } - } catch (Exception e) { - log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", postId, imageUrl, e); - throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); + // [유저 프로필] - 닉네임/이미지 결정 + private UserProfile resolveUserProfile(PostEntity post) { + UserEntity user = userRepository.findById(post.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + String nickname; + String userImageUrl; + if (user.getDeletedAt() == null) { + if (post.isAnonymous()) { + nickname = "익명"; + userImageUrl = s3Service.getDefaultProfileImageUrl(); + } else { + nickname = user.getNickname(); + userImageUrl = user.getProfileImageUrl(); + } + } else { + nickname = user.getNickname(); + userImageUrl = user.getProfileImageUrl(); } + return new UserProfile(nickname, userImageUrl); } + public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ return UserInfo.builder() .isLike(likeService.isLiked(LikeType.POST, postId, currentUserId)) @@ -280,41 +267,116 @@ public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId .build(); } - public PostPageResponse searchPosts(String keyword, int pageNumber) { - // 차단 목록 조회 - List blockedUsersId = blockService.getBlockedUsers(); + // ===================== 도메인별 부가 기능/서브 로직 (분리 예정) ===================== - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); - log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + // [ImageService] - 이미지 업로드 처리 + private List handleImageUpload(List postImages) { + return s3Service.handleImageUpload(postImages); } - public List getTop3BestPosts() { + // [ImageService] - 게시글 이미지 삭제 처리 + private void deletePostImageInternal(PostEntity post, String imageUrl) { + if (!post.getPostImageUrls().contains(imageUrl)) { + log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); + } + try { + s3Service.deleteFile(imageUrl); + post.removePostImage(imageUrl); + postRepository.save(post); + log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + } catch (Exception e) { + log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); + throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); + } + } + + // [HitsService] - 조회수 증가 처리 + private void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { + if (!hitsService.validateHits(post.get_id(), userId)) { + hitsService.addHits(post.get_id(), userId); + log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); + } + } + + // [BestService] - Top 3 베스트 게시물 조회 + private List getTop3BestPostsInternal() { Map posts = redisBestService.getBests(); - List bestPosts = posts.entrySet().stream() + return posts.entrySet().stream() .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) .orElseGet(() -> { redisBestService.deleteBest(post.getKey()); return null; })) - .filter(Objects::nonNull) // null 값 제거 + .filter(Objects::nonNull) .toList(); - log.info("Top 3 베스트 게시물 반환."); - return getPostListResponseDtos(bestPosts); } - public PostPageResponse getBestPosts(int pageNumber) { + // [BestService] - 베스트 게시물 페이지 조회 + private Page getBestPostsInternal(int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page bests = bestRepository.findAll(pageRequest); - Page page = bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) + return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - public Optional getPostDetailById(ObjectId postId) { - return postRepository.findById(postId) - .map(this::createPostDetailResponse); + // [BestService] - 베스트 점수 적용 처리 + private void applyBestScoreIfNeeded(PostEntity post) { + redisBestService.applyBestScore(1, post.get_id()); + log.info("베스트 점수 적용. PostId: {}", post.get_id()); + } + + // [CommentService/ReplyCommentService] - 댓글 수 증가 + private void increaseCommentCount(PostEntity post) { + post.plusCommentCount(); + postRepository.save(post); + log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); + } + + // [PollService] - 투표 관련 처리 (예시) + private void processPollIfNeeded(PostEntity post) { + // 투표 게시글일 경우 PollService와 협력하여 투표 생성/집계 등 처리 + // 예시: pollService.createPoll(...) + } + + // [좋아요] - 게시글 좋아요 수 조회 + private int getLikeCount(PostEntity post) { + return likeService.getLikeCount(LikeType.POST, post.get_id()); + } + + // [스크랩] - 게시글 스크랩 수 조회 + private int getScrapCount(PostEntity post) { + return scrapService.getScrapCount(post.get_id()); + } + + // [조회수] - 게시글 조회수 조회 + private int getHitsCount(PostEntity post) { + return hitsService.getHitsCount(post.get_id()); + } + + // [투표] - 투표 게시글 PollInfo 생성 + private PostPollDetailResponseDTO.PollInfo getPollInfo(PostEntity post, ObjectId userId) { + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); + long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); + List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) + .map(PollVoteEntity::getSelectedOptions) + .orElse(Collections.emptyList()); + boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); + boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); + return PostPollDetailResponseDTO.PollInfo.of( + poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), + poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + } + + // [내부 클래스] - 유저 프로필 정보 + private static class UserProfile { + final String nickname; + final String userImageUrl; + UserProfile(String nickname, String userImageUrl) { + this.nickname = nickname; + this.userImageUrl = userImageUrl; + } } } From 87e59ed2193783bc1e5695a312c359547ecf2177 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 01:43:30 +0900 Subject: [PATCH 0800/1002] =?UTF-8?q?refactor:=20PollInfo/UserInfo=20DTO?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20builder+=EC=A0=95=EC=A0=81?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=ED=8C=A8=ED=84=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PollInfoResponseDTO.java | 49 ++++++++++++++++ .../dto/response/PostDetailResponseDTO.java | 58 ++++++++----------- .../post/dto/response/PostPageResponse.java | 13 +++-- .../response/PostPollDetailResponseDTO.java | 57 ++++-------------- .../dto/response/UserInfoResponseDTO.java | 26 +++++++++ .../domain/post/service/PostService.java | 26 ++++----- 6 files changed, 127 insertions(+), 102 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PollInfoResponseDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PollInfoResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PollInfoResponseDTO.java new file mode 100644 index 00000000..b2232644 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PollInfoResponseDTO.java @@ -0,0 +1,49 @@ +package inu.codin.codin.domain.post.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +public class PollInfoResponseDTO { + private final List pollOptions; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + private final LocalDateTime pollEndTime; + private final boolean multipleChoice; + private final List pollVotesCounts; + private final List userVotesOptions; + private final Long totalParticipants; + private final boolean hasUserVoted; + private final boolean pollFinished; + + @Builder + private PollInfoResponseDTO(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, + List pollVotesCounts, List userVotesOptions, Long totalParticipants, boolean hasUserVoted, boolean pollFinished) { + this.pollOptions = pollOptions; + this.pollEndTime = pollEndTime; + this.multipleChoice = multipleChoice; + this.pollVotesCounts = pollVotesCounts; + this.userVotesOptions = userVotesOptions; + this.totalParticipants = totalParticipants; + this.hasUserVoted = hasUserVoted; + this.pollFinished = pollFinished; + } + + public static PollInfoResponseDTO of(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, + List pollVotesCounts, List userVotesOptions, + Long totalParticipants, boolean hasUserVoted , boolean pollFinished) { + return PollInfoResponseDTO.builder() + .pollOptions(pollOptions) + .pollEndTime(pollEndTime) + .multipleChoice(multipleChoice) + .pollVotesCounts(pollVotesCounts) + .userVotesOptions(userVotesOptions) + .totalParticipants(totalParticipants) + .hasUserVoted(hasUserVoted) + .pollFinished(pollFinished) + .build(); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 34647c2d..5a99325f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -65,10 +65,11 @@ public class PostDetailResponseDTO { private final LocalDateTime createdAt; @Schema(description = "해당 게시글에 대한 유저 반응 여부") - private final UserInfo userInfo; + private final UserInfoResponseDTO userInfo; - public PostDetailResponseDTO(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, - boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfo userInfo){ + @Builder + private PostDetailResponseDTO(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List postImageUrl, + boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfoResponseDTO userInfo){ this.userId = userId; this._id = _id; this.title = title; @@ -76,7 +77,7 @@ public PostDetailResponseDTO(String userId, String _id, String title, String con this.nickname = nickname; this.postCategory = postCategory; this.userImageUrl = userImageUrl; - this.postImageUrl = postImageUrls; + this.postImageUrl = postImageUrl; this.isAnonymous = isAnonymous; this.likeCount = likeCount; this.scrapCount = scrapCount; @@ -86,37 +87,24 @@ public PostDetailResponseDTO(String userId, String _id, String title, String con this.userInfo = userInfo; } - @Getter - public static class UserInfo { - private final boolean isLike; - private final boolean isScrap; - private final boolean isMine; - - @Builder - public UserInfo(boolean isLike, boolean isScrap, boolean isMine) { - this.isLike = isLike; - this.isScrap = isScrap; - this.isMine = isMine; - } - } - - public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount ,UserInfo userInfo) { - return new PostDetailResponseDTO( - post.getUserId().toString(), - post.get_id().toString(), - post.getTitle(), - post.getContent(), - nickname, - post.getPostCategory(), - userImageUrl, - post.getPostImageUrls(), - post.isAnonymous(), - likeCount, - scrapCount, - hitsCount, - post.getCreatedAt(), - commentCount, - userInfo); + public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount ,UserInfoResponseDTO userInfo) { + return PostDetailResponseDTO.builder() + .userId(post.getUserId().toString()) + ._id(post.get_id().toString()) + .title(post.getTitle()) + .content(post.getContent()) + .nickname(nickname) + .postCategory(post.getPostCategory()) + .userImageUrl(userImageUrl) + .postImageUrl(post.getPostImageUrls()) + .isAnonymous(post.isAnonymous()) + .likeCount(likeCount) + .scrapCount(scrapCount) + .hits(hitsCount) + .createdAt(post.getCreatedAt()) + .commentCount(commentCount) + .userInfo(userInfo) + .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java index 26bda696..24330f62 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.dto.response; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,13 +9,13 @@ import java.util.List; @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostPageResponse { private List contents = new ArrayList<>(); private long lastPage; private long nextPage; + @Builder private PostPageResponse(List contents, long lastPage, long nextPage) { this.contents = contents; this.lastPage = lastPage; @@ -22,11 +23,11 @@ private PostPageResponse(List contents, long lastPage, lo } public static PostPageResponse of(List postPaging, long totalElements, long nextPage) { - return PostPageResponse.newPagingHasNext(postPaging, totalElements, nextPage); - } - - private static PostPageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { - return new PostPageResponse(posts, totalElements, nextPage); + return PostPageResponse.builder() + .contents(postPaging) + .lastPage(totalElements) + .nextPage(nextPage) + .build(); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 568829c5..9134fa69 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -1,67 +1,32 @@ package inu.codin.codin.domain.post.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; import lombok.Getter; import lombok.Setter; import java.time.LocalDateTime; import java.util.List; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; + @Getter -@Setter public class PostPollDetailResponseDTO extends PostDetailResponseDTO { - private final PollInfo poll; + private final PollInfoResponseDTO poll; - public PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfo poll) { + @Builder + private PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfoResponseDTO poll) { super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); this.poll = poll; } - public static PostPollDetailResponseDTO of(PostDetailResponseDTO base, PollInfo poll) { - return new PostPollDetailResponseDTO(base, poll); - } - - @Getter - public static class PollInfo { - //투표 선택지 - private final List pollOptions; - - //투표 종료시간 - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - private final LocalDateTime pollEndTime; - - //복수 투표 여부 - private final boolean multipleChoice; - //투표 항목별 총 카운트 - private final List pollVotesCounts; - //유저가 선택한 항목 - private final List userVotesOptions; - //투표 참여자 수 - private final Long totalParticipants; - //유저 투표 실시 여부 - private final boolean hasUserVoted; - //투표 종료 여부 - private final boolean pollFinished; - - public PollInfo(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, - List pollVotesCounts, List userVotesOptions, Long totalParticipants, boolean hasUserVoted ,boolean PollFinished) { - this.pollOptions = pollOptions; - this.pollEndTime = pollEndTime; - this.multipleChoice = multipleChoice; - this.pollVotesCounts = pollVotesCounts; - this.userVotesOptions = userVotesOptions; - this.totalParticipants = totalParticipants; - this.hasUserVoted = hasUserVoted; - this.pollFinished = PollFinished; - } - public static PollInfo of(List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice, - List pollVotesCounts, List userVotesOptions, - Long totalParticipants, boolean hasUserVoted , boolean pollFinished) { - return new PollInfo(pollOptions, pollEndTime, multipleChoice, pollVotesCounts, userVotesOptions, totalParticipants,hasUserVoted ,pollFinished); - } - + public static PostPollDetailResponseDTO of(PostDetailResponseDTO base, PollInfoResponseDTO poll) { + return PostPollDetailResponseDTO.builder() + .baseDTO(base) + .poll(poll) + .build(); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java new file mode 100644 index 00000000..06baa747 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.post.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class UserInfoResponseDTO { + private final boolean isLike; + private final boolean isScrap; + private final boolean isMine; + + @Builder + private UserInfoResponseDTO(boolean isLike, boolean isScrap, boolean isMine) { + this.isLike = isLike; + this.isScrap = isScrap; + this.isMine = isMine; + } + + public static UserInfoResponseDTO of(boolean isLike, boolean isScrap, boolean isMine) { + return UserInfoResponseDTO.builder() + .isLike(isLike) + .isScrap(isScrap) + .isMine(isMine) + .build(); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 965f2522..8346ca92 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -18,10 +18,7 @@ import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO.UserInfo; -import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.post.dto.response.PostPollDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.*; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; @@ -222,11 +219,11 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { int hitsCount = getHitsCount(post); int commentCount = post.getCommentCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); // 3. 투표 게시글 분기 if (post.getPostCategory() == PostCategory.POLL) { - PostPollDetailResponseDTO.PollInfo pollInfo = getPollInfo(post, userId); + PollInfoResponseDTO pollInfo = getPollInfo(post, userId); log.info("게시글-투표 상세정보 생성 성공. PostId: {}", post.get_id()); return PostPollDetailResponseDTO.of( PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), @@ -258,13 +255,12 @@ private UserProfile resolveUserProfile(PostEntity post) { return new UserProfile(nickname, userImageUrl); } - - public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ - return UserInfo.builder() - .isLike(likeService.isLiked(LikeType.POST, postId, currentUserId)) - .isScrap(scrapService.isPostScraped(postId, currentUserId)) - .isMine(postUserId.equals(currentUserId)) - .build(); + public UserInfoResponseDTO getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ + return UserInfoResponseDTO.of( + likeService.isLiked(LikeType.POST, postId, currentUserId), + scrapService.isPostScraped(postId, currentUserId), + postUserId.equals(currentUserId) + ); } // ===================== 도메인별 부가 기능/서브 로직 (분리 예정) ===================== @@ -355,7 +351,7 @@ private int getHitsCount(PostEntity post) { } // [투표] - 투표 게시글 PollInfo 생성 - private PostPollDetailResponseDTO.PollInfo getPollInfo(PostEntity post, ObjectId userId) { + private PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { PollEntity poll = pollRepository.findByPostId(post.get_id()) .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); @@ -364,7 +360,7 @@ private PostPollDetailResponseDTO.PollInfo getPollInfo(PostEntity post, ObjectId .orElse(Collections.emptyList()); boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); - return PostPollDetailResponseDTO.PollInfo.of( + return PollInfoResponseDTO.of( poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); } From 81cafb7cd63b7ff2cb1bddd120baa9b97d5ce9e2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 01:57:37 +0900 Subject: [PATCH 0801/1002] =?UTF-8?q?refactor:=20=EC=83=81=EC=86=8D=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20->=20=EC=A1=B0=ED=95=A9(composition)=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/PostPollDetailResponseDTO.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java index 9134fa69..0b6276c6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPollDetailResponseDTO.java @@ -1,31 +1,23 @@ package inu.codin.codin.domain.post.dto.response; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Builder; import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDateTime; -import java.util.List; +import lombok.Builder; -import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; @Getter -public class PostPollDetailResponseDTO extends PostDetailResponseDTO { - +public class PostPollDetailResponseDTO { + private final PostDetailResponseDTO post; private final PollInfoResponseDTO poll; @Builder - private PostPollDetailResponseDTO(PostDetailResponseDTO baseDTO, PollInfoResponseDTO poll) { - super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), baseDTO.getLikeCount(), - baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), baseDTO.getCommentCount(), baseDTO.getUserInfo()); + public PostPollDetailResponseDTO(PostDetailResponseDTO post, PollInfoResponseDTO poll) { + this.post = post; this.poll = poll; } - public static PostPollDetailResponseDTO of(PostDetailResponseDTO base, PollInfoResponseDTO poll) { + public static PostPollDetailResponseDTO of(PostDetailResponseDTO post, PollInfoResponseDTO poll) { return PostPollDetailResponseDTO.builder() - .baseDTO(base) + .post(post) .poll(poll) .build(); } From 8ca2fb3180ee5e661923de406e1ecd08b163dc60 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 02:00:34 +0900 Subject: [PATCH 0802/1002] =?UTF-8?q?refactor:=20=EC=83=81=EC=86=8D=20->?= =?UTF-8?q?=20=EC=A1=B0=ED=95=A9(composition)=20+=20wrapper=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PostPageItemResponseDTO.java | 23 +++++++++++++ .../post/dto/response/PostPageResponse.java | 6 ++-- .../domain/post/service/PostService.java | 34 +++++++++++++------ 3 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java new file mode 100644 index 00000000..e753499b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.post.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class PostPageItemResponseDTO { + private final PostDetailResponseDTO post; + private final PollInfoResponseDTO poll; + + @Builder + public PostPageItemResponseDTO(PostDetailResponseDTO post, PollInfoResponseDTO poll) { + this.post = post; + this.poll = poll; + } + + public static PostPageItemResponseDTO of(PostDetailResponseDTO post, PollInfoResponseDTO poll) { + return PostPageItemResponseDTO.builder() + .post(post) + .poll(poll) + .build(); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java index 24330f62..816555e3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageResponse.java @@ -11,18 +11,18 @@ @Getter public class PostPageResponse { - private List contents = new ArrayList<>(); + private List contents = new ArrayList<>(); private long lastPage; private long nextPage; @Builder - private PostPageResponse(List contents, long lastPage, long nextPage) { + private PostPageResponse(List contents, long lastPage, long nextPage) { this.contents = contents; this.lastPage = lastPage; this.nextPage = nextPage; } - public static PostPageResponse of(List postPaging, long totalElements, long nextPage) { + public static PostPageResponse of(List postPaging, long totalElements, long nextPage) { return PostPageResponse.builder() .contents(postPaging) .lastPage(totalElements) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 8346ca92..705c31ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -133,7 +133,6 @@ private void validateUserAndPost(PostEntity post) { // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { - // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); @@ -146,14 +145,29 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { } // 게시물 리스트 가져오기 - public List getPostListResponseDtos(List posts) { + public List getPostListResponseDtos(List posts) { return posts.stream() - .map(this::createPostDetailResponse) + .map(post -> { + UserProfile userProfile = resolveUserProfile(post); + int likeCount = getLikeCount(post); + int scrapCount = getScrapCount(post); + int hitsCount = getHitsCount(post); + int commentCount = post.getCommentCount(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + if (post.getPostCategory() == PostCategory.POLL) { + PollInfoResponseDTO pollInfo = getPollInfo(post, userId); + return PostPageItemResponseDTO.of(postDTO, pollInfo); + } else { + return PostPageItemResponseDTO.of(postDTO, null); + } + }) .toList(); } // 게시물 상세 조회 - public PostDetailResponseDTO getPostWithDetail(String postId) { + public Object getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); @@ -192,7 +206,7 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - public List getTop3BestPosts() { + public List getTop3BestPosts() { List bestPosts = getTop3BestPostsInternal(); // [BestService] - 베스트 게시물 조회 위임 log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); @@ -203,13 +217,13 @@ public PostPageResponse getBestPosts(int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - public Optional getPostDetailById(ObjectId postId) { + public Optional getPostDetailById(ObjectId postId) { return postRepository.findById(postId) .map(this::createPostDetailResponse); } // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 - private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { + private Object createPostDetailResponse(PostEntity post) { // 1. 닉네임/이미지 결정 UserProfile userProfile = resolveUserProfile(post); @@ -225,10 +239,8 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { if (post.getPostCategory() == PostCategory.POLL) { PollInfoResponseDTO pollInfo = getPollInfo(post, userId); log.info("게시글-투표 상세정보 생성 성공. PostId: {}", post.get_id()); - return PostPollDetailResponseDTO.of( - PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo), - pollInfo - ); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + return PostPollDetailResponseDTO.of(postDTO, pollInfo); } // 4. 일반 게시물 처리 return PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); From 6ace37980993c8c09624269060eeb713d4f762b7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 02:06:04 +0900 Subject: [PATCH 0803/1002] =?UTF-8?q?refactor:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=9D=91=EB=8B=B5=20DTO=20=EC=83=81=EC=86=8D=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=A1=B0?= =?UTF-8?q?=ED=95=A9(=EB=9E=98=ED=8D=BC=20DTO)=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PostDetailResponseDTO, PollInfoResponseDTO를 포함하는 PostPageItemResponseDTO 래퍼 도입 - 목록/상세/베스트 등 모든 게시글 응답을 래퍼 DTO(List 등)로 일원화 - 중복 DTO 변환 로직 toPageItemDTO로 통합, Object 반환/instanceof 분기 제거 --- .../domain/post/service/PostService.java | 68 ++++++------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 705c31ea..f64fbce8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -147,34 +147,35 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { // 게시물 리스트 가져오기 public List getPostListResponseDtos(List posts) { return posts.stream() - .map(post -> { - UserProfile userProfile = resolveUserProfile(post); - int likeCount = getLikeCount(post); - int scrapCount = getScrapCount(post); - int hitsCount = getHitsCount(post); - int commentCount = post.getCommentCount(); - ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); - if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = getPollInfo(post, userId); - return PostPageItemResponseDTO.of(postDTO, pollInfo); - } else { - return PostPageItemResponseDTO.of(postDTO, null); - } - }) + .map(this::toPageItemDTO) .toList(); } // 게시물 상세 조회 - public Object getPostWithDetail(String postId) { + public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - ObjectId userId = SecurityUtils.getCurrentUserId(); increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 + return toPageItemDTO(post); + } - return createPostDetailResponse(post); + // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) + private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { + UserProfile userProfile = resolveUserProfile(post); + int likeCount = getLikeCount(post); + int scrapCount = getScrapCount(post); + int hitsCount = getHitsCount(post); + int commentCount = post.getCommentCount(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + if (post.getPostCategory() == PostCategory.POLL) { + PollInfoResponseDTO pollInfo = getPollInfo(post, userId); + return PostPageItemResponseDTO.of(postDTO, pollInfo); + } else { + return PostPageItemResponseDTO.of(postDTO, null); + } } public void softDeletePost(String postId) { @@ -217,35 +218,6 @@ public PostPageResponse getBestPosts(int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - public Optional getPostDetailById(ObjectId postId) { - return postRepository.findById(postId) - .map(this::createPostDetailResponse); - } - - // Post 정보를 처리하여 DTO를 생성하는 공통 메소드 - private Object createPostDetailResponse(PostEntity post) { - // 1. 닉네임/이미지 결정 - UserProfile userProfile = resolveUserProfile(post); - - // 2. 부가 정보 조회 - int likeCount = getLikeCount(post); - int scrapCount = getScrapCount(post); - int hitsCount = getHitsCount(post); - int commentCount = post.getCommentCount(); - ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - - // 3. 투표 게시글 분기 - if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = getPollInfo(post, userId); - log.info("게시글-투표 상세정보 생성 성공. PostId: {}", post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); - return PostPollDetailResponseDTO.of(postDTO, pollInfo); - } - // 4. 일반 게시물 처리 - return PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); - } - // [유저 프로필] - 닉네임/이미지 결정 private UserProfile resolveUserProfile(PostEntity post) { UserEntity user = userRepository.findById(post.getUserId()) From daa3c83a9f55de9249126a24eea9f1a1f9f70234 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 23:55:50 +0900 Subject: [PATCH 0804/1002] =?UTF-8?q?refactor:=20Response=20Type=20paramet?= =?UTF-8?q?er=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 395830b5..d6b75583 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; @@ -41,21 +42,21 @@ public PostController(PostService postService) { description = "JSON 형식의 게시물 데이터(postContent)와 이미지 파일(postImages) 업로드" ) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> createPost( + public ResponseEntity> createPost( @RequestPart("postContent") @Valid PostCreateRequestDTO postCreateRequestDTO, @RequestPart(value = "postImages", required = false) List postImages) { // postImages가 null이면 빈 리스트로 처리 if (postImages == null) postImages = List.of(); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", postService.createPost(postCreateRequestDTO, postImages))); + .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", null)); } @Operation( summary = "게시물 내용 수정 및 이미지 수정&추가" ) @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> updatePostContent( + public ResponseEntity> updatePostContent( @PathVariable String postId, @RequestPart("postContent") @Valid PostContentUpdateRequestDTO requestDTO, @RequestPart(value = "postImages", required = false) List postImages) { @@ -69,7 +70,7 @@ public ResponseEntity> updatePostContent( summary = "상태 수정" ) @PatchMapping("/{postId}/status") - public ResponseEntity> updatePostStatus( + public ResponseEntity> updatePostStatus( @PathVariable String postId, @RequestBody PostStatusUpdateRequestDTO requestDTO) { postService.updatePostStatus(postId, requestDTO); @@ -80,7 +81,7 @@ public ResponseEntity> updatePostStatus( @Operation(summary = "게시물 익명 설정 수정") @PatchMapping("/{postId}/anonymous") - public ResponseEntity> updatePostAnonymous( + public ResponseEntity> updatePostAnonymous( @PathVariable String postId, @RequestBody @Valid PostAnonymousUpdateRequestDTO requestDTO) { postService.updatePostAnonymous(postId, requestDTO); @@ -103,8 +104,8 @@ public ResponseEntity> getAllPosts(@RequestPara @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") @GetMapping("/{postId}") - public ResponseEntity> getPostWithDetail(@PathVariable String postId) { - PostDetailResponseDTO post = postService.getPostWithDetail(postId); + public ResponseEntity> getPostWithDetail(@PathVariable String postId) { + PostPageItemResponseDTO post = postService.getPostWithDetail(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } @@ -112,7 +113,7 @@ public ResponseEntity> getPostWithDetail(@ @Operation(summary = "게시물 이미지 삭제") @DeleteMapping("/{postId}/images") - public ResponseEntity> deletePostImage( + public ResponseEntity> deletePostImage( @PathVariable String postId, @RequestParam String imageUrl) { @@ -123,7 +124,7 @@ public ResponseEntity> deletePostImage( @Operation(summary = "게시물 삭제 (Soft Delete)") @DeleteMapping("/{postId}") - public ResponseEntity> softDeletePost(@PathVariable String postId) { + public ResponseEntity> softDeletePost(@PathVariable String postId) { postService.softDeletePost(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); @@ -133,7 +134,7 @@ public ResponseEntity> softDeletePost(@PathVariable String pos summary = "검색 엔진" ) @GetMapping("/search") - public ResponseEntity> searchPosts(@RequestParam("keyword") @Size(min = 2) String keyword, + public ResponseEntity> searchPosts(@RequestParam("keyword") @Size(min = 2) String keyword, @RequestParam("pageNumber") @NotNull int pageNumber){ return ResponseEntity.ok() .body(new SingleResponse<>(200, "'"+keyword+"'"+"으로 검색된 게시글 반환 완료", postService.searchPosts(keyword, pageNumber))); @@ -141,14 +142,14 @@ public ResponseEntity> searchPosts(@RequestParam("keyword") @S @Operation(summary = "Top 3 베스트 게시글 가져오기") @GetMapping("/top3") - public ResponseEntity> getTop3BestPosts(){ + public ResponseEntity> getTop3BestPosts(){ return ResponseEntity.ok() .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postService.getTop3BestPosts())); } @Operation(summary = "Top3로 선정된 게시글들 모두 가져오기") @GetMapping("/best") - public ResponseEntity> getBestPosts(@RequestParam("pageNumber") int pageNumber){ + public ResponseEntity> getBestPosts(@RequestParam("pageNumber") int pageNumber){ return ResponseEntity.ok() .body(new SingleResponse<>(200, "Top3로 선정된 게시글들 모두 반환 완료", postService.getBestPosts(pageNumber))); } From 39ce89430fb68e6d52ae7e46b4c06962351e62cd Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 9 Jul 2025 23:57:00 +0900 Subject: [PATCH 0805/1002] =?UTF-8?q?refactor:=20extends(=EC=83=81?= =?UTF-8?q?=EC=86=8D)=20->=20composition(=EC=A1=B0=ED=95=A9)=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이전 postDetailResponse 변경 전파 사항 --- .../codin/domain/post/service/PostService.java | 12 +++++++++++- .../dto/response/ReportListResponseDto.java | 17 ++++++----------- .../response/ReportedPostDetailResponseDTO.java | 14 ++++++-------- .../domain/report/service/ReportService.java | 14 +++++++------- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index f64fbce8..52c6c424 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -151,7 +151,7 @@ public List getPostListResponseDtos(List po .toList(); } - // 게시물 상세 조회 + // 게시물 상세 조회 (기존코드) public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); @@ -160,6 +160,16 @@ public PostPageItemResponseDTO getPostWithDetail(String postId) { return toPageItemDTO(post); } + // ReportService 등에서 사용할 수 있도록 ObjectId로 단건 상세 조회 (Optional) + public Optional getPostDetailById(ObjectId postId) { + return postRepository.findByIdAndNotDeleted(postId) + .map(post -> { + ObjectId userId = SecurityUtils.getCurrentUserId(); + increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 + return toPageItemDTO(post); + }); + } + // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { UserProfile userProfile = resolveUserProfile(post); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java index 17d1ba2c..1551ec8d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java @@ -6,24 +6,19 @@ import lombok.Getter; @Getter - -public class ReportListResponseDto extends PostDetailResponseDTO { - +public class ReportListResponseDto { + private final PostDetailResponseDTO post; private final ReportInfo reportInfo; @Builder - public ReportListResponseDto(PostDetailResponseDTO baseDTO, ReportInfo reportInfo) { - super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), - baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), - baseDTO.getCommentCount(), baseDTO.getUserInfo()); + public ReportListResponseDto(PostDetailResponseDTO post, ReportInfo reportInfo) { + this.post = post; this.reportInfo = reportInfo; } - - public static ReportListResponseDto from(PostDetailResponseDTO base, ReportInfo reportInfo) { + public static ReportListResponseDto from(PostDetailResponseDTO post, ReportInfo reportInfo) { return ReportListResponseDto.builder() - .baseDTO(base) + .post(post) .reportInfo(reportInfo) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java index ba70583d..f0a225bc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportedPostDetailResponseDTO.java @@ -5,22 +5,20 @@ import lombok.Getter; @Getter -public class ReportedPostDetailResponseDTO extends PostDetailResponseDTO { +public class ReportedPostDetailResponseDTO { + private final PostDetailResponseDTO post; private final boolean isReported; @Builder - public ReportedPostDetailResponseDTO(PostDetailResponseDTO baseDTO, boolean isReported) { - super(baseDTO.getUserId(), baseDTO.get_id(), baseDTO.getTitle(), baseDTO.getContent(), baseDTO.getNickname(), - baseDTO.getPostCategory(), baseDTO.getUserImageUrl(), baseDTO.getPostImageUrl(), baseDTO.isAnonymous(), - baseDTO.getLikeCount(), baseDTO.getScrapCount(), baseDTO.getHits(), baseDTO.getCreatedAt(), - baseDTO.getCommentCount(), baseDTO.getUserInfo()); + public ReportedPostDetailResponseDTO(PostDetailResponseDTO post, boolean isReported) { + this.post = post; this.isReported = isReported; } - public static ReportedPostDetailResponseDTO from(boolean isReported, PostDetailResponseDTO postDetail) { + public static ReportedPostDetailResponseDTO from(PostDetailResponseDTO post, boolean isReported) { return ReportedPostDetailResponseDTO.builder() + .post(post) .isReported(isReported) - .baseDTO(postDetail) .build(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index e4b6fe7f..95421b39 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -40,6 +40,7 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; @Service @RequiredArgsConstructor @@ -322,17 +323,17 @@ private Optional getReportedPostDetail(ReportInfo reportI ObjectId entityId = new ObjectId(reportInfo.getReportedEntityId()); return switch (reportInfo.getEntityType()) { - case POST -> postService.getPostDetailById(entityId) // ✅ PostService 활용 - .map(postDTO -> ReportListResponseDto.from(postDTO, reportInfo)); + case POST -> postService.getPostDetailById(entityId) + .map(pageItem -> ReportListResponseDto.from(pageItem.getPost(), reportInfo)); case COMMENT -> commentRepository.findById(entityId) .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) - .map(postDTO -> ReportListResponseDto.from(postDTO, reportInfo))); + .map(pageItem -> ReportListResponseDto.from(pageItem.getPost(), reportInfo))); case REPLY -> replyCommentRepository.findById(entityId) .flatMap(reply -> commentRepository.findById(reply.getCommentId()) .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) - .map(postDTO -> ReportListResponseDto.from(postDTO, reportInfo)))); + .map(pageItem -> ReportListResponseDto.from(pageItem.getPost(), reportInfo)))); default -> Optional.empty(); }; @@ -348,11 +349,10 @@ public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, St throw new NotFoundException("해당 신고 대상이 존재하지 않습니다. 신고 ID: " + reportedEntityId); } PostDetailResponseDTO postDetailResponse = postService.getPostDetailById(entityId) + .map(PostPageItemResponseDTO::getPost) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); boolean isReported = entityId.equals(reportTargetId); - - - return ReportedPostDetailResponseDTO.from(isReported, postDetailResponse); + return ReportedPostDetailResponseDTO.from(postDetailResponse, isReported); } From 977d6802b973de81f2fa9f196f6ad9ece946cb49 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 10 Jul 2025 00:01:01 +0900 Subject: [PATCH 0806/1002] =?UTF-8?q?refactor:=20dto=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/PostServiceTest.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java index 884f9ab3..b2033d7f 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java @@ -15,6 +15,7 @@ import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.dto.request.*; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; @@ -164,8 +165,9 @@ void getAllPosts_success() { List posts = new ArrayList<>(); Page page = new PageImpl<>(posts); given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any())).willReturn(page); - PostPageResponse response = postService.getAllPosts(PostCategory.COMMUNICATION, 0); + var response = postService.getAllPosts(PostCategory.COMMUNICATION, 0); assertThat(response).isNotNull(); + assertThat(response.getContents()).isInstanceOf(List.class); } @Test @@ -183,15 +185,16 @@ void getPostWithDetail_success() { given(userRepository.findById(any())).willReturn(Optional.of(UserEntity.builder().nickname("닉네임").profileImageUrl("url").build())); // When - PostDetailResponseDTO response = postService.getPostWithDetail(postId); + PostPageItemResponseDTO response = postService.getPostWithDetail(postId); // Then assertThat(response).isNotNull(); + assertThat(response.getPost()).isNotNull(); } @Test void softDeletePost_success() { - // Given + // Given- String postId = new ObjectId().toString(); PostEntity post = createPostEntity(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); @@ -231,16 +234,17 @@ void searchPosts_success() { @Test void getTop3BestPosts_success() { given(redisBestService.getBests()).willReturn(new HashMap<>()); - List result = postService.getTop3BestPosts(); + var result = postService.getTop3BestPosts(); assertThat(result).isNotNull(); + assertThat(result).isInstanceOf(List.class); } @Test void getBestPosts_success() { - Page bests = new PageImpl<>(new ArrayList<>()); - given(bestRepository.findAll(any(PageRequest.class))).willReturn(bests); - PostPageResponse response = postService.getBestPosts(0); - assertThat(response).isNotNull(); + given(bestRepository.findAll(any(PageRequest.class))).willReturn(new PageImpl<>(new ArrayList<>())); + var result = postService.getBestPosts(0); + assertThat(result).isNotNull(); + assertThat(result.getContents()).isInstanceOf(List.class); } // --- 리플렉션 기반 DTO 생성 유틸리티 --- From 80c4684c092bd69b3a6bbaf9c6df37ffe5b56e52 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 10 Jul 2025 00:34:50 +0900 Subject: [PATCH 0807/1002] =?UTF-8?q?refactor:=20=EB=B6=80=EA=B0=80/?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=20=EB=A1=9C=EC=A7=81=20SeperatedPostService?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20=EC=9D=91?= =?UTF-8?q?=EC=A7=91=EB=8F=84=20=ED=96=A5=EC=83=81=20=EB=B0=8F=20=EA=B2=B0?= =?UTF-8?q?=ED=95=A9=EB=8F=84=20=EA=B0=90=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PostService의 이미지, 조회수, 베스트, 투표 등 부가 기능/서브 로직을 SeperatedPostService로 분리 - PostService는 핵심 비즈니스 흐름/응답 조립에만 집중하도록 응집도(cohesion) 강화 - 부가 기능은 별도 서비스로 위임하여 결합도(coupling) 낮춤 - 테스트 코드도 DI 구조에 맞게 리팩토링 --- .../domain/post/service/PostService.java | 162 ++++-------------- .../post/service/SeperatedPostService.java | 145 ++++++++++++++++ .../codin/domain/post/PostServiceTest.java | 30 ++-- 3 files changed, 190 insertions(+), 147 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 52c6c424..a4c70e1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -58,10 +58,11 @@ public class PostService { private final HitsService hitsService; private final RedisBestService redisBestService; private final BlockService blockService; + private final SeperatedPostService seperatedPostService; public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); - List imageUrls = handleImageUpload(postImages); + List imageUrls = seperatedPostService.handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -95,7 +96,7 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request .orElseThrow(()->new NotFoundException("해당 게시물 없음")); validateUserAndPost(post); - List imageUrls = handleImageUpload(postImages); + List imageUrls = seperatedPostService.handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); @@ -151,12 +152,12 @@ public List getPostListResponseDtos(List po .toList(); } - // 게시물 상세 조회 (기존코드) + // 게시물 상세 조회 public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 + seperatedPostService.increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 return toPageItemDTO(post); } @@ -165,29 +166,11 @@ public Optional getPostDetailById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) .map(post -> { ObjectId userId = SecurityUtils.getCurrentUserId(); - increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 + seperatedPostService.increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 return toPageItemDTO(post); }); } - // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) - private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { - UserProfile userProfile = resolveUserProfile(post); - int likeCount = getLikeCount(post); - int scrapCount = getScrapCount(post); - int hitsCount = getHitsCount(post); - int commentCount = post.getCommentCount(); - ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); - if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = getPollInfo(post, userId); - return PostPageItemResponseDTO.of(postDTO, pollInfo); - } else { - return PostPageItemResponseDTO.of(postDTO, null); - } - } - public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); @@ -204,7 +187,7 @@ public void deletePostImage(String postId, String imageUrl) { .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); validateUserAndPost(post); - deletePostImageInternal(post, imageUrl); + seperatedPostService.deletePostImageInternal(post, imageUrl); } public PostPageResponse searchPosts(String keyword, int pageNumber) { @@ -218,16 +201,35 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { } public List getTop3BestPosts() { - List bestPosts = getTop3BestPostsInternal(); // [BestService] - 베스트 게시물 조회 위임 + List bestPosts = seperatedPostService.getTop3BestPostsInternal(); // [BestService] - 베스트 게시물 조회 위임 log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); } public PostPageResponse getBestPosts(int pageNumber) { - Page page = getBestPostsInternal(pageNumber); // [BestService] - 베스트 게시물 페이지 조회 위임 + Page page = seperatedPostService.getBestPostsInternal(pageNumber); // [BestService] - 베스트 게시물 페이지 조회 위임 return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } + + // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) + private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { + UserProfile userProfile = resolveUserProfile(post); + int likeCount = seperatedPostService.getLikeCount(post); + int scrapCount = seperatedPostService.getScrapCount(post); + int hitsCount = seperatedPostService.getHitsCount(post); + int commentCount = post.getCommentCount(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + if (post.getPostCategory() == PostCategory.POLL) { + PollInfoResponseDTO pollInfo = seperatedPostService.getPollInfo(post, userId); + return PostPageItemResponseDTO.of(postDTO, pollInfo); + } else { + return PostPageItemResponseDTO.of(postDTO, null); + } + } + // [유저 프로필] - 닉네임/이미지 결정 private UserProfile resolveUserProfile(PostEntity post) { UserEntity user = userRepository.findById(post.getUserId()) @@ -249,6 +251,7 @@ private UserProfile resolveUserProfile(PostEntity post) { return new UserProfile(nickname, userImageUrl); } + // [유저 프로필] - 게시물에 대한 유저정보 추출 public UserInfoResponseDTO getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ return UserInfoResponseDTO.of( likeService.isLiked(LikeType.POST, postId, currentUserId), @@ -257,109 +260,7 @@ public UserInfoResponseDTO getUserInfoAboutPost(ObjectId currentUserId, ObjectId ); } - // ===================== 도메인별 부가 기능/서브 로직 (분리 예정) ===================== - - // [ImageService] - 이미지 업로드 처리 - private List handleImageUpload(List postImages) { - return s3Service.handleImageUpload(postImages); - } - - // [ImageService] - 게시글 이미지 삭제 처리 - private void deletePostImageInternal(PostEntity post, String imageUrl) { - if (!post.getPostImageUrls().contains(imageUrl)) { - log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); - } - try { - s3Service.deleteFile(imageUrl); - post.removePostImage(imageUrl); - postRepository.save(post); - log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - } catch (Exception e) { - log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); - throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); - } - } - - // [HitsService] - 조회수 증가 처리 - private void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { - if (!hitsService.validateHits(post.get_id(), userId)) { - hitsService.addHits(post.get_id(), userId); - log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); - } - } - - // [BestService] - Top 3 베스트 게시물 조회 - private List getTop3BestPostsInternal() { - Map posts = redisBestService.getBests(); - return posts.entrySet().stream() - .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) - .orElseGet(() -> { - redisBestService.deleteBest(post.getKey()); - return null; - })) - .filter(Objects::nonNull) - .toList(); - } - - // [BestService] - 베스트 게시물 페이지 조회 - private Page getBestPostsInternal(int pageNumber) { - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page bests = bestRepository.findAll(pageRequest); - return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); - } - - // [BestService] - 베스트 점수 적용 처리 - private void applyBestScoreIfNeeded(PostEntity post) { - redisBestService.applyBestScore(1, post.get_id()); - log.info("베스트 점수 적용. PostId: {}", post.get_id()); - } - - // [CommentService/ReplyCommentService] - 댓글 수 증가 - private void increaseCommentCount(PostEntity post) { - post.plusCommentCount(); - postRepository.save(post); - log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); - } - - // [PollService] - 투표 관련 처리 (예시) - private void processPollIfNeeded(PostEntity post) { - // 투표 게시글일 경우 PollService와 협력하여 투표 생성/집계 등 처리 - // 예시: pollService.createPoll(...) - } - - // [좋아요] - 게시글 좋아요 수 조회 - private int getLikeCount(PostEntity post) { - return likeService.getLikeCount(LikeType.POST, post.get_id()); - } - - // [스크랩] - 게시글 스크랩 수 조회 - private int getScrapCount(PostEntity post) { - return scrapService.getScrapCount(post.get_id()); - } - - // [조회수] - 게시글 조회수 조회 - private int getHitsCount(PostEntity post) { - return hitsService.getHitsCount(post.get_id()); - } - - // [투표] - 투표 게시글 PollInfo 생성 - private PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); - long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); - List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) - .map(PollVoteEntity::getSelectedOptions) - .orElse(Collections.emptyList()); - boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); - boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); - return PollInfoResponseDTO.of( - poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), - poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); - } - - // [내부 클래스] - 유저 프로필 정보 + // [내부 클래스] - 유저 프로필 정보 private static class UserProfile { final String nickname; final String userImageUrl; @@ -368,5 +269,8 @@ private static class UserProfile { this.userImageUrl = userImageUrl; } } + + // ===================== 도메인별 부가 기능/서브 로직 (임시 분리 - SeperatedPostService) ===================== + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java new file mode 100644 index 00000000..eaf7996c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java @@ -0,0 +1,145 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.infra.redis.service.RedisBestService; +import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.infra.s3.exception.ImageRemoveException; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SeperatedPostService { + private final S3Service s3Service; + private final PostRepository postRepository; + private final HitsService hitsService; + private final RedisBestService redisBestService; + private final BestRepository bestRepository; + private final ScrapService scrapService; + private final LikeService likeService; + private final PollRepository pollRepository; + private final PollVoteRepository pollVoteRepository; + + // [ImageService] - 이미지 업로드 처리 + public List handleImageUpload(List postImages) { + return s3Service.handleImageUpload(postImages); + } + + // [ImageService] - 게시글 이미지 삭제 처리 + public void deletePostImageInternal(PostEntity post, String imageUrl) { + if (!post.getPostImageUrls().contains(imageUrl)) { + log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); + } + try { + s3Service.deleteFile(imageUrl); + post.removePostImage(imageUrl); + postRepository.save(post); + log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + } catch (Exception e) { + log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); + throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); + } + } + + // [HitsService] - 조회수 증가 처리 + public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { + if (!hitsService.validateHits(post.get_id(), userId)) { + hitsService.addHits(post.get_id(), userId); + log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); + } + } + + // [BestService] - Top 3 베스트 게시물 조회 + public List getTop3BestPostsInternal() { + Map posts = redisBestService.getBests(); + return posts.entrySet().stream() + .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) + .orElseGet(() -> { + redisBestService.deleteBest(post.getKey()); + return null; + })) + .filter(Objects::nonNull) + .toList(); + } + + // [BestService] - 베스트 게시물 페이지 조회 + public Page getBestPostsInternal(int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page bests = bestRepository.findAll(pageRequest); + return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); + } + + // [BestService] - 베스트 점수 적용 처리 + public void applyBestScoreIfNeeded(PostEntity post) { + redisBestService.applyBestScore(1, post.get_id()); + log.info("베스트 점수 적용. PostId: {}", post.get_id()); + } + + // [CommentService/ReplyCommentService] - 댓글 수 증가 + public void increaseCommentCount(PostEntity post) { + post.plusCommentCount(); + postRepository.save(post); + log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); + } + + // [PollService] - 투표 관련 처리 (예시) + public void processPollIfNeeded(PostEntity post) { + // 투표 게시글일 경우 PollService와 협력하여 투표 생성/집계 등 처리 + // 예시: pollService.createPoll(...) + } + + // [좋아요] - 게시글 좋아요 수 조회 + public int getLikeCount(PostEntity post) { + return likeService.getLikeCount(LikeType.POST, post.get_id()); + } + + // [스크랩] - 게시글 스크랩 수 조회 + public int getScrapCount(PostEntity post) { + return scrapService.getScrapCount(post.get_id()); + } + + // [조회수] - 게시글 조회수 조회 + public int getHitsCount(PostEntity post) { + return hitsService.getHitsCount(post.get_id()); + } + + // [투표] - 투표 게시글 PollInfo 생성 + public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); + long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); + List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) + .map(PollVoteEntity::getSelectedOptions) + .orElse(Collections.emptyList()); + boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); + boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); + return PollInfoResponseDTO.of( + poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), + poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + } +} diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java index b2033d7f..d2708a6c 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java @@ -22,6 +22,7 @@ import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostService; +import inu.codin.codin.domain.post.service.SeperatedPostService; import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; @@ -56,6 +57,7 @@ class PostServiceTest { @Mock private HitsService hitsService; @Mock private RedisBestService redisBestService; @Mock private BlockService blockService; + @Mock private SeperatedPostService seperatedPostService; private static AutoCloseable securityUtilsMock; @BeforeEach @@ -76,7 +78,7 @@ void tearDown() throws Exception { ObjectId userId = new ObjectId(); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); - given(s3Service.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willAnswer(inv -> { PostEntity entity = inv.getArgument(0); java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); @@ -97,7 +99,7 @@ void tearDown() throws Exception { List images = new ArrayList<>(); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); - given(s3Service.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); // When & Then assertThatThrownBy(() -> postService.createPost(dto, images)).isInstanceOf(JwtException.class); } @@ -111,9 +113,8 @@ void updatePostContent_success() throws Exception { PostEntity post = createPostEntity(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - given(s3Service.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willReturn(post); - // When/Then assertThatCode(() -> postService.updatePostContent(postId, dto, images)).doesNotThrowAnyException(); } @@ -125,7 +126,6 @@ void updatePostContent_notFound() throws Exception { PostContentUpdateRequestDTO dto = createPostContentUpdateRequestDTO("수정내용"); List images = List.of(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When/Then assertThatThrownBy(() -> postService.updatePostContent(postId, dto, images)) .isInstanceOf(NotFoundException.class); @@ -140,7 +140,6 @@ void updatePostAnonymous_success() throws Exception { given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); - // When/Then assertThatCode(() -> postService.updatePostAnonymous(postId, dto)).doesNotThrowAnyException(); } @@ -154,7 +153,6 @@ void updatePostStatus_success() throws Exception { given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); - // When/Then assertThatCode(() -> postService.updatePostStatus(postId, dto)).doesNotThrowAnyException(); } @@ -178,15 +176,13 @@ void getPostWithDetail_success() { given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - given(hitsService.validateHits(any(), any())).willReturn(true); - given(likeService.getLikeCount(any(), any())).willReturn(0); - given(scrapService.getScrapCount(any())).willReturn(0); - given(hitsService.getHitsCount(any())).willReturn(0); + doNothing().when(seperatedPostService).increaseHitsIfNeeded(any(), any()); + given(seperatedPostService.getLikeCount(any())).willReturn(0); + given(seperatedPostService.getScrapCount(any())).willReturn(0); + given(seperatedPostService.getHitsCount(any())).willReturn(0); given(userRepository.findById(any())).willReturn(Optional.of(UserEntity.builder().nickname("닉네임").profileImageUrl("url").build())); - // When PostPageItemResponseDTO response = postService.getPostWithDetail(postId); - // Then assertThat(response).isNotNull(); assertThat(response.getPost()).isNotNull(); @@ -200,7 +196,6 @@ void softDeletePost_success() { given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); - // When/Then assertThatCode(() -> postService.softDeletePost(postId)).doesNotThrowAnyException(); } @@ -214,8 +209,7 @@ void deletePostImage_success() throws Exception { PostEntity post = PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).postImageUrls(imageList).build(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - willDoNothing().given(s3Service).deleteFile(anyString()); - given(postRepository.save(any())).willReturn(post); + doNothing().when(seperatedPostService).deletePostImageInternal(any(), any()); // When/Then assertThatCode(() -> postService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); @@ -233,7 +227,7 @@ void searchPosts_success() { @Test void getTop3BestPosts_success() { - given(redisBestService.getBests()).willReturn(new HashMap<>()); + given(seperatedPostService.getTop3BestPostsInternal()).willReturn(new ArrayList<>()); var result = postService.getTop3BestPosts(); assertThat(result).isNotNull(); assertThat(result).isInstanceOf(List.class); @@ -241,7 +235,7 @@ void getTop3BestPosts_success() { @Test void getBestPosts_success() { - given(bestRepository.findAll(any(PageRequest.class))).willReturn(new PageImpl<>(new ArrayList<>())); + given(seperatedPostService.getBestPostsInternal(anyInt())).willReturn(new PageImpl<>(new ArrayList<>())); var result = postService.getBestPosts(0); assertThat(result).isNotNull(); assertThat(result.getContents()).isInstanceOf(List.class); From f27dbc6591ab3b57269392b097b6f273df9cc662 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 10:16:21 +0900 Subject: [PATCH 0808/1002] =?UTF-8?q?refactor:=20PostService=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20Static=20DTO=20->=20dto/UserProfile=20=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20(=EA=B8=B0=EC=A1=B4=20UserDto)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/service/CommentService.java | 18 +-- .../reply/service/ReplyCommentService.java | 18 +-- .../codin/domain/post/dto/UserProfile.java | 25 ++++ .../dto/response/PostDetailResponseDTO.java | 7 +- .../domain/post/dto/response/UserDto.java | 4 - .../domain/post/service/PostService.java | 119 ++++++++++-------- .../post/service/SeperatedPostService.java | 8 +- .../codin/domain/post/CommentServiceTest.java | 1 - .../domain/hits/service/HitsServiceTest.java | 79 ++++++++++++ 9 files changed, 199 insertions(+), 80 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 428b5492..58c1a3b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -12,7 +12,7 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.dto.response.UserDto; +import inu.codin.codin.domain.post.dto.UserProfile; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -93,7 +93,7 @@ public List getCommentsByPostId(String id) { String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); - Map userMap = userRepository.findAllById( + Map userMap = userRepository.findAllById( comments.stream() .map(CommentEntity::getUserId) .distinct() @@ -101,25 +101,25 @@ public List getCommentsByPostId(String id) { ).stream() .collect(Collectors.toMap( UserEntity::get_id, - user -> new UserDto(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) + user -> new UserProfile(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) )); return comments.stream() .map(comment -> { - UserDto userDto = userMap.get(comment.getUserId()); + UserProfile userProfile = userMap.get(comment.getUserId()); int anonNum = post.getAnonymous().getAnonNumber(comment.getUserId().toString()); String nickname; String userImageUrl; - if (userDto.isDeleted()){ - nickname = userMap.get(comment.getUserId()).nickname(); - userImageUrl = userMap.get(comment.getUserId()).imageUrl(); + if (userProfile.isDeleted()){ + nickname = userMap.get(comment.getUserId()).getNickname(); + userImageUrl = userMap.get(comment.getUserId()).getImageUrl(); } else { nickname = comment.isAnonymous()? anonNum==0? "글쓴이" : "익명" + anonNum - : userMap.get(comment.getUserId()).nickname(); - userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).imageUrl(); + : userMap.get(comment.getUserId()).getNickname(); + userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).getImageUrl(); } return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 91adfc63..96cd259a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -12,7 +12,7 @@ import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.dto.response.UserDto; +import inu.codin.codin.domain.post.dto.UserProfile; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; @@ -94,30 +94,30 @@ public List getRepliesByCommentId(PostAnonymous postAnonymou List replies = replyCommentRepository.findByCommentId(commentId); String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); - Map userMap = userRepository.findAllById( + Map userMap = userRepository.findAllById( replies.stream() .map(ReplyCommentEntity::getUserId).distinct().toList() ).stream() .collect(Collectors.toMap( UserEntity::get_id, - user -> new UserDto(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) + user -> new UserProfile(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) )); return replies.stream() .map(reply -> { - UserDto userDto = userMap.get(reply.getUserId()); + UserProfile userProfile = userMap.get(reply.getUserId()); int anonNum = postAnonymous.getAnonNumber(reply.getUserId().toString()); String nickname; String userImageUrl; - if (userDto.isDeleted()){ - nickname = userMap.get(reply.getUserId()).nickname(); - userImageUrl = userMap.get(reply.getUserId()).imageUrl(); + if (userProfile.isDeleted()){ + nickname = userMap.get(reply.getUserId()).getNickname(); + userImageUrl = userMap.get(reply.getUserId()).getImageUrl(); } else { nickname = reply.isAnonymous()? anonNum==0? "글쓴이" : "익명"+anonNum - : userMap.get(reply.getUserId()).nickname(); - userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); + : userMap.get(reply.getUserId()).getNickname(); + userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).getImageUrl(); } return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), likeService.getLikeCount(LikeType.REPLY, reply.get_id()), // 대댓글 좋아요 수 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java new file mode 100644 index 00000000..defc5aea --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.post.dto; + +import lombok.Getter; + +@Getter +public class UserProfile { + private String nickname; + private String imageUrl; + private boolean isDeleted; + + public UserProfile(String nickname, String imageUrl, boolean isDeleted) { + this.nickname = nickname; + this.imageUrl = imageUrl; + this.isDeleted = isDeleted; + } + + public UserProfile(String nickname, String imageUrl) { + this.nickname = nickname; + this.imageUrl = imageUrl; + } + + public static UserProfile of(String nickname, String imageUrl) { + return new UserProfile(nickname, imageUrl); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index 5a99325f..c016addb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.domain.post.dto.UserProfile; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import io.swagger.v3.oas.annotations.media.Schema; @@ -87,15 +88,15 @@ private PostDetailResponseDTO(String userId, String _id, String title, String co this.userInfo = userInfo; } - public static PostDetailResponseDTO of(PostEntity post, String nickname, String userImageUrl, int likeCount, int scrapCount, int hitsCount, int commentCount ,UserInfoResponseDTO userInfo) { + public static PostDetailResponseDTO of(PostEntity post, UserProfile userProfile, int likeCount, int scrapCount, int hitsCount, int commentCount , UserInfoResponseDTO userInfo) { return PostDetailResponseDTO.builder() .userId(post.getUserId().toString()) ._id(post.get_id().toString()) .title(post.getTitle()) .content(post.getContent()) - .nickname(nickname) + .nickname(userProfile.getNickname()) .postCategory(post.getPostCategory()) - .userImageUrl(userImageUrl) + .userImageUrl(userProfile.getImageUrl()) .postImageUrl(post.getPostImageUrls()) .isAnonymous(post.isAnonymous()) .likeCount(likeCount) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java deleted file mode 100644 index 022b8ad8..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package inu.codin.codin.domain.post.dto.response; - -public record UserDto(String nickname, String imageUrl, boolean isDeleted) { -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index a4c70e1d..dc94d767 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -14,6 +14,7 @@ import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.dto.UserProfile; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; @@ -47,19 +48,19 @@ @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; - private final BestRepository bestRepository; private final UserRepository userRepository; - private final PollRepository pollRepository; - private final PollVoteRepository pollVoteRepository; - - private final S3Service s3Service; - private final LikeService likeService; - private final ScrapService scrapService; - private final HitsService hitsService; - private final RedisBestService redisBestService; private final BlockService blockService; private final SeperatedPostService seperatedPostService; + private final LikeService likeService; + private final ScrapService scrapService; + private final S3Service s3Service; + /** + * 게시글 생성 + * @param postCreateRequestDTO 게시글 생성 요청 DTO + * @param postImages 이미지 파일 리스트 + * @return 생성된 게시글의 postId를 담은 Map (불변) + */ public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); List imageUrls = seperatedPostService.handleImageUpload(postImages); @@ -75,11 +76,9 @@ public Map createPost(PostCreateRequestDTO postCreateRequestDTO, .userId(userId) .title(postCreateRequestDTO.getTitle()) .content(postCreateRequestDTO.getContent()) - //이미지 Url List 저장 .postImageUrls(imageUrls) .isAnonymous(postCreateRequestDTO.isAnonymous()) .postCategory(postCreateRequestDTO.getPostCategory()) - //Default Status = Active .postStatus(PostStatus.ACTIVE) .build(); postRepository.save(postEntity); @@ -89,35 +88,39 @@ public Map createPost(PostCreateRequestDTO postCreateRequestDTO, return response; } + /** + * 게시글 내용 및 이미지 수정 + */ public void updatePostContent(String postId, PostContentUpdateRequestDTO requestDTO, List postImages) { log.info("게시물 수정 시작. PostId: {}", postId); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); validateUserAndPost(post); - List imageUrls = seperatedPostService.handleImageUpload(postImages); - post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); log.info("게시물 수정 성공. PostId: {}", postId); } + /** + * 게시글 익명 설정 수정 + */ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); validateUserAndPost(post); - post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); log.info("게시물 익명 수정 성공. PostId: {}", postId); } + /** + * 게시글 상태 수정 + */ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(()->new NotFoundException("해당 게시물 없음")); + .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); validateUserAndPost(post); - post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); @@ -132,82 +135,98 @@ private void validateUserAndPost(PostEntity post) { SecurityUtils.validateUser(post.getUserId()); } - // 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 + /** + * 카테고리별 삭제되지 않은 게시물 목록 조회 + * @return PostPageResponse (불변 리스트) + */ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { - // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page; - page = postRepository.getPostsByCategoryWithBlockedUsers(postCategory.toString(), blockedUsersId ,pageRequest); - + Page page = postRepository.getPostsByCategoryWithBlockedUsers(postCategory.toString(), blockedUsersId, pageRequest); log.info("모든 글 반환 성공 Category: {}, Page: {}", postCategory, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - // 게시물 리스트 가져오기 + /** + * 게시물 리스트 DTO 변환 (불변 리스트) + */ public List getPostListResponseDtos(List posts) { return posts.stream() .map(this::toPageItemDTO) .toList(); } - // 게시물 상세 조회 + /** + * 게시물 상세 조회 + */ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); ObjectId userId = SecurityUtils.getCurrentUserId(); - seperatedPostService.increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 + seperatedPostService.increaseHitsIfNeeded(post, userId); return toPageItemDTO(post); } - // ReportService 등에서 사용할 수 있도록 ObjectId로 단건 상세 조회 (Optional) + /** + * Optional로 게시물 상세 조회 (null-safe) + * 사용 예시: getPostDetailById(id).ifPresent(dto -> ...) + */ public Optional getPostDetailById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) .map(post -> { ObjectId userId = SecurityUtils.getCurrentUserId(); - seperatedPostService.increaseHitsIfNeeded(post, userId); // [HitsService] - 조회수 증가 위임 + seperatedPostService.increaseHitsIfNeeded(post, userId); return toPageItemDTO(post); }); } + /** + * 게시물 소프트 삭제 + */ public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(()-> new NotFoundException("게시물을 찾을 수 없음.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없음. id=" + postId)); validateUserAndPost(post); - post.delete(); - log.info("게시물 안전 삭제. PostId: {}", postId); postRepository.save(post); } + /** + * 게시물 이미지 삭제 + */ public void deletePostImage(String postId, String imageUrl) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); validateUserAndPost(post); - seperatedPostService.deletePostImageInternal(post, imageUrl); } + /** + * 키워드 기반 게시물 검색 + */ public PostPageResponse searchPosts(String keyword, int pageNumber) { - // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } + /** + * Top 3 베스트 게시물 조회 (불변 리스트) + */ public List getTop3BestPosts() { - List bestPosts = seperatedPostService.getTop3BestPostsInternal(); // [BestService] - 베스트 게시물 조회 위임 + List bestPosts = seperatedPostService.getTop3BestPostsInternal(); log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); } + /** + * 베스트 게시물 페이지 조회 + */ public PostPageResponse getBestPosts(int pageNumber) { - Page page = seperatedPostService.getBestPostsInternal(pageNumber); // [BestService] - 베스트 게시물 페이지 조회 위임 + Page page = seperatedPostService.getBestPostsInternal(pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } @@ -221,7 +240,7 @@ private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { int commentCount = post.getCommentCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile.nickname, userProfile.userImageUrl, likeCount, scrapCount, hitsCount, commentCount, userInfo); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile, likeCount, scrapCount, hitsCount, commentCount, userInfo); if (post.getPostCategory() == PostCategory.POLL) { PollInfoResponseDTO pollInfo = seperatedPostService.getPollInfo(post, userId); return PostPageItemResponseDTO.of(postDTO, pollInfo); @@ -260,15 +279,15 @@ public UserInfoResponseDTO getUserInfoAboutPost(ObjectId currentUserId, ObjectId ); } - // [내부 클래스] - 유저 프로필 정보 - private static class UserProfile { - final String nickname; - final String userImageUrl; - UserProfile(String nickname, String userImageUrl) { - this.nickname = nickname; - this.userImageUrl = userImageUrl; - } - } +// // [내부 클래스] - 유저 프로필 정보 +// private static class UserProfile { +// final String nickname; +// final String userImageUrl; +// UserProfile(String nickname, String userImageUrl) { +// this.nickname = nickname; +// this.userImageUrl = userImageUrl; +// } +// } // ===================== 도메인별 부가 기능/서브 로직 (임시 분리 - SeperatedPostService) ===================== diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java index eaf7996c..cad713e2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java @@ -113,22 +113,22 @@ public void processPollIfNeeded(PostEntity post) { // 예시: pollService.createPoll(...) } - // [좋아요] - 게시글 좋아요 수 조회 + // [likeService] - 게시글 좋아요 수 조회 public int getLikeCount(PostEntity post) { return likeService.getLikeCount(LikeType.POST, post.get_id()); } - // [스크랩] - 게시글 스크랩 수 조회 + // [ScrapService] - 게시글 스크랩 수 조회 public int getScrapCount(PostEntity post) { return scrapService.getScrapCount(post.get_id()); } - // [조회수] - 게시글 조회수 조회 + // [HitsService] - 게시글 조회수 조회 public int getHitsCount(PostEntity post) { return hitsService.getHitsCount(post.get_id()); } - // [투표] - 투표 게시글 PollInfo 생성 + // [PollService] - 투표 게시글 PollInfo 생성 public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { PollEntity poll = pollRepository.findByPostId(post.get_id()) .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java index 2d555df4..41dbcab6 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java @@ -11,7 +11,6 @@ import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java new file mode 100644 index 00000000..e2bc6873 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java @@ -0,0 +1,79 @@ +package inu.codin.codin.domain.post.domain.hits.service; + +import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; +import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisHitsService; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class HitsServiceTest { + @InjectMocks + private HitsService hitsService; + @Mock private RedisHitsService redisHitsService; + @Mock private RedisHealthChecker redisHealthChecker; + @Mock private HitsRepository hitsRepository; + + @Test + void 게시글_조회수_추가_성공() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + willDoNothing().given(redisHitsService).addHits(postId); + given(hitsRepository.save(any())).willAnswer(inv -> inv.getArgument(0)); + // When + hitsService.addHits(postId, userId); + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(HitsEntity.class); + verify(hitsRepository).save(captor.capture()); + HitsEntity saved = captor.getValue(); + assertThat(saved.getPostId()).isEqualTo(postId); + assertThat(saved.getUserId()).isEqualTo(userId); + } + + @Test + void 게시글_조회여부_판단_성공() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + given(hitsRepository.existsByPostIdAndUserId(postId, userId)).willReturn(true); + // When + boolean result = hitsService.validateHits(postId, userId); + // Then + assertThat(result).isTrue(); + } + + @Test + void 게시글_조회수_반환_캐시있음() { + // Given + ObjectId postId = new ObjectId(); + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn("5"); + // When + int result = hitsService.getHitsCount(postId); + // Then + assertThat(result).isEqualTo(5); + } + + @Test + void 게시글_조회수_반환_캐시없음_DB조회() { + // Given + ObjectId postId = new ObjectId(); + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn(null); + given(hitsRepository.countAllByPostId(postId)).willReturn(7); + // When + int result = hitsService.getHitsCount(postId); + // Then + assertThat(result).isEqualTo(7); + } +} \ No newline at end of file From 741573f7d7b08e46757a73eac51d16313e8fcd7d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 14:19:32 +0900 Subject: [PATCH 0809/1002] =?UTF-8?q?refactor:UserProfile=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20UserDto=20=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - post, comment, reply 의 user 정보 mapping --- .../codin/codin/domain/post/dto/UserDto.java | 120 ++++++++++++++++++ ...UserInfoResponseDTO.java => UserInfo.java} | 10 +- .../codin/domain/post/dto/UserProfile.java | 25 ---- .../dto/response/PostDetailResponseDTO.java | 9 +- .../domain/post/service/PostService.java | 53 ++------ 5 files changed, 140 insertions(+), 77 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java rename codin-core/src/main/java/inu/codin/codin/domain/post/dto/{response/UserInfoResponseDTO.java => UserInfo.java} (56%) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java new file mode 100644 index 00000000..ac54e507 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java @@ -0,0 +1,120 @@ +package inu.codin.codin.domain.post.dto; + +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.user.entity.UserEntity; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class UserDto { + private final String nickname; + private final String imageUrl; + private final boolean deleted; + + private UserDto(String nickname, String imageUrl, boolean deleted) { + this.nickname = nickname; + this.imageUrl = imageUrl; + this.deleted = deleted; + } + + /** + * 게시물용 사용자 DTO 생성 + * + * @param post 게시물 엔티티 + * @param user 사용자 엔티티 + * @param defaultProfileImageUrl 기본 프로필 이미지 URL + * @return 게시물용 사용자 DTO + */ + public static UserDto ofPost(PostEntity post, UserEntity user, String defaultProfileImageUrl) { + if (user.getDeletedAt() != null) { + return ofDeletedUser(user); + } + + if (post.isAnonymous()) { + return UserDto.builder() + .nickname("익명") + .imageUrl(defaultProfileImageUrl) + .deleted(false) + .build(); + } + + return ofNormalUser(user); + } + + /** + * 댓글용 사용자 DTO 생성 + * + * @param comment 댓글 엔티티 + * @param user 사용자 엔티티 + * @param anonNum 익명 번호 + * @param defaultProfileImageUrl 기본 프로필 이미지 URL + * @return 댓글용 사용자 DTO + */ + public static UserDto ofComment(CommentEntity comment, UserEntity user, int anonNum, String defaultProfileImageUrl) { + if (user.getDeletedAt() != null) { + return ofDeletedUser(user); + } + + if (comment.isAnonymous()) { + String nickname = anonNum == 0 ? "글쓴이" : "익명" + anonNum; + return UserDto.builder() + .nickname(nickname) + .imageUrl(defaultProfileImageUrl) + .deleted(false) + .build(); + } + + return ofNormalUser(user); + } + + /** + * 댓글용 사용자 DTO 생성 + * + * @param reply 대댓글 엔티티 + * @param user 사용자 엔티티 + * @param anonNum 익명 번호 + * @param defaultProfileImageUrl 기본 프로필 이미지 URL + * @return 댓글용 사용자 DTO + */ + public static UserDto ofReply(ReplyCommentEntity reply, UserEntity user, int anonNum, String defaultProfileImageUrl) { + if (user.getDeletedAt() != null) { + return ofDeletedUser(user); + } + + if (reply.isAnonymous()) { + String nickname = (anonNum == 0) ? "글쓴이" : "익명" + anonNum; + return UserDto.builder() + .nickname(nickname) + .imageUrl(defaultProfileImageUrl) + .deleted(false) + .build(); + } + + return ofNormalUser(user); + } + + /** + * 삭제된 사용자 DTO 생성 + */ + public static UserDto ofDeletedUser(UserEntity user) { + return UserDto.builder() + .nickname(user.getNickname()) + .imageUrl(user.getProfileImageUrl()) + .deleted(true) + .build(); + } + + /** + * 일반 사용자 DTO 생성 + */ + public static UserDto ofNormalUser(UserEntity user) { + return UserDto.builder() + .nickname(user.getNickname()) + .imageUrl(user.getProfileImageUrl()) + .deleted(false) + .build(); + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java similarity index 56% rename from codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java index 06baa747..38df0b68 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/UserInfoResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java @@ -1,23 +1,23 @@ -package inu.codin.codin.domain.post.dto.response; +package inu.codin.codin.domain.post.dto; import lombok.Builder; import lombok.Getter; @Getter -public class UserInfoResponseDTO { +public class UserInfo { private final boolean isLike; private final boolean isScrap; private final boolean isMine; @Builder - private UserInfoResponseDTO(boolean isLike, boolean isScrap, boolean isMine) { + private UserInfo(boolean isLike, boolean isScrap, boolean isMine) { this.isLike = isLike; this.isScrap = isScrap; this.isMine = isMine; } - public static UserInfoResponseDTO of(boolean isLike, boolean isScrap, boolean isMine) { - return UserInfoResponseDTO.builder() + public static UserInfo of(boolean isLike, boolean isScrap, boolean isMine) { + return UserInfo.builder() .isLike(isLike) .isScrap(isScrap) .isMine(isMine) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java deleted file mode 100644 index defc5aea..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserProfile.java +++ /dev/null @@ -1,25 +0,0 @@ -package inu.codin.codin.domain.post.dto; - -import lombok.Getter; - -@Getter -public class UserProfile { - private String nickname; - private String imageUrl; - private boolean isDeleted; - - public UserProfile(String nickname, String imageUrl, boolean isDeleted) { - this.nickname = nickname; - this.imageUrl = imageUrl; - this.isDeleted = isDeleted; - } - - public UserProfile(String nickname, String imageUrl) { - this.nickname = nickname; - this.imageUrl = imageUrl; - } - - public static UserProfile of(String nickname, String imageUrl) { - return new UserProfile(nickname, imageUrl); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java index c016addb..12715b44 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostDetailResponseDTO.java @@ -1,7 +1,8 @@ package inu.codin.codin.domain.post.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; -import inu.codin.codin.domain.post.dto.UserProfile; +import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import io.swagger.v3.oas.annotations.media.Schema; @@ -66,11 +67,11 @@ public class PostDetailResponseDTO { private final LocalDateTime createdAt; @Schema(description = "해당 게시글에 대한 유저 반응 여부") - private final UserInfoResponseDTO userInfo; + private final UserInfo userInfo; @Builder private PostDetailResponseDTO(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List postImageUrl, - boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfoResponseDTO userInfo){ + boolean isAnonymous, int likeCount, int scrapCount, int hits, LocalDateTime createdAt, int commentCount, UserInfo userInfo){ this.userId = userId; this._id = _id; this.title = title; @@ -88,7 +89,7 @@ private PostDetailResponseDTO(String userId, String _id, String title, String co this.userInfo = userInfo; } - public static PostDetailResponseDTO of(PostEntity post, UserProfile userProfile, int likeCount, int scrapCount, int hitsCount, int commentCount , UserInfoResponseDTO userInfo) { + public static PostDetailResponseDTO of(PostEntity post, UserDto userProfile, int likeCount, int scrapCount, int hitsCount, int commentCount , UserInfo userInfo) { return PostDetailResponseDTO.builder() .userId(post.getUserId().toString()) ._id(post.get_id().toString()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index dc94d767..2fdd2202 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -7,14 +7,8 @@ import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.best.BestEntity; -import inu.codin.codin.domain.post.domain.best.BestRepository; -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; -import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; -import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; -import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; -import inu.codin.codin.domain.post.dto.UserProfile; +import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; @@ -28,9 +22,7 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisBestService; import inu.codin.codin.infra.s3.S3Service; -import inu.codin.codin.infra.s3.exception.ImageRemoveException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -40,7 +32,6 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.time.LocalDateTime; import java.util.*; @Slf4j @@ -233,14 +224,14 @@ public PostPageResponse getBestPosts(int pageNumber) { // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { - UserProfile userProfile = resolveUserProfile(post); + UserDto userDto = resolveUserProfile(post); int likeCount = seperatedPostService.getLikeCount(post); int scrapCount = seperatedPostService.getScrapCount(post); int hitsCount = seperatedPostService.getHitsCount(post); int commentCount = post.getCommentCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfoResponseDTO userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userProfile, likeCount, scrapCount, hitsCount, commentCount, userInfo); + UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo); if (post.getPostCategory() == PostCategory.POLL) { PollInfoResponseDTO pollInfo = seperatedPostService.getPollInfo(post, userId); return PostPageItemResponseDTO.of(postDTO, pollInfo); @@ -250,45 +241,21 @@ private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { } // [유저 프로필] - 닉네임/이미지 결정 - private UserProfile resolveUserProfile(PostEntity post) { + private UserDto resolveUserProfile(PostEntity post) { UserEntity user = userRepository.findById(post.getUserId()) - .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); - String nickname; - String userImageUrl; - if (user.getDeletedAt() == null) { - if (post.isAnonymous()) { - nickname = "익명"; - userImageUrl = s3Service.getDefaultProfileImageUrl(); - } else { - nickname = user.getNickname(); - userImageUrl = user.getProfileImageUrl(); - } - } else { - nickname = user.getNickname(); - userImageUrl = user.getProfileImageUrl(); - } - return new UserProfile(nickname, userImageUrl); + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + return UserDto.ofPost(post, user, s3Service.getDefaultProfileImageUrl()); } // [유저 프로필] - 게시물에 대한 유저정보 추출 - public UserInfoResponseDTO getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ - return UserInfoResponseDTO.of( + public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ + return UserInfo.of( likeService.isLiked(LikeType.POST, postId, currentUserId), scrapService.isPostScraped(postId, currentUserId), postUserId.equals(currentUserId) ); } -// // [내부 클래스] - 유저 프로필 정보 -// private static class UserProfile { -// final String nickname; -// final String userImageUrl; -// UserProfile(String nickname, String userImageUrl) { -// this.nickname = nickname; -// this.userImageUrl = userImageUrl; -// } -// } - // ===================== 도메인별 부가 기능/서브 로직 (임시 분리 - SeperatedPostService) ===================== } From 5728e178f2f627a547bcc39a833ac3ab3c894039 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 14:36:52 +0900 Subject: [PATCH 0810/1002] =?UTF-8?q?refactor:=20Comment,=20Reply=20Contro?= =?UTF-8?q?ller=20=EB=B0=98=ED=99=98=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/comment/controller/CommentController.java | 6 +++--- .../domain/reply/controller/ReplyCommentController.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 4c08d2fb..30b75c2a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -27,7 +27,7 @@ public CommentController(CommentService commentService) { @Operation(summary = "댓글 추가") @PostMapping("/{postId}") - public ResponseEntity> addComment(@PathVariable String postId, + public ResponseEntity> addComment(@PathVariable String postId, @RequestBody @Valid CommentCreateRequestDTO requestDTO) { commentService.addComment(postId, requestDTO); return ResponseEntity.status(HttpStatus.CREATED) @@ -45,7 +45,7 @@ public ResponseEntity> getCommentsByPostId(@Pat @Operation(summary = "댓글 삭제") @DeleteMapping("/{commentId}") - public ResponseEntity> softDeleteComment(@PathVariable String commentId) { + public ResponseEntity> softDeleteComment(@PathVariable String commentId) { commentService.softDeleteComment(commentId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "댓글이 삭제되었습니다.", null)); @@ -53,7 +53,7 @@ public ResponseEntity> softDeleteComment(@PathVariable String @Operation(summary = "댓글 수정") @PatchMapping("/{commentId}") - public ResponseEntity> updateComment(@PathVariable String commentId, @RequestBody @Valid CommentUpdateRequestDTO requestDTO){ + public ResponseEntity> updateComment(@PathVariable String commentId, @RequestBody @Valid CommentUpdateRequestDTO requestDTO){ commentService.updateComment(commentId, requestDTO); return ResponseEntity.status(HttpStatus.OK). body(new SingleResponse<>(200, "댓글 수정되었습니다.", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index b2529210..2c77173a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -22,7 +22,7 @@ public class ReplyCommentController { @Operation(summary = "대댓글 추가") @PostMapping("/{commentId}") - public ResponseEntity> addReply(@PathVariable String commentId, + public ResponseEntity> addReply(@PathVariable String commentId, @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { replyCommentService.addReply(commentId, requestDTO); return ResponseEntity.status(HttpStatus.CREATED) @@ -32,7 +32,7 @@ public ResponseEntity> addReply(@PathVariable String commentId @Operation(summary = "대댓글 삭제") @DeleteMapping("/{replyId}") - public ResponseEntity> softDeleteReply(@PathVariable String replyId) { + public ResponseEntity> softDeleteReply(@PathVariable String replyId) { replyCommentService.softDeleteReply(replyId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); @@ -40,7 +40,7 @@ public ResponseEntity> softDeleteReply(@PathVariable String re @Operation(summary = "대댓글 수정") @PatchMapping("/{replyId}") - public ResponseEntity> updateReply(@PathVariable String replyId, @RequestBody @Valid ReplyUpdateRequestDTO requestDTO){ + public ResponseEntity> updateReply(@PathVariable String replyId, @RequestBody @Valid ReplyUpdateRequestDTO requestDTO){ replyCommentService.updateReply(replyId, requestDTO); return ResponseEntity.status(HttpStatus.OK). body(new SingleResponse<>(200, "대댓글이 수정되었습니다.", null)); From a0654ac56e70b5672b18d4bd8218bc5dccf30ebe Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 14:54:54 +0900 Subject: [PATCH 0811/1002] =?UTF-8?q?refactor:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8C=80=EB=8C=93=EA=B8=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 댓글/대댓글 추가/삭제 시 댓글 수 및 BestScore 반영 순서 정비 - 조회 로직 내 사용자 정보 매핑, 응답 DTO 생성 등 책임 분리 - 중복 제거 및 성능 고려 유저 정보 조회 방식 적용 --- .../comment/service/CommentService.java | 127 +++++++++++++----- .../reply/service/ReplyCommentService.java | 116 +++++++++++----- .../codin/domain/post/entity/PostEntity.java | 7 +- 3 files changed, 177 insertions(+), 73 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 58c1a3b8..88f8b66c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -12,7 +12,7 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.dto.UserProfile; +import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.user.entity.UserEntity; @@ -24,6 +24,7 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -56,11 +57,11 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { .anonymous(requestDTO.isAnonymous()) .content(requestDTO.getContent()) .build(); - commentRepository.save(comment); - // 댓글 수 증가 post.plusCommentCount(); post.getAnonymous().setAnonNumber(post, userId); + + commentRepository.save(comment); postRepository.save(post); redisBestService.applyBestScore(1, postId); @@ -76,6 +77,15 @@ public void softDeleteComment(String id) { .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); SecurityUtils.validateUser(comment.getUserId()); + ObjectId postId = comment.getPostId(); + PostEntity post = postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + + post.minusCommentCount(); + postRepository.save(post); + + redisBestService.applyBestScore(-1, postId); + // 댓글 Soft Delete 처리 comment.delete(); commentRepository.save(comment); @@ -84,49 +94,87 @@ public void softDeleteComment(String id) { } - // 특정 게시물의 댓글 및 대댓글 조회 + /** + * 특정 게시물의 댓글 및 대댓글 조회 + */ public List getCommentsByPostId(String id) { - ObjectId postId = new ObjectId(id); - PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + // 1. 입력 검증 및 게시물 조회 + ObjectId postId = validateAndConvertPostId(id); + PostEntity post = findPostById(postId); + + // 2. 댓글 목록 조회 List comments = commentRepository.findByPostId(postId); + if (comments.isEmpty()) { + return Collections.emptyList(); + } + // 3. 사용자 정보 맵 생성 + Map userMap = createUserMap(comments); String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); - Map userMap = userRepository.findAllById( - comments.stream() - .map(CommentEntity::getUserId) - .distinct() - .toList() - ).stream() + // 4. 댓글 DTO 변환 + return comments.stream() + .map(comment -> buildCommentResponseDTO(post, comment, userMap, defaultImageUrl)) + .collect(Collectors.toList()); + } + + /** + * 게시물 ID 검증 및 ObjectId 변환 + */ + private ObjectId validateAndConvertPostId(String id) { + if (id == null || id.trim().isEmpty()) { + throw new IllegalArgumentException("게시물 ID는 필수입니다."); + }return new ObjectId(id); + } + + private PostEntity findPostById(ObjectId postId) { + return postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + } + /** + * 댓글 작성자들의 사용자 정보 맵 생성 + */ + private Map createUserMap(List comments) { + List userIdsInOrder = comments.stream() + .map(CommentEntity::getUserId) + .toList(); + + List distinctIds = userIdsInOrder.stream() + .distinct() + .toList(); + + return userRepository.findAllById(distinctIds) + .stream() .collect(Collectors.toMap( UserEntity::get_id, - user -> new UserProfile(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) + user -> user )); + } - - return comments.stream() - .map(comment -> { - UserProfile userProfile = userMap.get(comment.getUserId()); - int anonNum = post.getAnonymous().getAnonNumber(comment.getUserId().toString()); - String nickname; - String userImageUrl; - - if (userProfile.isDeleted()){ - nickname = userMap.get(comment.getUserId()).getNickname(); - userImageUrl = userMap.get(comment.getUserId()).getImageUrl(); - } else { - nickname = comment.isAnonymous()? - anonNum==0? "글쓴이" : "익명" + anonNum - : userMap.get(comment.getUserId()).getNickname(); - userImageUrl = comment.isAnonymous()? defaultImageUrl: userMap.get(comment.getUserId()).getImageUrl(); - } - return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, - replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), - likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), - getUserInfoAboutComment(comment.get_id())); - }) - .toList(); + /** + * 댓글 응답 DTO 생성 + */ + private CommentResponseDTO buildCommentResponseDTO( + PostEntity post, + CommentEntity comment, + Map userMap, + String defaultImageUrl) { + + int anonNum = post.getAnonymous().getAnonNumber(comment.getUserId().toString()); + + UserEntity user = userMap.get(comment.getUserId()); + + // 댓글용 사용자 DTO 생성 + UserDto commentUserDto = UserDto.ofComment(comment, user, anonNum ,defaultImageUrl); + + return CommentResponseDTO.commentOf( + comment, + commentUserDto.getNickname(), + commentUserDto.getImageUrl(), + replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), + likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), + getUserInfoAboutComment(comment.get_id()) + ); } public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { @@ -136,6 +184,11 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + //본인 댓글만 수정 가능 + ObjectId userId = SecurityUtils.getCurrentUserId(); + SecurityUtils.validateUser(userId); + + comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 96cd259a..1eacb1cf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -12,7 +12,7 @@ import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.dto.UserProfile; +import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; @@ -27,8 +27,11 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; + import java.util.stream.Collectors; @Service @@ -52,6 +55,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); @@ -64,11 +68,10 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { .anonymous(requestDTO.isAnonymous()) .build(); - replyCommentRepository.save(reply); - - // 댓글 수 증가 (대댓글도 댓글 수에 포함) post.plusCommentCount(); post.getAnonymous().setAnonNumber(post, userId); + + replyCommentRepository.save(reply); postRepository.save(post); redisBestService.applyBestScore(1, post.get_id()); @@ -82,6 +85,21 @@ public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); SecurityUtils.validateUser(reply.getUserId()); + + ObjectId commentId = reply.getCommentId(); + CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + + ObjectId postId = comment.getPostId(); + PostEntity post = postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + + + post.minusCommentCount(); + postRepository.save(post); + + redisBestService.applyBestScore(-1, postId); + // 대댓글 삭제 reply.delete(); replyCommentRepository.save(reply); @@ -89,47 +107,75 @@ public void softDeleteReply(String replyId) { log.info("대댓글 성공적 삭제 replyId: {}", replyId); } - // 특정 댓글의 대댓글 조회 + /** + * 특정 댓글의 대댓글 조회 + */ public List getRepliesByCommentId(PostAnonymous postAnonymous, ObjectId commentId) { + // 1. 대댓글 목록 조회 List replies = replyCommentRepository.findByCommentId(commentId); - String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); + if (replies.isEmpty()) { + return Collections.emptyList(); + } - Map userMap = userRepository.findAllById( - replies.stream() - .map(ReplyCommentEntity::getUserId).distinct().toList() - ).stream() - .collect(Collectors.toMap( - UserEntity::get_id, - user -> new UserProfile(user.getNickname(), user.getProfileImageUrl(), user.getDeletedAt() != null) - )); + // 2. 사용자 정보 맵 생성 + Map userMap = createUserMapFromReplies(replies); + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); + // 3. 대댓글 DTO 변환 return replies.stream() - .map(reply -> { - UserProfile userProfile = userMap.get(reply.getUserId()); - int anonNum = postAnonymous.getAnonNumber(reply.getUserId().toString()); - String nickname; - String userImageUrl; - - if (userProfile.isDeleted()){ - nickname = userMap.get(reply.getUserId()).getNickname(); - userImageUrl = userMap.get(reply.getUserId()).getImageUrl(); - } else { - nickname = reply.isAnonymous()? - anonNum==0? "글쓴이" : "익명"+anonNum - : userMap.get(reply.getUserId()).getNickname(); - userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).getImageUrl(); - } - return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), - likeService.getLikeCount(LikeType.REPLY, reply.get_id()), // 대댓글 좋아요 수 - getUserInfoAboutReply(reply.get_id())); - }).toList(); + .map(reply -> buildReplyResponseDTO(reply, postAnonymous, userMap, defaultImageUrl)) + .toList(); + } + + /** + * 대댓글 작성자들의 사용자 정보 맵 생성 + */ + private Map createUserMapFromReplies(List replies) { + List userIdsInOrder = replies.stream() + .map(ReplyCommentEntity::getUserId) + .toList(); + + List distinctIds = userIdsInOrder.stream() + .distinct() + .toList(); + + return userRepository.findAllById(distinctIds) + .stream() + .collect(Collectors.toMap( + UserEntity::get_id, + user -> user + )); + } + + /** + * 대댓글 응답 DTO 생성 + */ + private CommentResponseDTO buildReplyResponseDTO( + ReplyCommentEntity reply, + PostAnonymous postAnonymous, + Map userMap, + String defaultImageUrl) { + + ObjectId userId = reply.getUserId(); + UserEntity user = userMap.get(userId); + int anonNum = postAnonymous.getAnonNumber(userId.toString()); + + UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); + + return CommentResponseDTO.replyOf( + reply, + replyUserDto.getNickname(), + replyUserDto.getImageUrl(), + List.of(), // 대댓글의 대댓글은 없음 + likeService.getLikeCount(LikeType.REPLY, reply.get_id()), + getUserInfoAboutReply(reply.get_id()) + ); } public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); - //log.info("대댓글 userInfo - replyId: {}, userId: {}", replyId, userId); return CommentResponseDTO.UserInfo.builder() - .isLike(likeService.isLiked(LikeType.REPLY, replyId, userId)) + .isLike(likeService.isLiked(LikeType.COMMENT, replyId, userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index c38f8330..2c6fe9d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -66,11 +66,16 @@ public void removePostImage(String imageUrl) { this.postImageUrls.remove(imageUrl); } - //댓글+대댓글 수 업데이트 + //댓글+대댓글 수 증가 public void plusCommentCount() { this.commentCount++; } + //댓글+대댓글 수 감소 + public void minusCommentCount() { + this.commentCount--; + } + //신고 수 업데이트 public void updateReportCount(int reportCount) { this.reportCount=reportCount; From b018767ebc8b8e0773159d3dccb18ea05316ad04 Mon Sep 17 00:00:00 2001 From: gisu1102 Date: Fri, 11 Jul 2025 17:34:59 +0900 Subject: [PATCH 0812/1002] =?UTF-8?q?refactor=20:=20post=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=A0=95=EB=B3=B4(=EC=9D=B5=EB=AA=85,=20c?= =?UTF-8?q?ount)=20=ED=95=A0=EB=8B=B9=20=EC=B1=85=EC=9E=84=EC=9D=84=20Post?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=82=B4=EB=B6=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B4=80=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20+=20=EC=A0=95=EC=A0=81=20=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=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 --- .../dto/response/CommentResponseDTO.java | 15 +-- .../domain/comment/entity/CommentEntity.java | 11 +++ .../comment/service/CommentService.java | 36 +++---- .../reply/entity/ReplyCommentEntity.java | 14 ++- .../reply/service/ReplyCommentService.java | 55 +++++------ .../domain/post/entity/PostAnonymous.java | 35 ++++--- .../codin/domain/post/entity/PostEntity.java | 23 ++++- .../domain/post/service/PostService.java | 95 ++++++++++++++----- .../post/service/SeperatedPostService.java | 7 -- .../codin/domain/post/PostServiceTest.java | 3 +- 10 files changed, 179 insertions(+), 115 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 76b941a0..2af63ce9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -71,13 +72,13 @@ public CommentResponseDTO(String _id, String userId, String content, this.userInfo = userInfo; } - public static CommentResponseDTO commentOf(CommentEntity commentEntity, String nickname, String userImageUrl, List repliesByCommentId, int like, UserInfo userInfoAboutPost){ + public static CommentResponseDTO commentOf(CommentEntity commentEntity, UserDto commentUserDto, List repliesByCommentId, int like, UserInfo userInfoAboutPost){ return CommentResponseDTO.builder() ._id(commentEntity.get_id().toString()) .userId(commentEntity.getUserId().toString()) .content(commentEntity.getContent()) - .nickname(nickname) - .userImageUrl(userImageUrl) + .nickname(commentUserDto.getNickname()) + .userImageUrl(commentUserDto.getImageUrl()) .anonymous(commentEntity.isAnonymous()) .replies(repliesByCommentId) .likeCount(like) @@ -87,15 +88,15 @@ public static CommentResponseDTO commentOf(CommentEntity commentEntity, String n .build(); } - public static CommentResponseDTO replyOf(ReplyCommentEntity replyCommentEntity, String nickname, String userImageUrl, List repliesByCommentId, int like, UserInfo userInfoAboutPost){ + public static CommentResponseDTO replyOf(ReplyCommentEntity replyCommentEntity, UserDto userDto, int like, UserInfo userInfoAboutPost){ return CommentResponseDTO.builder() ._id(replyCommentEntity.get_id().toString()) .userId(replyCommentEntity.getUserId().toString()) .content(replyCommentEntity.getContent()) - .nickname(nickname) - .userImageUrl(userImageUrl) + .nickname(userDto.getNickname()) + .userImageUrl(userDto.getImageUrl()) .anonymous(replyCommentEntity.isAnonymous()) - .replies(repliesByCommentId) + .replies(List.of()) .likeCount(like) .isDeleted(replyCommentEntity.getDeletedAt() != null) .createdAt(replyCommentEntity.getCreatedAt()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index c3f6ca06..1dda85be 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -29,6 +30,16 @@ public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String cont this.anonymous = anonymous; } + public static CommentEntity create(ObjectId postId, ObjectId userId, CommentCreateRequestDTO requestDTO) { + return new CommentEntity( + new ObjectId(), + postId, + userId, + requestDTO.getContent(), + requestDTO.isAnonymous() + ); + } + public void updateComment(String content) { this.content = content; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 88f8b66c..3e533529 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -13,8 +13,10 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisBestService; @@ -42,29 +44,24 @@ public class CommentService { private final ReplyCommentService replyCommentService; private final NotificationService notificationService; private final RedisBestService redisBestService; + private final PostService postService; private final S3Service s3Service; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { + ObjectId postId = new ObjectId(id); PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); ObjectId userId = SecurityUtils.getCurrentUserId(); - CommentEntity comment = CommentEntity.builder() - .postId(postId) - .userId(userId) - .anonymous(requestDTO.isAnonymous()) - .content(requestDTO.getContent()) - .build(); - - post.plusCommentCount(); - post.getAnonymous().setAnonNumber(post, userId); + CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); commentRepository.save(comment); - postRepository.save(post); + postService.handleCommentCreation(post, userId); redisBestService.applyBestScore(1, postId); + log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); @@ -81,15 +78,13 @@ public void softDeleteComment(String id) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - post.minusCommentCount(); - postRepository.save(post); - - redisBestService.applyBestScore(-1, postId); - // 댓글 Soft Delete 처리 comment.delete(); commentRepository.save(comment); + postService.decreaseCommentCount(post); + redisBestService.applyBestScore(-1, postId); + log.info("삭제된 commentId: {}", commentId); } @@ -114,7 +109,7 @@ public List getCommentsByPostId(String id) { // 4. 댓글 DTO 변환 return comments.stream() - .map(comment -> buildCommentResponseDTO(post, comment, userMap, defaultImageUrl)) + .map(comment -> buildCommentResponseDTO(post.getAnonymous(), comment, userMap, defaultImageUrl)) .collect(Collectors.toList()); } @@ -155,12 +150,12 @@ private Map createUserMap(List comments) { * 댓글 응답 DTO 생성 */ private CommentResponseDTO buildCommentResponseDTO( - PostEntity post, + PostAnonymous postAnonymous, CommentEntity comment, Map userMap, String defaultImageUrl) { - int anonNum = post.getAnonymous().getAnonNumber(comment.getUserId().toString()); + int anonNum = postService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); UserEntity user = userMap.get(comment.getUserId()); @@ -169,9 +164,8 @@ private CommentResponseDTO buildCommentResponseDTO( return CommentResponseDTO.commentOf( comment, - commentUserDto.getNickname(), - commentUserDto.getImageUrl(), - replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), + commentUserDto, + replyCommentService.getRepliesByCommentId(postAnonymous, comment.get_id()), likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), getUserInfoAboutComment(comment.get_id()) ); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index c15a5162..7722406e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.post.domain.reply.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -21,7 +23,7 @@ public class ReplyCommentEntity extends BaseTimeEntity { private boolean anonymous; @Builder - public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, boolean anonymous, String content) { + public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId,String content, boolean anonymous) { this._id = _id; this.commentId = commentId; this.userId = userId; @@ -29,6 +31,16 @@ public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId, boo this.anonymous = anonymous; } + public static ReplyCommentEntity create(ObjectId commentId, ObjectId userId, ReplyCreateRequestDTO requestDTO) { + return new ReplyCommentEntity( + new ObjectId(), + commentId, + userId, + requestDTO.getContent(), + requestDTO.isAnonymous() + ); + } + public void updateReply(String content) { this.content = content; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 1eacb1cf..c7927c8d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -16,6 +16,7 @@ import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.report.repository.ReportRepository; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; @@ -43,7 +44,7 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; - private final ReportRepository reportRepository; + private final PostService postService; private final LikeService likeService; private final NotificationService notificationService; @@ -61,25 +62,31 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { ObjectId userId = SecurityUtils.getCurrentUserId(); - ReplyCommentEntity reply = ReplyCommentEntity.builder() - .commentId(commentId) - .userId(userId) - .content(requestDTO.getContent()) - .anonymous(requestDTO.isAnonymous()) - .build(); - - post.plusCommentCount(); - post.getAnonymous().setAnonNumber(post, userId); + ReplyCommentEntity reply = ReplyCommentEntity.create(commentId, userId, requestDTO); replyCommentRepository.save(reply); - postRepository.save(post); + postService.handleCommentCreation(post, userId); redisBestService.applyBestScore(1, post.get_id()); + log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } + public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { + + ObjectId replyId = new ObjectId(id); + ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + + reply.updateReply(requestDTO.getContent()); + replyCommentRepository.save(reply); + + log.info("대댓글 수정 완료 - replyId: {}", replyId); + + } + // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) @@ -94,16 +101,13 @@ public void softDeleteReply(String replyId) { PostEntity post = postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); - - post.minusCommentCount(); - postRepository.save(post); - - redisBestService.applyBestScore(-1, postId); - // 대댓글 삭제 reply.delete(); replyCommentRepository.save(reply); + postService.decreaseCommentCount(post); + redisBestService.applyBestScore(-1, postId); + log.info("대댓글 성공적 삭제 replyId: {}", replyId); } @@ -158,15 +162,13 @@ private CommentResponseDTO buildReplyResponseDTO( ObjectId userId = reply.getUserId(); UserEntity user = userMap.get(userId); - int anonNum = postAnonymous.getAnonNumber(userId.toString()); + int anonNum = postService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); return CommentResponseDTO.replyOf( reply, - replyUserDto.getNickname(), - replyUserDto.getImageUrl(), - List.of(), // 대댓글의 대댓글은 없음 + replyUserDto, likeService.getLikeCount(LikeType.REPLY, reply.get_id()), getUserInfoAboutReply(reply.get_id()) ); @@ -180,17 +182,6 @@ public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { } - public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { - - ObjectId replyId = new ObjectId(id); - ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) - .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); - - reply.updateReply(requestDTO.getContent()); - replyCommentRepository.save(reply); - - log.info("대댓글 수정 완료 - replyId: {}", replyId); - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java index f2d8c1f8..b00e2c2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostAnonymous.java @@ -12,36 +12,33 @@ public class PostAnonymous { private int anonymousNumber = 1; /** - * Map을 통해 유저의 익명 번호 설정 및 반환 + * Map을 통해 유저의 익명 번호 반환 (없으면 null) * @param userId 유저 _id * @return 게시글에서 유저의 익명 번호 */ - public Integer getAnonNumber(String userId){ - if (userAnonymousMap.containsKey(userId)) - return userAnonymousMap.get(userId); - else { - userAnonymousMap.put(userId, anonymousNumber); - return anonymousNumber++; - } + public Integer getAnonNumber(ObjectId userId) { + return userAnonymousMap.get(userId.toString()); } /** * 글쓴이는 따로 관리하기 위해 0으로 설정 * @param userId */ - public void setWriter(String userId){ - userAnonymousMap.put(userId, 0); + public void setWriter(ObjectId userId){ + userAnonymousMap.put(userId.toString(), 0); } - - public void setAnonNumber(PostEntity post, ObjectId userId) { - if (post.getUserId().equals(userId)){ //글쓴이 - post.getAnonymous().setWriter(userId.toString()); - } else { - post.getAnonymous().getAnonNumber(userId.toString()); - } + /** + * 유저에게 익명 번호 할당 + */ + public void setAnonNumber(ObjectId userId) { + userAnonymousMap.put(userId.toString(), this.anonymousNumber++); } - - + /** + * 해당 유저가 이미 익명 번호를 받았는지 확인 + */ + public boolean hasAnonNumber(ObjectId userId) { + return userAnonymousMap.containsKey(userId.toString()); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 2c6fe9d7..47abda01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; @@ -44,6 +45,19 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri this.postStatus = postStatus; } + public static PostEntity create(ObjectId userId, PostCreateRequestDTO dto, List imageUrls) { + return new PostEntity( + new ObjectId(), + userId, + dto.getPostCategory(), + dto.getTitle(), + dto.getContent(), + imageUrls != null ? new ArrayList<>(imageUrls) : new ArrayList<>(), + dto.isAnonymous(), + PostStatus.ACTIVE + ); + } + public void updatePostContent(String content, List postImageUrls) { this.content = content; this.postImageUrls = postImageUrls != null ? new ArrayList<>(postImageUrls) : new ArrayList<>(); @@ -73,7 +87,9 @@ public void plusCommentCount() { //댓글+대댓글 수 감소 public void minusCommentCount() { - this.commentCount--; + if (this.commentCount > 0) { + this.commentCount--; + } } //신고 수 업데이트 @@ -81,4 +97,9 @@ public void updateReportCount(int reportCount) { this.reportCount=reportCount; } + //작성자 확인 로직 + public boolean isWriter(ObjectId userId) { + return this.userId.equals(userId); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 2fdd2202..209b4b49 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -14,9 +14,9 @@ import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.dto.response.*; +import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserEntity; @@ -50,9 +50,8 @@ public class PostService { * 게시글 생성 * @param postCreateRequestDTO 게시글 생성 요청 DTO * @param postImages 이미지 파일 리스트 - * @return 생성된 게시글의 postId를 담은 Map (불변) */ - public Map createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); List imageUrls = seperatedPostService.handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -63,20 +62,9 @@ public Map createPost(PostCreateRequestDTO postCreateRequestDTO, throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } - PostEntity postEntity = PostEntity.builder() - .userId(userId) - .title(postCreateRequestDTO.getTitle()) - .content(postCreateRequestDTO.getContent()) - .postImageUrls(imageUrls) - .isAnonymous(postCreateRequestDTO.isAnonymous()) - .postCategory(postCreateRequestDTO.getPostCategory()) - .postStatus(PostStatus.ACTIVE) - .build(); + PostEntity postEntity = PostEntity.create(userId, postCreateRequestDTO, imageUrls); postRepository.save(postEntity); log.info("게시물 성공적으로 생성됨. PostId: {}, UserId: {}", postEntity.get_id(), userId); - Map response = new HashMap<>(); - response.put("postId", postEntity.get_id().toString()); - return response; } /** @@ -117,15 +105,6 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); } - private void validateUserAndPost(PostEntity post) { - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } - SecurityUtils.validateUser(post.getUserId()); - } - /** * 카테고리별 삭제되지 않은 게시물 목록 조회 * @return PostPageResponse (불변 리스트) @@ -160,7 +139,6 @@ public PostPageItemResponseDTO getPostWithDetail(String postId) { /** * Optional로 게시물 상세 조회 (null-safe) - * 사용 예시: getPostDetailById(id).ifPresent(dto -> ...) */ public Optional getPostDetailById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) @@ -221,6 +199,64 @@ public PostPageResponse getBestPosts(int pageNumber) { return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } + /** + * 댓글 생성시 필요한 모든 처리를 한 번에 + */ + public void handleCommentCreation(PostEntity post, ObjectId userId) { + assignAnonymousNumber(post, userId); + increaseCommentCount(post); + postRepository.save(post); + } + + /** + * 댓글/ 대댓글 작성시 댓글/대댓글 작성수 증가 + * @param post - postEntity + */ + public void increaseCommentCount(PostEntity post) { + post.plusCommentCount(); + log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); + } + + /** + * 댓글/ 대댓글 작성시 댓글/대댓글 작성수 감소 + * @param post - postEntity + */ + public void decreaseCommentCount(PostEntity post) { + post.minusCommentCount(); + postRepository.save(post); + log.info("댓글 수 감소. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); + } + + /** + * 익명 번호 할당 + */ + public void assignAnonymousNumber(PostEntity post, ObjectId userId) { + if (!post.isAnonymous()) { + return; + } + + PostAnonymous anonymous = post.getAnonymous(); + + // 이미 익명 번호가 있으면 할당하지 않음 + if (anonymous.hasAnonNumber(userId)) { + return; + } + + if (post.isWriter(userId)) { + anonymous.setWriter(userId); + } else {anonymous.setAnonNumber(userId); + } + postRepository.save(post); + log.info("익명 번호 할당. PostId: {}, UserId: {}", post.get_id(), userId); + } + + /** + * 유저의 익명 번호 조회 + */ + public Integer getUserAnonymousNumber(PostAnonymous postAnonymous, ObjectId userId) { + return postAnonymous.getAnonNumber(userId); + } + // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { @@ -256,6 +292,15 @@ public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId ); } + private void validateUserAndPost(PostEntity post) { + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); + } + SecurityUtils.validateUser(post.getUserId()); + } + // ===================== 도메인별 부가 기능/서브 로직 (임시 분리 - SeperatedPostService) ===================== } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java index cad713e2..187e4f42 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java @@ -100,13 +100,6 @@ public void applyBestScoreIfNeeded(PostEntity post) { log.info("베스트 점수 적용. PostId: {}", post.get_id()); } - // [CommentService/ReplyCommentService] - 댓글 수 증가 - public void increaseCommentCount(PostEntity post) { - post.plusCommentCount(); - postRepository.save(post); - log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); - } - // [PollService] - 투표 관련 처리 (예시) public void processPollIfNeeded(PostEntity post) { // 투표 게시글일 경우 PollService와 협력하여 투표 생성/집계 등 처리 diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java index d2708a6c..fc5b686c 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java @@ -87,9 +87,8 @@ void tearDown() throws Exception { return entity; }); // When - Map result = postService.createPost(dto, images); // Then - assertThat(result).containsKey("postId"); + assertThatCode(() -> postService.createPost(dto, images)).doesNotThrowAnyException(); } @Test From 8133571d5063c44b5e6e6137bb017da8364a5802 Mon Sep 17 00:00:00 2001 From: gisu1102 Date: Fri, 11 Jul 2025 17:35:19 +0900 Subject: [PATCH 0813/1002] =?UTF-8?q?refactor=20:=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EB=90=9C=20=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/controller/PostController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index d6b75583..59937d90 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -48,6 +48,7 @@ public ResponseEntity> createPost( // postImages가 null이면 빈 리스트로 처리 if (postImages == null) postImages = List.of(); + postService.createPost(postCreateRequestDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", null)); } From dadb7524dfb12c63ca1dc1d0547d6b9414647906 Mon Sep 17 00:00:00 2001 From: gisu1102 Date: Fri, 11 Jul 2025 18:13:59 +0900 Subject: [PATCH 0814/1002] =?UTF-8?q?refactor=20:=20poll=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/poll/service/PollService.java | 16 +++++++++ .../domain/post/service/PostService.java | 4 ++- .../post/service/SeperatedPostService.java | 34 +++++-------------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index 507d01e1..1888c6c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; @@ -21,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; @Service @RequiredArgsConstructor @@ -151,4 +153,18 @@ public void deleteVoting(String postId) { pollVoteRepository.delete(pollVote); log.info("투표 취소 완료 - pollId: {}, userId: {}", poll.get_id(), userId); } + + public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); + long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); + List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) + .map(PollVoteEntity::getSelectedOptions) + .orElse(Collections.emptyList()); + boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); + boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); + return PollInfoResponseDTO.of( + poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), + poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 209b4b49..f171d665 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.poll.service.PollService; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; @@ -45,6 +46,7 @@ public class PostService { private final LikeService likeService; private final ScrapService scrapService; private final S3Service s3Service; + private final PollService pollService; /** * 게시글 생성 @@ -269,7 +271,7 @@ private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo); if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = seperatedPostService.getPollInfo(post, userId); + PollInfoResponseDTO pollInfo = pollService.getPollInfo(post, userId); return PostPageItemResponseDTO.of(postDTO, pollInfo); } else { return PostPageItemResponseDTO.of(postDTO, null); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java index 187e4f42..13a2bb11 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java @@ -65,13 +65,6 @@ public void deletePostImageInternal(PostEntity post, String imageUrl) { } } - // [HitsService] - 조회수 증가 처리 - public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { - if (!hitsService.validateHits(post.get_id(), userId)) { - hitsService.addHits(post.get_id(), userId); - log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); - } - } // [BestService] - Top 3 베스트 게시물 조회 public List getTop3BestPostsInternal() { @@ -100,11 +93,7 @@ public void applyBestScoreIfNeeded(PostEntity post) { log.info("베스트 점수 적용. PostId: {}", post.get_id()); } - // [PollService] - 투표 관련 처리 (예시) - public void processPollIfNeeded(PostEntity post) { - // 투표 게시글일 경우 PollService와 협력하여 투표 생성/집계 등 처리 - // 예시: pollService.createPoll(...) - } + // [likeService] - 게시글 좋아요 수 조회 public int getLikeCount(PostEntity post) { @@ -121,18 +110,13 @@ public int getHitsCount(PostEntity post) { return hitsService.getHitsCount(post.get_id()); } - // [PollService] - 투표 게시글 PollInfo 생성 - public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); - long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); - List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) - .map(PollVoteEntity::getSelectedOptions) - .orElse(Collections.emptyList()); - boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); - boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); - return PollInfoResponseDTO.of( - poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), - poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + + // [HitsService] - 조회수 증가 처리 + public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { + if (!hitsService.validateHits(post.get_id(), userId)) { + hitsService.addHits(post.get_id(), userId); + log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); + } } + } From 6eaf9e382a2e813e38f2bb3206e5fac5c67f6e72 Mon Sep 17 00:00:00 2001 From: gisu1102 Date: Fri, 11 Jul 2025 18:29:41 +0900 Subject: [PATCH 0815/1002] =?UTF-8?q?refactor=20:=20=EC=B4=88=EA=B8=B0=20p?= =?UTF-8?q?ostService=20=EB=B6=84=EB=A6=AC=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/service/BestPostService.java | 56 ++++ .../post/service/PostCommandService.java | 187 +++++++++++++ .../post/service/PostInteractionService.java | 78 ++++++ .../domain/post/service/PostQueryService.java | 148 ++++++++++ .../domain/post/service/PostService.java | 256 ------------------ .../post/service/SeperatedPostService.java | 54 ++-- 6 files changed, 497 insertions(+), 282 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java new file mode 100644 index 00000000..ac634e38 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java @@ -0,0 +1,56 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestRepository; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.infra.redis.service.RedisBestService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BestPostService { + private final RedisBestService redisBestService; + private final BestRepository bestRepository; + private final PostRepository postRepository; + + // [BestService] - Top 3 베스트 게시물 조회 + public List getTop3BestPostsInternal() { + Map posts = redisBestService.getBests(); + return posts.entrySet().stream() + .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) + .orElseGet(() -> { + redisBestService.deleteBest(post.getKey()); + return null; + })) + .filter(Objects::nonNull) + .toList(); + } + + // [BestService] - 베스트 게시물 페이지 조회 + public Page getBestPostsInternal(int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page bests = bestRepository.findAll(pageRequest); + return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); + } + + // [BestService] - 베스트 점수 적용 처리 + public void applyBestScoreIfNeeded(PostEntity post) { + redisBestService.applyBestScore(1, post.get_id()); + log.info("베스트 점수 적용. PostId: {}", post.get_id()); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java new file mode 100644 index 00000000..d7b4aee6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -0,0 +1,187 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.service.BlockService; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; +import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; +import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; +import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PostCommandService { + private final PostRepository postRepository; + private final SeperatedPostService seperatedPostService; + + /** + * 게시글 생성 + * @param postCreateRequestDTO 게시글 생성 요청 DTO + * @param postImages 이미지 파일 리스트 + */ + public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { + log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); + List imageUrls = seperatedPostService.handleImageUpload(postImages); + ObjectId userId = SecurityUtils.getCurrentUserId(); + + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + log.error("비교과 게시물에 대한 접근권한 없음. UserId: {}", userId); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); + } + + PostEntity postEntity = PostEntity.create(userId, postCreateRequestDTO, imageUrls); + postRepository.save(postEntity); + log.info("게시물 성공적으로 생성됨. PostId: {}, UserId: {}", postEntity.get_id(), userId); + } + + /** + * 게시글 내용 및 이미지 수정 + */ + public void updatePostContent(String postId, PostContentUpdateRequestDTO requestDTO, List postImages) { + log.info("게시물 수정 시작. PostId: {}", postId); + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); + validateUserAndPost(post); + List imageUrls = seperatedPostService.handleImageUpload(postImages); + post.updatePostContent(requestDTO.getContent(), imageUrls); + postRepository.save(post); + log.info("게시물 수정 성공. PostId: {}", postId); + } + + /** + * 게시글 익명 설정 수정 + */ + public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); + validateUserAndPost(post); + post.updatePostAnonymous(requestDTO.isAnonymous()); + postRepository.save(post); + log.info("게시물 익명 수정 성공. PostId: {}", postId); + } + + /** + * 게시글 상태 수정 + */ + public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); + validateUserAndPost(post); + post.updatePostStatus(requestDTO.getPostStatus()); + postRepository.save(post); + log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); + } + + + + /** + * 게시물 소프트 삭제 + */ + public void softDeletePost(String postId) { + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없음. id=" + postId)); + validateUserAndPost(post); + post.delete(); + log.info("게시물 안전 삭제. PostId: {}", postId); + postRepository.save(post); + } + + /** + * 게시물 이미지 삭제 + */ + public void deletePostImage(String postId, String imageUrl) { + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); + validateUserAndPost(post); + seperatedPostService.deletePostImageInternal(post, imageUrl); + } + + /** + * 댓글 생성시 필요한 모든 처리를 한 번에 + */ + public void handleCommentCreation(PostEntity post, ObjectId userId) { + assignAnonymousNumber(post, userId); + increaseCommentCount(post); + postRepository.save(post); + } + + /** + * 댓글/ 대댓글 작성시 댓글/대댓글 작성수 증가 + * @param post - postEntity + */ + public void increaseCommentCount(PostEntity post) { + post.plusCommentCount(); + log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); + } + + /** + * 댓글/ 대댓글 작성시 댓글/대댓글 작성수 감소 + * @param post - postEntity + */ + public void decreaseCommentCount(PostEntity post) { + post.minusCommentCount(); + postRepository.save(post); + log.info("댓글 수 감소. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); + } + + /** + * 익명 번호 할당 + */ + public void assignAnonymousNumber(PostEntity post, ObjectId userId) { + if (!post.isAnonymous()) { + return; + } + + PostAnonymous anonymous = post.getAnonymous(); + + // 이미 익명 번호가 있으면 할당하지 않음 + if (anonymous.hasAnonNumber(userId)) { + return; + } + + if (post.isWriter(userId)) { + anonymous.setWriter(userId); + } else {anonymous.setAnonNumber(userId); + } + postRepository.save(post); + log.info("익명 번호 할당. PostId: {}, UserId: {}", post.get_id(), userId); + } + + /** + * 유저의 익명 번호 조회 + */ + public Integer getUserAnonymousNumber(PostAnonymous postAnonymous, ObjectId userId) { + return postAnonymous.getAnonNumber(userId); + } + + private void validateUserAndPost(PostEntity post) { + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ + log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); + } + SecurityUtils.validateUser(post.getUserId()); + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java new file mode 100644 index 00000000..92bb2c9d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java @@ -0,0 +1,78 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.infra.s3.exception.ImageRemoveException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + + +@Slf4j +@Service +@RequiredArgsConstructor +public class PostInteractionService { + private final S3Service s3Service; + private final PostRepository postRepository; + private final HitsService hitsService; + private final ScrapService scrapService; + private final LikeService likeService; + + // [ImageService] - 이미지 업로드 처리 + public List handleImageUpload(List postImages) { + return s3Service.handleImageUpload(postImages); + } + + // [ImageService] - 게시글 이미지 삭제 처리 + public void deletePostImageInternal(PostEntity post, String imageUrl) { + if (!post.getPostImageUrls().contains(imageUrl)) { + log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); + } + try { + s3Service.deleteFile(imageUrl); + post.removePostImage(imageUrl); + postRepository.save(post); + log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + } catch (Exception e) { + log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); + throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); + } + } + + + // [likeService] - 게시글 좋아요 수 조회 + public int getLikeCount(PostEntity post) { + return likeService.getLikeCount(LikeType.POST, post.get_id()); + } + + // [ScrapService] - 게시글 스크랩 수 조회 + public int getScrapCount(PostEntity post) { + return scrapService.getScrapCount(post.get_id()); + } + + // [HitsService] - 게시글 조회수 조회 + public int getHitsCount(PostEntity post) { + return hitsService.getHitsCount(post.get_id()); + } + + + // [HitsService] - 조회수 증가 처리 + public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { + if (!hitsService.validateHits(post.get_id(), userId)) { + hitsService.addHits(post.get_id(), userId); + log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); + } + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java new file mode 100644 index 00000000..16b15e1c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -0,0 +1,148 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.service.BlockService; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.user.entity.UserEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PostQueryService +{ + private final PostRepository postRepository; + private final PollService pollService; + private final BlockService blockService; + private final SeperatedPostService seperatedPostService; + + /** + * 카테고리별 삭제되지 않은 게시물 목록 조회 + * @return PostPageResponse (불변 리스트) + */ + public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { + List blockedUsersId = blockService.getBlockedUsers(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = postRepository.getPostsByCategoryWithBlockedUsers(postCategory.toString(), blockedUsersId, pageRequest); + log.info("모든 글 반환 성공 Category: {}, Page: {}", postCategory, pageNumber); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } + + /** + * 게시물 리스트 DTO 변환 (불변 리스트) + */ + public List getPostListResponseDtos(List posts) { + return posts.stream() + .map(this::toPageItemDTO) + .toList(); + } + + /** + * 게시물 상세 조회 + */ + public PostPageItemResponseDTO getPostWithDetail(String postId) { + PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); + ObjectId userId = SecurityUtils.getCurrentUserId(); + seperatedPostService.increaseHitsIfNeeded(post, userId); + return toPageItemDTO(post); + } + + /** + * Optional로 게시물 상세 조회 (null-safe) + */ + public Optional getPostDetailById(ObjectId postId) { + return postRepository.findByIdAndNotDeleted(postId) + .map(post -> { + ObjectId userId = SecurityUtils.getCurrentUserId(); + seperatedPostService.increaseHitsIfNeeded(post, userId); + return toPageItemDTO(post); + }); + } + + /** + * 키워드 기반 게시물 검색 + */ + public PostPageResponse searchPosts(String keyword, int pageNumber) { + List blockedUsersId = blockService.getBlockedUsers(); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); + log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } + + /** + * Top 3 베스트 게시물 조회 (불변 리스트) + */ + public List getTop3BestPosts() { + List bestPosts = seperatedPostService.getTop3BestPostsInternal(); + log.info("Top 3 베스트 게시물 반환."); + return getPostListResponseDtos(bestPosts); + } + + /** + * 베스트 게시물 페이지 조회 + */ + public PostPageResponse getBestPosts(int pageNumber) { + Page page = seperatedPostService.getBestPostsInternal(pageNumber); + return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + } + + + + // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) + private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { + UserDto userDto = resolveUserProfile(post); + int likeCount = seperatedPostService.getLikeCount(post); + int scrapCount = seperatedPostService.getScrapCount(post); + int hitsCount = seperatedPostService.getHitsCount(post); + int commentCount = post.getCommentCount(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo); + if (post.getPostCategory() == PostCategory.POLL) { + PollInfoResponseDTO pollInfo = pollService.getPollInfo(post, userId); + return PostPageItemResponseDTO.of(postDTO, pollInfo); + } else { + return PostPageItemResponseDTO.of(postDTO, null); + } + } + + // [유저 프로필] - 닉네임/이미지 결정 + private UserDto resolveUserProfile(PostEntity post) { + UserEntity user = userRepository.findById(post.getUserId()) + .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + return UserDto.ofPost(post, user, s3Service.getDefaultProfileImageUrl()); + } + + // [유저 프로필] - 게시물에 대한 유저정보 추출 + public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ + return UserInfo.of( + likeService.isLiked(LikeType.POST, postId, currentUserId), + scrapService.isPostScraped(postId, currentUserId), + postUserId.equals(currentUserId) + ); + } + + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index f171d665..bdceba43 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -39,269 +39,13 @@ @Service @RequiredArgsConstructor public class PostService { - private final PostRepository postRepository; - private final UserRepository userRepository; - private final BlockService blockService; - private final SeperatedPostService seperatedPostService; - private final LikeService likeService; - private final ScrapService scrapService; - private final S3Service s3Service; - private final PollService pollService; - /** - * 게시글 생성 - * @param postCreateRequestDTO 게시글 생성 요청 DTO - * @param postImages 이미지 파일 리스트 - */ - public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); - List imageUrls = seperatedPostService.handleImageUpload(postImages); - ObjectId userId = SecurityUtils.getCurrentUserId(); - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시물에 대한 접근권한 없음. UserId: {}", userId); - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } - PostEntity postEntity = PostEntity.create(userId, postCreateRequestDTO, imageUrls); - postRepository.save(postEntity); - log.info("게시물 성공적으로 생성됨. PostId: {}, UserId: {}", postEntity.get_id(), userId); - } - /** - * 게시글 내용 및 이미지 수정 - */ - public void updatePostContent(String postId, PostContentUpdateRequestDTO requestDTO, List postImages) { - log.info("게시물 수정 시작. PostId: {}", postId); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); - validateUserAndPost(post); - List imageUrls = seperatedPostService.handleImageUpload(postImages); - post.updatePostContent(requestDTO.getContent(), imageUrls); - postRepository.save(post); - log.info("게시물 수정 성공. PostId: {}", postId); - } - /** - * 게시글 익명 설정 수정 - */ - public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); - validateUserAndPost(post); - post.updatePostAnonymous(requestDTO.isAnonymous()); - postRepository.save(post); - log.info("게시물 익명 수정 성공. PostId: {}", postId); - } - /** - * 게시글 상태 수정 - */ - public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); - validateUserAndPost(post); - post.updatePostStatus(requestDTO.getPostStatus()); - postRepository.save(post); - log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); - } - /** - * 카테고리별 삭제되지 않은 게시물 목록 조회 - * @return PostPageResponse (불변 리스트) - */ - public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { - List blockedUsersId = blockService.getBlockedUsers(); - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.getPostsByCategoryWithBlockedUsers(postCategory.toString(), blockedUsersId, pageRequest); - log.info("모든 글 반환 성공 Category: {}, Page: {}", postCategory, pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); - } - - /** - * 게시물 리스트 DTO 변환 (불변 리스트) - */ - public List getPostListResponseDtos(List posts) { - return posts.stream() - .map(this::toPageItemDTO) - .toList(); - } - - /** - * 게시물 상세 조회 - */ - public PostPageItemResponseDTO getPostWithDetail(String postId) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); - ObjectId userId = SecurityUtils.getCurrentUserId(); - seperatedPostService.increaseHitsIfNeeded(post, userId); - return toPageItemDTO(post); - } - - /** - * Optional로 게시물 상세 조회 (null-safe) - */ - public Optional getPostDetailById(ObjectId postId) { - return postRepository.findByIdAndNotDeleted(postId) - .map(post -> { - ObjectId userId = SecurityUtils.getCurrentUserId(); - seperatedPostService.increaseHitsIfNeeded(post, userId); - return toPageItemDTO(post); - }); - } - - /** - * 게시물 소프트 삭제 - */ - public void softDeletePost(String postId) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없음. id=" + postId)); - validateUserAndPost(post); - post.delete(); - log.info("게시물 안전 삭제. PostId: {}", postId); - postRepository.save(post); - } - - /** - * 게시물 이미지 삭제 - */ - public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); - validateUserAndPost(post); - seperatedPostService.deletePostImageInternal(post, imageUrl); - } - - /** - * 키워드 기반 게시물 검색 - */ - public PostPageResponse searchPosts(String keyword, int pageNumber) { - List blockedUsersId = blockService.getBlockedUsers(); - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); - log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); - } - - /** - * Top 3 베스트 게시물 조회 (불변 리스트) - */ - public List getTop3BestPosts() { - List bestPosts = seperatedPostService.getTop3BestPostsInternal(); - log.info("Top 3 베스트 게시물 반환."); - return getPostListResponseDtos(bestPosts); - } - - /** - * 베스트 게시물 페이지 조회 - */ - public PostPageResponse getBestPosts(int pageNumber) { - Page page = seperatedPostService.getBestPostsInternal(pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); - } - - /** - * 댓글 생성시 필요한 모든 처리를 한 번에 - */ - public void handleCommentCreation(PostEntity post, ObjectId userId) { - assignAnonymousNumber(post, userId); - increaseCommentCount(post); - postRepository.save(post); - } - - /** - * 댓글/ 대댓글 작성시 댓글/대댓글 작성수 증가 - * @param post - postEntity - */ - public void increaseCommentCount(PostEntity post) { - post.plusCommentCount(); - log.info("댓글 수 증가. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); - } - - /** - * 댓글/ 대댓글 작성시 댓글/대댓글 작성수 감소 - * @param post - postEntity - */ - public void decreaseCommentCount(PostEntity post) { - post.minusCommentCount(); - postRepository.save(post); - log.info("댓글 수 감소. PostId: {}, 현재: {}", post.get_id(), post.getCommentCount()); - } - - /** - * 익명 번호 할당 - */ - public void assignAnonymousNumber(PostEntity post, ObjectId userId) { - if (!post.isAnonymous()) { - return; - } - - PostAnonymous anonymous = post.getAnonymous(); - - // 이미 익명 번호가 있으면 할당하지 않음 - if (anonymous.hasAnonNumber(userId)) { - return; - } - - if (post.isWriter(userId)) { - anonymous.setWriter(userId); - } else {anonymous.setAnonNumber(userId); - } - postRepository.save(post); - log.info("익명 번호 할당. PostId: {}, UserId: {}", post.get_id(), userId); - } - - /** - * 유저의 익명 번호 조회 - */ - public Integer getUserAnonymousNumber(PostAnonymous postAnonymous, ObjectId userId) { - return postAnonymous.getAnonNumber(userId); - } - - - // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) - private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { - UserDto userDto = resolveUserProfile(post); - int likeCount = seperatedPostService.getLikeCount(post); - int scrapCount = seperatedPostService.getScrapCount(post); - int hitsCount = seperatedPostService.getHitsCount(post); - int commentCount = post.getCommentCount(); - ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo); - if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = pollService.getPollInfo(post, userId); - return PostPageItemResponseDTO.of(postDTO, pollInfo); - } else { - return PostPageItemResponseDTO.of(postDTO, null); - } - } - - // [유저 프로필] - 닉네임/이미지 결정 - private UserDto resolveUserProfile(PostEntity post) { - UserEntity user = userRepository.findById(post.getUserId()) - .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); - return UserDto.ofPost(post, user, s3Service.getDefaultProfileImageUrl()); - } - - // [유저 프로필] - 게시물에 대한 유저정보 추출 - public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ - return UserInfo.of( - likeService.isLiked(LikeType.POST, postId, currentUserId), - scrapService.isPostScraped(postId, currentUserId), - postUserId.equals(currentUserId) - ); - } - - private void validateUserAndPost(PostEntity post) { - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } - SecurityUtils.validateUser(post.getUserId()); - } // ===================== 도메인별 부가 기능/서브 로직 (임시 분리 - SeperatedPostService) ===================== diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java index 13a2bb11..e9ff5e66 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java @@ -40,32 +40,6 @@ public class SeperatedPostService { private final BestRepository bestRepository; private final ScrapService scrapService; private final LikeService likeService; - private final PollRepository pollRepository; - private final PollVoteRepository pollVoteRepository; - - // [ImageService] - 이미지 업로드 처리 - public List handleImageUpload(List postImages) { - return s3Service.handleImageUpload(postImages); - } - - // [ImageService] - 게시글 이미지 삭제 처리 - public void deletePostImageInternal(PostEntity post, String imageUrl) { - if (!post.getPostImageUrls().contains(imageUrl)) { - log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); - } - try { - s3Service.deleteFile(imageUrl); - post.removePostImage(imageUrl); - postRepository.save(post); - log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - } catch (Exception e) { - log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); - throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); - } - } - - // [BestService] - Top 3 베스트 게시물 조회 public List getTop3BestPostsInternal() { Map posts = redisBestService.getBests(); @@ -95,6 +69,31 @@ public void applyBestScoreIfNeeded(PostEntity post) { + // + + // [ImageService] - 이미지 업로드 처리 + public List handleImageUpload(List postImages) { + return s3Service.handleImageUpload(postImages); + } + + // [ImageService] - 게시글 이미지 삭제 처리 + public void deletePostImageInternal(PostEntity post, String imageUrl) { + if (!post.getPostImageUrls().contains(imageUrl)) { + log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); + } + try { + s3Service.deleteFile(imageUrl); + post.removePostImage(imageUrl); + postRepository.save(post); + log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + } catch (Exception e) { + log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); + throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); + } + } + + // [likeService] - 게시글 좋아요 수 조회 public int getLikeCount(PostEntity post) { return likeService.getLikeCount(LikeType.POST, post.get_id()); @@ -119,4 +118,7 @@ public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { } } + + + } From b0bccbb84ed491b0588c63958d74f37e0888b461 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 22:35:47 +0900 Subject: [PATCH 0816/1002] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20Post?= =?UTF-8?q?Service=20->=20Command,=20Query,=20interaction,=20BestPost=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 33 +++++------ .../comment/service/CommentService.java | 13 +++-- .../reply/service/ReplyCommentService.java | 13 +++-- .../post/service/PostCommandService.java | 14 ++--- .../post/service/PostInteractionService.java | 22 +------ .../domain/post/service/PostQueryService.java | 57 ++++++++++++++++--- .../domain/report/service/ReportService.java | 11 ++-- .../domain/user/service/UserService.java | 11 ++-- .../codin/domain/post/PostServiceTest.java | 31 +++++----- 9 files changed, 116 insertions(+), 89 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 59937d90..326fe3f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -10,12 +10,15 @@ import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -28,14 +31,12 @@ @RestController @RequestMapping("/posts") @Validated +@RequiredArgsConstructor @Tag(name = "POST API", description = "게시글 API") public class PostController { - private final PostService postService; - - public PostController(PostService postService) { - this.postService = postService; - } + private final PostCommandService postCommandService; + private final PostQueryService postQueryService; @Operation( summary = "게시물 작성", @@ -48,7 +49,7 @@ public ResponseEntity> createPost( // postImages가 null이면 빈 리스트로 처리 if (postImages == null) postImages = List.of(); - postService.createPost(postCreateRequestDTO, postImages); + postCommandService.createPost(postCreateRequestDTO, postImages); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "게시물이 작성되었습니다.", null)); } @@ -62,7 +63,7 @@ public ResponseEntity> updatePostContent( @RequestPart("postContent") @Valid PostContentUpdateRequestDTO requestDTO, @RequestPart(value = "postImages", required = false) List postImages) { - postService.updatePostContent(postId, requestDTO, postImages); + postCommandService.updatePostContent(postId, requestDTO, postImages); return ResponseEntity.status(HttpStatus.OK) .body(new SingleResponse<>(200, "게시물 내용이 수정되었습니다.", null)); } @@ -74,7 +75,7 @@ public ResponseEntity> updatePostContent( public ResponseEntity> updatePostStatus( @PathVariable String postId, @RequestBody PostStatusUpdateRequestDTO requestDTO) { - postService.updatePostStatus(postId, requestDTO); + postCommandService.updatePostStatus(postId, requestDTO); return ResponseEntity.status(HttpStatus.OK) .body(new SingleResponse<>(200, "게시물 상태가 수정되었습니다.", null)); } @@ -85,7 +86,7 @@ public ResponseEntity> updatePostStatus( public ResponseEntity> updatePostAnonymous( @PathVariable String postId, @RequestBody @Valid PostAnonymousUpdateRequestDTO requestDTO) { - postService.updatePostAnonymous(postId, requestDTO); + postCommandService.updatePostAnonymous(postId, requestDTO); return ResponseEntity.status(HttpStatus.OK) .body(new SingleResponse<>(200, "게시물 익명 설정이 수정되었습니다.", null)); } @@ -97,7 +98,7 @@ public ResponseEntity> updatePostAnonymous( @GetMapping("/category") public ResponseEntity> getAllPosts(@RequestParam PostCategory postCategory, @RequestParam("page") @NotNull int pageNumber) { - PostPageResponse postpages= postService.getAllPosts(postCategory, pageNumber); + PostPageResponse postpages= postQueryService.getAllPosts(postCategory, pageNumber); return ResponseEntity.ok() .body(new SingleResponse<>(200, "카테고리별 삭제 되지 않은 모든 게시물 조회 성공", postpages)); } @@ -106,7 +107,7 @@ public ResponseEntity> getAllPosts(@RequestPara @Operation(summary = "해당 게시물 상세 조회 (댓글 조회는 Comment에서 따로 조회)") @GetMapping("/{postId}") public ResponseEntity> getPostWithDetail(@PathVariable String postId) { - PostPageItemResponseDTO post = postService.getPostWithDetail(postId); + PostPageItemResponseDTO post = postQueryService.getPostWithDetail(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물 상세 조회 성공", post)); } @@ -118,7 +119,7 @@ public ResponseEntity> deletePostImage( @PathVariable String postId, @RequestParam String imageUrl) { - postService.deletePostImage(postId, imageUrl); + postCommandService.deletePostImage(postId, imageUrl); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물 이미지가 삭제되었습니다.", null)); } @@ -126,7 +127,7 @@ public ResponseEntity> deletePostImage( @Operation(summary = "게시물 삭제 (Soft Delete)") @DeleteMapping("/{postId}") public ResponseEntity> softDeletePost(@PathVariable String postId) { - postService.softDeletePost(postId); + postCommandService.softDeletePost(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "게시물이 삭제되었습니다.", null)); } @@ -138,20 +139,20 @@ public ResponseEntity> softDeletePost(@PathVariable String public ResponseEntity> searchPosts(@RequestParam("keyword") @Size(min = 2) String keyword, @RequestParam("pageNumber") @NotNull int pageNumber){ return ResponseEntity.ok() - .body(new SingleResponse<>(200, "'"+keyword+"'"+"으로 검색된 게시글 반환 완료", postService.searchPosts(keyword, pageNumber))); + .body(new SingleResponse<>(200, "'"+keyword+"'"+"으로 검색된 게시글 반환 완료", postQueryService.searchPosts(keyword, pageNumber))); } @Operation(summary = "Top 3 베스트 게시글 가져오기") @GetMapping("/top3") public ResponseEntity> getTop3BestPosts(){ return ResponseEntity.ok() - .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postService.getTop3BestPosts())); + .body(new ListResponse<>(200, "Top3 베스트 게시글 반환 완료", postQueryService.getTop3BestPosts())); } @Operation(summary = "Top3로 선정된 게시글들 모두 가져오기") @GetMapping("/best") public ResponseEntity> getBestPosts(@RequestParam("pageNumber") int pageNumber){ return ResponseEntity.ok() - .body(new SingleResponse<>(200, "Top3로 선정된 게시글들 모두 반환 완료", postService.getBestPosts(pageNumber))); + .body(new SingleResponse<>(200, "Top3로 선정된 게시글들 모두 반환 완료", postQueryService.getBestPosts(pageNumber))); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 3e533529..3534ca97 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -16,6 +16,8 @@ import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; @@ -44,7 +46,10 @@ public class CommentService { private final ReplyCommentService replyCommentService; private final NotificationService notificationService; private final RedisBestService redisBestService; - private final PostService postService; + private final PostCommandService postCommandService; + private final PostQueryService postQueryService; + + private final S3Service s3Service; // 댓글 추가 @@ -59,7 +64,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); commentRepository.save(comment); - postService.handleCommentCreation(post, userId); + postCommandService.handleCommentCreation(post, userId); redisBestService.applyBestScore(1, postId); log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); @@ -82,7 +87,7 @@ public void softDeleteComment(String id) { comment.delete(); commentRepository.save(comment); - postService.decreaseCommentCount(post); + postCommandService.decreaseCommentCount(post); redisBestService.applyBestScore(-1, postId); log.info("삭제된 commentId: {}", commentId); @@ -155,7 +160,7 @@ private CommentResponseDTO buildCommentResponseDTO( Map userMap, String defaultImageUrl) { - int anonNum = postService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); + int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); UserEntity user = userMap.get(comment.getUserId()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index c7927c8d..ed359909 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -16,8 +16,8 @@ import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.service.PostService; -import inu.codin.codin.domain.report.repository.ReportRepository; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisBestService; @@ -44,7 +44,8 @@ public class ReplyCommentService { private final PostRepository postRepository; private final ReplyCommentRepository replyCommentRepository; private final UserRepository userRepository; - private final PostService postService; + private final PostCommandService postCommandService; + private final PostQueryService postQueryService; private final LikeService likeService; private final NotificationService notificationService; @@ -66,7 +67,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { ReplyCommentEntity reply = ReplyCommentEntity.create(commentId, userId, requestDTO); replyCommentRepository.save(reply); - postService.handleCommentCreation(post, userId); + postCommandService.handleCommentCreation(post, userId); redisBestService.applyBestScore(1, post.get_id()); log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", @@ -105,7 +106,7 @@ public void softDeleteReply(String replyId) { reply.delete(); replyCommentRepository.save(reply); - postService.decreaseCommentCount(post); + postCommandService.decreaseCommentCount(post); redisBestService.applyBestScore(-1, postId); log.info("대댓글 성공적 삭제 replyId: {}", replyId); @@ -162,7 +163,7 @@ private CommentResponseDTO buildReplyResponseDTO( ObjectId userId = reply.getUserId(); UserEntity user = userMap.get(userId); - int anonNum = postService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); + int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index d7b4aee6..96f9f5cf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -31,7 +31,7 @@ @RequiredArgsConstructor public class PostCommandService { private final PostRepository postRepository; - private final SeperatedPostService seperatedPostService; + private final PostInteractionService postInteractionService; /** * 게시글 생성 @@ -40,7 +40,7 @@ public class PostCommandService { */ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); - List imageUrls = seperatedPostService.handleImageUpload(postImages); + List imageUrls = postInteractionService.handleImageUpload(postImages); ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && @@ -62,7 +62,7 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); validateUserAndPost(post); - List imageUrls = seperatedPostService.handleImageUpload(postImages); + List imageUrls = postInteractionService.handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); log.info("게시물 수정 성공. PostId: {}", postId); @@ -113,7 +113,7 @@ public void deletePostImage(String postId, String imageUrl) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); validateUserAndPost(post); - seperatedPostService.deletePostImageInternal(post, imageUrl); + postInteractionService.deletePostImageInternal(post, imageUrl); } /** @@ -167,12 +167,6 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { log.info("익명 번호 할당. PostId: {}, UserId: {}", post.get_id(), userId); } - /** - * 유저의 익명 번호 조회 - */ - public Integer getUserAnonymousNumber(PostAnonymous postAnonymous, ObjectId userId) { - return postAnonymous.getAnonNumber(userId); - } private void validateUserAndPost(PostEntity post) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java index 92bb2c9d..57547ab3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java @@ -25,8 +25,7 @@ public class PostInteractionService { private final S3Service s3Service; private final PostRepository postRepository; private final HitsService hitsService; - private final ScrapService scrapService; - private final LikeService likeService; + // [ImageService] - 이미지 업로드 처리 public List handleImageUpload(List postImages) { @@ -50,25 +49,8 @@ public void deletePostImageInternal(PostEntity post, String imageUrl) { } } - - // [likeService] - 게시글 좋아요 수 조회 - public int getLikeCount(PostEntity post) { - return likeService.getLikeCount(LikeType.POST, post.get_id()); - } - - // [ScrapService] - 게시글 스크랩 수 조회 - public int getScrapCount(PostEntity post) { - return scrapService.getScrapCount(post.get_id()); - } - - // [HitsService] - 게시글 조회수 조회 - public int getHitsCount(PostEntity post) { - return hitsService.getHitsCount(post.get_id()); - } - - // [HitsService] - 조회수 증가 처리 - public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { + public void increaseHits(PostEntity post, ObjectId userId) { if (!hitsService.validateHits(post.get_id(), userId)) { hitsService.addHits(post.get_id(), userId); log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 16b15e1c..d072bcf7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -4,6 +4,8 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; import inu.codin.codin.domain.post.domain.poll.service.PollService; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.dto.UserInfo; @@ -11,10 +13,15 @@ import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.service.UserService; +import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -32,10 +39,16 @@ public class PostQueryService { private final PostRepository postRepository; + private final UserRepository userRepository; + private final PollService pollService; private final BlockService blockService; - private final SeperatedPostService seperatedPostService; - + private final PostInteractionService postInteractionService; + private final BestPostService bestPostService; + private final ScrapService scrapService; + private final LikeService likeService; + private final S3Service s3Service; + private final HitsService hitsService; /** * 카테고리별 삭제되지 않은 게시물 목록 조회 * @return PostPageResponse (불변 리스트) @@ -64,7 +77,7 @@ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); ObjectId userId = SecurityUtils.getCurrentUserId(); - seperatedPostService.increaseHitsIfNeeded(post, userId); + postInteractionService.increaseHits(post, userId); return toPageItemDTO(post); } @@ -75,7 +88,7 @@ public Optional getPostDetailById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) .map(post -> { ObjectId userId = SecurityUtils.getCurrentUserId(); - seperatedPostService.increaseHitsIfNeeded(post, userId); + postInteractionService.increaseHits(post, userId); return toPageItemDTO(post); }); } @@ -95,7 +108,7 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { * Top 3 베스트 게시물 조회 (불변 리스트) */ public List getTop3BestPosts() { - List bestPosts = seperatedPostService.getTop3BestPostsInternal(); + List bestPosts = bestPostService.getTop3BestPostsInternal(); log.info("Top 3 베스트 게시물 반환."); return getPostListResponseDtos(bestPosts); } @@ -104,7 +117,7 @@ public List getTop3BestPosts() { * 베스트 게시물 페이지 조회 */ public PostPageResponse getBestPosts(int pageNumber) { - Page page = seperatedPostService.getBestPostsInternal(pageNumber); + Page page = bestPostService.getBestPostsInternal(pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } @@ -113,9 +126,9 @@ public PostPageResponse getBestPosts(int pageNumber) { // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { UserDto userDto = resolveUserProfile(post); - int likeCount = seperatedPostService.getLikeCount(post); - int scrapCount = seperatedPostService.getScrapCount(post); - int hitsCount = seperatedPostService.getHitsCount(post); + int likeCount = getLikeCount(post); + int scrapCount = getScrapCount(post); + int hitsCount = getHitsCount(post); int commentCount = post.getCommentCount(); ObjectId userId = SecurityUtils.getCurrentUserId(); UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); @@ -145,4 +158,30 @@ public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId } + /** + * 유저의 익명 번호 조회 + */ + public Integer getUserAnonymousNumber(PostAnonymous postAnonymous, ObjectId userId) { + return postAnonymous.getAnonNumber(userId); + } + + + // [likeService] - 게시글 좋아요 수 조회 + public int getLikeCount(PostEntity post) { + return likeService.getLikeCount(LikeType.POST, post.get_id()); + } + + // [ScrapService] - 게시글 스크랩 수 조회 + public int getScrapCount(PostEntity post) { + return scrapService.getScrapCount(post.get_id()); + } + + // [HitsService] - 게시글 조회수 조회 + public int getHitsCount(PostEntity post) { + return hitsService.getHitsCount(post.get_id()); + } + + + + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 95421b39..9bce1409 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -13,6 +13,7 @@ import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.report.dto.ReportInfo; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; @@ -47,7 +48,7 @@ @Slf4j public class ReportService { - private final PostService postService; + private final PostQueryService postQueryService; private final CommentService commentService; private final ReplyCommentService replyCommentService; @@ -323,16 +324,16 @@ private Optional getReportedPostDetail(ReportInfo reportI ObjectId entityId = new ObjectId(reportInfo.getReportedEntityId()); return switch (reportInfo.getEntityType()) { - case POST -> postService.getPostDetailById(entityId) + case POST -> postQueryService.getPostDetailById(entityId) .map(pageItem -> ReportListResponseDto.from(pageItem.getPost(), reportInfo)); case COMMENT -> commentRepository.findById(entityId) - .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) + .flatMap(comment -> postQueryService.getPostDetailById(comment.getPostId()) .map(pageItem -> ReportListResponseDto.from(pageItem.getPost(), reportInfo))); case REPLY -> replyCommentRepository.findById(entityId) .flatMap(reply -> commentRepository.findById(reply.getCommentId()) - .flatMap(comment -> postService.getPostDetailById(comment.getPostId()) + .flatMap(comment -> postQueryService.getPostDetailById(comment.getPostId()) .map(pageItem -> ReportListResponseDto.from(pageItem.getPost(), reportInfo)))); default -> Optional.empty(); @@ -348,7 +349,7 @@ public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, St if (!existsInReportDB) { throw new NotFoundException("해당 신고 대상이 존재하지 않습니다. 신고 ID: " + reportedEntityId); } - PostDetailResponseDTO postDetailResponse = postService.getPostDetailById(entityId) + PostDetailResponseDTO postDetailResponse = postQueryService.getPostDetailById(entityId) .map(PostPageItemResponseDTO::getPost) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); boolean isReported = entityId.equals(reportTargetId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 620aaf24..c25c39d0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; @@ -46,7 +47,7 @@ public class UserService { private final ScrapRepository scrapRepository; private final CommentRepository commentRepository; - private final PostService postService; + private final PostQueryService postQueryService; private final S3Service s3Service; private final JwtService jwtService; @@ -61,7 +62,7 @@ public PostPageResponse getAllUserPosts(int pageNumber) { log.info("[게시글 조회 성공] 조회된 게시글 수: {}, 총 페이지 수: {}", page.getContent().size(), page.getTotalPages()); return PostPageResponse.of( - postService.getPostListResponseDtos(page.getContent()), + postQueryService.getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1 ); @@ -81,7 +82,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) .toList(); log.info("[좋아요 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", likePage.getTotalPages(), likePage.hasNext()); - return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages() - 1, likePage.hasNext() ? likePage.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postQueryService.getPostListResponseDtos(postUserLike), likePage.getTotalPages() - 1, likePage.hasNext() ? likePage.getPageable().getPageNumber() + 1 : -1); } case SCRAP -> { log.info("[스크랩 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); @@ -91,7 +92,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) .toList(); log.info("[스크랩 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", scrapPage.getTotalPages(), scrapPage.hasNext()); - return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages() - 1, scrapPage.hasNext() ? scrapPage.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postQueryService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages() - 1, scrapPage.hasNext() ? scrapPage.getPageable().getPageNumber() + 1 : -1); } case COMMENT -> { log.info("[댓글 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); @@ -110,7 +111,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i .stream() .toList(); log.info("[댓글 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", commentPage.getTotalPages(), commentPage.hasNext()); - return PostPageResponse.of(postService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages() - 1, commentPage.hasNext() ? commentPage.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postQueryService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages() - 1, commentPage.hasNext() ? commentPage.getPageable().getPageNumber() + 1 : -1); } default -> { log.warn("[유효하지 않은 상호작용 타입] 유저 ID: {}, 상호작용 타입: {}", userId, interactionType); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java index fc5b686c..3b733edc 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java @@ -21,6 +21,8 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.post.service.SeperatedPostService; import inu.codin.codin.domain.scrap.service.ScrapService; @@ -47,7 +49,8 @@ @ExtendWith(MockitoExtension.class) class PostServiceTest { @InjectMocks - private PostService postService; + private PostCommandService postCommandService; + private PostQueryService postQueryService; @Mock private PostRepository postRepository; @Mock private BestRepository bestRepository; @Mock private UserRepository userRepository; @@ -88,7 +91,7 @@ void tearDown() throws Exception { }); // When // Then - assertThatCode(() -> postService.createPost(dto, images)).doesNotThrowAnyException(); + assertThatCode(() -> postCommandService.createPost(dto, images)).doesNotThrowAnyException(); } @Test @@ -100,7 +103,7 @@ void tearDown() throws Exception { given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); // When & Then - assertThatThrownBy(() -> postService.createPost(dto, images)).isInstanceOf(JwtException.class); + assertThatThrownBy(() -> postCommandService.createPost(dto, images)).isInstanceOf(JwtException.class); } @Test @@ -115,7 +118,7 @@ void updatePostContent_success() throws Exception { given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willReturn(post); // When/Then - assertThatCode(() -> postService.updatePostContent(postId, dto, images)).doesNotThrowAnyException(); + assertThatCode(() -> postCommandService.updatePostContent(postId, dto, images)).doesNotThrowAnyException(); } @Test @@ -126,7 +129,7 @@ void updatePostContent_notFound() throws Exception { List images = List.of(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); // When/Then - assertThatThrownBy(() -> postService.updatePostContent(postId, dto, images)) + assertThatThrownBy(() -> postCommandService.updatePostContent(postId, dto, images)) .isInstanceOf(NotFoundException.class); } @@ -140,7 +143,7 @@ void updatePostAnonymous_success() throws Exception { given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); // When/Then - assertThatCode(() -> postService.updatePostAnonymous(postId, dto)).doesNotThrowAnyException(); + assertThatCode(() -> postCommandService.updatePostAnonymous(postId, dto)).doesNotThrowAnyException(); } @Test @@ -153,7 +156,7 @@ void updatePostStatus_success() throws Exception { given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); // When/Then - assertThatCode(() -> postService.updatePostStatus(postId, dto)).doesNotThrowAnyException(); + assertThatCode(() -> postCommandService.updatePostStatus(postId, dto)).doesNotThrowAnyException(); } @Test @@ -162,7 +165,7 @@ void getAllPosts_success() { List posts = new ArrayList<>(); Page page = new PageImpl<>(posts); given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any())).willReturn(page); - var response = postService.getAllPosts(PostCategory.COMMUNICATION, 0); + var response = postQueryService.getAllPosts(PostCategory.COMMUNICATION, 0); assertThat(response).isNotNull(); assertThat(response.getContents()).isInstanceOf(List.class); } @@ -181,7 +184,7 @@ void getPostWithDetail_success() { given(seperatedPostService.getHitsCount(any())).willReturn(0); given(userRepository.findById(any())).willReturn(Optional.of(UserEntity.builder().nickname("닉네임").profileImageUrl("url").build())); // When - PostPageItemResponseDTO response = postService.getPostWithDetail(postId); + PostPageItemResponseDTO response = postQueryService.getPostWithDetail(postId); // Then assertThat(response).isNotNull(); assertThat(response.getPost()).isNotNull(); @@ -196,7 +199,7 @@ void softDeletePost_success() { given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); // When/Then - assertThatCode(() -> postService.softDeletePost(postId)).doesNotThrowAnyException(); + assertThatCode(() -> postCommandService.softDeletePost(postId)).doesNotThrowAnyException(); } @Test @@ -211,7 +214,7 @@ void deletePostImage_success() throws Exception { doNothing().when(seperatedPostService).deletePostImageInternal(any(), any()); // When/Then - assertThatCode(() -> postService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); + assertThatCode(() -> postCommandService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); } @Test @@ -220,14 +223,14 @@ void searchPosts_success() { List posts = new ArrayList<>(); Page page = new PageImpl<>(posts); given(postRepository.findAllByKeywordAndDeletedAtIsNull(anyString(), anyList(), any())).willReturn(page); - PostPageResponse response = postService.searchPosts("테스트", 0); + PostPageResponse response = postQueryService.searchPosts("테스트", 0); assertThat(response).isNotNull(); } @Test void getTop3BestPosts_success() { given(seperatedPostService.getTop3BestPostsInternal()).willReturn(new ArrayList<>()); - var result = postService.getTop3BestPosts(); + var result = postQueryService.getTop3BestPosts(); assertThat(result).isNotNull(); assertThat(result).isInstanceOf(List.class); } @@ -235,7 +238,7 @@ void getTop3BestPosts_success() { @Test void getBestPosts_success() { given(seperatedPostService.getBestPostsInternal(anyInt())).willReturn(new PageImpl<>(new ArrayList<>())); - var result = postService.getBestPosts(0); + var result = postQueryService.getBestPosts(0); assertThat(result).isNotNull(); assertThat(result.getContents()).isInstanceOf(List.class); } From 7c2b370af0891724efd00dcbcea597f74981398b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 23:23:32 +0900 Subject: [PATCH 0817/1002] =?UTF-8?q?refactor:=20Error=20Code=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/exception/CommentErrorCode.java | 24 ++++++++++++++++ .../comment/exception/CommentException.java | 13 +++++++++ .../comment/service/CommentService.java | 12 ++++---- .../domain/hits/exception/HitsErrorCode.java | 24 ++++++++++++++++ .../domain/hits/exception/HitsException.java | 13 +++++++++ .../post/domain/hits/service/HitsService.java | 2 ++ .../domain/poll/exception/PollErrorCode.java | 28 +++++++++++++++++++ .../domain/poll/exception/PollException.java | 13 +++++++++ .../post/domain/poll/service/PollService.java | 28 +++++++++---------- .../reply/exception/ReplyErrorCode.java | 24 ++++++++++++++++ .../reply/exception/ReplyException.java | 13 +++++++++ .../reply/service/ReplyCommentService.java | 18 ++++++++---- .../codin/domain/post/entity/PostEntity.java | 7 +++-- .../domain/post/exception/PostErrorCode.java | 25 +++++++++++++++++ .../domain/post/exception/PostException.java | 13 +++++++++ .../post/exception/SchedulerException.java | 7 ----- .../post/exception/StateUpdateException.java | 7 ----- .../domain/post/schedular/PostsScheduler.java | 7 +++-- .../exception/SchedulerErrorCode.java | 24 ++++++++++++++++ .../exception/SchedulerException.java | 13 +++++++++ .../domain/post/service/BestPostService.java | 4 ++- .../post/service/PostCommandService.java | 12 ++++---- .../post/service/PostInteractionService.java | 4 ++- .../domain/post/service/PostQueryService.java | 6 ++-- 24 files changed, 286 insertions(+), 55 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java new file mode 100644 index 00000000..ba34e390 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.post.domain.comment.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum CommentErrorCode implements GlobalErrorCode { + COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."), + POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java new file mode 100644 index 00000000..9004c1fd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.domain.comment.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class CommentException extends GlobalException { + private final CommentErrorCode errorCode; + public CommentException(CommentErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 3534ca97..0963ff40 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -11,6 +11,8 @@ import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.exception.CommentException; +import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.entity.PostAnonymous; @@ -57,7 +59,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { ObjectId postId = new ObjectId(id); PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.POST_NOT_FOUND)); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -76,12 +78,12 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { public void softDeleteComment(String id) { ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); SecurityUtils.validateUser(comment.getUserId()); ObjectId postId = comment.getPostId(); PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.POST_NOT_FOUND)); // 댓글 Soft Delete 처리 comment.delete(); @@ -129,7 +131,7 @@ private ObjectId validateAndConvertPostId(String id) { private PostEntity findPostById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.POST_NOT_FOUND)); } /** * 댓글 작성자들의 사용자 정보 맵 생성 @@ -181,7 +183,7 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); //본인 댓글만 수정 가능 ObjectId userId = SecurityUtils.getCurrentUserId(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java new file mode 100644 index 00000000..8f297788 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.post.domain.hits.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum HitsErrorCode implements GlobalErrorCode { + HITS_NOT_FOUND(HttpStatus.NOT_FOUND, "조회수를 찾을 수 없습니다."), + POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java new file mode 100644 index 00000000..26c44b89 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.domain.hits.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class HitsException extends GlobalException { + private final HitsErrorCode errorCode; + public HitsException(HitsErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java index fe97041f..ef3cf220 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java @@ -2,6 +2,8 @@ import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; +import inu.codin.codin.domain.post.domain.hits.exception.HitsException; +import inu.codin.codin.domain.post.domain.hits.exception.HitsErrorCode; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisHitsService; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java new file mode 100644 index 00000000..4da8d44e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.post.domain.poll.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum PollErrorCode implements GlobalErrorCode { + POLL_NOT_FOUND(HttpStatus.NOT_FOUND, "투표 정보를 찾을 수 없습니다."), + POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."), + POLL_FINISHED(HttpStatus.BAD_REQUEST, "이미 종료된 투표입니다."), + POLL_DUPLICATED(HttpStatus.CONFLICT, "이미 투표하셨습니다."), + MULTIPLE_CHOICE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "복수 선택이 허용되지 않은 투표입니다."), + INVALID_OPTION(HttpStatus.BAD_REQUEST, "잘못된 선택지입니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java new file mode 100644 index 00000000..a0cd3bed --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.domain.poll.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class PollException extends GlobalException { + private final PollErrorCode errorCode; + public PollException(PollErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index 1888c6c3..66b4b014 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -1,14 +1,12 @@ package inu.codin.codin.domain.post.domain.poll.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; -import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; -import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; -import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; +import inu.codin.codin.domain.post.domain.poll.exception.PollException; +import inu.codin.codin.domain.post.domain.poll.exception.PollErrorCode; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; @@ -68,37 +66,37 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> { log.warn("투표 실패 - 게시글 없음 - postId: {}", postId); - return new NotFoundException("해당 게시물을 찾을 수 없습니다."); + return new PollException(PollErrorCode.POST_NOT_FOUND); }); PollEntity poll = pollRepository.findByPostId(post.get_id()) .orElseThrow(() -> { log.warn("투표 실패 - 투표 데이터 없음 - postId: {}", postId); - return new NotFoundException("투표 데이터가 존재하지 않습니다."); + return new PollException(PollErrorCode.POLL_NOT_FOUND); }); if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { log.warn("투표 실패 - 투표 종료됨 - pollId: {}", poll.get_id()); - throw new PollTimeFailException("이미 종료된 투표입니다."); + throw new PollException(PollErrorCode.POLL_FINISHED); } ObjectId userId = SecurityUtils.getCurrentUserId(); boolean hasAlreadyVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); if (hasAlreadyVoted) { log.warn("투표 실패 - 중복 투표 - pollId: {}, userId: {}", poll.get_id(), userId); - throw new PollDuplicateVoteException("이미 투표하셨습니다."); + throw new PollException(PollErrorCode.POLL_DUPLICATED); } List selectedOptions = pollRequestDTO.getSelectedOptions(); if (!poll.isMultipleChoice() && selectedOptions.size() > 1) { log.warn("투표 실패 - 복수 선택 허용 안됨 - pollId: {}, userId: {}", poll.get_id(), userId); - throw new PollOptionChoiceException("복수 선택이 허용되지 않은 투표입니다."); + throw new PollException(PollErrorCode.MULTIPLE_CHOICE_NOT_ALLOWED); } for (Integer index : selectedOptions) { if (index < 0 || index >= poll.getPollOptions().size()) { log.warn("투표 실패 - 잘못된 선택지 - pollId: {}, optionIndex: {}", poll.get_id(), index); - throw new PollOptionChoiceException("잘못된 선택지입니다."); + throw new PollException(PollErrorCode.INVALID_OPTION); } } @@ -124,25 +122,25 @@ public void deleteVoting(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> { log.warn("투표 취소 실패 - 게시글 없음 - postId: {}", postId); - return new NotFoundException("해당 게시물을 찾을 수 없습니다."); + return new PollException(PollErrorCode.POST_NOT_FOUND); }); PollEntity poll = pollRepository.findByPostId(post.get_id()) .orElseThrow(() -> { log.warn("투표 취소 실패 - 투표 데이터 없음 - postId: {}", postId); - return new NotFoundException("투표 데이터가 존재하지 않습니다."); + return new PollException(PollErrorCode.POLL_NOT_FOUND); }); if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { log.warn("투표 취소 실패 - 투표 종료됨 - pollId: {}", poll.get_id()); - throw new PollTimeFailException("이미 종료된 투표입니다."); + throw new PollException(PollErrorCode.POLL_FINISHED); } ObjectId userId = SecurityUtils.getCurrentUserId(); PollVoteEntity pollVote = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) .orElseThrow(() -> { log.warn("투표 취소 실패 - 유저 투표 내역 없음 - pollId: {}, userId: {}", poll.get_id(), userId); - return new NotFoundException("유저의 투표 내역이 존재하지 않습니다."); + return new PollException(PollErrorCode.POLL_NOT_FOUND); }); for (Integer index : pollVote.getSelectedOptions()) { @@ -156,7 +154,7 @@ public void deleteVoting(String postId) { public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new NotFoundException("투표 정보를 찾을 수 없습니다.")); + .orElseThrow(() -> new PollException(PollErrorCode.POLL_NOT_FOUND)); long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) .map(PollVoteEntity::getSelectedOptions) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java new file mode 100644 index 00000000..b22b5492 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.post.domain.reply.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum ReplyErrorCode implements GlobalErrorCode { + REPLY_NOT_FOUND(HttpStatus.NOT_FOUND, "대댓글을 찾을 수 없습니다."), + COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java new file mode 100644 index 00000000..d4b7911b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.domain.reply.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class ReplyException extends GlobalException { + private final ReplyErrorCode errorCode; + public ReplyException(ReplyErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index ed359909..f3fb7d10 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -7,14 +7,20 @@ import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; +import inu.codin.codin.domain.post.domain.comment.exception.CommentException; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; +import inu.codin.codin.domain.post.domain.reply.exception.ReplyErrorCode; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostErrorCode; +import inu.codin.codin.domain.post.exception.PostException; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; @@ -56,10 +62,10 @@ public class ReplyCommentService { public void addReply(String id, ReplyCreateRequestDTO requestDTO) { ObjectId commentId = new ObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -79,7 +85,7 @@ public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { ObjectId replyId = new ObjectId(id); ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) - .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); reply.updateReply(requestDTO.getContent()); replyCommentRepository.save(reply); @@ -91,16 +97,16 @@ public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) - .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); SecurityUtils.validateUser(reply.getUserId()); ObjectId commentId = reply.getCommentId(); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); ObjectId postId = comment.getPostId(); PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); // 대댓글 삭제 reply.delete(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 47abda01..13494ca7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -2,7 +2,8 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; -import inu.codin.codin.domain.post.exception.StateUpdateException; +import inu.codin.codin.domain.post.schedular.exception.SchedulerErrorCode; +import inu.codin.codin.domain.post.schedular.exception.SchedulerException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -65,14 +66,14 @@ public void updatePostContent(String content, List postImageUrls) { public void updatePostAnonymous(boolean isAnonymous) { if (this.isAnonymous == isAnonymous) { - throw new StateUpdateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); + throw new SchedulerException(SchedulerErrorCode.DUPLICATE_ANONYMOUS_STATE); } this.isAnonymous = isAnonymous; } public void updatePostStatus(PostStatus postStatus) { if (this.postStatus == postStatus) { - throw new StateUpdateException("현재 상태와 동일한 상태로 변경할 수 없습니다."); + throw new SchedulerException(SchedulerErrorCode.DUPLICATE_POST_STATUS); } this.postStatus = postStatus; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java new file mode 100644 index 00000000..bcd662ab --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.post.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum PostErrorCode implements GlobalErrorCode { + POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."), + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다."), + SCHEDULER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "스케줄러 실행 중 오류가 발생했습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java new file mode 100644 index 00000000..abb17beb --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class PostException extends GlobalException { + private final PostErrorCode errorCode; + public PostException(PostErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java deleted file mode 100644 index 5979ae7f..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/SchedulerException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.post.exception; - -public class SchedulerException extends RuntimeException{ - public SchedulerException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java deleted file mode 100644 index c49a84df..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/StateUpdateException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.post.exception; - -public class StateUpdateException extends RuntimeException{ - public StateUpdateException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index a15280eb..00408987 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.schedular; -import inu.codin.codin.domain.post.exception.SchedulerException; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.exception.PostErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; @@ -40,7 +41,7 @@ public void departmentPostsScheduler() { else log.warn("[PostsScheduler] 학과 공지사항 업데이트 실패"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); - throw new SchedulerException(e.getMessage()); + throw new PostException(PostErrorCode.SCHEDULER_ERROR); } } @@ -63,7 +64,7 @@ public void starinuPostsScheduler(){ else log.warn("[PostsScheduler] STARINU 공지사항 업데이트 실패"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); - throw new SchedulerException(e.getMessage()); + throw new PostException(PostErrorCode.SCHEDULER_ERROR); } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java new file mode 100644 index 00000000..62fcf471 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.post.schedular.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum SchedulerErrorCode implements GlobalErrorCode { + DUPLICATE_ANONYMOUS_STATE(HttpStatus.CONFLICT, "현재 익명 상태와 동일한 상태로 변경할 수 없습니다."), + DUPLICATE_POST_STATUS(HttpStatus.CONFLICT, "현재 게시글 상태와 동일한 상태로 변경할 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java new file mode 100644 index 00000000..0f81030f --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.schedular.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class SchedulerException extends GlobalException { + private final SchedulerErrorCode errorCode; + public SchedulerException(SchedulerErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java index ac634e38..35c81479 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java @@ -5,6 +5,8 @@ import inu.codin.codin.domain.post.domain.best.BestRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.infra.redis.service.RedisBestService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -44,7 +46,7 @@ public Page getBestPostsInternal(int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page bests = bestRepository.findAll(pageRequest); return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND))); } // [BestService] - 베스트 점수 적용 처리 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index 96f9f5cf..af2787c9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -13,6 +13,8 @@ import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserRole; @@ -60,7 +62,7 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 수정 시작. PostId: {}", postId); PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); validateUserAndPost(post); List imageUrls = postInteractionService.handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); @@ -73,7 +75,7 @@ public void updatePostContent(String postId, PostContentUpdateRequestDTO request */ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); validateUserAndPost(post); post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); @@ -85,7 +87,7 @@ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO req */ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("해당 게시물 없음. id=" + postId)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); validateUserAndPost(post); post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); @@ -99,7 +101,7 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT */ public void softDeletePost(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없음. id=" + postId)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); validateUserAndPost(post); post.delete(); log.info("게시물 안전 삭제. PostId: {}", postId); @@ -111,7 +113,7 @@ public void softDeletePost(String postId) { */ public void deletePostImage(String postId, String imageUrl) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); validateUserAndPost(post); postInteractionService.deletePostImageInternal(post, imageUrl); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java index 57547ab3..c5b4c9c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java @@ -9,6 +9,8 @@ import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.exception.PostErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -36,7 +38,7 @@ public List handleImageUpload(List postImages) { public void deletePostImageInternal(PostEntity post, String imageUrl) { if (!post.getPostImageUrls().contains(imageUrl)) { log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); + throw new PostException(PostErrorCode.POST_NOT_FOUND); } try { s3Service.deleteFile(imageUrl); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index d072bcf7..33913c7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -16,6 +16,8 @@ import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserEntity; @@ -75,7 +77,7 @@ public List getPostListResponseDtos(List po */ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다. id=" + postId)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); ObjectId userId = SecurityUtils.getCurrentUserId(); postInteractionService.increaseHits(post, userId); return toPageItemDTO(post); @@ -144,7 +146,7 @@ private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { // [유저 프로필] - 닉네임/이미지 결정 private UserDto resolveUserProfile(PostEntity post) { UserEntity user = userRepository.findById(post.getUserId()) - .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다.")); + .orElseThrow(() -> new PostException(PostErrorCode.USER_NOT_FOUND)); return UserDto.ofPost(post, user, s3Service.getDefaultProfileImageUrl()); } From 965eb0928c891064ad8c0687b5bee911e2dddf4c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 23:33:30 +0900 Subject: [PATCH 0818/1002] =?UTF-8?q?refactor:=20service=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20test=20code=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/PostServiceTest.java | 55 +++++-------- .../domain/hits/service/HitsServiceTest.java | 79 ------------------- 2 files changed, 20 insertions(+), 114 deletions(-) delete mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java index 3b733edc..0f5357d8 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java @@ -4,34 +4,17 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.best.BestEntity; -import inu.codin.codin.domain.post.domain.best.BestRepository; -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; -import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; -import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; -import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.dto.request.*; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.service.PostCommandService; -import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.post.service.PostService; -import inu.codin.codin.domain.post.service.SeperatedPostService; -import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.post.service.*; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisBestService; -import inu.codin.codin.infra.s3.S3Service; -import inu.codin.codin.infra.s3.exception.ImageRemoveException; import org.bson.types.ObjectId; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -50,17 +33,19 @@ class PostServiceTest { @InjectMocks private PostCommandService postCommandService; + + @InjectMocks private PostQueryService postQueryService; + + @InjectMocks + private PostInteractionService postInteractionService; + + @InjectMocks + private BestPostService bestpostService; + @Mock private PostRepository postRepository; - @Mock private BestRepository bestRepository; @Mock private UserRepository userRepository; - @Mock private S3Service s3Service; - @Mock private LikeService likeService; - @Mock private ScrapService scrapService; - @Mock private HitsService hitsService; - @Mock private RedisBestService redisBestService; @Mock private BlockService blockService; - @Mock private SeperatedPostService seperatedPostService; private static AutoCloseable securityUtilsMock; @BeforeEach @@ -81,7 +66,7 @@ void tearDown() throws Exception { ObjectId userId = new ObjectId(); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); - given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willAnswer(inv -> { PostEntity entity = inv.getArgument(0); java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); @@ -101,7 +86,7 @@ void tearDown() throws Exception { List images = new ArrayList<>(); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); - given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); // When & Then assertThatThrownBy(() -> postCommandService.createPost(dto, images)).isInstanceOf(JwtException.class); } @@ -115,7 +100,7 @@ void updatePostContent_success() throws Exception { PostEntity post = createPostEntity(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - given(seperatedPostService.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willReturn(post); // When/Then assertThatCode(() -> postCommandService.updatePostContent(postId, dto, images)).doesNotThrowAnyException(); @@ -178,10 +163,10 @@ void getPostWithDetail_success() { given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - doNothing().when(seperatedPostService).increaseHitsIfNeeded(any(), any()); - given(seperatedPostService.getLikeCount(any())).willReturn(0); - given(seperatedPostService.getScrapCount(any())).willReturn(0); - given(seperatedPostService.getHitsCount(any())).willReturn(0); + doNothing().when(postInteractionService).increaseHits(any(), any()); + given(postQueryService.getLikeCount(any())).willReturn(0); + given(postQueryService.getScrapCount(any())).willReturn(0); + given(postQueryService.getHitsCount(any())).willReturn(0); given(userRepository.findById(any())).willReturn(Optional.of(UserEntity.builder().nickname("닉네임").profileImageUrl("url").build())); // When PostPageItemResponseDTO response = postQueryService.getPostWithDetail(postId); @@ -211,7 +196,7 @@ void deletePostImage_success() throws Exception { PostEntity post = PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).postImageUrls(imageList).build(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - doNothing().when(seperatedPostService).deletePostImageInternal(any(), any()); + doNothing().when(postInteractionService).deletePostImageInternal(any(), any()); // When/Then assertThatCode(() -> postCommandService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); @@ -229,7 +214,7 @@ void searchPosts_success() { @Test void getTop3BestPosts_success() { - given(seperatedPostService.getTop3BestPostsInternal()).willReturn(new ArrayList<>()); + given(bestpostService.getTop3BestPostsInternal()).willReturn(new ArrayList<>()); var result = postQueryService.getTop3BestPosts(); assertThat(result).isNotNull(); assertThat(result).isInstanceOf(List.class); @@ -237,7 +222,7 @@ void getTop3BestPosts_success() { @Test void getBestPosts_success() { - given(seperatedPostService.getBestPostsInternal(anyInt())).willReturn(new PageImpl<>(new ArrayList<>())); + given(bestpostService.getBestPostsInternal(anyInt())).willReturn(new PageImpl<>(new ArrayList<>())); var result = postQueryService.getBestPosts(0); assertThat(result).isNotNull(); assertThat(result.getContents()).isInstanceOf(List.class); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java deleted file mode 100644 index e2bc6873..00000000 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/service/HitsServiceTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package inu.codin.codin.domain.post.domain.hits.service; - -import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; -import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; -import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisHitsService; -import org.bson.types.ObjectId; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class HitsServiceTest { - @InjectMocks - private HitsService hitsService; - @Mock private RedisHitsService redisHitsService; - @Mock private RedisHealthChecker redisHealthChecker; - @Mock private HitsRepository hitsRepository; - - @Test - void 게시글_조회수_추가_성공() { - // Given - ObjectId postId = new ObjectId(); - ObjectId userId = new ObjectId(); - given(redisHealthChecker.isRedisAvailable()).willReturn(true); - willDoNothing().given(redisHitsService).addHits(postId); - given(hitsRepository.save(any())).willAnswer(inv -> inv.getArgument(0)); - // When - hitsService.addHits(postId, userId); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(HitsEntity.class); - verify(hitsRepository).save(captor.capture()); - HitsEntity saved = captor.getValue(); - assertThat(saved.getPostId()).isEqualTo(postId); - assertThat(saved.getUserId()).isEqualTo(userId); - } - - @Test - void 게시글_조회여부_판단_성공() { - // Given - ObjectId postId = new ObjectId(); - ObjectId userId = new ObjectId(); - given(hitsRepository.existsByPostIdAndUserId(postId, userId)).willReturn(true); - // When - boolean result = hitsService.validateHits(postId, userId); - // Then - assertThat(result).isTrue(); - } - - @Test - void 게시글_조회수_반환_캐시있음() { - // Given - ObjectId postId = new ObjectId(); - given(redisHealthChecker.isRedisAvailable()).willReturn(true); - given(redisHitsService.getHitsCount(postId)).willReturn("5"); - // When - int result = hitsService.getHitsCount(postId); - // Then - assertThat(result).isEqualTo(5); - } - - @Test - void 게시글_조회수_반환_캐시없음_DB조회() { - // Given - ObjectId postId = new ObjectId(); - given(redisHealthChecker.isRedisAvailable()).willReturn(true); - given(redisHitsService.getHitsCount(postId)).willReturn(null); - given(hitsRepository.countAllByPostId(postId)).willReturn(7); - // When - int result = hitsService.getHitsCount(postId); - // Then - assertThat(result).isEqualTo(7); - } -} \ No newline at end of file From 101a36baa1cb5276947675fd581a904fdb5c7244 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 11 Jul 2025 23:50:14 +0900 Subject: [PATCH 0819/1002] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 2 - .../comment/service/CommentService.java | 2 - .../domain/post/service/PostService.java | 53 -------- .../post/service/SeperatedPostService.java | 124 ------------------ .../domain/report/service/ReportService.java | 1 - .../domain/user/service/UserService.java | 1 - 6 files changed, 183 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 326fe3f9..60bb3976 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -6,13 +6,11 @@ import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.post.service.PostService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 0963ff40..6b1d31f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.post.domain.comment.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; @@ -20,7 +19,6 @@ import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.redis.service.RedisBestService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java deleted file mode 100644 index bdceba43..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ /dev/null @@ -1,53 +0,0 @@ -package inu.codin.codin.domain.post.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.poll.service.PollService; -import inu.codin.codin.domain.post.dto.UserDto; -import inu.codin.codin.domain.post.dto.UserInfo; -import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; -import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; -import inu.codin.codin.domain.post.dto.response.*; -import inu.codin.codin.domain.post.entity.PostAnonymous; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.scrap.service.ScrapService; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.entity.UserRole; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.s3.S3Service; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.util.*; - -@Slf4j -@Service -@RequiredArgsConstructor -public class PostService { - - - - - - - - - // ===================== 도메인별 부가 기능/서브 로직 (임시 분리 - SeperatedPostService) ===================== - -} - diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java deleted file mode 100644 index e9ff5e66..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/SeperatedPostService.java +++ /dev/null @@ -1,124 +0,0 @@ -package inu.codin.codin.domain.post.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.best.BestEntity; -import inu.codin.codin.domain.post.domain.best.BestRepository; -import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; -import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; -import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; -import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; -import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.scrap.service.ScrapService; -import inu.codin.codin.infra.redis.service.RedisBestService; -import inu.codin.codin.infra.s3.S3Service; -import inu.codin.codin.infra.s3.exception.ImageRemoveException; -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.*; - -@Slf4j -@Service -@RequiredArgsConstructor -public class SeperatedPostService { - private final S3Service s3Service; - private final PostRepository postRepository; - private final HitsService hitsService; - private final RedisBestService redisBestService; - private final BestRepository bestRepository; - private final ScrapService scrapService; - private final LikeService likeService; - // [BestService] - Top 3 베스트 게시물 조회 - public List getTop3BestPostsInternal() { - Map posts = redisBestService.getBests(); - return posts.entrySet().stream() - .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) - .orElseGet(() -> { - redisBestService.deleteBest(post.getKey()); - return null; - })) - .filter(Objects::nonNull) - .toList(); - } - - // [BestService] - 베스트 게시물 페이지 조회 - public Page getBestPostsInternal(int pageNumber) { - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page bests = bestRepository.findAll(pageRequest); - return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다."))); - } - - // [BestService] - 베스트 점수 적용 처리 - public void applyBestScoreIfNeeded(PostEntity post) { - redisBestService.applyBestScore(1, post.get_id()); - log.info("베스트 점수 적용. PostId: {}", post.get_id()); - } - - - - // - - // [ImageService] - 이미지 업로드 처리 - public List handleImageUpload(List postImages) { - return s3Service.handleImageUpload(postImages); - } - - // [ImageService] - 게시글 이미지 삭제 처리 - public void deletePostImageInternal(PostEntity post, String imageUrl) { - if (!post.getPostImageUrls().contains(imageUrl)) { - log.error("게시물에 이미지 없음. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - throw new NotFoundException("이미지가 게시물에 존재하지 않습니다."); - } - try { - s3Service.deleteFile(imageUrl); - post.removePostImage(imageUrl); - postRepository.save(post); - log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); - } catch (Exception e) { - log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); - throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); - } - } - - - // [likeService] - 게시글 좋아요 수 조회 - public int getLikeCount(PostEntity post) { - return likeService.getLikeCount(LikeType.POST, post.get_id()); - } - - // [ScrapService] - 게시글 스크랩 수 조회 - public int getScrapCount(PostEntity post) { - return scrapService.getScrapCount(post.get_id()); - } - - // [HitsService] - 게시글 조회수 조회 - public int getHitsCount(PostEntity post) { - return hitsService.getHitsCount(post.get_id()); - } - - - // [HitsService] - 조회수 증가 처리 - public void increaseHitsIfNeeded(PostEntity post, ObjectId userId) { - if (!hitsService.validateHits(post.get_id(), userId)) { - hitsService.addHits(post.get_id(), userId); - log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); - } - } - - - - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 9bce1409..ecea7f8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -14,7 +14,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.report.dto.ReportInfo; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index c25c39d0..db321bb8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -12,7 +12,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.post.service.PostService; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; From 84e62008184983e61c05fa9c075333026406d103 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 14 Jul 2025 23:17:27 +0900 Subject: [PATCH 0820/1002] =?UTF-8?q?refactor:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=EC=97=90=EC=84=9C=20=5Fid=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0,=20MongoDB=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=83=9D=EC=84=B1=EC=97=90=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/comment/entity/CommentEntity.java | 4 +--- .../codin/domain/post/domain/poll/entity/PollEntity.java | 3 +-- .../codin/domain/post/domain/poll/entity/PollVoteEntity.java | 3 +-- .../domain/post/domain/reply/entity/ReplyCommentEntity.java | 4 +--- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 4 +--- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 1dda85be..a4ad7a21 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -22,8 +22,7 @@ public class CommentEntity extends BaseTimeEntity { private boolean anonymous; @Builder - public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String content, Boolean anonymous) { - this._id = _id; + public CommentEntity(ObjectId postId, ObjectId userId, String content, Boolean anonymous) { this.postId = postId; this.userId = userId; this.content = content; @@ -32,7 +31,6 @@ public CommentEntity(ObjectId _id, ObjectId postId, ObjectId userId, String cont public static CommentEntity create(ObjectId postId, ObjectId userId, CommentCreateRequestDTO requestDTO) { return new CommentEntity( - new ObjectId(), postId, userId, requestDTO.getContent(), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 7103b58c..affbc611 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -32,9 +32,8 @@ public class PollEntity extends BaseTimeEntity { @Builder - public PollEntity(ObjectId _id, ObjectId postId, List pollOptions, + public PollEntity(ObjectId postId, List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice) { - this._id = _id; this.postId = postId; this.pollOptions = pollOptions; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java index 4a85aea7..d424e56e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java @@ -23,8 +23,7 @@ public class PollVoteEntity { private List selectedOptions; // 선택한 옵션 인덱스 @Builder - public PollVoteEntity(ObjectId _id, ObjectId pollId, ObjectId userId, List selectedOptions) { - this._id = _id; + public PollVoteEntity(ObjectId pollId, ObjectId userId, List selectedOptions) { this.pollId = pollId; this.userId = userId; this.selectedOptions = selectedOptions; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index 7722406e..56153764 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -23,8 +23,7 @@ public class ReplyCommentEntity extends BaseTimeEntity { private boolean anonymous; @Builder - public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId,String content, boolean anonymous) { - this._id = _id; + public ReplyCommentEntity(ObjectId commentId, ObjectId userId,String content, boolean anonymous) { this.commentId = commentId; this.userId = userId; this.content = content; @@ -33,7 +32,6 @@ public ReplyCommentEntity(ObjectId _id, ObjectId commentId, ObjectId userId,Stri public static ReplyCommentEntity create(ObjectId commentId, ObjectId userId, ReplyCreateRequestDTO requestDTO) { return new ReplyCommentEntity( - new ObjectId(), commentId, userId, requestDTO.getContent(), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 13494ca7..da3eb86e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -35,8 +35,7 @@ public class PostEntity extends BaseTimeEntity { private PostAnonymous anonymous = new PostAnonymous(); @Builder - public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus) { - this._id = _id; + public PostEntity(ObjectId userId, PostCategory postCategory, String title, String content, List postImageUrls ,boolean isAnonymous, PostStatus postStatus) { this.userId = userId; this.title = title; this.content = content; @@ -48,7 +47,6 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri public static PostEntity create(ObjectId userId, PostCreateRequestDTO dto, List imageUrls) { return new PostEntity( - new ObjectId(), userId, dto.getPostCategory(), dto.getTitle(), From 10fd61177379059fe745b75c3edad187252c7d80 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 15 Jul 2025 00:04:26 +0900 Subject: [PATCH 0821/1002] =?UTF-8?q?refactor:=20id=20=ED=98=95=EB=B3=80?= =?UTF-8?q?=ED=99=98ObjectUtil=20=EC=82=AC=EC=9A=A9=20=EB=B0=8F=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/exception/CommentErrorCode.java | 3 +-- .../comment/service/CommentService.java | 27 ++++++++----------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java index ba34e390..6da27025 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java @@ -6,8 +6,7 @@ @RequiredArgsConstructor public enum CommentErrorCode implements GlobalErrorCode { - COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."), - POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."); + COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 6b1d31f5..07bff5a9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.service; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; @@ -16,6 +17,8 @@ import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostErrorCode; +import inu.codin.codin.domain.post.exception.PostException; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; @@ -55,9 +58,9 @@ public class CommentService { // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { - ObjectId postId = new ObjectId(id); - PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new CommentException(CommentErrorCode.POST_NOT_FOUND)); + ObjectId postId = ObjectIdUtil.toObjectId(id); + PostEntity post = postRepository.findById(postId) + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -74,14 +77,14 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { // 댓글 삭제 (Soft Delete) public void softDeleteComment(String id) { - ObjectId commentId = new ObjectId(id); + ObjectId commentId = ObjectIdUtil.toObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); SecurityUtils.validateUser(comment.getUserId()); ObjectId postId = comment.getPostId(); PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new CommentException(CommentErrorCode.POST_NOT_FOUND)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); // 댓글 Soft Delete 처리 comment.delete(); @@ -99,7 +102,7 @@ public void softDeleteComment(String id) { */ public List getCommentsByPostId(String id) { // 1. 입력 검증 및 게시물 조회 - ObjectId postId = validateAndConvertPostId(id); + ObjectId postId = ObjectIdUtil.toObjectId(id); PostEntity post = findPostById(postId); // 2. 댓글 목록 조회 @@ -118,18 +121,10 @@ public List getCommentsByPostId(String id) { .collect(Collectors.toList()); } - /** - * 게시물 ID 검증 및 ObjectId 변환 - */ - private ObjectId validateAndConvertPostId(String id) { - if (id == null || id.trim().isEmpty()) { - throw new IllegalArgumentException("게시물 ID는 필수입니다."); - }return new ObjectId(id); - } private PostEntity findPostById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new CommentException(CommentErrorCode.POST_NOT_FOUND)); + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); } /** * 댓글 작성자들의 사용자 정보 맵 생성 @@ -179,7 +174,7 @@ private CommentResponseDTO buildCommentResponseDTO( public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); - ObjectId commentId = new ObjectId(id); + ObjectId commentId = ObjectIdUtil.toObjectId(id); CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); From 6c687f9992aefd3fbc1aa4a72968068a763c34f0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 19:42:28 +0900 Subject: [PATCH 0822/1002] =?UTF-8?q?fix=20:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=20origin=20=EC=84=9C=EB=B2=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/inu/codin/codin/common/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 75888707..f0552cc6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -186,6 +186,7 @@ public CorsConfigurationSource corsConfigurationSource() { "http://localhost:8080", BASEURL, "https://front-end-peach-two.vercel.app", + "https://front-end-dun-mu.vercel.app", "https://e876-2406-5900-1080-882f-b519-f7cf-62b3-4ba4.ngrok-free.app", "http://e876-2406-5900-1080-882f-b519-f7cf-62b3-4ba4.ngrok-free.app", "https://appleid.apple.com" From 2130cd18e24f12fdfef3d679179b3aca905d45e3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 21:44:42 +0900 Subject: [PATCH 0823/1002] =?UTF-8?q?fix=20:=20google=20oauth2=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20=EC=B8=A1=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 4 ++-- .../controller/AppleEventController.java | 13 ++----------- .../security/controller/AuthController.java | 16 ++++++++++------ .../security/service/AuthCommonService.java | 2 +- .../security/service/AuthSessionService.java | 18 ++++++++++++++++++ .../{ => oauth2}/AbstractAuthService.java | 16 +++------------- .../{ => oauth2}/AppleAuthService.java | 3 ++- .../{ => oauth2}/AppleOAuth2UserService.java | 2 +- .../{ => oauth2}/CustomOAuth2UserService.java | 2 +- .../{ => oauth2}/Oauth2AuthService.java | 2 +- .../util/OAuth2LoginSuccessHandler.java | 19 +++++++++++-------- 11 files changed, 52 insertions(+), 45 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/AbstractAuthService.java (66%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/AppleAuthService.java (96%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/AppleOAuth2UserService.java (98%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/CustomOAuth2UserService.java (98%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/Oauth2AuthService.java (84%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index f0552cc6..409b872c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -6,8 +6,8 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.common.security.service.AppleOAuth2UserService; -import inu.codin.codin.common.security.service.CustomOAuth2UserService; +import inu.codin.codin.common.security.service.oauth2.AppleOAuth2UserService; +import inu.codin.codin.common.security.service.oauth2.CustomOAuth2UserService; import inu.codin.codin.common.security.util.*; import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java index 33c6a6cd..457bb4ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -1,10 +1,5 @@ package inu.codin.codin.common.security.controller; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; -import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; -import inu.codin.codin.common.security.service.AppleOAuth2UserService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -12,21 +7,17 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.Map; -import java.util.stream.Collectors; @RestController @RequiredArgsConstructor @Slf4j public class AppleEventController { - private final ObjectMapper objectMapper; - @PostMapping("/login/oauth2/code/apple") public ResponseEntity appleCallback(HttpServletRequest request, HttpServletResponse response) { try { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 8ab3da2c..ba1df43e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -3,12 +3,14 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthCommonService; +import inu.codin.codin.common.security.service.AuthSessionService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -26,10 +28,13 @@ public class AuthController { private final JwtService jwtService; private final AuthCommonService authCommonService; + private final AuthSessionService authSessionService; @GetMapping("/google") - public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/api/oauth2/authorization/google"); + public ResponseEntity> googleLogin(HttpServletResponse response, + @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { + authSessionService.setSession(redirect_url); + response.sendRedirect("/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } @@ -57,10 +62,9 @@ public ResponseEntity> devAppleLogin(HttpServletResponse respo @Operation(summary = "회원 정보 입력 마무리") @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> completeUserProfile( - @RequestPart @Valid UserProfileRequestDto userProfileRequestDto, - @RequestPart(value = "userImage", required = false) MultipartFile userImage, - HttpServletResponse response) { + public ResponseEntity> completeUserProfile(@RequestPart @Valid UserProfileRequestDto userProfileRequestDto, + @RequestPart(value = "userImage", required = false) MultipartFile userImage, + HttpServletResponse response) { authCommonService.completeUserProfile(userProfileRequestDto, userImage, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 4ea8107b..56fbe406 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; @@ -9,7 +10,6 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java new file mode 100644 index 00000000..d019b15c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java @@ -0,0 +1,18 @@ +package inu.codin.codin.common.security.service; + +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +@Service +@RequiredArgsConstructor +public class AuthSessionService { + + private final HttpSession httpSession; + + public void setSession(String redirectUrl) { + if (!Objects.equals(redirectUrl, null)) httpSession.setAttribute("redirect_url", redirectUrl); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java similarity index 66% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java index d76dab54..20df4266 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java @@ -1,26 +1,16 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java index 9df979bb..21ccfec4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java @@ -1,8 +1,9 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java index 71d75bad..4ae07d16 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java index 051558d4..936ca98b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.security.dto.AccessEmailProperties; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java index bd2596f0..4c01f481 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.security.enums.AuthResultStatus; import jakarta.servlet.http.HttpServletResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index aa935779..50958c0a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,9 +1,8 @@ package inu.codin.codin.common.security.util; -import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.AppleAuthService; -import inu.codin.codin.common.security.service.GoogleAuthService; +import inu.codin.codin.common.security.service.oauth2.AppleAuthService; +import inu.codin.codin.common.security.service.oauth2.GoogleAuthService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -19,6 +18,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.Objects; @Component @RequiredArgsConstructor @@ -55,25 +55,28 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); + String redirectUrl = request.getSession().getAttribute("redirect_url").toString(); + if (Objects.equals(redirectUrl, null)) redirectUrl = BASEURL; + switch (result) { case LOGIN_SUCCESS -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/main"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/main"); log.info("{\"code\":200, \"message\":\"정상 로그인 완료: {}\"}", email); } case NEW_USER_REGISTERED -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료: {}\"}", email); } case PROFILE_INCOMPLETE -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료: {}\"}", email); } case SUSPENDED_USER -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/api/suspends"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/api/suspends"); log.info("{\"code\":200, \"message\":\"정지된 회원: {}\"}", email); } default -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/login"); log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생: {}\"}", email); } } From 46e4343579f9bbb1e12e5a3e222c69ac2fa8fd0d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 21:44:50 +0900 Subject: [PATCH 0824/1002] =?UTF-8?q?fix=20:=20google=20oauth2=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20=EC=B8=A1=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/{ => oauth2}/GoogleAuthService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/GoogleAuthService.java (97%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index 64e03c8a..557562b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -1,15 +1,15 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; From 90edfd310b70de8eda6b0753dff360cf6e0a8032 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 21:46:58 +0900 Subject: [PATCH 0825/1002] =?UTF-8?q?fix=20:=20google=20oauth2=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20=EC=B8=A1=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index ba1df43e..7bff17a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -10,7 +10,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -34,7 +33,7 @@ public class AuthController { public ResponseEntity> googleLogin(HttpServletResponse response, @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { authSessionService.setSession(redirect_url); - response.sendRedirect("/oauth2/authorization/google"); + response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } From 85e347ef04be9aadebd4171bc18db6182a798ce3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:00:59 +0900 Subject: [PATCH 0826/1002] =?UTF-8?q?Revert=20"fix=20:=20google=20oauth2?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 2130cd18e24f12fdfef3d679179b3aca905d45e3. --- .../codin/common/security/controller/AuthController.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 7bff17a3..2e3172ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -61,9 +61,10 @@ public ResponseEntity> devAppleLogin(HttpServletResponse respo @Operation(summary = "회원 정보 입력 마무리") @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> completeUserProfile(@RequestPart @Valid UserProfileRequestDto userProfileRequestDto, - @RequestPart(value = "userImage", required = false) MultipartFile userImage, - HttpServletResponse response) { + public ResponseEntity> completeUserProfile( + @RequestPart @Valid UserProfileRequestDto userProfileRequestDto, + @RequestPart(value = "userImage", required = false) MultipartFile userImage, + HttpServletResponse response) { authCommonService.completeUserProfile(userProfileRequestDto, userImage, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); From 914b240e2f18f141a3362cafeb35c738b0ae8ac5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:02:31 +0900 Subject: [PATCH 0827/1002] =?UTF-8?q?Revert=20"fix=20:=20google=20oauth2?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 90edfd310b70de8eda6b0753dff360cf6e0a8032. --- .../codin/codin/common/security/controller/AuthController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 2e3172ab..5245b546 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -33,7 +34,7 @@ public class AuthController { public ResponseEntity> googleLogin(HttpServletResponse response, @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { authSessionService.setSession(redirect_url); - response.sendRedirect("/api/oauth2/authorization/google"); + response.sendRedirect("/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } From 897b85e0674d08230f5ed4348458f5746fedc5ae Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:02:35 +0900 Subject: [PATCH 0828/1002] =?UTF-8?q?Revert=20"fix=20:=20google=20oauth2?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 46e4343579f9bbb1e12e5a3e222c69ac2fa8fd0d. --- .../security/service/{oauth2 => }/GoogleAuthService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{oauth2 => }/GoogleAuthService.java (97%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java index 557562b3..64e03c8a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java @@ -1,15 +1,15 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; From 5d534bed44ba00d25f09921184677dba024ea7ac Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:02:41 +0900 Subject: [PATCH 0829/1002] =?UTF-8?q?Revert=20"fix=20:=20google=20oauth2?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Param?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 2130cd18e24f12fdfef3d679179b3aca905d45e3. --- .../codin/common/config/SecurityConfig.java | 4 ++-- .../controller/AppleEventController.java | 13 +++++++++++-- .../security/controller/AuthController.java | 9 ++------- .../{oauth2 => }/AbstractAuthService.java | 16 +++++++++++++--- .../{oauth2 => }/AppleAuthService.java | 3 +-- .../{oauth2 => }/AppleOAuth2UserService.java | 2 +- .../security/service/AuthCommonService.java | 2 +- .../security/service/AuthSessionService.java | 18 ------------------ .../{oauth2 => }/CustomOAuth2UserService.java | 2 +- .../{oauth2 => }/Oauth2AuthService.java | 2 +- .../util/OAuth2LoginSuccessHandler.java | 19 ++++++++----------- 11 files changed, 41 insertions(+), 49 deletions(-) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{oauth2 => }/AbstractAuthService.java (66%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{oauth2 => }/AppleAuthService.java (96%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{oauth2 => }/AppleOAuth2UserService.java (98%) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java rename codin-core/src/main/java/inu/codin/codin/common/security/service/{oauth2 => }/CustomOAuth2UserService.java (98%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{oauth2 => }/Oauth2AuthService.java (84%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 409b872c..f0552cc6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -6,8 +6,8 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.common.security.service.oauth2.AppleOAuth2UserService; -import inu.codin.codin.common.security.service.oauth2.CustomOAuth2UserService; +import inu.codin.codin.common.security.service.AppleOAuth2UserService; +import inu.codin.codin.common.security.service.CustomOAuth2UserService; import inu.codin.codin.common.security.util.*; import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java index 457bb4ec..33c6a6cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -1,5 +1,10 @@ package inu.codin.codin.common.security.controller; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; +import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; +import inu.codin.codin.common.security.service.AppleOAuth2UserService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -7,17 +12,21 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.*; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.Map; +import java.util.stream.Collectors; @RestController @RequiredArgsConstructor @Slf4j public class AppleEventController { + private final ObjectMapper objectMapper; + @PostMapping("/login/oauth2/code/apple") public ResponseEntity appleCallback(HttpServletRequest request, HttpServletResponse response) { try { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 5245b546..8ab3da2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -3,14 +3,12 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthCommonService; -import inu.codin.codin.common.security.service.AuthSessionService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -28,13 +26,10 @@ public class AuthController { private final JwtService jwtService; private final AuthCommonService authCommonService; - private final AuthSessionService authSessionService; @GetMapping("/google") - public ResponseEntity> googleLogin(HttpServletResponse response, - @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { - authSessionService.setSession(redirect_url); - response.sendRedirect("/oauth2/authorization/google"); + public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { + response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java similarity index 66% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java index 20df4266..d76dab54 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java @@ -1,16 +1,26 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java index 21ccfec4..9df979bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java @@ -1,9 +1,8 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java index 4ae07d16..71d75bad 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 56fbe406..4ea8107b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; @@ -10,6 +9,7 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java deleted file mode 100644 index d019b15c..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java +++ /dev/null @@ -1,18 +0,0 @@ -package inu.codin.codin.common.security.service; - -import jakarta.servlet.http.HttpSession; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.Objects; - -@Service -@RequiredArgsConstructor -public class AuthSessionService { - - private final HttpSession httpSession; - - public void setSession(String redirectUrl) { - if (!Objects.equals(redirectUrl, null)) httpSession.setAttribute("redirect_url", redirectUrl); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java index 936ca98b..051558d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; import inu.codin.codin.common.security.dto.AccessEmailProperties; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java index 4c01f481..bd2596f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; import inu.codin.codin.common.security.enums.AuthResultStatus; import jakarta.servlet.http.HttpServletResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 50958c0a..aa935779 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,8 +1,9 @@ package inu.codin.codin.common.security.util; +import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.oauth2.AppleAuthService; -import inu.codin.codin.common.security.service.oauth2.GoogleAuthService; +import inu.codin.codin.common.security.service.AppleAuthService; +import inu.codin.codin.common.security.service.GoogleAuthService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -18,7 +19,6 @@ import java.io.IOException; import java.io.PrintWriter; -import java.util.Objects; @Component @RequiredArgsConstructor @@ -55,28 +55,25 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - String redirectUrl = request.getSession().getAttribute("redirect_url").toString(); - if (Objects.equals(redirectUrl, null)) redirectUrl = BASEURL; - switch (result) { case LOGIN_SUCCESS -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/main"); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/main"); log.info("{\"code\":200, \"message\":\"정상 로그인 완료: {}\"}", email); } case NEW_USER_REGISTERED -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료: {}\"}", email); } case PROFILE_INCOMPLETE -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료: {}\"}", email); } case SUSPENDED_USER -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/api/suspends"); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/api/suspends"); log.info("{\"code\":200, \"message\":\"정지된 회원: {}\"}", email); } default -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/login"); + getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login"); log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생: {}\"}", email); } } From f4cdf49d25464d21c5ef2f3178e145157c5cf27b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:12:53 +0900 Subject: [PATCH 0830/1002] =?UTF-8?q?Revert=20"Revert=20"fix=20:=20google?= =?UTF-8?q?=20oauth2=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Par?= =?UTF-8?q?am=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5d534bed44ba00d25f09921184677dba024ea7ac. --- .../codin/common/config/SecurityConfig.java | 4 ++-- .../controller/AppleEventController.java | 13 ++----------- .../security/controller/AuthController.java | 9 +++++++-- .../security/service/AuthCommonService.java | 2 +- .../security/service/AuthSessionService.java | 18 ++++++++++++++++++ .../{ => oauth2}/AbstractAuthService.java | 16 +++------------- .../{ => oauth2}/AppleAuthService.java | 3 ++- .../{ => oauth2}/AppleOAuth2UserService.java | 2 +- .../{ => oauth2}/CustomOAuth2UserService.java | 2 +- .../{ => oauth2}/Oauth2AuthService.java | 2 +- .../util/OAuth2LoginSuccessHandler.java | 19 +++++++++++-------- 11 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/AbstractAuthService.java (66%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/AppleAuthService.java (96%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/AppleOAuth2UserService.java (98%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/CustomOAuth2UserService.java (98%) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/Oauth2AuthService.java (84%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index f0552cc6..409b872c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -6,8 +6,8 @@ import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.jwt.JwtTokenProvider; import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.common.security.service.AppleOAuth2UserService; -import inu.codin.codin.common.security.service.CustomOAuth2UserService; +import inu.codin.codin.common.security.service.oauth2.AppleOAuth2UserService; +import inu.codin.codin.common.security.service.oauth2.CustomOAuth2UserService; import inu.codin.codin.common.security.util.*; import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java index 33c6a6cd..457bb4ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -1,10 +1,5 @@ package inu.codin.codin.common.security.controller; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; -import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; -import inu.codin.codin.common.security.service.AppleOAuth2UserService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -12,21 +7,17 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.Map; -import java.util.stream.Collectors; @RestController @RequiredArgsConstructor @Slf4j public class AppleEventController { - private final ObjectMapper objectMapper; - @PostMapping("/login/oauth2/code/apple") public ResponseEntity appleCallback(HttpServletRequest request, HttpServletResponse response) { try { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 8ab3da2c..5245b546 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -3,12 +3,14 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthCommonService; +import inu.codin.codin.common.security.service.AuthSessionService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -26,10 +28,13 @@ public class AuthController { private final JwtService jwtService; private final AuthCommonService authCommonService; + private final AuthSessionService authSessionService; @GetMapping("/google") - public ResponseEntity> googleLogin(HttpServletResponse response) throws IOException { - response.sendRedirect("/api/oauth2/authorization/google"); + public ResponseEntity> googleLogin(HttpServletResponse response, + @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { + authSessionService.setSession(redirect_url); + response.sendRedirect("/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 4ea8107b..56fbe406 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; @@ -9,7 +10,6 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java new file mode 100644 index 00000000..d019b15c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java @@ -0,0 +1,18 @@ +package inu.codin.codin.common.security.service; + +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +@Service +@RequiredArgsConstructor +public class AuthSessionService { + + private final HttpSession httpSession; + + public void setSession(String redirectUrl) { + if (!Objects.equals(redirectUrl, null)) httpSession.setAttribute("redirect_url", redirectUrl); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java similarity index 66% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java index d76dab54..20df4266 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java @@ -1,26 +1,16 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.exception.UserCreateFailException; -import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java index 9df979bb..21ccfec4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java @@ -1,8 +1,9 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java index 71d75bad..4ae07d16 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AppleOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java similarity index 98% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java index 051558d4..936ca98b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/CustomOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.security.dto.AccessEmailProperties; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java index bd2596f0..4c01f481 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/Oauth2AuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/Oauth2AuthService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.security.enums.AuthResultStatus; import jakarta.servlet.http.HttpServletResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index aa935779..50958c0a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -1,9 +1,8 @@ package inu.codin.codin.common.security.util; -import com.fasterxml.jackson.databind.ObjectMapper; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.AppleAuthService; -import inu.codin.codin.common.security.service.GoogleAuthService; +import inu.codin.codin.common.security.service.oauth2.AppleAuthService; +import inu.codin.codin.common.security.service.oauth2.GoogleAuthService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -19,6 +18,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.Objects; @Component @RequiredArgsConstructor @@ -55,25 +55,28 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); + String redirectUrl = request.getSession().getAttribute("redirect_url").toString(); + if (Objects.equals(redirectUrl, null)) redirectUrl = BASEURL; + switch (result) { case LOGIN_SUCCESS -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/main"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/main"); log.info("{\"code\":200, \"message\":\"정상 로그인 완료: {}\"}", email); } case NEW_USER_REGISTERED -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료: {}\"}", email); } case PROFILE_INCOMPLETE -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료: {}\"}", email); } case SUSPENDED_USER -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/api/suspends"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/api/suspends"); log.info("{\"code\":200, \"message\":\"정지된 회원: {}\"}", email); } default -> { - getRedirectStrategy().sendRedirect(request, response, BASEURL + "/login"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/login"); log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생: {}\"}", email); } } From c274707cb533d73f3a0f80970c0a740a7a3f80c9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:12:57 +0900 Subject: [PATCH 0831/1002] =?UTF-8?q?Revert=20"Revert=20"fix=20:=20google?= =?UTF-8?q?=20oauth2=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Par?= =?UTF-8?q?am=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 897b85e0674d08230f5ed4348458f5746fedc5ae. --- .../security/service/{ => oauth2}/GoogleAuthService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename codin-core/src/main/java/inu/codin/codin/common/security/service/{ => oauth2}/GoogleAuthService.java (97%) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java rename to codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index 64e03c8a..557562b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -1,15 +1,15 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.user.OAuth2User; From 64ce8ddf3a35681aedeec76bc2020c236c9baa0d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:13:00 +0900 Subject: [PATCH 0832/1002] =?UTF-8?q?Revert=20"Revert=20"fix=20:=20google?= =?UTF-8?q?=20oauth2=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Par?= =?UTF-8?q?am=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 914b240e2f18f141a3362cafeb35c738b0ae8ac5. --- .../codin/codin/common/security/controller/AuthController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 5245b546..2e3172ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -10,7 +10,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -34,7 +33,7 @@ public class AuthController { public ResponseEntity> googleLogin(HttpServletResponse response, @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { authSessionService.setSession(redirect_url); - response.sendRedirect("/oauth2/authorization/google"); + response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } From 72d1a59c51be56306bf9462a4ca9ce2f4286cae8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:13:03 +0900 Subject: [PATCH 0833/1002] =?UTF-8?q?Revert=20"Revert=20"fix=20:=20google?= =?UTF-8?q?=20oauth2=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20Url=20Par?= =?UTF-8?q?am=EC=9D=84=20=ED=86=B5=ED=95=B4=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20redirect?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 85e347ef04be9aadebd4171bc18db6182a798ce3. --- .../codin/common/security/controller/AuthController.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 2e3172ab..7bff17a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -61,10 +61,9 @@ public ResponseEntity> devAppleLogin(HttpServletResponse respo @Operation(summary = "회원 정보 입력 마무리") @PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> completeUserProfile( - @RequestPart @Valid UserProfileRequestDto userProfileRequestDto, - @RequestPart(value = "userImage", required = false) MultipartFile userImage, - HttpServletResponse response) { + public ResponseEntity> completeUserProfile(@RequestPart @Valid UserProfileRequestDto userProfileRequestDto, + @RequestPart(value = "userImage", required = false) MultipartFile userImage, + HttpServletResponse response) { authCommonService.completeUserProfile(userProfileRequestDto, userImage, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "회원 정보 입력 마무리 성공", null)); From 6b79303286e31e19ce4687e5dd82971cf6f8a587 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:21:16 +0900 Subject: [PATCH 0834/1002] =?UTF-8?q?fix=20:=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20NPE=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 2 +- .../codin/common/security/util/OAuth2LoginSuccessHandler.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 7bff17a3..09f9434c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -33,7 +33,7 @@ public class AuthController { public ResponseEntity> googleLogin(HttpServletResponse response, @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { authSessionService.setSession(redirect_url); - response.sendRedirect("/api/oauth2/authorization/google"); + response.sendRedirect("/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 50958c0a..148e1401 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -55,8 +55,9 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - String redirectUrl = request.getSession().getAttribute("redirect_url").toString(); + String redirectUrl = (String) request.getSession().getAttribute("redirect_url"); if (Objects.equals(redirectUrl, null)) redirectUrl = BASEURL; + request.getSession().removeAttribute("redirect_url"); switch (result) { case LOGIN_SUCCESS -> { From 955a610ce1e9da845928c2ca40505592a6816a00 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 15 Jul 2025 22:24:08 +0900 Subject: [PATCH 0835/1002] =?UTF-8?q?fix=20:=20redirect=20prefix=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 09f9434c..7bff17a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -33,7 +33,7 @@ public class AuthController { public ResponseEntity> googleLogin(HttpServletResponse response, @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { authSessionService.setSession(redirect_url); - response.sendRedirect("/oauth2/authorization/google"); + response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); } From ca32063414be086de58b2b4a84158be257309b50 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Wed, 16 Jul 2025 15:51:26 +0900 Subject: [PATCH 0836/1002] =?UTF-8?q?refactor:=20access=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=ED=97=A4=EB=8D=94=20=EC=B6=94=EA=B0=80,=20refresh?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=BF=A0=ED=82=A4=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 쿠키에 access_token, refresh_token은 유지 (추후 제거) - 헤더: Authorization: Bearer (accessToken) - 쿠키: x-refresh-token: (refreshToken) --- .../common/security/service/JwtService.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index c660b238..17e81668 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -94,21 +94,34 @@ private void createBothToken(HttpServletResponse response) { var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); + // Authorization 헤더에 Access Token 추가 + response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); + + // todo: access token 헤더 방식으로 전환 Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 - jwtCookie.setMaxAge(60 * 60); // 1시간 유지 + jwtCookie.setMaxAge(30 * 60); // 30분 유지 jwtCookie.setDomain(BASERURL.split("//")[1]); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); + Cookie newRefreshToken = new Cookie("x-refresh-token", newToken.getRefreshToken()); + newRefreshToken.setHttpOnly(true); + newRefreshToken.setSecure(true); + newRefreshToken.setPath("/"); + newRefreshToken.setMaxAge(10 * 24 * 60 * 60); // 10일 + newRefreshToken.setDomain(BASERURL.split("//")[1]); + newRefreshToken.setAttribute("SameSite", "None"); + response.addCookie(newRefreshToken); + // todo: refresh token 명칭 변경 Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); refreshCookie.setSecure(true); refreshCookie.setPath("/"); - refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 + refreshCookie.setMaxAge(10 * 24 * 60 * 60); // 10일 refreshCookie.setDomain(BASERURL.split("//")[1]); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); From 44072cd2d9efec92aeaef728c59450b452539001 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 00:39:01 +0900 Subject: [PATCH 0837/1002] =?UTF-8?q?refactor:=20ObjectIdUtil=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/util/ObjectIdUtil.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java b/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java new file mode 100644 index 00000000..869d9ad2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java @@ -0,0 +1,39 @@ +package inu.codin.codin.common.util; + +import org.bson.types.ObjectId; + +public class ObjectIdUtil { + private ObjectIdUtil() {} + + /** + * String을 ObjectId로 변환 + * @param objectIdString ObjectId 문자열 + * @return ObjectId 객체 + * @throws IllegalArgumentException 유효하지 않은 ObjectId 형식인 경우 + */ + public static ObjectId toObjectId(String objectIdString) { + try { + return new ObjectId(objectIdString); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("유효하지 않은 ObjectId 형식입니다: " + objectIdString, e); + } + } + + /** + * ObjectId를 String으로 변환 + * @param objectId ObjectId 객체 + * @return ObjectId 문자열 + */ + public static String toString(ObjectId objectId) { + return objectId != null ? objectId.toHexString() : null; + } + + /** + * ObjectId 유효성 검증 + * @param objectIdString 검증할 문자열 + * @return 유효한 ObjectId 형식이면 true + */ + public static boolean isValid(String objectIdString) { + return ObjectId.isValid(objectIdString); + } +} From 96f443bb9309c540b513044d18076403ef08440e Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 00:46:25 +0900 Subject: [PATCH 0838/1002] =?UTF-8?q?refactor:=20Block=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20#21?= =?UTF-8?q?4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BlockController를 제외한 Block Domain 리팩토링 - User Domain에 UserValidator 추가#214 --- .../domain/block/entity/BlockEntity.java | 45 +++++++-- .../block/repository/BlockRepository.java | 11 +-- .../domain/block/service/BlockService.java | 95 ++++++++----------- .../codin/domain/user/entity/UserEntity.java | 8 +- .../domain/user/validator/UserValidator.java | 23 +++++ 5 files changed, 105 insertions(+), 77 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 74aa2bb7..a27c52eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,25 +1,56 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.ArrayList; +import java.util.List; + @Getter @Document(collection = "blocks") public class BlockEntity extends BaseTimeEntity { + @Id - private ObjectId id; // MongoDB의 기본 ID + private ObjectId id; + + @Indexed + private ObjectId userId; - private ObjectId blockingUserId; // 차단한 사용자 - private ObjectId blockedUserId; // 차단된 사용자 + private List blockedUsers = new ArrayList<>(); @Builder - public BlockEntity(ObjectId blockingUserId, ObjectId blockedUserId) { - this.blockingUserId = blockingUserId; - this.blockedUserId = blockedUserId; + public BlockEntity(ObjectId userId, ObjectId blockedUser) { + this.userId = userId; + this.blockedUsers.add(blockedUser); + } + + public static BlockEntity ofNew(ObjectId userId) { + return BlockEntity.builder() + .userId(userId) + .build(); + } + + public BlockEntity addBlockedUser(ObjectId blockedUser) { + if (this.blockedUsers.contains(blockedUser)) { + throw new AlreadyBlockedException("이미 차단한 유저입니다."); + } + this.blockedUsers.add(blockedUser); + return this; + } + + public BlockEntity removeBockedUser(ObjectId blockedUser) { + if (this.blockedUsers.contains(blockedUser)) { + this.blockedUsers.remove(blockedUser); + return this; + } + throw new NotFoundException("차단 목록에 없는 유저입니다."); } -} +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java index 9b01adac..813af7db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/repository/BlockRepository.java @@ -3,6 +3,7 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -10,10 +11,6 @@ @Repository public interface BlockRepository extends MongoRepository { - - boolean existsByBlockingUserIdAndBlockedUserId(ObjectId blockingUserId, ObjectId blockedUserId); - Optional findByBlockingUserIdAndBlockedUserId(ObjectId blockingUserId, ObjectId blockedUserId); - -} - - + @Query("{'_id': ?0, 'deletedAt': null}") + Optional findByUserId(ObjectId userId); +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 3fe064b5..8402a211 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -2,13 +2,11 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; -import inu.codin.codin.domain.block.exception.AlreadyBlockedException; -import inu.codin.codin.domain.block.exception.NotBlockedException; import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.domain.user.validator.UserValidator; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; @@ -19,70 +17,55 @@ @RequiredArgsConstructor public class BlockService { private final BlockRepository blockRepository; - private final UserRepository userRepository; + private final UserValidator userValidator; + /** + * 유저 차단 + * SecurityContextHolder에서 현재 유저를 가져옴 + * @param strBlockedUserId + */ public void blockUser(String strBlockedUserId) { - ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); - if (blockingUserId.equals(new ObjectId(strBlockedUserId))){ - throw new SelfBlockedException("자기 자신을 차단할 수 없습니다."); + if (userId.equals(blockedId)) { + throw new SelfBlockedException("자신을 차단할 수 없습니다."); } - // 유저 엔티티 조회 - UserEntity user = userRepository.findById(blockingUserId) - .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); - - - ObjectId blockedUserId = new ObjectId(strBlockedUserId); - userRepository.findById(blockedUserId) - .orElseThrow(() -> new NotFoundException("차단할 사용자를 찾을 수 없습니다.")); - - // 중복 차단 방지 - checkUserBlocked(blockingUserId,blockedUserId); - - - // 차단 정보 저 - BlockEntity block = BlockEntity.builder() - .blockingUserId(blockingUserId) - .blockedUserId(blockedUserId) - .build(); - - blockRepository.save(block); + userValidator.validateUserExists(userId, "차단하는 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(blockedId, "차단할 사용자를 찾을 수 없습니다."); - //유저 차단 리스트에 추가 ( 조회 필터링 목적) - user.getBlockedUsers().add(blockedUserId); - userRepository.save(user); + blockRepository.save(blockRepository.findByUserId(userId) + .orElseGet(() -> BlockEntity.ofNew(userId)) + .addBlockedUser(blockedId)); } + /** + * 유저 차단해제 + * SecurityContextHolder에서 현재 유저를 가져옴 + * @param strBlockedUserId 차단 해제할 유저 + */ public void unblockUser(String strBlockedUserId) { - ObjectId blockingUserId = SecurityUtils.getCurrentUserId(); - // 유저 엔티티 조회 - UserEntity user = userRepository.findById(blockingUserId) - .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다.")); + ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); - ObjectId blockedUserId = new ObjectId(strBlockedUserId); - // 차단 정보 조회 - BlockEntity block = blockRepository.findByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId) - .orElseThrow(() -> new NotBlockedException("차단되지 않은 사용자입니다.")); - - // 차단 해제 - blockRepository.delete(block); - - - //유저 차단 리스트에서 삭제 ( 조회 필터링 목적) - user.getBlockedUsers().remove(blockedUserId); - userRepository.save(user); - } - - public void checkUserBlocked(ObjectId blockingUserId, ObjectId blockedUserId) { - if (blockRepository.existsByBlockingUserIdAndBlockedUserId(blockingUserId, blockedUserId)) { - throw new AlreadyBlockedException("이미 상대방을 차단했습니다."); + if (userId.equals(blockedId)) { + throw new SelfBlockedException("자신을 차단 해제할 수 없습니다."); } + userValidator.validateUserExists(userId, "차단 해제하는 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(blockedId, "차단 해제할 사용자를 찾을 수 없습니다."); + + blockRepository.save(blockRepository.findByUserId(userId) + .orElseThrow(() -> new NotFoundException("차단 정보가 존재하지 않습니다.")) + .removeBockedUser(blockedId)); } + /** + * 현재 유저의 차단된 유저 목록 반환 + * @return 차단한 유저 목록 (빈 리스트가 제공될 수 있음) + */ public List getBlockedUsers() { - ObjectId userId = SecurityUtils.getCurrentUserId(); - return userRepository.findById(userId) - .map(UserEntity::getBlockedUsers) - .orElseThrow(() -> new NotFoundException("존재하지 않는 회원입니다.")); + return blockRepository.findByUserId(SecurityUtils.getCurrentUserId()) + .map(BlockEntity::getBlockedUsers) + .orElse(List.of()); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 02fd094e..eed72fd9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -12,8 +12,6 @@ import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; @Document(collection = "users") @Getter @@ -46,12 +44,10 @@ public class UserEntity extends BaseTimeEntity { private LocalDateTime totalSuspensionEndDate; //정지 게시물이 늘어날수록 정지 종료일이 중첩 - private List blockedUsers = new ArrayList<>(); - private NotificationPreference notificationPreference = new NotificationPreference(); @Builder - public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status, List blockedUsers) { + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status) { this.email = email; this.password = password; this.studentId = studentId; @@ -63,7 +59,6 @@ public UserEntity(String email, String password, String studentId, String name, this.undergraduate = undergraduate; this.role = role; this.status = status; - this.blockedUsers = (blockedUsers != null) ? blockedUsers : new ArrayList<>(); // ✅ 기본값 설정 } public void updateNickname(String nickname) { @@ -87,7 +82,6 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .profileImageUrl("") .role(UserRole.USER) .status(UserStatus.ACTIVE) - .blockedUsers(new ArrayList<>()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java new file mode 100644 index 00000000..3bb7a5b3 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.user.validator; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class UserValidator { + private UserRepository userRepository; + + /** + * User 존재 여부 검증 + * @param userId 존재 검증할 userId - 삭제된 유저는 검색되지 않음 + * @param exceptionMsg Exception 메세지 + */ + public void validateUserExists(ObjectId userId, String exceptionMsg) { + userRepository.findByUserId(userId) + .orElseThrow(() -> new NotFoundException(exceptionMsg)); + } +} \ No newline at end of file From 2fd81e2f1c7dc176dea92367cdadb041960ba1ec Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 16:56:54 +0900 Subject: [PATCH 0839/1002] =?UTF-8?q?refactor:=20UserValidator=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/service/BlockService.java | 2 +- .../domain/user/{validator => service}/UserValidator.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/user/{validator => service}/UserValidator.java (88%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 8402a211..d1dd1605 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -6,7 +6,7 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; -import inu.codin.codin.domain.user.validator.UserValidator; +import inu.codin.codin.domain.user.service.UserValidator; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java rename to codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java index 3bb7a5b3..7f34a32a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/validator/UserValidator.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.user.validator; +package inu.codin.codin.domain.user.service; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.user.repository.UserRepository; @@ -9,7 +9,7 @@ @Component @RequiredArgsConstructor public class UserValidator { - private UserRepository userRepository; + private final UserRepository userRepository; /** * User 존재 여부 검증 From 7cf7657e64ffd3894ce9c297f51c6a0004bb9783 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 16:58:07 +0900 Subject: [PATCH 0840/1002] =?UTF-8?q?test:=20TestSecurityConfig=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/config/TestSecurityConfig.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java diff --git a/codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java b/codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java new file mode 100644 index 00000000..3e6924a9 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/config/TestSecurityConfig.java @@ -0,0 +1,26 @@ +package inu.codin.codin.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@TestConfiguration +@EnableWebSecurity +public class TestSecurityConfig { + + @Bean + public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll() + ) + .sessionManagement(session -> session + .sessionCreationPolicy(org.springframework.security.config.http.SessionCreationPolicy.STATELESS) + ); + return http.build(); + } +} From ab880304db62846bfd2e6e00c49606af376623e7 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 16:58:28 +0900 Subject: [PATCH 0841/1002] =?UTF-8?q?test:=20Block=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/controller/BlockControllerTest.java | 152 ++++++++++++ .../block/service/BlockServiceTest.java | 221 ++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java new file mode 100644 index 00000000..37c60ab1 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java @@ -0,0 +1,152 @@ +package inu.codin.codin.domain.block.controller; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.block.exception.AlreadyBlockedException; +import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.service.BlockService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BlockControllerTest { + + @InjectMocks + private BlockController blockController; + + @Mock + private BlockService blockService; + + private final String testUserId = "686373fdaa87fd9618a63b49"; + private final String blockedUserId = "6863740ceeb4a94ee959f592"; + + @Test + @DisplayName("사용자 차단 성공") + void blockUser_성공() { + //given + doNothing().when(blockService).blockUser(anyString()); + + //when + ResponseEntity response = blockController.blockUser(blockedUserId); + + //then + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.getBody()).isInstanceOf(SingleResponse.class); + + SingleResponse singleResponse = (SingleResponse) response.getBody(); + Assertions.assertNotNull(singleResponse); + assertThat(singleResponse.getCode()).isEqualTo(201); + assertThat(singleResponse.getMessage()).isEqualTo("사용자 차단 완료"); + assertThat(singleResponse.getData()).isNull(); + + verify(blockService).blockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 실패 - 자기 자신 차단") + void blockUser_실패_자기자신차단() { + //given + doThrow(new SelfBlockedException("자신을 차단할 수 없습니다.")) + .when(blockService).blockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.blockUser(testUserId)) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단할 수 없습니다."); + + verify(blockService).blockUser(testUserId); + } + + @Test + @DisplayName("사용자 차단 실패 - 이미 차단된 사용자") + void blockUser_실패_이미차단된사용자() { + //given + doThrow(new AlreadyBlockedException("이미 차단한 유저입니다.")) + .when(blockService).blockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) + .isInstanceOf(AlreadyBlockedException.class) + .hasMessage("이미 차단한 유저입니다."); + + verify(blockService).blockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 실패 - 차단할 사용자를 찾을 수 없음") + void blockUser_실패_사용자없음() { + //given + doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) + .when(blockService).blockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단할 사용자를 찾을 수 없습니다."); + + verify(blockService).blockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 해제 성공") + void unblockUser_성공() { + //given + doNothing().when(blockService).unblockUser(anyString()); + + //when + ResponseEntity response = blockController.unblockUser(blockedUserId); + + //then + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isInstanceOf(SingleResponse.class); + + SingleResponse singleResponse = (SingleResponse) response.getBody(); + assertThat(singleResponse.getCode()).isEqualTo(200); + assertThat(singleResponse.getMessage()).isEqualTo("사용자 차단 해제 완료"); + assertThat(singleResponse.getData()).isNull(); + + verify(blockService).unblockUser(blockedUserId); + } + + @Test + @DisplayName("사용자 차단 해제 실패 - 자기 자신 차단 해제") + void unblockUser_실패_자기자신차단해제() { + //given + doThrow(new SelfBlockedException("자신을 차단 해제할 수 없습니다.")) + .when(blockService).unblockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.unblockUser(testUserId)) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단 해제할 수 없습니다."); + + verify(blockService).unblockUser(testUserId); + } + + @Test + @DisplayName("사용자 차단 해제 실패 - 차단 정보 없음") + void unblockUser_실패_차단정보없음() { + //given + doThrow(new NotFoundException("차단 정보가 존재하지 않습니다.")) + .when(blockService).unblockUser(anyString()); + + //when & then + assertThatThrownBy(() -> blockController.unblockUser(blockedUserId)) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단 정보가 존재하지 않습니다."); + + verify(blockService).unblockUser(blockedUserId); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java new file mode 100644 index 00000000..dca94b87 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java @@ -0,0 +1,221 @@ +package inu.codin.codin.domain.block.service; + +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.block.entity.BlockEntity; +import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.repository.BlockRepository; +import inu.codin.codin.domain.user.service.UserValidator; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BlockServiceTest { + + @InjectMocks + BlockService blockService; + + @Mock + BlockRepository blockRepository; + @Mock + UserValidator userValidator; + + private final ObjectId testUserId = ObjectIdUtil.toObjectId("686373fdaa87fd9618a63b49"); + private final ObjectId blockedUserId = ObjectIdUtil.toObjectId("6863740ceeb4a94ee959f592"); + + @Test + @DisplayName("유저 차단 성공") + void blockUser_성공() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)){ + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + when(blockRepository.save(any(BlockEntity.class))).thenReturn(existingBlock); + + //when + blockService.blockUser(blockedUserId.toString()); + + //then + verify(userValidator).validateUserExists(testUserId, "차단하는 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(blockedUserId, "차단할 사용자를 찾을 수 없습니다."); + verify(blockRepository).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단 성공 - 새로운 BlockEntity 생성") + void blockUser_성공_새로운BlockEntity생성() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); + when(blockRepository.save(any(BlockEntity.class))).thenReturn(BlockEntity.ofNew(testUserId)); + + //when + blockService.blockUser(blockedUserId.toString()); + + //then + verify(blockRepository).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단 실패 - 자기 자신 차단") + void blockUser_실패_자기자신차단() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(testUserId.toString())) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단할 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단 실패 - 차단유저 존재하지 않음") + void blockUser_실패_차단유저X() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + doNothing().when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); + doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) + .when(userValidator).validateUserExists(any(ObjectId.class), eq("차단할 사용자를 찾을 수 없습니다.")); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단할 사용자를 찾을 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단 실패 - 차단 실행 유저 존재하지 않음") + void blockUser_실패_차단실행유저X() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + doThrow(new NotFoundException("차단하는 사용자를 찾을 수 없습니다.")) + .when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단하는 사용자를 찾을 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단해제 성공") + void unblockUser_성공() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + existingBlock.addBlockedUser(blockedUserId); + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + when(blockRepository.save(any(BlockEntity.class))).thenReturn(existingBlock); + + //when + blockService.unblockUser(blockedUserId.toString()); + + //then + verify(userValidator).validateUserExists(testUserId, "차단 해제하는 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(blockedUserId, "차단 해제할 사용자를 찾을 수 없습니다."); + verify(blockRepository).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단해제 실패 - 자기 자신 차단해제") + void unblockUser_실패_자기자신차단해제() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + //when & then + assertThatThrownBy(() -> blockService.unblockUser(testUserId.toString())) + .isInstanceOf(SelfBlockedException.class) + .hasMessage("자신을 차단 해제할 수 없습니다."); + } + } + + @Test + @DisplayName("유저 차단해제 실패 - 차단 정보 없음") + void unblockUser_실패_차단정보없음() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); + + //when & then + assertThatThrownBy(() -> blockService.unblockUser(blockedUserId.toString())) + .isInstanceOf(NotFoundException.class) + .hasMessage("차단 정보가 존재하지 않습니다."); + } + } + + @Test + @DisplayName("차단된 유저 목록 조회 성공") + void getBlockedUsers_성공() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity blockEntity = spy(BlockEntity.ofNew(testUserId)); + when(blockEntity.getBlockedUsers()).thenReturn(List.of(blockedUserId)); + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(blockEntity)); + + //when + List result = blockService.getBlockedUsers(); + + //then + assertThat(result).isNotNull() + .hasSize(1) + .doesNotContainNull() + .contains(blockedUserId); + verify(blockRepository).findByUserId(testUserId); + } + } + + @Test + @DisplayName("차단된 유저 목록 조회 - 차단 정보 없음") + void getBlockedUsers_차단정보없음() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); + + //when + List result = blockService.getBlockedUsers(); + + //then + assertThat(result).isEmpty(); + verify(blockRepository).findByUserId(testUserId); + } + } +} \ No newline at end of file From d396b372ec3ae8805b283d25fde460ce19c9832f Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min <67214970+doma17@users.noreply.github.com> Date: Tue, 1 Jul 2025 20:26:48 +0900 Subject: [PATCH 0842/1002] =?UTF-8?q?refactor:=20BlockService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/inu/codin/codin/domain/block/service/BlockService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index d1dd1605..bb9edde2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -56,7 +56,7 @@ public void unblockUser(String strBlockedUserId) { blockRepository.save(blockRepository.findByUserId(userId) .orElseThrow(() -> new NotFoundException("차단 정보가 존재하지 않습니다.")) - .removeBockedUser(blockedId)); + .removeBlockedUser(blockedId)); } /** From 37961352edbf0963966a2b428fa93165aec886c3 Mon Sep 17 00:00:00 2001 From: kbm Date: Tue, 1 Jul 2025 21:17:05 +0900 Subject: [PATCH 0843/1002] =?UTF-8?q?test:=20removeBlockedUser=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20throw=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/entity/BlockEntity.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index a27c52eb..8aeaea68 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -46,11 +46,8 @@ public BlockEntity addBlockedUser(ObjectId blockedUser) { return this; } - public BlockEntity removeBockedUser(ObjectId blockedUser) { - if (this.blockedUsers.contains(blockedUser)) { - this.blockedUsers.remove(blockedUser); - return this; - } - throw new NotFoundException("차단 목록에 없는 유저입니다."); + public BlockEntity removeBlockedUser(ObjectId blockedUser) { + this.blockedUsers.remove(blockedUser); + return this; } } \ No newline at end of file From 1b4c2284827a329e1d9765aef48a9015c40d9385 Mon Sep 17 00:00:00 2001 From: kbm Date: Wed, 2 Jul 2025 15:21:30 +0900 Subject: [PATCH 0844/1002] =?UTF-8?q?chore:=20oauth=20conflict=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 --- .../common/exception/GlobalExceptionHandler.java | 6 ++++-- .../codin/common/ratelimit/ClientIpUtil.java | 2 -- .../common/ratelimit/RateLimitInterceptor.java | 4 ++-- .../controller/AppleEventController.java | 13 +++++++++++-- .../security/dto/apple/AppleAuthRequest.java | 1 + .../common/security/jwt/JwtTokenProvider.java | 5 ++++- .../service/oauth2/AbstractAuthService.java | 16 +++++++++++++--- .../service/oauth2/AppleOAuth2UserService.java | 4 ---- .../AppleOAuth2AccessTokenResponseClient.java | 3 --- .../MultipartJackson2HttpMessageConverter.java | 1 - .../codin/domain/block/entity/BlockEntity.java | 1 - .../chat/chatroom/entity/ParticipantInfo.java | 4 +++- .../chat/chatroom/service/ChatRoomService.java | 1 - .../repository/CustomChattingRepository.java | 2 -- .../dto/request/PartnerCreateRequestDto.java | 1 - .../inu/codin/codin/domain/info/entity/Lab.java | 2 +- .../domain/info/repository/InfoRepository.java | 2 +- .../codin/domain/info/service/OfficeService.java | 2 +- .../domain/info/service/PartnerService.java | 1 - .../domain/info/service/ProfessorService.java | 2 +- .../review/controller/ReviewController.java | 2 +- .../review/dto/CreateReviewRequestDto.java | 4 +++- .../domain/like/controller/LikeController.java | 5 ++++- .../codin/domain/like/service/LikeService.java | 4 ++-- .../notification/entity/NotificationEntity.java | 2 -- .../domain/poll/dto/PollVotingRequestDTO.java | 2 -- .../poll/repository/PollVoteRepository.java | 1 - .../post/domain/poll/service/PollService.java | 3 --- .../reply/dto/request/ReplyCreateRequestDTO.java | 1 - .../domain/post/schedular/PostsScheduler.java | 1 - .../report/controller/ReportController.java | 9 ++++----- .../report/controller/SuspendMvcController.java | 2 +- .../dto/request/ReportCreateRequestDto.java | 1 - .../dto/request/ReportExecuteRequestDto.java | 4 ---- .../dto/response/ReportCountResponseDto.java | 5 ----- .../dto/response/ReportListResponseDto.java | 9 +-------- .../report/dto/response/ReportResponseDto.java | 14 ++++++-------- .../dto/response/ReportSummaryResponseDTO.java | 1 - .../domain/report/entity/SuspensionPeriod.java | 1 - .../repository/CustomReportRepository.java | 6 ++---- .../domain/report/service/ReportService.java | 5 ----- .../domain/scrap/repository/ScrapRepository.java | 1 - .../user/dto/request/UserNicknameRequestDto.java | 1 - .../domain/user/repository/UserRepository.java | 1 - .../codin/infra/fcm/dto/FcmMessageUserDto.java | 1 - .../codin/infra/fcm/entity/FcmTokenEntity.java | 2 -- .../infra/redis/service/RedisBestService.java | 5 ++++- 47 files changed, 70 insertions(+), 96 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index ae643bc1..f7ed371d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,8 +2,10 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; -import inu.codin.codin.domain.chat.exception.*; +import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; +import inu.codin.codin.domain.chat.exception.ChatRoomException; +import inu.codin.codin.domain.chat.exception.ChattingErrorCode; +import inu.codin.codin.domain.chat.exception.ChattingException; import inu.codin.codin.domain.info.exception.InfoErrorCode; import inu.codin.codin.domain.info.exception.InfoException; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java index e9d30f66..5d80709e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java @@ -2,8 +2,6 @@ import jakarta.servlet.http.HttpServletRequest; -import java.util.Arrays; - public class ClientIpUtil { private static final String[] IP_HEADER_CANDIDATES = { "X-Forwarded-For", diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java index 56d22261..0ce93b5d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java @@ -12,11 +12,11 @@ import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; -import static inu.codin.codin.common.ratelimit.RateLimitBucketConstants.*; - import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static inu.codin.codin.common.ratelimit.RateLimitBucketConstants.*; + /** * Reference : https://velog.io/@whcksdud8/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Rate-limit-%ED%95%B8%EB%93%A4%EB%A7%81-%EB%B0%8F-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81 */ diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java index 457bb4ec..33c6a6cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AppleEventController.java @@ -1,5 +1,10 @@ package inu.codin.codin.common.security.controller; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.codin.common.security.dto.apple.AppleAuthRequest; +import inu.codin.codin.common.security.dto.apple.AppleLoginResponse; +import inu.codin.codin.common.security.service.AppleOAuth2UserService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -7,17 +12,21 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.*; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.Map; +import java.util.stream.Collectors; @RestController @RequiredArgsConstructor @Slf4j public class AppleEventController { + private final ObjectMapper objectMapper; + @PostMapping("/login/oauth2/code/apple") public ResponseEntity appleCallback(HttpServletRequest request, HttpServletResponse response) { try { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java index 2c262358..547833d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/apple/AppleAuthRequest.java @@ -2,6 +2,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; + import java.util.Map; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index f85a437a..ae4345d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -3,7 +3,10 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.infra.redis.RedisStorageService; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import lombok.*; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java index 20df4266..d76dab54 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java @@ -1,16 +1,26 @@ -package inu.codin.codin.common.security.service.oauth2; +package inu.codin.codin.common.security.service; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.exception.UserCreateFailException; +import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java index 4ae07d16..8061ed78 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleOAuth2UserService.java @@ -7,8 +7,6 @@ import inu.codin.codin.common.security.feign.AppleAuthClient; import inu.codin.codin.common.security.jwt.IdentityTokenValidator; import inu.codin.codin.common.security.util.ApplePublicKeyGenerator; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; @@ -25,8 +23,6 @@ import java.util.HashMap; import java.util.Map; -import static java.util.Objects.isNull; - @Service @RequiredArgsConstructor @Slf4j diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java index b729f52c..6221c19d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/AppleOAuth2AccessTokenResponseClient.java @@ -2,9 +2,7 @@ import lombok.extern.slf4j.Slf4j; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.*; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -15,7 +13,6 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import java.net.URI; diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java index 5fd59d8c..be518234 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java @@ -1,7 +1,6 @@ package inu.codin.codin.common.util; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; import org.springframework.stereotype.Component; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 8aeaea68..596f5674 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index ce4dbea3..6e4cc10a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -1,7 +1,9 @@ package inu.codin.codin.domain.chat.chatroom.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import lombok.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import java.time.LocalDateTime; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 25150c0a..2c1ccbbb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -12,7 +12,6 @@ import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomExistedException; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; -import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java index 0aa7e48c..a216b53b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/repository/CustomChattingRepository.java @@ -4,11 +4,9 @@ import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; -import reactor.core.publisher.Mono; @Repository public class CustomChattingRepository { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java index 5b006f94..b51ed77b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.info.dto.request; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.info.entity.PartnerImg; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java index 0bd63eda..36224b63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 51efec33..23d13c05 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.info.repository; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.domain.info.entity.Lab; import inu.codin.codin.domain.info.entity.Office; import inu.codin.codin.domain.info.entity.Professor; -import inu.codin.codin.domain.info.entity.Info; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java index 42f21447..7a2639e8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.info.service; +import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.OfficeDetailsResponseDto; import inu.codin.codin.domain.info.entity.Office; -import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.OfficeMember; import inu.codin.codin.domain.info.repository.InfoRepository; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java index 506390b3..00e12192 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/PartnerService.java @@ -12,7 +12,6 @@ import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java index b03bd4fe..031468f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java @@ -1,9 +1,9 @@ package inu.codin.codin.domain.info.service; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.ProfessorListResponseDto; import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; -import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.entity.Professor; import inu.codin.codin.domain.info.exception.InfoErrorCode; import inu.codin.codin.domain.info.exception.InfoException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java index 19e3d68b..4b1da7ac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.lecture.domain.review.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java index 7044b113..b219d93d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java @@ -1,7 +1,9 @@ package inu.codin.codin.domain.lecture.domain.review.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 3d3cfe25..29bc860d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -9,7 +9,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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; @RestController @RequestMapping("/likes") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 1f320f06..541fbd5c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -3,16 +3,16 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; +import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisLikeService; import inu.codin.codin.infra.redis.service.RedisBestService; +import inu.codin.codin.infra.redis.service.RedisLikeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index 66d4d8aa..c8494559 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.notification.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.user.entity.UserEntity; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; @@ -9,7 +8,6 @@ import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java index c06f44ed..c95f5db4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java @@ -1,9 +1,7 @@ package inu.codin.codin.domain.post.domain.poll.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; import lombok.Getter; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java index abc24ba5..4051c311 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollVoteRepository.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.post.domain.poll.repository; -import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import jakarta.validation.constraints.NotBlank; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index e8b35c99..507d01e1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -1,8 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; @@ -16,7 +14,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.user.entity.UserRole; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java index f00ae843..0795530f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -3,7 +3,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Builder; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 1cc1bf38..a15280eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.post.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; - import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index b900d17c..146f8d7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,14 +1,13 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; - import inu.codin.codin.domain.post.domain.comment.service.CommentService; - - import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; -import inu.codin.codin.domain.report.dto.response.*; - +import inu.codin.codin.domain.report.dto.response.ReportPageResponse; +import inu.codin.codin.domain.report.dto.response.ReportSummaryResponseDTO; +import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; +import inu.codin.codin.domain.report.dto.response.ReportedPostDetailResponseDTO; import inu.codin.codin.domain.report.service.ReportService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java index a6e5266d..8cc189d7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/SuspendMvcController.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.report.controller; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.ui.Model; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java index b26f9343..de6c9019 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportCreateRequestDto.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; -import org.bson.types.ObjectId; @Getter public class ReportCreateRequestDto { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java index 37412852..153c60ce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/request/ReportExecuteRequestDto.java @@ -1,12 +1,8 @@ package inu.codin.codin.domain.report.dto.request; -import inu.codin.codin.domain.report.entity.ReportStatus; import inu.codin.codin.domain.report.entity.SuspensionPeriod; import lombok.Getter; import lombok.NoArgsConstructor; -import org.bson.types.ObjectId; - -import java.time.LocalDateTime; @Getter @NoArgsConstructor diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java index 40a2b430..8099d6a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportCountResponseDto.java @@ -1,16 +1,11 @@ package inu.codin.codin.domain.report.dto.response; -import inu.codin.codin.domain.report.entity.ReportEntity; -import inu.codin.codin.domain.report.entity.ReportStatus; -import inu.codin.codin.domain.report.entity.ReportTargetType; -import inu.codin.codin.domain.report.entity.ReportType; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.bson.Document; -import org.bson.types.ObjectId; import java.util.List; import java.util.stream.Collectors; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java index 92c4b5ad..17d1ba2c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportListResponseDto.java @@ -1,18 +1,11 @@ package inu.codin.codin.domain.report.dto.response; -import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.report.dto.ReportInfo; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; -import java.util.List;@Getter +@Getter public class ReportListResponseDto extends PostDetailResponseDTO { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java index 10e3f91e..5299f3ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportResponseDto.java @@ -1,17 +1,15 @@ package inu.codin.codin.domain.report.dto.response; -import inu.codin.codin.domain.report.entity.*; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import org.bson.Document; -import org.bson.types.ObjectId; - +import inu.codin.codin.domain.report.entity.ReportEntity; +import inu.codin.codin.domain.report.entity.ReportEntity.ReportActionEntity; +import inu.codin.codin.domain.report.entity.ReportStatus; import inu.codin.codin.domain.report.entity.ReportTargetType; import inu.codin.codin.domain.report.entity.ReportType; -import inu.codin.codin.domain.report.entity.ReportStatus; -import inu.codin.codin.domain.report.entity.ReportEntity.ReportActionEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; +import org.bson.Document; @Getter @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java index 34957f3c..76271f89 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/dto/response/ReportSummaryResponseDTO.java @@ -2,7 +2,6 @@ import inu.codin.codin.domain.report.entity.ReportType; import lombok.Getter; -import org.bson.types.ObjectId; import java.util.Map; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java index 1acf3896..71debf63 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/SuspensionPeriod.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.report.entity; import lombok.Getter; -import software.amazon.awssdk.services.s3.endpoints.internal.Value; @Getter public enum SuspensionPeriod { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java index b703f92f..1828b647 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/repository/CustomReportRepository.java @@ -1,16 +1,14 @@ package inu.codin.codin.domain.report.repository; -import inu.codin.codin.domain.chat.chatting.entity.Chatting; import inu.codin.codin.domain.report.entity.ReportEntity; import org.bson.Document; -import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; -import org.springframework.data.mongodb.core.aggregation.Aggregation; + import java.util.List; @Repository diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index ace713c0..e4b6fe7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -2,16 +2,13 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; - import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; - import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; - import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; @@ -29,10 +26,8 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import jakarta.validation.Valid; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bson.Document; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java index d2b8d50b..f00e12d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/repository/ScrapRepository.java @@ -7,7 +7,6 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java index d6114a90..6aeac2db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNicknameRequestDto.java @@ -3,7 +3,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Getter; -import lombok.Setter; import java.beans.ConstructorProperties; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index b0e9f395..e65c0b34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.user.repository; -import inu.codin.codin.domain.report.entity.ReportEntity; import inu.codin.codin.domain.user.entity.UserEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java index f6f7a106..44d4f7a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmMessageUserDto.java @@ -1,6 +1,5 @@ package inu.codin.codin.infra.fcm.dto; -import inu.codin.codin.domain.user.entity.UserEntity; import lombok.Builder; import lombok.Data; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index 733765b0..6673cceb 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,14 +1,12 @@ package inu.codin.codin.infra.fcm.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 51cf7743..9868a7a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -15,7 +15,10 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; From 1507a8be7ed164ea424adc2947fbf61cf013e5ca Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 3 Jul 2025 16:32:16 +0900 Subject: [PATCH 0845/1002] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20#211=20=EC=BD=94=EB=93=9C=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #211 에서 적용한 GlobalException, GlobalErrorCode 형태로 코드를 리팩토링 했습니다. --- .../exception/GlobalExceptionHandler.java | 9 ++++ .../domain/block/entity/BlockEntity.java | 6 +-- .../block/exception/BlockErrorCode.java | 27 ++++++++++++ .../block/exception/BlockException.java | 15 +++++++ .../domain/block/service/BlockService.java | 44 +++++++++++++------ .../domain/block/service/BlockValidator.java | 11 +++++ .../domain/user/service/UserValidator.java | 8 ++-- 7 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index f7ed371d..0aea23b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -2,6 +2,8 @@ import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; import inu.codin.codin.domain.chat.exception.ChatRoomException; import inu.codin.codin.domain.chat.exception.ChattingErrorCode; @@ -34,6 +36,13 @@ protected ResponseEntity handleGlobalException(GlobalExceptio .body(new ExceptionResponse(code.message(), code.httpStatus().value())); } + @ExceptionHandler(BlockException.class) + protected ResponseEntity handleBlockException(BlockException e) { + BlockErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + @ExceptionHandler(ChatRoomException.class) protected ResponseEntity handleChatRoomException(ChatRoomException e) { ChatRoomErrorCode code = e.getErrorCode(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 596f5674..1c5f877d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -38,15 +38,11 @@ public static BlockEntity ofNew(ObjectId userId) { } public BlockEntity addBlockedUser(ObjectId blockedUser) { - if (this.blockedUsers.contains(blockedUser)) { - throw new AlreadyBlockedException("이미 차단한 유저입니다."); - } this.blockedUsers.add(blockedUser); return this; } - public BlockEntity removeBlockedUser(ObjectId blockedUser) { + public void removeBlockedUser(ObjectId blockedUser) { this.blockedUsers.remove(blockedUser); - return this; } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java new file mode 100644 index 00000000..d99b98bd --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.block.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum BlockErrorCode implements GlobalErrorCode { + SELF_BLOCKED(HttpStatus.BAD_REQUEST, "자신을 차단할 수 없습니다."), + SELF_UNBLOCKED(HttpStatus.BAD_REQUEST, "자신을 차단 해제할 수 없습니다."), + ALREADY_BLOCKED(HttpStatus.BAD_REQUEST, "이미 차단한 사용자입니다."), + BLOCKING_USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "차단하는 사용자를 찾을 수 없습니다."), + BLOCKED_USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "차단할 사용자를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java new file mode 100644 index 00000000..a7fc2d36 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.block.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class BlockException extends GlobalException { + + private final BlockErrorCode errorCode; + + public BlockException(BlockErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index bb9edde2..5abf20aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -1,19 +1,22 @@ package inu.codin.codin.domain.block.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.service.UserValidator; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.stereotype.Service; import java.util.List; @Service +@Slf4j @RequiredArgsConstructor public class BlockService { private final BlockRepository blockRepository; @@ -29,14 +32,22 @@ public void blockUser(String strBlockedUserId) { ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { - throw new SelfBlockedException("자신을 차단할 수 없습니다."); + log.error(""); + throw new BlockException(BlockErrorCode.SELF_BLOCKED); } - userValidator.validateUserExists(userId, "차단하는 사용자를 찾을 수 없습니다."); - userValidator.validateUserExists(blockedId, "차단할 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(userId, () -> new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)); + userValidator.validateUserExists(blockedId, () -> new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)); - blockRepository.save(blockRepository.findByUserId(userId) - .orElseGet(() -> BlockEntity.ofNew(userId)) - .addBlockedUser(blockedId)); + blockRepository.findByUserId(userId) + .ifPresentOrElse(blockEntity -> { + if (BlockValidator.validateBlockedUserExists(blockEntity, blockedId)) { + throw new BlockException(BlockErrorCode.ALREADY_BLOCKED); + } + blockEntity.addBlockedUser(blockedId); + blockRepository.save(blockEntity); + }, () -> blockRepository.save(BlockEntity.ofNew(userId) + .addBlockedUser(blockedId)) + ); } /** @@ -49,14 +60,21 @@ public void unblockUser(String strBlockedUserId) { ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { - throw new SelfBlockedException("자신을 차단 해제할 수 없습니다."); + throw new BlockException(BlockErrorCode.SELF_UNBLOCKED); } - userValidator.validateUserExists(userId, "차단 해제하는 사용자를 찾을 수 없습니다."); - userValidator.validateUserExists(blockedId, "차단 해제할 사용자를 찾을 수 없습니다."); + userValidator.validateUserExists(userId, () -> new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)); + userValidator.validateUserExists(blockedId, () -> new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)); - blockRepository.save(blockRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException("차단 정보가 존재하지 않습니다.")) - .removeBlockedUser(blockedId)); + blockRepository.findByUserId(userId) + .ifPresentOrElse(blockEntity -> { + if (!BlockValidator.validateBlockedUserExists(blockEntity, blockedId)) { + throw new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND); + } + blockEntity.removeBlockedUser(blockedId); + blockRepository.save(blockEntity); + }, () -> { + throw new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND); + }); } /** diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java new file mode 100644 index 00000000..96adfb0e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockValidator.java @@ -0,0 +1,11 @@ +package inu.codin.codin.domain.block.service; + +import inu.codin.codin.domain.block.entity.BlockEntity; +import org.bson.types.ObjectId; + +public class BlockValidator { + + public static boolean validateBlockedUserExists(BlockEntity blockEntity, ObjectId blockedUser) { + return blockEntity.getBlockedUsers().contains(blockedUser); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java index 7f34a32a..9aa92121 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java @@ -6,6 +6,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Component; +import java.util.function.Supplier; + @Component @RequiredArgsConstructor public class UserValidator { @@ -14,10 +16,10 @@ public class UserValidator { /** * User 존재 여부 검증 * @param userId 존재 검증할 userId - 삭제된 유저는 검색되지 않음 - * @param exceptionMsg Exception 메세지 + * @param exceptionSupplier Exception Class 지정 */ - public void validateUserExists(ObjectId userId, String exceptionMsg) { + public void validateUserExists(ObjectId userId, Supplier exceptionSupplier) { userRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException(exceptionMsg)); + .orElseThrow(exceptionSupplier); } } \ No newline at end of file From c21e7e172411005efd1d1c168b7b4984f1e95a7d Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 3 Jul 2025 16:32:49 +0900 Subject: [PATCH 0846/1002] =?UTF-8?q?test:=20=EB=B3=80=EA=B2=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20#211=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #211 에서 적용한 GlobalException, GlobalErrorCode Block 도메인에 대한 테스트 코드를 리팩토링했습니다. --- .../block/controller/BlockControllerTest.java | 40 ++++---- .../block/service/BlockServiceTest.java | 99 +++++++++++++++---- 2 files changed, 100 insertions(+), 39 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java index 37c60ab1..6909b8b1 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java @@ -1,9 +1,8 @@ package inu.codin.codin.domain.block.controller; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.block.exception.AlreadyBlockedException; -import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.service.BlockService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -58,13 +57,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 실패 - 자기 자신 차단") void blockUser_실패_자기자신차단() { //given - doThrow(new SelfBlockedException("자신을 차단할 수 없습니다.")) + doThrow(new BlockException(BlockErrorCode.SELF_BLOCKED)) .when(blockService).blockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.blockUser(testUserId)) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_BLOCKED); verify(blockService).blockUser(testUserId); } @@ -73,13 +73,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 실패 - 이미 차단된 사용자") void blockUser_실패_이미차단된사용자() { //given - doThrow(new AlreadyBlockedException("이미 차단한 유저입니다.")) + doThrow(new BlockException(BlockErrorCode.ALREADY_BLOCKED)) .when(blockService).blockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) - .isInstanceOf(AlreadyBlockedException.class) - .hasMessage("이미 차단한 유저입니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.ALREADY_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.ALREADY_BLOCKED); verify(blockService).blockUser(blockedUserId); } @@ -88,13 +89,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 실패 - 차단할 사용자를 찾을 수 없음") void blockUser_실패_사용자없음() { //given - doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) + doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) .when(blockService).blockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.blockUser(blockedUserId)) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단할 사용자를 찾을 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); verify(blockService).blockUser(blockedUserId); } @@ -124,13 +126,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 해제 실패 - 자기 자신 차단 해제") void unblockUser_실패_자기자신차단해제() { //given - doThrow(new SelfBlockedException("자신을 차단 해제할 수 없습니다.")) + doThrow(new BlockException(BlockErrorCode.SELF_UNBLOCKED)) .when(blockService).unblockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.unblockUser(testUserId)) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단 해제할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_UNBLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_UNBLOCKED); verify(blockService).unblockUser(testUserId); } @@ -139,13 +142,14 @@ class BlockControllerTest { @DisplayName("사용자 차단 해제 실패 - 차단 정보 없음") void unblockUser_실패_차단정보없음() { //given - doThrow(new NotFoundException("차단 정보가 존재하지 않습니다.")) + doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) .when(blockService).unblockUser(anyString()); //when & then assertThatThrownBy(() -> blockController.unblockUser(blockedUserId)) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단 정보가 존재하지 않습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); verify(blockService).unblockUser(blockedUserId); } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java index dca94b87..bfb5b7a3 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java @@ -1,10 +1,10 @@ package inu.codin.codin.domain.block.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; -import inu.codin.codin.domain.block.exception.SelfBlockedException; +import inu.codin.codin.domain.block.exception.BlockErrorCode; +import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.service.UserValidator; import org.bson.types.ObjectId; @@ -53,8 +53,8 @@ class BlockServiceTest { blockService.blockUser(blockedUserId.toString()); //then - verify(userValidator).validateUserExists(testUserId, "차단하는 사용자를 찾을 수 없습니다."); - verify(userValidator).validateUserExists(blockedUserId, "차단할 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); verify(blockRepository).save(any(BlockEntity.class)); } } @@ -86,8 +86,9 @@ class BlockServiceTest { //when & then assertThatThrownBy(() -> blockService.blockUser(testUserId.toString())) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_BLOCKED); } } @@ -98,14 +99,15 @@ class BlockServiceTest { //given mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); - doNothing().when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); - doThrow(new NotFoundException("차단할 사용자를 찾을 수 없습니다.")) - .when(userValidator).validateUserExists(any(ObjectId.class), eq("차단할 사용자를 찾을 수 없습니다.")); + doNothing().when(userValidator).validateUserExists(eq(testUserId), any()); + doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) + .when(userValidator).validateUserExists(eq(blockedUserId), any()); //when & then assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단할 사용자를 찾을 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); } } @@ -116,13 +118,14 @@ class BlockServiceTest { //given mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); - doThrow(new NotFoundException("차단하는 사용자를 찾을 수 없습니다.")) - .when(userValidator).validateUserExists(eq(testUserId), eq("차단하는 사용자를 찾을 수 없습니다.")); + doThrow(new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)) + .when(userValidator).validateUserExists(eq(testUserId), any()); //when & then assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단하는 사용자를 찾을 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKING_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKING_USER_NOT_FOUND); } } @@ -142,8 +145,8 @@ class BlockServiceTest { blockService.unblockUser(blockedUserId.toString()); //then - verify(userValidator).validateUserExists(testUserId, "차단 해제하는 사용자를 찾을 수 없습니다."); - verify(userValidator).validateUserExists(blockedUserId, "차단 해제할 사용자를 찾을 수 없습니다."); + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); verify(blockRepository).save(any(BlockEntity.class)); } } @@ -157,8 +160,9 @@ class BlockServiceTest { //when & then assertThatThrownBy(() -> blockService.unblockUser(testUserId.toString())) - .isInstanceOf(SelfBlockedException.class) - .hasMessage("자신을 차단 해제할 수 없습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.SELF_UNBLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.SELF_UNBLOCKED); } } @@ -173,8 +177,61 @@ class BlockServiceTest { //when & then assertThatThrownBy(() -> blockService.unblockUser(blockedUserId.toString())) - .isInstanceOf(NotFoundException.class) - .hasMessage("차단 정보가 존재하지 않습니다."); + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); + } + } + + @Test + @DisplayName("유저 차단 실패 - 이미 차단된 사용자") + void blockUser_실패_이미차단된사용자() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + existingBlock.addBlockedUser(blockedUserId); // 이미 차단된 상태 + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + + //when & then + assertThatThrownBy(() -> blockService.blockUser(blockedUserId.toString())) + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.ALREADY_BLOCKED.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.ALREADY_BLOCKED); + + // userValidator 호출 검증 + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); + // save가 호출되지 않았는지 검증 + verify(blockRepository, never()).save(any(BlockEntity.class)); + } + } + + @Test + @DisplayName("유저 차단해제 실패 - 차단되지 않은 사용자") + void unblockUser_실패_차단되지않은사용자() { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + //given + mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + + // 다른 사용자는 차단되어 있지만, 요청한 사용자는 차단되지 않은 상태 + ObjectId otherBlockedUserId = ObjectIdUtil.toObjectId("507f1f77bcf86cd799439011"); + BlockEntity existingBlock = BlockEntity.ofNew(testUserId); + existingBlock.addBlockedUser(otherBlockedUserId); // 다른 사용자만 차단됨 + when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); + + //when & then + assertThatThrownBy(() -> blockService.unblockUser(blockedUserId.toString())) + .isInstanceOf(BlockException.class) + .hasMessage(BlockErrorCode.BLOCKED_USER_NOT_FOUND.message()) + .hasFieldOrPropertyWithValue("errorCode", BlockErrorCode.BLOCKED_USER_NOT_FOUND); + + // userValidator 호출 검증 + verify(userValidator).validateUserExists(eq(testUserId), any()); + verify(userValidator).validateUserExists(eq(blockedUserId), any()); + // save가 호출되지 않았는지 검증 + verify(blockRepository, never()).save(any(BlockEntity.class)); } } From e4643678fd0b8ef18574efaaa6030f67f71e25e6 Mon Sep 17 00:00:00 2001 From: kbm Date: Thu, 3 Jul 2025 17:17:30 +0900 Subject: [PATCH 0847/1002] =?UTF-8?q?refactor:=20Block=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20Exception=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/block/entity/BlockEntity.java | 1 - .../domain/block/exception/AlreadyBlockedException.java | 7 ------- .../codin/domain/block/exception/NotBlockedException.java | 7 ------- .../codin/domain/block/exception/SelfBlockedException.java | 7 ------- .../inu/codin/codin/domain/block/service/BlockService.java | 1 - 5 files changed, 23 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 1c5f877d..4b74eb8b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.block.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.block.exception.AlreadyBlockedException; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java deleted file mode 100644 index 223c9844..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/AlreadyBlockedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.block.exception; - -public class AlreadyBlockedException extends RuntimeException{ - public AlreadyBlockedException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java deleted file mode 100644 index cb91fafb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/NotBlockedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.block.exception; - -public class NotBlockedException extends RuntimeException{ - public NotBlockedException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java deleted file mode 100644 index 878b28e7..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/SelfBlockedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.block.exception; - -public class SelfBlockedException extends RuntimeException{ - public SelfBlockedException(String message){ - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 5abf20aa..974b2eac 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -5,7 +5,6 @@ import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; -import inu.codin.codin.domain.block.exception.SelfBlockedException; import inu.codin.codin.domain.block.repository.BlockRepository; import inu.codin.codin.domain.user.service.UserValidator; import lombok.RequiredArgsConstructor; From 8542985538fcb1b566b2e6ec8c5ce0ed435a21c9 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Wed, 16 Jul 2025 15:51:26 +0900 Subject: [PATCH 0848/1002] =?UTF-8?q?refactor:=20access=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=ED=97=A4=EB=8D=94=20=EC=B6=94=EA=B0=80,=20refresh?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=BF=A0=ED=82=A4=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 쿠키에 access_token, refresh_token은 유지 (추후 제거) - 헤더: Authorization: Bearer (accessToken) - 쿠키: x-refresh-token: (refreshToken) --- .../common/security/service/JwtService.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index c660b238..17e81668 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -94,21 +94,34 @@ private void createBothToken(HttpServletResponse response) { var authentication = SecurityContextHolder.getContext().getAuthentication(); JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); + // Authorization 헤더에 Access Token 추가 + response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); + + // todo: access token 헤더 방식으로 전환 Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 jwtCookie.setPath("/"); // 모든 요청에 포함 - jwtCookie.setMaxAge(60 * 60); // 1시간 유지 + jwtCookie.setMaxAge(30 * 60); // 30분 유지 jwtCookie.setDomain(BASERURL.split("//")[1]); jwtCookie.setAttribute("SameSite", "None"); response.addCookie(jwtCookie); + Cookie newRefreshToken = new Cookie("x-refresh-token", newToken.getRefreshToken()); + newRefreshToken.setHttpOnly(true); + newRefreshToken.setSecure(true); + newRefreshToken.setPath("/"); + newRefreshToken.setMaxAge(10 * 24 * 60 * 60); // 10일 + newRefreshToken.setDomain(BASERURL.split("//")[1]); + newRefreshToken.setAttribute("SameSite", "None"); + response.addCookie(newRefreshToken); + // todo: refresh token 명칭 변경 Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); refreshCookie.setHttpOnly(true); refreshCookie.setSecure(true); refreshCookie.setPath("/"); - refreshCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 + refreshCookie.setMaxAge(10 * 24 * 60 * 60); // 10일 refreshCookie.setDomain(BASERURL.split("//")[1]); refreshCookie.setAttribute("SameSite", "None"); response.addCookie(refreshCookie); From 50f0fd615d70ad8c183f41bbad09f41e079e8f7a Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Wed, 16 Jul 2025 16:09:05 +0900 Subject: [PATCH 0849/1002] =?UTF-8?q?build:=20AbstractAuthService=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/service/AuthCommonService.java | 2 +- .../codin/common/security/service/oauth2/AppleAuthService.java | 1 + .../codin/common/security/service/oauth2/GoogleAuthService.java | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 56fbe406..0fd30873 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,7 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; +import inu.codin.codin.common.security.service.AbstractAuthService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java index 21ccfec4..380bc517 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.service.AbstractAuthService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index 557562b3..51e75860 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; +import inu.codin.codin.common.security.service.AbstractAuthService; import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; From d5d8b55b0a8c829bfc0e925373462b7c16a5341d Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 6 Apr 2025 22:50:18 +0900 Subject: [PATCH 0850/1002] =?UTF-8?q?refactor=20:=20Entity=EC=97=90=20NoAr?= =?UTF-8?q?gsConstructor=20=EB=B0=8F=20validation=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/email/entity/EmailAuthEntity.java | 6 +++++- .../codin/domain/like/entity/LikeEntity.java | 11 ++++++++++- .../domain/comment/entity/CommentEntity.java | 11 ++++++++++- .../post/domain/poll/entity/PollVoteEntity.java | 7 +++++-- .../domain/reply/entity/ReplyCommentEntity.java | 12 +++++++++++- .../codin/domain/post/entity/PostEntity.java | 17 +++++++++++++---- .../domain/report/entity/ReportEntity.java | 13 +++++++++++-- .../codin/domain/scrap/entity/ScrapEntity.java | 8 +++++++- .../codin/domain/user/entity/UserEntity.java | 4 ---- .../codin/infra/fcm/entity/FcmTokenEntity.java | 7 ++++++- 10 files changed, 78 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 6b3c4b4c..8f24b736 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -2,8 +2,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; @@ -13,9 +16,10 @@ @Document(collection = "auth-emails") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class EmailAuthEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; @Indexed diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java index f6beb2e9..ca396aaa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java @@ -1,19 +1,28 @@ package inu.codin.codin.domain.like.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "likes") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class LikeEntity extends BaseTimeEntity { - @Id + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId likeTypeId; // 게시글, 댓글, 대댓글의 ID + + @NotNull private LikeType likeType; // 엔티티 타입 (post, comment, reply) + + @NotNull private ObjectId userId; // 좋아요를 누른 사용자 ID @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index c3f6ca06..08d5c09b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -2,20 +2,29 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "comments") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class CommentEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId postId; //게시글 ID 참조 + + @NotNull private ObjectId userId; + + @NotBlank private String content; private boolean anonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java index 4a85aea7..3d0f0ab6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java @@ -1,18 +1,21 @@ package inu.codin.codin.domain.post.domain.poll.entity; import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; -@Document(collection = "poll_votes") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Document(collection = "poll_votes") public class PollVoteEntity { - @Id + @Id @NotNull private ObjectId _id; @NotNull diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java index c15a5162..86faa31a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java @@ -2,20 +2,30 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "replies") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ReplyCommentEntity extends BaseTimeEntity { @Id - @NotBlank + @NotNull private ObjectId _id; + + @NotNull private ObjectId commentId; // 댓글 ID 참조 + + @NotNull private ObjectId userId; // 작성자 ID + + @NotBlank private String content; private boolean anonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 4c80dfc7..0577197a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -3,8 +3,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.exception.StateUpdateException; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,18 +15,24 @@ import java.util.List; @Document(collection = "posts") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class PostEntity extends BaseTimeEntity { - @Id @NotBlank + @Id @NotNull private ObjectId _id; - private final ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 - private final String title; + @NotNull + private ObjectId userId; // User 엔티티와의 관계를 유지하기 위한 필드 + @NotBlank + private String title; + @NotBlank private String content; private List postImageUrls; private boolean isAnonymous; - private final PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) + @NotNull + private PostCategory postCategory; // Enum('구해요', '소통해요', '비교과', ...) + @NotNull private PostStatus postStatus; // Enum(ACTIVE, DISABLED, SUSPENDED) private int commentCount = 0; // 댓글 + 대댓글 카운트 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index e4115baa..f30be721 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -2,9 +2,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; -import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,27 +14,34 @@ import java.time.LocalDateTime; @Document(collection = "reports") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ReportEntity extends BaseTimeEntity { @Id - @NotBlank + @NotNull private ObjectId _id; + @NotNull //신고한 유저 private ObjectId reportingUserId; + @NotNull //신고당한 유저 private ObjectId reportedUserId; + @NotNull //신고 대상 타입 ( 유저, 게시물, 댓글, 대댓글) private ReportTargetType reportTargetType; + @NotNull //신고 대상 ID ( 유저, 게시물, 댓글, 대댓글) private ObjectId reportTargetId; + @NotNull //신고 유형 ( 게시글 부적절, 스팸 ,,.) private ReportType reportType; + @NotNull //신고 처리 상태 Pending <-> Reloved private ReportStatus reportStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java index 85a1e6a8..e2887743 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java @@ -1,18 +1,24 @@ package inu.codin.codin.domain.scrap.entity; import inu.codin.codin.common.dto.BaseTimeEntity; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "scraps") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ScrapEntity extends BaseTimeEntity { - @Id + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId postId; + @NotNull private ObjectId userId; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index eed72fd9..5b430428 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -36,8 +36,6 @@ public class UserEntity extends BaseTimeEntity { private String college; - private Boolean undergraduate; - private UserRole role; private UserStatus status; @@ -56,7 +54,6 @@ public UserEntity(String email, String password, String studentId, String name, this.profileImageUrl = profileImageUrl; this.department = department; this.college = college; - this.undergraduate = undergraduate; this.role = role; this.status = status; } @@ -77,7 +74,6 @@ public static UserEntity of(PortalLoginResponseDto userPortalLoginResponseDto){ .password(userPortalLoginResponseDto.getPassword()) .department(userPortalLoginResponseDto.getDepartment()) .college(userPortalLoginResponseDto.getCollege()) - .undergraduate(userPortalLoginResponseDto.getUndergraduate()) .nickname("") .profileImageUrl("") .role(UserRole.USER) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index 6673cceb..d5991104 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -3,8 +3,11 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,12 +15,14 @@ import java.util.List; @Document(collection = "fcmToken") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class FcmTokenEntity extends BaseTimeEntity { - @Id + @Id @NotNull private ObjectId _id; + @NotNull private ObjectId userId; private List fcmTokenList; From fa96cc739e151c9fb07fc1071c2c9136053639b5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 7 Apr 2025 22:23:32 +0900 Subject: [PATCH 0851/1002] =?UTF-8?q?fix=20:=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20BestEntity=EC=9D=98=20sc?= =?UTF-8?q?ore=20=EC=A0=90=EC=88=98=EA=B0=80=20=EB=B3=80=EA=B2=BD=EB=90=98?= =?UTF-8?q?=EC=97=88=EB=8B=A4=EB=A9=B4=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/domain/best/BestEntity.java | 4 ++++ .../codin/domain/post/domain/best/BestRepository.java | 4 +++- .../codin/infra/redis/service/RedisBestService.java | 9 +++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java index d81986ba..886ccf33 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestEntity.java @@ -32,4 +32,8 @@ public BestEntity(ObjectId postId, int score) { this.postId = postId; this.score = score; } + + public void updateScore(int score){ + this.score = score; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java index 739a8afb..ada1777a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestRepository.java @@ -3,6 +3,8 @@ import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import java.util.Optional; + public interface BestRepository extends MongoRepository { - boolean existsByPostId(ObjectId postId); + Optional findByPostId(ObjectId postId); } diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 9868a7a8..992d233f 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -200,12 +200,17 @@ private void checkHits(String postId, String minPostId) { * 만약 bestEntity에 없다면 저장 */ public void saveBests(String postId, int score) { - boolean existedPost = bestRepository.existsByPostId(new ObjectId(postId)); - if (!existedPost) { + Optional existedPost = bestRepository.findByPostId(new ObjectId(postId)); + if (existedPost.isEmpty()) { bestRepository.save(BestEntity.builder() .postId(new ObjectId(postId)) .score(score) .build()); + } else { + if (existedPost.get().getScore() != score){ + existedPost.get().updateScore(score); + bestRepository.save(existedPost.get()); + } } } From ce51b81e37b751d83a3e54b65e2077ff28d4a0a5 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 7 Apr 2025 22:35:19 +0900 Subject: [PATCH 0852/1002] =?UTF-8?q?fix=20:=20=EC=B5=9C=EC=86=8C=204?= =?UTF-8?q?=EC=A0=90=20=EC=9D=B4=EC=83=81=EB=B6=80=ED=84=B0=20Best=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20?= =?UTF-8?q?BestEntity=EC=9D=98=20score=20=EA=B0=92=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/redis/service/RedisBestService.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index 992d233f..aa306341 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -53,18 +53,20 @@ public Map delicatedBestsScheduler(int N) { Map result = new HashMap<>(); for (int i = 0; i < 24; i++) { String redisKey = now.minusHours(i).format(formatter); - if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { - Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, -1); + if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) { //해당 시각의 key가 존재한다면 + Set> members = redisTemplate.opsForZSet().reverseRangeWithScores(redisKey, 0, -1); //value 반환 if (members != null) { for (ZSetOperations.TypedTuple member : members) { String postId = member.getValue(); Double score = member.getScore(); + //post가 삭제되었는지 확인하고, 삭제되었다면 지우기 if (!postRepository.existsBy_idAndDeletedAtIsNull(new ObjectId(postId))){ redisTemplate.opsForZSet().remove(redisKey, postId); deleteBest(postId); - break; + continue; } - result.put(postId, score); + if (score >= 4) + result.put(postId, score); } } } @@ -144,12 +146,12 @@ public void applyBestScore(int score, ObjectId postId){ redisKey = now.format(formatter); redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); //하루가 지나면 필요없는 데이터 redisTemplate.opsForZSet().add(redisKey, postId.toString(), score); - updateBests(redisKey, postId.toString()); } } /** * 게시글에 점수가 반영 될 때마다 베스트 게시글을 관리하는 ZSet에서 업데이트 + * 4점 미만이라면 Best 게시글로 취급하지 않음 * 베스트 게시글에서 최소 점수보다 같거나 크거나, 베스트 게시글이 3개 이하거나 할 때 베스트 게시글에 포함 * * 만약 포함 후 3개 초과라면 @@ -158,12 +160,13 @@ public void applyBestScore(int score, ObjectId postId){ */ private void updateBests(String redisKey, String postId){ Double score = redisTemplate.opsForZSet().score(redisKey, postId); - Set> minEntry = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, 0); + if (score < 4) return; //총 점수가 4점 미만이면 Best 게시글로 취급하지 않음 + Set> minEntry = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, 0); if (minEntry!=null && !minEntry.isEmpty()){ ZSetOperations.TypedTuple minTuple = minEntry.iterator().next(); //최소 score의 Tuple - Double min = minTuple.getScore(); - Long totalSize = redisTemplate.opsForZSet().size(BEST_KEY); + Double min = minTuple.getScore(); //Best 게시글 중 최소 score + Long totalSize = redisTemplate.opsForZSet().size(BEST_KEY); //현재 Best 게시글 개수 //최소 점수보다 같거나 큰 값이거나, 총 베스트 게시글 개수가 3개 미만 이면 포함 if (score >= min || totalSize < 3) @@ -197,7 +200,7 @@ private void checkHits(String postId, String minPostId) { } /** - * 만약 bestEntity에 없다면 저장 + * 만약 bestEntity에 없다면 저장, 있는데 score가 변경되었다면 업데이트 */ public void saveBests(String postId, int score) { Optional existedPost = bestRepository.findByPostId(new ObjectId(postId)); @@ -206,7 +209,7 @@ public void saveBests(String postId, int score) { .postId(new ObjectId(postId)) .score(score) .build()); - } else { + } else { // if (existedPost.get().getScore() != score){ existedPost.get().updateScore(score); bestRepository.save(existedPost.get()); From 18036d24a2cc561d1d19e90bbda36e6afcc5dd41 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 8 Apr 2025 22:56:52 +0900 Subject: [PATCH 0853/1002] =?UTF-8?q?refactor=20:=20request,=20response=20?= =?UTF-8?q?dto=20=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/chatroom/controller/ChatRoomController.java | 4 ++-- .../dto/{ => request}/ChatRoomCreateRequestDto.java | 2 +- .../dto/{ => response}/ChatRoomListResponseDto.java | 2 +- .../codin/codin/domain/chat/chatroom/entity/ChatRoom.java | 2 +- .../codin/domain/chat/chatroom/service/ChatRoomService.java | 4 ++-- .../email/dto/{ => request}/JoinEmailCheckRequestDto.java | 2 +- .../email/dto/{ => request}/JoinEmailSendRequestDto.java | 2 +- .../review/dto/{ => request}/CreateReviewRequestDto.java | 2 +- .../review/dto/{ => response}/ReviewListResposneDto.java | 2 +- .../review/dto/{ => response}/ReviewPageResponse.java | 2 +- .../domain/lecture/domain/review/entity/ReviewEntity.java | 2 +- .../domain/lecture/domain/review/service/ReviewService.java | 6 +++--- .../room/dto/{ => response}/EmptyRoomResponseDto.java | 2 +- .../lecture/domain/room/service/LectureRoomService.java | 2 +- .../dto/{ => response}/LectureDetailResponseDto.java | 3 ++- .../lecture/dto/{ => response}/LectureListResponseDto.java | 2 +- .../lecture/dto/{ => response}/LecturePageResponse.java | 2 +- .../dto/{ => response}/LectureSearchListResponseDto.java | 2 +- .../codin/codin/domain/lecture/service/LectureService.java | 4 ++++ .../codin/codin/domain/like/controller/LikeController.java | 2 +- .../codin/domain/like/dto/{ => request}/LikeRequestDto.java | 2 +- .../{ => controller}/NotificationController.java | 2 +- .../dto/{ => response}/NotificationListResponseDto.java | 2 +- .../domain/notification/service/NotificationService.java | 2 +- .../domain/post/domain/poll/controller/PollController.java | 4 ++-- .../domain/poll/dto/{ => request}/PollCreateRequestDTO.java | 2 +- .../domain/poll/dto/{ => request}/PollVotingRequestDTO.java | 2 +- .../codin/domain/post/domain/poll/service/PollService.java | 4 ++-- .../inu/codin/codin/infra/fcm/controller/FcmController.java | 2 +- .../codin/infra/fcm/dto/{ => request}/FcmTokenRequest.java | 2 +- .../java/inu/codin/codin/infra/fcm/service/FcmService.java | 2 +- 31 files changed, 41 insertions(+), 36 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/{ => request}/ChatRoomCreateRequestDto.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/{ => response}/ChatRoomListResponseDto.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/email/dto/{ => request}/JoinEmailCheckRequestDto.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/email/dto/{ => request}/JoinEmailSendRequestDto.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/{ => request}/CreateReviewRequestDto.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/{ => response}/ReviewListResposneDto.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/{ => response}/ReviewPageResponse.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/{ => response}/EmptyRoomResponseDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LectureDetailResponseDto.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LectureListResponseDto.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LecturePageResponse.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/{ => response}/LectureSearchListResponseDto.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/like/dto/{ => request}/LikeRequestDto.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/notification/{ => controller}/NotificationController.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/notification/dto/{ => response}/NotificationListResponseDto.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/{ => request}/PollCreateRequestDTO.java (96%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/{ => request}/PollVotingRequestDTO.java (85%) rename codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/{ => request}/FcmTokenRequest.java (88%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 73344597..3b94ba40 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/request/ChatRoomCreateRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/request/ChatRoomCreateRequestDto.java index ac998c98..d91e314a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/request/ChatRoomCreateRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.chat.chatroom.dto; +package inu.codin.codin.domain.chat.chatroom.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/response/ChatRoomListResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/response/ChatRoomListResponseDto.java index ebdcb985..038e87d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/ChatRoomListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/dto/response/ChatRoomListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.chat.chatroom.dto; +package inu.codin.codin.domain.chat.chatroom.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index e7767a05..21cfbb14 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 2c1ccbbb..793bcc07 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -3,8 +3,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomCreateRequestDto; -import inu.codin.codin.domain.chat.chatroom.dto.ChatRoomListResponseDto; +import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; +import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.dto.event.ChatRoomNotificationEvent; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailCheckRequestDto.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailCheckRequestDto.java index 6d33bad5..5b13f03d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailCheckRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailCheckRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.email.dto; +package inu.codin.codin.domain.email.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailSendRequestDto.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailSendRequestDto.java index 94a1eb50..aad9b7aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/dto/JoinEmailSendRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/dto/request/JoinEmailSendRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.email.dto; +package inu.codin.codin.domain.email.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/request/CreateReviewRequestDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/request/CreateReviewRequestDto.java index b219d93d..f0473187 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/CreateReviewRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/request/CreateReviewRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.review.dto; +package inu.codin.codin.domain.lecture.domain.review.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Digits; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewListResposneDto.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewListResposneDto.java index 4d613755..2d7bdc34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewListResposneDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewListResposneDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.review.dto; +package inu.codin.codin.domain.lecture.domain.review.dto.response; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewPageResponse.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewPageResponse.java index 95671318..f766e9d8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/ReviewPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/dto/response/ReviewPageResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.review.dto; +package inu.codin.codin.domain.lecture.domain.review.dto.response; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index aaae7d88..1459b2bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.lecture.domain.review.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index e4dd991d..5c8ec6c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -2,9 +2,9 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; -import inu.codin.codin.domain.lecture.domain.review.dto.ReviewListResposneDto; -import inu.codin.codin.domain.lecture.domain.review.dto.ReviewPageResponse; +import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewListResposneDto; +import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewPageResponse; import inu.codin.codin.domain.lecture.domain.review.entity.ReviewEntity; import inu.codin.codin.domain.lecture.domain.review.exception.ReviewExistenceException; import inu.codin.codin.domain.lecture.domain.review.exception.WrongRatingException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/response/EmptyRoomResponseDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/response/EmptyRoomResponseDto.java index 7c294194..948bea34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/EmptyRoomResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/dto/response/EmptyRoomResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.domain.room.dto; +package inu.codin.codin.domain.lecture.domain.room.dto.response; import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java index 9a8d22e7..a12bd3ec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/service/LectureRoomService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.domain.room.service; -import inu.codin.codin.domain.lecture.domain.room.dto.EmptyRoomResponseDto; +import inu.codin.codin.domain.lecture.domain.room.dto.response.EmptyRoomResponseDto; import inu.codin.codin.domain.lecture.domain.room.entity.LectureRoomEntity; import inu.codin.codin.domain.lecture.repository.LectureRepositoryCustomImpl; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureDetailResponseDto.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureDetailResponseDto.java index 9a59dddb..4b8c73a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureDetailResponseDto.java @@ -1,5 +1,6 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; +import inu.codin.codin.domain.lecture.dto.Emotion; import inu.codin.codin.domain.lecture.entity.LectureEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureListResponseDto.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureListResponseDto.java index c2d59ac7..d6484ff1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; import inu.codin.codin.domain.lecture.entity.LectureEntity; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LecturePageResponse.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LecturePageResponse.java index bfb8a75b..10501b8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LecturePageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LecturePageResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureSearchListResponseDto.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureSearchListResponseDto.java index 1bcd055f..ca9fa0b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/LectureSearchListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/dto/response/LectureSearchListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.lecture.dto; +package inu.codin.codin.domain.lecture.dto.response; import inu.codin.codin.domain.lecture.entity.LectureEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index 106424a4..e6d2e410 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -3,6 +3,10 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.dto.*; +import inu.codin.codin.domain.lecture.dto.response.LectureDetailResponseDto; +import inu.codin.codin.domain.lecture.dto.response.LectureListResponseDto; +import inu.codin.codin.domain.lecture.dto.response.LecturePageResponse; +import inu.codin.codin.domain.lecture.dto.response.LectureSearchListResponseDto; import inu.codin.codin.domain.lecture.entity.LectureEntity; import inu.codin.codin.domain.lecture.exception.WrongInputException; import inu.codin.codin.domain.lecture.repository.LectureRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 29bc860d..181fd8c5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.like.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.dto.request.LikeRequestDto; import inu.codin.codin.domain.like.service.LikeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java index b84b3186..2ba9d792 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.like.dto; +package inu.codin.codin.domain.like.dto.request; import inu.codin.codin.domain.like.entity.LikeType; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java rename to codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java index b0490c88..d0baaaa0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.notification; +package inu.codin.codin.domain.notification.controller; import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java rename to codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java index ea8ac8fb..eb72a1c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.notification.dto; +package inu.codin.codin.domain.notification.dto.response; import inu.codin.codin.domain.notification.entity.NotificationEntity; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 07e05bd1..9edee894 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.notification.dto.NotificationListResponseDto; +import inu.codin.codin.domain.notification.dto.response.NotificationListResponseDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index f1924f4a..8d70487d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.post.domain.poll.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; -import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.service.PollService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollCreateRequestDTO.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollCreateRequestDTO.java index 2721bc45..77b8c90e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.poll.dto; +package inu.codin.codin.domain.post.domain.poll.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.entity.PostCategory; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollVotingRequestDTO.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollVotingRequestDTO.java index c95f5db4..b13b169f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/PollVotingRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/dto/request/PollVotingRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.poll.dto; +package inu.codin.codin.domain.post.domain.poll.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java index 507d01e1..c33c4846 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; -import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java index 3d1cd4de..a7522bb3 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java @@ -1,7 +1,7 @@ package inu.codin.codin.infra.fcm.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; +import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import inu.codin.codin.infra.fcm.service.FcmService; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java rename to codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java index d873c705..97f4bead 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/FcmTokenRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.infra.fcm.dto; +package inu.codin.codin.infra.fcm.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 2ed53662..eda13223 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -8,7 +8,7 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; -import inu.codin.codin.infra.fcm.dto.FcmTokenRequest; +import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import inu.codin.codin.infra.fcm.entity.FcmTokenEntity; import inu.codin.codin.infra.fcm.exception.FcmTokenNotFoundException; import inu.codin.codin.infra.fcm.repository.FcmTokenRepository; From c974ddebfbd91d7cb928f871831c8a90ba4b835e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sat, 12 Apr 2025 21:48:47 +0900 Subject: [PATCH 0854/1002] =?UTF-8?q?refactor=20:=20reply=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A5=BC=20comment=20domain=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/dto/request/LikeRequestDto.java | 2 +- .../domain}/reply/controller/ReplyCommentController.java | 8 ++++---- .../dto/request/ReplyAnonnymousUpdateRequestDTO.java | 2 +- .../domain}/reply/dto/request/ReplyCreateRequestDTO.java | 2 +- .../domain}/reply/dto/request/ReplyUpdateRequestDTO.java | 2 +- .../domain}/reply/entity/ReplyCommentEntity.java | 2 +- .../domain}/reply/repository/ReplyCommentRepository.java | 4 ++-- .../domain/comment/dto/response/CommentResponseDTO.java | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/controller/ReplyCommentController.java (83%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java (78%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/dto/request/ReplyCreateRequestDTO.java (85%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/dto/request/ReplyUpdateRequestDTO.java (78%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/entity/ReplyCommentEntity.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/repository/ReplyCommentRepository.java (74%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java index 2ba9d792..cb8ae060 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/request/LikeRequestDto.java @@ -15,5 +15,5 @@ public class LikeRequestDto { @NotBlank @Schema(description = "좋아요를 반영할 entity 의 _id 값", example = "111111") - private String id; + private String likeTypeId; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/controller/ReplyCommentController.java similarity index 83% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/controller/ReplyCommentController.java index b2529210..f0442ec1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/controller/ReplyCommentController.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.post.domain.reply.controller; +package inu.codin.codin.domain.post.domain.comment.domain.reply.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.service.ReplyCommentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java similarity index 78% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java index 0e4774f7..f6859b5a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyCreateRequestDTO.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyCreateRequestDTO.java index 0795530f..a0866cd1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyUpdateRequestDTO.java similarity index 78% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyUpdateRequestDTO.java index 3d152789..68906e50 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/request/ReplyUpdateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/entity/ReplyCommentEntity.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/entity/ReplyCommentEntity.java index 86faa31a..24fbad6b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/entity/ReplyCommentEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.entity; +package inu.codin.codin.domain.post.domain.comment.domain.reply.entity; import inu.codin.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/repository/ReplyCommentRepository.java similarity index 74% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/repository/ReplyCommentRepository.java index 7be2aeb6..209ce552 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/repository/ReplyCommentRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.domain.reply.repository; +package inu.codin.codin.domain.post.domain.comment.domain.reply.repository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 76b941a0..2012abc5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; From 59b9e133fdbf0ef4ca7fb5815ff315733dfda6d3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 13 Apr 2025 00:24:45 +0900 Subject: [PATCH 0855/1002] =?UTF-8?q?refactor=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20eventListener=EB=A1=9C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ChattingEventListener.java | 1 - .../like/dto/event/LikeNotificationEvent.java | 19 +++++++ .../like/service/LikeEventListener.java | 22 ++++++++ .../domain/like/service/LikeService.java | 56 ++++++++++--------- .../service/NotificationService.java | 14 ++--- .../dto/event/ReplyNotificationEvent.java | 25 +++++++++ .../reply/service/ReplyCommentService.java | 16 ++++-- .../reply/service/ReplyEventListener.java | 22 ++++++++ .../dto/event/CommentNotificationEvent.java | 25 +++++++++ .../comment/service/CommentEventListener.java | 22 ++++++++ .../comment/service/CommentService.java | 9 ++- 11 files changed, 188 insertions(+), 43 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment/domain}/reply/service/ReplyCommentService.java (87%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index ca6a5f2c..4a353a8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -37,7 +37,6 @@ public class ChattingEventListener { 2. 채팅방의 마지막 메세지 업데이트 3. /queue/chatroom/unread 를 통해 상대방의 채팅방 목록 실시간 업데이트 */ - @Async @EventListener public void handleChattingArrivedEvent(ChattingArrivedEvent event){ Chatting chatting = event.getChatting(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java new file mode 100644 index 00000000..82aed5ec --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/event/LikeNotificationEvent.java @@ -0,0 +1,19 @@ +package inu.codin.codin.domain.like.dto.event; + +import inu.codin.codin.domain.like.entity.LikeType; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class LikeNotificationEvent extends ApplicationEvent { + private final LikeType likeType; + + private final ObjectId likeTypeId; + + public LikeNotificationEvent(Object source, LikeType likeType, ObjectId likeTypeId) { + super(source); + this.likeType = likeType; + this.likeTypeId = likeTypeId; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java new file mode 100644 index 00000000..f9e51a24 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeEventListener.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.like.service; + +import inu.codin.codin.domain.like.dto.event.LikeNotificationEvent; +import inu.codin.codin.domain.notification.service.NotificationService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +public class LikeEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleLikeNotificationEvent(LikeNotificationEvent event){ + notificationService.sendNotificationMessageByLike(event.getLikeType(), event.getLikeTypeId()); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 541fbd5c..97dceaad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -3,19 +3,21 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; -import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.dto.event.LikeNotificationEvent; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.like.dto.request.LikeRequestDto; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisBestService; import inu.codin.codin.infra.redis.service.RedisLikeService; +import inu.codin.codin.infra.redis.service.RedisBestService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -31,22 +33,23 @@ public class LikeService { private final ReplyCommentRepository replyCommentRepository; private final ReviewRepository reviewRepository; + private final ApplicationEventPublisher eventPublisher; private final RedisLikeService redisLikeService; private final RedisBestService redisBestService; private final RedisHealthChecker redisHealthChecker; public String toggleLike(LikeRequestDto likeRequestDto) { - ObjectId likeId = new ObjectId(likeRequestDto.getId()); + ObjectId likeTypeId = new ObjectId(likeRequestDto.getLikeTypeId()); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 - Optional like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - return getResult(likeRequestDto, like, likeId, userId); + Optional like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeTypeId, userId); + return getResult(likeRequestDto, like, likeTypeId, userId); } - private String getResult(LikeRequestDto likeRequestDto, Optional like, ObjectId likeId, ObjectId userId) { + private String getResult(LikeRequestDto likeRequestDto, Optional like, ObjectId likeTypeId, ObjectId userId) { if (like.isPresent()){ if (like.get().getDeletedAt() == null) { removeLike(like.get()); @@ -56,25 +59,26 @@ private String getResult(LikeRequestDto likeRequestDto, Optional lik return "좋아요가 복구되었습니다"; } } else { - addLike(likeRequestDto.getLikeType(), likeId, userId); + addLike(likeRequestDto.getLikeType(), likeTypeId, userId); + eventPublisher.publishEvent(new LikeNotificationEvent(this, likeRequestDto.getLikeType(), likeTypeId)); return "좋아요가 추가되었습니다."; } } - public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId){ + public void addLike(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ if (redisHealthChecker.isRedisAvailable()) { - redisLikeService.addLike(likeType.name(), likeId); - log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); + redisLikeService.addLike(likeType.name(), likeTypeId); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeTypeId, userId); } likeRepository.save(LikeEntity.builder() .likeType(likeType) - .likeTypeId(likeId) + .likeTypeId(likeTypeId) .userId(userId) .build()); if (likeType == LikeType.POST) { - redisBestService.applyBestScore(1, likeId); - log.info("Redis에 Best Score 적용 - postId: {}", likeId); + redisBestService.applyBestScore(1, likeTypeId); + log.info("Redis에 Best Score 적용 - postId: {}", likeTypeId); } } @@ -101,21 +105,21 @@ public void removeLike(LikeEntity like) { log.info("좋아요 삭제 완료 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); } - public int getLikeCount(LikeType entityType, ObjectId entityId) { + public int getLikeCount(LikeType likeType, ObjectId likeTypeId) { Object redisResult = null; if (redisHealthChecker.isRedisAvailable()) { - redisResult = redisLikeService.getLikeCount(entityType.name(), entityId); + redisResult = redisLikeService.getLikeCount(likeType.name(), likeTypeId); } if (redisResult == null){ - recoveryLike(entityType, entityId); - return likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); + recoveryLike(likeType, likeTypeId); + return likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeTypeId); } else return Integer.parseInt(String.valueOf(redisResult)); } @Async - protected void recoveryLike(LikeType entityType, ObjectId entityId) { - int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); - redisLikeService.recoveryLike(entityType, entityId, likeCount); + protected void recoveryLike(LikeType likeType, ObjectId likeTypeId) { + int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(likeType, likeTypeId); + redisLikeService.recoveryLike(likeType, likeTypeId, likeCount); } public boolean isLiked(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ @@ -123,15 +127,15 @@ public boolean isLiked(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ } private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ - ObjectId id = new ObjectId(likeRequestDto.getId()); + ObjectId likeTypeId = new ObjectId(likeRequestDto.getLikeTypeId()); switch(likeRequestDto.getLikeType()){ - case POST -> postRepository.findByIdAndNotDeleted(id) + case POST -> postRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - case REPLY -> replyCommentRepository.findByIdAndNotDeleted(id) + case REPLY -> replyCommentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); - case COMMENT -> commentRepository.findByIdAndNotDeleted(id) + case COMMENT -> commentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); - case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(id) + case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(likeTypeId) .orElseThrow(() ->new NotFoundException("수강 후기를 찾을 수 없습니다")); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 9edee894..e9d60e88 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; @@ -40,7 +40,7 @@ public class NotificationService { private final FcmService fcmService; private final String NOTI_COMMENT = "댓글이 달렸습니다: "; private final String NOTI_REPLY = "대댓글이 달렸습니다: "; - private final String NOTI_LIKE = ""; + private final String NOTI_LIKE = "좋아요가 달렸습니다"; private final String NOTI_CHAT = "새로운 채팅이 있습니다."; @@ -146,10 +146,10 @@ public void sendNotificationMessageByReply(PostCategory postCategory, ObjectId u sendFcmMessageToUser(title, NOTI_REPLY+content, post, userId); } - public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { + public void sendNotificationMessageByLike(LikeType likeType, ObjectId likeTypeId) { switch(likeType){ case POST -> { - PostEntity postEntity = postRepository.findByIdAndNotDeleted(id) + PostEntity postEntity = postRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); userRepository.findById(postEntity.getUserId()) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); @@ -158,7 +158,7 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { sendFcmMessageToUser(NOTI_LIKE, "내 게시글 보러 가기", post, postEntity.getUserId()); } case REPLY -> { - ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(id) + ReplyCommentEntity replyCommentEntity = replyCommentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(replyCommentEntity.getCommentId()) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); @@ -171,7 +171,7 @@ public void sendNotificationMessageByLike(LikeType likeType, ObjectId id) { sendFcmMessageToUser(NOTI_LIKE, "내 답글 보러 가기", post, replyCommentEntity.getUserId()); } case COMMENT -> { - CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(id) + CommentEntity commentEntity = commentRepository.findByIdAndNotDeleted(likeTypeId) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); PostEntity postEntity = postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java new file mode 100644 index 00000000..0226e913 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/dto/event/ReplyNotificationEvent.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.post.domain.comment.domain.reply.dto.event; + +import inu.codin.codin.domain.post.entity.PostCategory; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class ReplyNotificationEvent extends ApplicationEvent { + private final PostCategory postCategory; + + private final ObjectId userId; + + private final String postId; + + private final String content; + + public ReplyNotificationEvent(Object source, PostCategory postCategory, ObjectId userId, String postId, String content) { + super(source); + this.postCategory = postCategory; + this.userId = userId; + this.postId = postId; + this.content = content; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyCommentService.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyCommentService.java index 91adfc63..a4c2fda2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyCommentService.java @@ -1,17 +1,18 @@ -package inu.codin.codin.domain.post.domain.reply.service; +package inu.codin.codin.domain.post.domain.comment.domain.reply.service; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.event.ReplyNotificationEvent; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; @@ -25,6 +26,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import java.util.List; @@ -46,6 +48,7 @@ public class ReplyCommentService { private final NotificationService notificationService; private final RedisBestService redisBestService; private final S3Service s3Service; + private final ApplicationEventPublisher eventPublisher; // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { @@ -74,7 +77,8 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { redisBestService.applyBestScore(1, post.get_id()); log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (!userId.equals(post.getUserId())) + eventPublisher.publishEvent(new ReplyNotificationEvent(this, post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent())); } // 대댓글 삭제 (Soft Delete) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java new file mode 100644 index 00000000..2a27d3d1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/domain/reply/service/ReplyEventListener.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.post.domain.comment.domain.reply.service; + +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.dto.event.ReplyNotificationEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReplyEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleReplyNotificationEvent(ReplyNotificationEvent event){ + notificationService.sendNotificationMessageByReply(event.getPostCategory(), event.getUserId(), event.getPostId(), event.getContent()); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java new file mode 100644 index 00000000..987e1391 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/event/CommentNotificationEvent.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.post.domain.comment.dto.event; + +import inu.codin.codin.domain.post.entity.PostCategory; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEvent; + +@Getter +public class CommentNotificationEvent extends ApplicationEvent { + private final PostCategory postCategory; + + private final ObjectId userId; + + private final String postId; + + private final String content; + + public CommentNotificationEvent(Object source, PostCategory postCategory, ObjectId userId, String postId, String content) { + super(source); + this.postCategory = postCategory; + this.userId = userId; + this.postId = postId; + this.content = content; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java new file mode 100644 index 00000000..c439cf96 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentEventListener.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.post.domain.comment.service; + +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.event.CommentNotificationEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CommentEventListener { + + private final NotificationService notificationService; + + @Async + @EventListener + public void handleCommentNotificationEvent(CommentNotificationEvent event){ + notificationService.sendNotificationMessageByComment(event.getPostCategory(), event.getUserId(), event.getPostId(), event.getContent()); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 428b5492..b4776e66 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -5,13 +5,14 @@ import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.event.CommentNotificationEvent; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.comment.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.UserDto; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; @@ -22,6 +23,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import java.util.List; @@ -42,6 +44,7 @@ public class CommentService { private final NotificationService notificationService; private final RedisBestService redisBestService; private final S3Service s3Service; + private final ApplicationEventPublisher eventPublisher; // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -65,8 +68,8 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { redisBestService.applyBestScore(1, postId); log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); - + if (!userId.equals(post.getUserId())) + eventPublisher.publishEvent(new CommentNotificationEvent(this, post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent())); } // 댓글 삭제 (Soft Delete) From f0d5dd9ec7e201c1ab5edc1212913323811dfe4f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 16 Jul 2025 20:28:39 +0900 Subject: [PATCH 0856/1002] =?UTF-8?q?refactor=20:=20dto=20response,=20requ?= =?UTF-8?q?est=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EB=A1=9C=20=EB=82=98?= =?UTF-8?q?=EB=88=94=EC=97=90=20=EB=94=B0=EB=9D=BC=20import=20=EC=9E=AC?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/email/controller/EmailController.java | 4 ++-- .../codin/domain/email/service/JoinEmailAuthService.java | 4 ++-- .../codin/domain/email/service/PasswordResetEmailService.java | 2 +- .../lecture/domain/review/controller/ReviewController.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 22f0f632..7a55b765 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.email.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.service.JoinEmailAuthService; import inu.codin.codin.domain.email.service.PasswordResetEmailService; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java index 4b8f47cb..ee05fabe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/JoinEmailAuthService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.domain.email.dto.JoinEmailCheckRequestDto; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailCheckRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailAuthFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java index 82e676bf..1c5761b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.email.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.email.dto.JoinEmailSendRequestDto; +import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; import inu.codin.codin.domain.email.repository.EmailAuthRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java index 4b1da7ac..7974d6ad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.lecture.domain.review.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.lecture.domain.review.dto.CreateReviewRequestDto; +import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; From a93d393e5ab65c6f0e541bf6f1c050c339281cb4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 16 Jul 2025 20:30:48 +0900 Subject: [PATCH 0857/1002] =?UTF-8?q?refactor=20:=20import=20=EC=9E=AC?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/service/RedisBestService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index aa306341..57263ba4 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -15,10 +15,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; From 168b9268080c9e7154ee0f2a7081185ce2317d20 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 17 Jul 2025 10:44:17 +0900 Subject: [PATCH 0858/1002] =?UTF-8?q?refactor:=20Comment=20=EB=B0=8F=20Rep?= =?UTF-8?q?ly=20CQRS=20=EA=B5=AC=EC=A1=B0=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상위 도메인 Repository 직접 참조 제거 (Comment → Post 등) - 상위 도메인 쿼리 서비스(PostQueryService 등)에서 유효성 검사 처리 - 의존성 순환 문제 발생 → Query/Command 분리로 해결 --- .../comment/controller/CommentController.java | 35 +- .../comment/repository/CommentRepository.java | 2 + .../service/CommentCommandService.java | 84 ++++ .../comment/service/CommentQueryService.java | 130 ++++++ .../comment/service/CommentService.java | 411 +++++++++--------- .../controller/ReplyCommentController.java | 10 +- .../reply/service/ReplyCommandService.java | 89 ++++ .../reply/service/ReplyCommentService.java | 404 ++++++++--------- .../reply/service/ReplyQueryService.java | 123 ++++++ .../post/service/PostCommandService.java | 66 ++- .../domain/post/service/PostQueryService.java | 12 +- .../report/controller/ReportController.java | 2 - .../domain/report/service/ReportService.java | 12 +- 13 files changed, 913 insertions(+), 467 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index 30b75c2a..c46f08e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -5,10 +5,12 @@ import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.service.CommentService; +import inu.codin.codin.domain.post.domain.comment.service.CommentCommandService; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,37 +18,27 @@ import java.util.List; @RestController +@RequiredArgsConstructor @RequestMapping("/comments") @Tag(name = "Comment API", description = "댓글 API") public class CommentController { - private final CommentService commentService; + private final CommentCommandService commentCommandService; + private final CommentQueryService commentQueryService; - public CommentController(CommentService commentService) { - this.commentService = commentService; - } @Operation(summary = "댓글 추가") @PostMapping("/{postId}") public ResponseEntity> addComment(@PathVariable String postId, @RequestBody @Valid CommentCreateRequestDTO requestDTO) { - commentService.addComment(postId, requestDTO); + commentCommandService.addComment(postId, requestDTO); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "댓글이 추가되었습니다.", null)); } - @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회 (삭제된 내역도 모두 반환)") - @GetMapping("/post/{postId}") - public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { - List response = commentService.getCommentsByPostId(postId); - return ResponseEntity.ok() - .body(new ListResponse<>(200, "해당 게시물의 댓글 및 대댓글 조회 성공", response)); - - } - @Operation(summary = "댓글 삭제") @DeleteMapping("/{commentId}") public ResponseEntity> softDeleteComment(@PathVariable String commentId) { - commentService.softDeleteComment(commentId); + commentCommandService.softDeleteComment(commentId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "댓글이 삭제되었습니다.", null)); } @@ -54,10 +46,19 @@ public ResponseEntity> softDeleteComment(@PathVariable Stri @Operation(summary = "댓글 수정") @PatchMapping("/{commentId}") public ResponseEntity> updateComment(@PathVariable String commentId, @RequestBody @Valid CommentUpdateRequestDTO requestDTO){ - commentService.updateComment(commentId, requestDTO); + commentCommandService.updateComment(commentId, requestDTO); return ResponseEntity.status(HttpStatus.OK). body(new SingleResponse<>(200, "댓글 수정되었습니다.", null)); } + @Operation(summary = "해당 게시물의 댓글 및 대댓글 조회 (삭제된 내역도 모두 반환)") + @GetMapping("/post/{postId}") + public ResponseEntity> getCommentsByPostId(@PathVariable String postId) { + List response = commentQueryService.getCommentsByPostId(postId); + return ResponseEntity.ok() + .body(new ListResponse<>(200, "해당 게시물의 댓글 및 대댓글 조회 성공", response)); + + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java index 80bbfe83..12294fea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/repository/CommentRepository.java @@ -6,10 +6,12 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; +@Repository public interface CommentRepository extends MongoRepository { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java new file mode 100644 index 00000000..0681bb8d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -0,0 +1,84 @@ +package inu.codin.codin.domain.post.domain.comment.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.infra.redis.service.RedisBestService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentCommandService { + private final CommentRepository commentRepository; + private final NotificationService notificationService; + private final RedisBestService redisBestService; + private final PostCommandService postCommandService; + private final PostQueryService postQueryService; + private final CommentQueryService commentQueryService; + // 댓글 추가 + public void addComment(String id, CommentCreateRequestDTO requestDTO) { + + ObjectId postId = ObjectIdUtil.toObjectId(id); + PostEntity post = postQueryService.findPostById(postId); + + ObjectId userId = SecurityUtils.getCurrentUserId(); + + CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); + commentRepository.save(comment); + + postCommandService.handleCommentCreation(post, userId); + redisBestService.applyBestScore(1, postId); + + log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); + + } + + public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { + log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); + + ObjectId commentId = ObjectIdUtil.toObjectId(id); + CommentEntity comment = commentQueryService.findCommentById(commentId); + + ObjectId userId = SecurityUtils.getCurrentUserId(); + SecurityUtils.validateUser(userId); + + + comment.updateComment(requestDTO.getContent()); + commentRepository.save(comment); + + log.info("댓글 업데이트 완료. commentId: {}", commentId); + + } + + // 댓글 삭제 (Soft Delete) + public void softDeleteComment(String id) { + ObjectId commentId = ObjectIdUtil.toObjectId(id); + CommentEntity comment = commentQueryService.findCommentById(commentId); + + SecurityUtils.validateUser(comment.getUserId()); + + ObjectId postId = comment.getPostId(); + PostEntity post = postQueryService.findPostById(postId); + + // 댓글 Soft Delete 처리 + comment.delete(); + commentRepository.save(comment); + + postCommandService.decreaseCommentCount(post); + redisBestService.applyBestScore(-1, postId); + + log.info("삭제된 commentId: {}", commentId); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java new file mode 100644 index 00000000..e0a837ce --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -0,0 +1,130 @@ +package inu.codin.codin.domain.post.domain.comment.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; +import inu.codin.codin.domain.post.domain.comment.exception.CommentException; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentQueryService { + private final CommentRepository commentRepository; + private final UserRepository userRepository; + + private final LikeService likeService; + private final PostQueryService postQueryService; + private final S3Service s3Service; + private final ReplyQueryService replyQueryService; + + /** + * 특정 게시물의 댓글 및 대댓글 조회 + */ + public List getCommentsByPostId(String id) { + // 1. 입력 검증 및 게시물 조회 + ObjectId postId = ObjectIdUtil.toObjectId(id); + PostEntity post = postQueryService.findPostById(postId); + + // 2. 댓글 목록 조회 + List comments = commentRepository.findByPostId(postId); + if (comments.isEmpty()) { + return Collections.emptyList(); + } + + // 3. 사용자 정보 맵 생성 + Map userMap = createUserMap(comments); + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); + + // 4. 댓글 DTO 변환 + return comments.stream() + .map(comment -> buildCommentResponseDTO(post.getAnonymous(), comment, userMap, defaultImageUrl)) + .collect(Collectors.toList()); + } + + + + /** + * 댓글 작성자들의 사용자 정보 맵 생성 + */ + private Map createUserMap(List comments) { + List userIdsInOrder = comments.stream() + .map(CommentEntity::getUserId) + .toList(); + + List distinctIds = userIdsInOrder.stream() + .distinct() + .toList(); + + return userRepository.findAllById(distinctIds) + .stream() + .collect(Collectors.toMap( + UserEntity::get_id, + user -> user + )); + } + + /** + * 댓글 응답 DTO 생성 + */ + private CommentResponseDTO buildCommentResponseDTO( + PostAnonymous postAnonymous, + CommentEntity comment, + Map userMap, + String defaultImageUrl) { + + int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); + + UserEntity user = userMap.get(comment.getUserId()); + + // 댓글용 사용자 DTO 생성 + UserDto commentUserDto = UserDto.ofComment(comment, user, anonNum ,defaultImageUrl); + + return CommentResponseDTO.commentOf( + comment, + commentUserDto, + replyQueryService.getRepliesByCommentId(postAnonymous, comment.get_id()), + likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), + getUserInfoAboutComment(comment.get_id()) + ); + } + + + public CommentResponseDTO.UserInfo getUserInfoAboutComment(ObjectId commentId) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + return CommentResponseDTO.UserInfo.builder() + .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) + .build(); + } + + /** + * + * @param commentId + * @return validated PostEntity + */ + public CommentEntity findCommentById(ObjectId commentId) { + return commentRepository.findByIdAndNotDeleted(commentId) + .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 07bff5a9..3b599700 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -1,201 +1,210 @@ -package inu.codin.codin.domain.post.domain.comment.service; - -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.notification.service.NotificationService; -import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.comment.exception.CommentException; -import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.dto.UserDto; -import inu.codin.codin.domain.post.entity.PostAnonymous; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.exception.PostErrorCode; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.service.PostCommandService; -import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisBestService; -import inu.codin.codin.infra.s3.S3Service; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.stereotype.Service; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CommentService { - - private final PostRepository postRepository; - private final CommentRepository commentRepository; - - private final UserRepository userRepository; - private final LikeService likeService; - private final ReplyCommentService replyCommentService; - private final NotificationService notificationService; - private final RedisBestService redisBestService; - private final PostCommandService postCommandService; - private final PostQueryService postQueryService; - - - private final S3Service s3Service; - - // 댓글 추가 - public void addComment(String id, CommentCreateRequestDTO requestDTO) { - - ObjectId postId = ObjectIdUtil.toObjectId(id); - PostEntity post = postRepository.findById(postId) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - - ObjectId userId = SecurityUtils.getCurrentUserId(); - - CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); - commentRepository.save(comment); - - postCommandService.handleCommentCreation(post, userId); - redisBestService.applyBestScore(1, postId); - - log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); - - } - - // 댓글 삭제 (Soft Delete) - public void softDeleteComment(String id) { - ObjectId commentId = ObjectIdUtil.toObjectId(id); - CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); - SecurityUtils.validateUser(comment.getUserId()); - - ObjectId postId = comment.getPostId(); - PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - - // 댓글 Soft Delete 처리 - comment.delete(); - commentRepository.save(comment); - - postCommandService.decreaseCommentCount(post); - redisBestService.applyBestScore(-1, postId); - - log.info("삭제된 commentId: {}", commentId); - } - - - /** - * 특정 게시물의 댓글 및 대댓글 조회 - */ - public List getCommentsByPostId(String id) { - // 1. 입력 검증 및 게시물 조회 - ObjectId postId = ObjectIdUtil.toObjectId(id); - PostEntity post = findPostById(postId); - - // 2. 댓글 목록 조회 - List comments = commentRepository.findByPostId(postId); - if (comments.isEmpty()) { - return Collections.emptyList(); - } - - // 3. 사용자 정보 맵 생성 - Map userMap = createUserMap(comments); - String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); - - // 4. 댓글 DTO 변환 - return comments.stream() - .map(comment -> buildCommentResponseDTO(post.getAnonymous(), comment, userMap, defaultImageUrl)) - .collect(Collectors.toList()); - } - - - private PostEntity findPostById(ObjectId postId) { - return postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - } - /** - * 댓글 작성자들의 사용자 정보 맵 생성 - */ - private Map createUserMap(List comments) { - List userIdsInOrder = comments.stream() - .map(CommentEntity::getUserId) - .toList(); - - List distinctIds = userIdsInOrder.stream() - .distinct() - .toList(); - - return userRepository.findAllById(distinctIds) - .stream() - .collect(Collectors.toMap( - UserEntity::get_id, - user -> user - )); - } - - /** - * 댓글 응답 DTO 생성 - */ - private CommentResponseDTO buildCommentResponseDTO( - PostAnonymous postAnonymous, - CommentEntity comment, - Map userMap, - String defaultImageUrl) { - - int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); - - UserEntity user = userMap.get(comment.getUserId()); - - // 댓글용 사용자 DTO 생성 - UserDto commentUserDto = UserDto.ofComment(comment, user, anonNum ,defaultImageUrl); - - return CommentResponseDTO.commentOf( - comment, - commentUserDto, - replyCommentService.getRepliesByCommentId(postAnonymous, comment.get_id()), - likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), - getUserInfoAboutComment(comment.get_id()) - ); - } - - public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { - log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); - - ObjectId commentId = ObjectIdUtil.toObjectId(id); - CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); - - //본인 댓글만 수정 가능 - ObjectId userId = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateUser(userId); - - - comment.updateComment(requestDTO.getContent()); - commentRepository.save(comment); - - log.info("댓글 업데이트 완료. commentId: {}", commentId); - - } - - public UserInfo getUserInfoAboutComment(ObjectId commentId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); - return UserInfo.builder() - .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) - .build(); - } - - -} \ No newline at end of file +//package inu.codin.codin.domain.post.domain.comment.service; +// +//import inu.codin.codin.common.security.util.SecurityUtils; +//import inu.codin.codin.common.util.ObjectIdUtil; +//import inu.codin.codin.domain.like.entity.LikeType; +//import inu.codin.codin.domain.like.service.LikeService; +//import inu.codin.codin.domain.notification.service.NotificationService; +//import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +//import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +//import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +//import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; +//import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +//import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +//import inu.codin.codin.domain.post.domain.comment.exception.CommentException; +//import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; +//import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +//import inu.codin.codin.domain.post.dto.UserDto; +//import inu.codin.codin.domain.post.entity.PostAnonymous; +//import inu.codin.codin.domain.post.entity.PostEntity; +//import inu.codin.codin.domain.post.exception.PostErrorCode; +//import inu.codin.codin.domain.post.exception.PostException; +//import inu.codin.codin.domain.post.repository.PostRepository; +//import inu.codin.codin.domain.post.service.PostCommandService; +//import inu.codin.codin.domain.post.service.PostQueryService; +//import inu.codin.codin.domain.user.entity.UserEntity; +//import inu.codin.codin.domain.user.repository.UserRepository; +//import inu.codin.codin.infra.redis.service.RedisBestService; +//import inu.codin.codin.infra.s3.S3Service; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.bson.types.ObjectId; +//import org.springframework.stereotype.Service; +// +//import java.util.Collections; +//import java.util.List; +//import java.util.Map; +//import java.util.stream.Collectors; +// +//@Service +//@RequiredArgsConstructor +//@Slf4j +//public class CommentService { +// +// private final CommentRepository commentRepository; +// private final UserRepository userRepository; +// +// private final LikeService likeService; +// private final ReplyCommentService replyCommentService; +// private final NotificationService notificationService; +// private final RedisBestService redisBestService; +// private final PostCommandService postCommandService; +// private final PostQueryService postQueryService; +// private final S3Service s3Service; +// +// +// /** +// * Command Method +// */ +// +// // 댓글 추가 +// public void addComment(String id, CommentCreateRequestDTO requestDTO) { +// +// ObjectId postId = ObjectIdUtil.toObjectId(id); +// PostEntity post = postQueryService.findPostById(postId); +// +// ObjectId userId = SecurityUtils.getCurrentUserId(); +// +// CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); +// commentRepository.save(comment); +// +// postCommandService.handleCommentCreation(post, userId); +// redisBestService.applyBestScore(1, postId); +// +// log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); +// if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); +// +// } +// +// public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { +// log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); +// +// ObjectId commentId = ObjectIdUtil.toObjectId(id); +// CommentEntity comment = findCommentById(commentId); +// +// ObjectId userId = SecurityUtils.getCurrentUserId(); +// SecurityUtils.validateUser(userId); +// +// +// comment.updateComment(requestDTO.getContent()); +// commentRepository.save(comment); +// +// log.info("댓글 업데이트 완료. commentId: {}", commentId); +// +// } +// +// // 댓글 삭제 (Soft Delete) +// public void softDeleteComment(String id) { +// ObjectId commentId = ObjectIdUtil.toObjectId(id); +// CommentEntity comment = findCommentById(commentId); +// +// SecurityUtils.validateUser(comment.getUserId()); +// +// ObjectId postId = comment.getPostId(); +// PostEntity post = postQueryService.findPostById(postId); +// +// // 댓글 Soft Delete 처리 +// comment.delete(); +// commentRepository.save(comment); +// +// postCommandService.decreaseCommentCount(post); +// redisBestService.applyBestScore(-1, postId); +// +// log.info("삭제된 commentId: {}", commentId); +// } +// +// /** +// * Query Method +// */ +// +// /** +// * 특정 게시물의 댓글 및 대댓글 조회 +// */ +// public List getCommentsByPostId(String id) { +// // 1. 입력 검증 및 게시물 조회 +// ObjectId postId = ObjectIdUtil.toObjectId(id); +// PostEntity post = postQueryService.findPostById(postId); +// +// // 2. 댓글 목록 조회 +// List comments = commentRepository.findByPostId(postId); +// if (comments.isEmpty()) { +// return Collections.emptyList(); +// } +// +// // 3. 사용자 정보 맵 생성 +// Map userMap = createUserMap(comments); +// String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); +// +// // 4. 댓글 DTO 변환 +// return comments.stream() +// .map(comment -> buildCommentResponseDTO(post.getAnonymous(), comment, userMap, defaultImageUrl)) +// .collect(Collectors.toList()); +// } +// +// +// +// /** +// * 댓글 작성자들의 사용자 정보 맵 생성 +// */ +// private Map createUserMap(List comments) { +// List userIdsInOrder = comments.stream() +// .map(CommentEntity::getUserId) +// .toList(); +// +// List distinctIds = userIdsInOrder.stream() +// .distinct() +// .toList(); +// +// return userRepository.findAllById(distinctIds) +// .stream() +// .collect(Collectors.toMap( +// UserEntity::get_id, +// user -> user +// )); +// } +// +// /** +// * 댓글 응답 DTO 생성 +// */ +// private CommentResponseDTO buildCommentResponseDTO( +// PostAnonymous postAnonymous, +// CommentEntity comment, +// Map userMap, +// String defaultImageUrl) { +// +// int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); +// +// UserEntity user = userMap.get(comment.getUserId()); +// +// // 댓글용 사용자 DTO 생성 +// UserDto commentUserDto = UserDto.ofComment(comment, user, anonNum ,defaultImageUrl); +// +// return CommentResponseDTO.commentOf( +// comment, +// commentUserDto, +// replyCommentService.getRepliesByCommentId(postAnonymous, comment.get_id()), +// likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), +// getUserInfoAboutComment(comment.get_id()) +// ); +// } +// +// +// public UserInfo getUserInfoAboutComment(ObjectId commentId) { +// ObjectId userId = SecurityUtils.getCurrentUserId(); +// return UserInfo.builder() +// .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) +// .build(); +// } +// +// /** +// * +// * @param commentId +// * @return validated PostEntity +// */ +// public CommentEntity findCommentById(ObjectId commentId) { +// return commentRepository.findByIdAndNotDeleted(commentId) +// .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); +// } +// +// +//} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java index 2c77173a..2d6f67a6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommandService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -18,13 +18,13 @@ @Tag(name = "ReplyComment API", description = "대댓글 API") public class ReplyCommentController { - private final ReplyCommentService replyCommentService; + private final ReplyCommandService replyCommandService; @Operation(summary = "대댓글 추가") @PostMapping("/{commentId}") public ResponseEntity> addReply(@PathVariable String commentId, @RequestBody @Valid ReplyCreateRequestDTO requestDTO) { - replyCommentService.addReply(commentId, requestDTO); + replyCommandService.addReply(commentId, requestDTO); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "대댓글이 추가되었습니다.", null)); @@ -33,7 +33,7 @@ public ResponseEntity> addReply(@PathVariable String commen @Operation(summary = "대댓글 삭제") @DeleteMapping("/{replyId}") public ResponseEntity> softDeleteReply(@PathVariable String replyId) { - replyCommentService.softDeleteReply(replyId); + replyCommandService.softDeleteReply(replyId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "대댓글이 삭제되었습니다.", null)); } @@ -41,7 +41,7 @@ public ResponseEntity> softDeleteReply(@PathVariable String @Operation(summary = "대댓글 수정") @PatchMapping("/{replyId}") public ResponseEntity> updateReply(@PathVariable String replyId, @RequestBody @Valid ReplyUpdateRequestDTO requestDTO){ - replyCommentService.updateReply(replyId, requestDTO); + replyCommandService.updateReply(replyId, requestDTO); return ResponseEntity.status(HttpStatus.OK). body(new SingleResponse<>(200, "대댓글이 수정되었습니다.", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java new file mode 100644 index 00000000..71d30d19 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java @@ -0,0 +1,89 @@ +package inu.codin.codin.domain.post.domain.reply.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.infra.redis.service.RedisBestService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ReplyCommandService { + + private final ReplyCommentRepository replyCommentRepository; + private final PostCommandService postCommandService; + private final PostQueryService postQueryService; + + private final NotificationService notificationService; + private final RedisBestService redisBestService; + private final CommentQueryService commentQueryService; + private final ReplyQueryService replyQueryService; + + /** + *Command Method + */ + // 대댓글 추가 + public void addReply(String id, ReplyCreateRequestDTO requestDTO) { + + CommentEntity comment = commentQueryService.findCommentById(ObjectIdUtil.toObjectId(id)); + PostEntity post = postQueryService.findPostById(comment.getPostId()); + ObjectId userId = SecurityUtils.getCurrentUserId(); + + + ReplyCommentEntity reply = ReplyCommentEntity.create(comment.get_id(), userId, requestDTO); + replyCommentRepository.save(reply); + + postCommandService.handleCommentCreation(post, userId); + redisBestService.applyBestScore(1, post.get_id()); + + log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", + reply.get_id(), post.get_id(), post.getCommentCount()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + } + + public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { + + ObjectId replyId = ObjectIdUtil.toObjectId(id); + ReplyCommentEntity reply = replyQueryService.findReplyById(replyId); + + reply.updateReply(requestDTO.getContent()); + replyCommentRepository.save(reply); + + log.info("대댓글 수정 완료 - replyId: {}", replyId); + + } + + // 대댓글 삭제 (Soft Delete) + public void softDeleteReply(String id) { + ReplyCommentEntity reply = replyQueryService.findReplyById(ObjectIdUtil.toObjectId(id)); + + SecurityUtils.validateUser(reply.getUserId()); + + CommentEntity comment = commentQueryService.findCommentById(reply.getCommentId()); + + PostEntity post = postQueryService.findPostById(comment.getPostId()); + + // 대댓글 삭제 + reply.delete(); + replyCommentRepository.save(reply); + + postCommandService.decreaseCommentCount(post); + redisBestService.applyBestScore(-1, post.get_id()); + + log.info("대댓글 성공적 삭제 replyId: {}", reply.get_id()); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index f3fb7d10..5d31fe10 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -1,194 +1,210 @@ -package inu.codin.codin.domain.post.domain.reply.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.notification.service.NotificationService; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; -import inu.codin.codin.domain.post.domain.comment.exception.CommentException; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; -import inu.codin.codin.domain.post.domain.reply.exception.ReplyErrorCode; -import inu.codin.codin.domain.post.dto.UserDto; -import inu.codin.codin.domain.post.entity.PostAnonymous; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.exception.PostErrorCode; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.service.PostCommandService; -import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisBestService; -import inu.codin.codin.infra.s3.S3Service; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.stereotype.Service; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Slf4j -public class ReplyCommentService { - - private final CommentRepository commentRepository; - private final PostRepository postRepository; - private final ReplyCommentRepository replyCommentRepository; - private final UserRepository userRepository; - private final PostCommandService postCommandService; - private final PostQueryService postQueryService; - - private final LikeService likeService; - private final NotificationService notificationService; - private final RedisBestService redisBestService; - private final S3Service s3Service; - - // 대댓글 추가 - public void addReply(String id, ReplyCreateRequestDTO requestDTO) { - ObjectId commentId = new ObjectId(id); - CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); - - PostEntity post = postRepository.findByIdAndNotDeleted(comment.getPostId()) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - - ObjectId userId = SecurityUtils.getCurrentUserId(); - - - ReplyCommentEntity reply = ReplyCommentEntity.create(commentId, userId, requestDTO); - replyCommentRepository.save(reply); - - postCommandService.handleCommentCreation(post, userId); - redisBestService.applyBestScore(1, post.get_id()); - - log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", - reply.get_id(), post.get_id(), post.getCommentCount()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); - } - - public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { - - ObjectId replyId = new ObjectId(id); - ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(replyId) - .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); - - reply.updateReply(requestDTO.getContent()); - replyCommentRepository.save(reply); - - log.info("대댓글 수정 완료 - replyId: {}", replyId); - - } - - // 대댓글 삭제 (Soft Delete) - public void softDeleteReply(String replyId) { - ReplyCommentEntity reply = replyCommentRepository.findByIdAndNotDeleted(new ObjectId(replyId)) - .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); - SecurityUtils.validateUser(reply.getUserId()); - - ObjectId commentId = reply.getCommentId(); - CommentEntity comment = commentRepository.findByIdAndNotDeleted(commentId) - .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); - - ObjectId postId = comment.getPostId(); - PostEntity post = postRepository.findByIdAndNotDeleted(postId) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - - // 대댓글 삭제 - reply.delete(); - replyCommentRepository.save(reply); - - postCommandService.decreaseCommentCount(post); - redisBestService.applyBestScore(-1, postId); - - log.info("대댓글 성공적 삭제 replyId: {}", replyId); - } - - /** - * 특정 댓글의 대댓글 조회 - */ - public List getRepliesByCommentId(PostAnonymous postAnonymous, ObjectId commentId) { - // 1. 대댓글 목록 조회 - List replies = replyCommentRepository.findByCommentId(commentId); - if (replies.isEmpty()) { - return Collections.emptyList(); - } - - // 2. 사용자 정보 맵 생성 - Map userMap = createUserMapFromReplies(replies); - String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); - - // 3. 대댓글 DTO 변환 - return replies.stream() - .map(reply -> buildReplyResponseDTO(reply, postAnonymous, userMap, defaultImageUrl)) - .toList(); - } - - /** - * 대댓글 작성자들의 사용자 정보 맵 생성 - */ - private Map createUserMapFromReplies(List replies) { - List userIdsInOrder = replies.stream() - .map(ReplyCommentEntity::getUserId) - .toList(); - - List distinctIds = userIdsInOrder.stream() - .distinct() - .toList(); - - return userRepository.findAllById(distinctIds) - .stream() - .collect(Collectors.toMap( - UserEntity::get_id, - user -> user - )); - } - - /** - * 대댓글 응답 DTO 생성 - */ - private CommentResponseDTO buildReplyResponseDTO( - ReplyCommentEntity reply, - PostAnonymous postAnonymous, - Map userMap, - String defaultImageUrl) { - - ObjectId userId = reply.getUserId(); - UserEntity user = userMap.get(userId); - int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); - - UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); - - return CommentResponseDTO.replyOf( - reply, - replyUserDto, - likeService.getLikeCount(LikeType.REPLY, reply.get_id()), - getUserInfoAboutReply(reply.get_id()) - ); - } - - public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); - return CommentResponseDTO.UserInfo.builder() - .isLike(likeService.isLiked(LikeType.COMMENT, replyId, userId)) - .build(); - } - - - - -} +//package inu.codin.codin.domain.post.domain.reply.service; +// +//import inu.codin.codin.common.exception.NotFoundException; +//import inu.codin.codin.common.security.util.SecurityUtils; +//import inu.codin.codin.common.util.ObjectIdUtil; +//import inu.codin.codin.domain.like.entity.LikeType; +//import inu.codin.codin.domain.like.service.LikeService; +//import inu.codin.codin.domain.notification.service.NotificationService; +//import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +//import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +//import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; +//import inu.codin.codin.domain.post.domain.comment.exception.CommentException; +//import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +//import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; +//import inu.codin.codin.domain.post.domain.comment.service.CommentService; +//import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +//import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; +//import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +//import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +//import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; +//import inu.codin.codin.domain.post.domain.reply.exception.ReplyErrorCode; +//import inu.codin.codin.domain.post.dto.UserDto; +//import inu.codin.codin.domain.post.entity.PostAnonymous; +//import inu.codin.codin.domain.post.entity.PostEntity; +//import inu.codin.codin.domain.post.exception.PostErrorCode; +//import inu.codin.codin.domain.post.exception.PostException; +//import inu.codin.codin.domain.post.repository.PostRepository; +//import inu.codin.codin.domain.post.service.PostCommandService; +//import inu.codin.codin.domain.post.service.PostQueryService; +//import inu.codin.codin.domain.user.entity.UserEntity; +//import inu.codin.codin.domain.user.repository.UserRepository; +//import inu.codin.codin.domain.user.service.UserService; +//import inu.codin.codin.infra.redis.service.RedisBestService; +//import inu.codin.codin.infra.s3.S3Service; +//import jakarta.validation.Valid; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.bson.types.ObjectId; +//import org.springframework.stereotype.Service; +// +//import java.util.Collections; +//import java.util.List; +//import java.util.Map; +//import java.util.Set; +// +//import java.util.stream.Collectors; +// +//@Service +//@RequiredArgsConstructor +//@Slf4j +//public class ReplyCommentService { +// +// private final ReplyCommentRepository replyCommentRepository; +// private final UserRepository userRepository; +// private final PostCommandService postCommandService; +// private final PostQueryService postQueryService; +// +// private final LikeService likeService; +// private final NotificationService notificationService; +// private final RedisBestService redisBestService; +// private final S3Service s3Service; +// +// +// /** +// *Command Method +// */ +// // 대댓글 추가 +// public void addReply(String id, ReplyCreateRequestDTO requestDTO) { +// ObjectId commentId = ObjectIdUtil.toObjectId(id); +// CommentEntity comment = commentQueryService.findCommentById(commentId); +// +// PostEntity post = postQueryService.findPostById(comment.getPostId()); +// +// ObjectId userId = SecurityUtils.getCurrentUserId(); +// +// +// ReplyCommentEntity reply = ReplyCommentEntity.create(commentId, userId, requestDTO); +// replyCommentRepository.save(reply); +// +// postCommandService.handleCommentCreation(post, userId); +// redisBestService.applyBestScore(1, post.get_id()); +// +// log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", +// reply.get_id(), post.get_id(), post.getCommentCount()); +// if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); +// } +// +// public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { +// +// ObjectId replyId = new ObjectId(id); +// ReplyCommentEntity reply = findReplyById(replyId); +// +// reply.updateReply(requestDTO.getContent()); +// replyCommentRepository.save(reply); +// +// log.info("대댓글 수정 완료 - replyId: {}", replyId); +// +// } +// +// // 대댓글 삭제 (Soft Delete) +// public void softDeleteReply(String id) { +// ObjectId replyId = new ObjectId(id); +// ReplyCommentEntity reply = findReplyById(replyId); +// +// SecurityUtils.validateUser(reply.getUserId()); +// +// ObjectId commentId = reply.getCommentId(); +// CommentEntity comment = commentQueryService.findCommentById(commentId); +// +// ObjectId postId = comment.getPostId(); +// PostEntity post = postQueryService.findPostById(comment.getPostId()); +// +// // 대댓글 삭제 +// reply.delete(); +// replyCommentRepository.save(reply); +// +// postCommandService.decreaseCommentCount(post); +// redisBestService.applyBestScore(-1, postId); +// +// log.info("대댓글 성공적 삭제 replyId: {}", replyId); +// } +// +// /** +// * Query Method +// */ +// +// /** +// * 특정 댓글의 대댓글 조회 +// */ +// public List getRepliesByCommentId(PostAnonymous postAnonymous, ObjectId commentId) { +// // 1. 대댓글 목록 조회 +// List replies = replyCommentRepository.findByCommentId(commentId); +// if (replies.isEmpty()) { +// return Collections.emptyList(); +// } +// +// // 2. 사용자 정보 맵 생성 +// Map userMap = createUserMapFromReplies(replies); +// String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); +// +// // 3. 대댓글 DTO 변환 +// return replies.stream() +// .map(reply -> buildReplyResponseDTO(reply, postAnonymous, userMap, defaultImageUrl)) +// .toList(); +// } +// +// /** +// * 대댓글 작성자들의 사용자 정보 맵 생성 +// */ +// private Map createUserMapFromReplies(List replies) { +// List userIdsInOrder = replies.stream() +// .map(ReplyCommentEntity::getUserId) +// .toList(); +// +// List distinctIds = userIdsInOrder.stream() +// .distinct() +// .toList(); +// +// return userRepository.findAllById(distinctIds) +// .stream() +// .collect(Collectors.toMap( +// UserEntity::get_id, +// user -> user +// )); +// } +// +// /** +// * 대댓글 응답 DTO 생성 +// */ +// private CommentResponseDTO buildReplyResponseDTO( +// ReplyCommentEntity reply, +// PostAnonymous postAnonymous, +// Map userMap, +// String defaultImageUrl) { +// +// ObjectId userId = reply.getUserId(); +// UserEntity user = userMap.get(userId); +// int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); +// +// UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); +// +// return CommentResponseDTO.replyOf( +// reply, +// replyUserDto, +// likeService.getLikeCount(LikeType.REPLY, reply.get_id()), +// getUserInfoAboutReply(reply.get_id()) +// ); +// } +// +// public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { +// ObjectId userId = SecurityUtils.getCurrentUserId(); +// return CommentResponseDTO.UserInfo.builder() +// .isLike(likeService.isLiked(LikeType.COMMENT, replyId, userId)) +// .build(); +// } +// +// /** +// * +// * @param replyId +// * @return validated PostEntity +// */ +// public ReplyCommentEntity findReplyById(ObjectId replyId) { +// return replyCommentRepository.findByIdAndNotDeleted(replyId) +// .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); +// } +// +// +// +// +//} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java new file mode 100644 index 00000000..86b60cc1 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java @@ -0,0 +1,123 @@ +package inu.codin.codin.domain.post.domain.reply.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.exception.ReplyErrorCode; +import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ReplyQueryService { + + private final ReplyCommentRepository replyCommentRepository; + private final UserRepository userRepository; + private final PostQueryService postQueryService; + + private final LikeService likeService; + private final S3Service s3Service; + + /** + * Query Method + */ + + /** + * 특정 댓글의 대댓글 조회 + */ + public List getRepliesByCommentId(PostAnonymous postAnonymous, ObjectId commentId) { + // 1. 대댓글 목록 조회 + List replies = replyCommentRepository.findByCommentId(commentId); + if (replies.isEmpty()) { + return Collections.emptyList(); + } + + // 2. 사용자 정보 맵 생성 + Map userMap = createUserMapFromReplies(replies); + String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); + + // 3. 대댓글 DTO 변환 + return replies.stream() + .map(reply -> buildReplyResponseDTO(reply, postAnonymous, userMap, defaultImageUrl)) + .toList(); + } + + /** + * 대댓글 작성자들의 사용자 정보 맵 생성 + */ + private Map createUserMapFromReplies(List replies) { + List userIdsInOrder = replies.stream() + .map(ReplyCommentEntity::getUserId) + .toList(); + + List distinctIds = userIdsInOrder.stream() + .distinct() + .toList(); + + return userRepository.findAllById(distinctIds) + .stream() + .collect(Collectors.toMap( + UserEntity::get_id, + user -> user + )); + } + + /** + * 대댓글 응답 DTO 생성 + */ + private CommentResponseDTO buildReplyResponseDTO( + ReplyCommentEntity reply, + PostAnonymous postAnonymous, + Map userMap, + String defaultImageUrl) { + + ObjectId userId = reply.getUserId(); + UserEntity user = userMap.get(userId); + int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); + + UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); + + return CommentResponseDTO.replyOf( + reply, + replyUserDto, + likeService.getLikeCount(LikeType.REPLY, reply.get_id()), + getUserInfoAboutReply(reply.get_id()) + ); + } + + public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { + ObjectId userId = SecurityUtils.getCurrentUserId(); + return CommentResponseDTO.UserInfo.builder() + .isLike(likeService.isLiked(LikeType.COMMENT, replyId, userId)) + .build(); + } + + /** + * + * @param replyId + * @return validated PostEntity + */ + public ReplyCommentEntity findReplyById(ObjectId replyId) { + return replyCommentRepository.findByIdAndNotDeleted(replyId) + .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index af2787c9..a55b438e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -1,25 +1,18 @@ package inu.codin.codin.domain.post.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostStatusUpdateRequestDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserRole; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -34,6 +27,7 @@ public class PostCommandService { private final PostRepository postRepository; private final PostInteractionService postInteractionService; + private final PostQueryService postQueryService; /** * 게시글 생성 @@ -43,17 +37,12 @@ public class PostCommandService { public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); List imageUrls = postInteractionService.handleImageUpload(postImages); - ObjectId userId = SecurityUtils.getCurrentUserId(); - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - postCreateRequestDTO.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시물에 대한 접근권한 없음. UserId: {}", userId); - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); - } + ObjectId userId = validateUserAndPost(postCreateRequestDTO.getPostCategory()); PostEntity postEntity = PostEntity.create(userId, postCreateRequestDTO, imageUrls); postRepository.save(postEntity); - log.info("게시물 성공적으로 생성됨. PostId: {}, UserId: {}", postEntity.get_id(), userId); + log.info("게시물 성공적으로 생성됨. UserId: {}, PostId: {} ", postEntity.get_id(), userId); } /** @@ -61,37 +50,34 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 수정 시작. PostId: {}", postId); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - validateUserAndPost(post); + PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); + ObjectId userId = validateUserAndPost(post.getPostCategory()); List imageUrls = postInteractionService.handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); - log.info("게시물 수정 성공. PostId: {}", postId); + log.info("게시물 수정 성공. UserId: {}, PostId: {}",userId, postId); } /** * 게시글 익명 설정 수정 */ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - validateUserAndPost(post); + PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); + ObjectId userId = validateUserAndPost(post.getPostCategory()); post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); - log.info("게시물 익명 수정 성공. PostId: {}", postId); + log.info("게시물 익명 수정 성공. UserId: {}, PostId: {}",userId, postId); } /** * 게시글 상태 수정 */ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - validateUserAndPost(post); + PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); + ObjectId userId = validateUserAndPost(post.getPostCategory()); post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); - log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); + log.info("게시물 상태 수정 성공. UserId : {}. PostId: {}, Status: {}", userId, postId, requestDTO.getPostStatus()); } @@ -100,22 +86,21 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT * 게시물 소프트 삭제 */ public void softDeletePost(String postId) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - validateUserAndPost(post); + PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); + ObjectId userId = validateUserAndPost(post.getPostCategory()); post.delete(); - log.info("게시물 안전 삭제. PostId: {}", postId); postRepository.save(post); + log.info("게시물 안전 삭제. UserId: {} PostId: {}", userId, postId); } /** * 게시물 이미지 삭제 */ public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); - validateUserAndPost(post); + PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); + ObjectId userId = validateUserAndPost(post.getPostCategory()); postInteractionService.deletePostImageInternal(post, imageUrl); + log.info("게시물 이미지 삭제. UserId: {} PostId: {}", userId, postId); } /** @@ -170,14 +155,15 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { } - private void validateUserAndPost(PostEntity post) { + private ObjectId validateUserAndPost(PostCategory postCategory) { + ObjectId userId = SecurityUtils.getCurrentUserId(); if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); + postCategory.toString().split("_")[0].equals("EXTRACURRICULAR")){ + log.error("비교과 게시글에 대한 권한이 없음. userId: {}", userId); throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } - SecurityUtils.validateUser(post.getUserId()); + SecurityUtils.validateUser(userId); + return userId; } - } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 33913c7f..e308492a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.post.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.like.entity.LikeType; @@ -22,7 +21,6 @@ import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.domain.user.service.UserService; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -167,6 +165,16 @@ public Integer getUserAnonymousNumber(PostAnonymous postAnonymous, ObjectId user return postAnonymous.getAnonNumber(userId); } + /** + * + * @param postId + * @return validated PostEntity + */ + public PostEntity findPostById(ObjectId postId) { + return postRepository.findByIdAndNotDeleted(postId) + .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); + } + // [likeService] - 게시글 좋아요 수 조회 public int getLikeCount(PostEntity post) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index 146f8d7c..ae3eb2eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.report.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.comment.service.CommentService; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportPageResponse; @@ -27,7 +26,6 @@ @Tag(name = "Reoprt API", description = "사용자 신고 기능") public class ReportController { private final ReportService reportService; - private final CommentService commentService; //(User)신고 작성 /** diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index ecea7f8c..6d2d6b89 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -5,10 +5,10 @@ import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.comment.service.CommentService; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; +import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; @@ -48,8 +48,8 @@ public class ReportService { private final PostQueryService postQueryService; - private final CommentService commentService; - private final ReplyCommentService replyCommentService; + private final CommentQueryService commentQueryService; + private final ReplyQueryService replyQueryService; private final ReportRepository reportRepository; private final UserRepository userRepository; @@ -357,7 +357,7 @@ public ReportedPostDetailResponseDTO getReportedPostWithDetail(String postId, St public List getReportedCommentsByPostId(String postId, String reportedEntityId) { - List comments = commentService.getCommentsByPostId(postId); + List comments = commentQueryService.getCommentsByPostId(postId); PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")); @@ -379,7 +379,7 @@ public List getReportedCommentsByPostId(String public List getReportedRepliesByCommentId(PostAnonymous postAnonymous, String id, String reportedEntityId) { ObjectId commentId = new ObjectId(id); - List replies = replyCommentService.getRepliesByCommentId(postAnonymous, commentId); + List replies = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); return replies.stream() .map(reply -> { From 54e80b5c4d0ff6981e2dd13fc7f0f049b52a485d Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 17 Jul 2025 10:48:52 +0900 Subject: [PATCH 0859/1002] =?UTF-8?q?refactor:=20Post=20=E2=86=94=20Schedu?= =?UTF-8?q?ler=20ErrorCode=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Post 도메인 내 Scheduler 및 ErrorCode 의존성 정리 - 도메인 계층별로 올바른 ErrorCode 및 예외 처리 방식 적용 --- .../post/domain/reply/exception/ReplyErrorCode.java | 3 +-- .../inu/codin/codin/domain/post/entity/PostEntity.java | 4 +++- .../codin/codin/domain/post/exception/PostErrorCode.java | 2 +- .../codin/codin/domain/post/schedular/PostsScheduler.java | 8 ++++---- .../post/schedular/exception/SchedulerErrorCode.java | 3 ++- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java index b22b5492..9f1e7414 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java @@ -6,8 +6,7 @@ @RequiredArgsConstructor public enum ReplyErrorCode implements GlobalErrorCode { - REPLY_NOT_FOUND(HttpStatus.NOT_FOUND, "대댓글을 찾을 수 없습니다."), - COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."); + REPLY_NOT_FOUND(HttpStatus.NOT_FOUND, "대댓글을 찾을 수 없습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index da3eb86e..b919461f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -2,6 +2,8 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; +import inu.codin.codin.domain.post.exception.PostErrorCode; +import inu.codin.codin.domain.post.exception.PostException; import inu.codin.codin.domain.post.schedular.exception.SchedulerErrorCode; import inu.codin.codin.domain.post.schedular.exception.SchedulerException; import jakarta.validation.constraints.NotBlank; @@ -64,7 +66,7 @@ public void updatePostContent(String content, List postImageUrls) { public void updatePostAnonymous(boolean isAnonymous) { if (this.isAnonymous == isAnonymous) { - throw new SchedulerException(SchedulerErrorCode.DUPLICATE_ANONYMOUS_STATE); + throw new PostException(PostErrorCode.DUPLICATE_ANONYMOUS_STATE); } this.isAnonymous = isAnonymous; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java index bcd662ab..416c211d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java @@ -8,7 +8,7 @@ public enum PostErrorCode implements GlobalErrorCode { POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."), USER_NOT_FOUND(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다."), - SCHEDULER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "스케줄러 실행 중 오류가 발생했습니다."); + DUPLICATE_ANONYMOUS_STATE(HttpStatus.CONFLICT, "현재 익명 상태와 동일한 상태로 변경할 수 없습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 00408987..15d16b5e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.schedular; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.exception.PostErrorCode; +import inu.codin.codin.domain.post.schedular.exception.SchedulerErrorCode; +import inu.codin.codin.domain.post.schedular.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; @@ -41,7 +41,7 @@ public void departmentPostsScheduler() { else log.warn("[PostsScheduler] 학과 공지사항 업데이트 실패"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); - throw new PostException(PostErrorCode.SCHEDULER_ERROR); + throw new SchedulerException(SchedulerErrorCode.SCHEDULER_INTERRUPT_ERROR); } } @@ -64,7 +64,7 @@ public void starinuPostsScheduler(){ else log.warn("[PostsScheduler] STARINU 공지사항 업데이트 실패"); } catch (IOException | InterruptedException e) { log.error(e.getMessage(), e.getStackTrace()[0]); - throw new PostException(PostErrorCode.SCHEDULER_ERROR); + throw new SchedulerException(SchedulerErrorCode.SCHEDULER_INTERRUPT_ERROR); } } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java index 62fcf471..54bcbb7e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java @@ -7,7 +7,8 @@ @RequiredArgsConstructor public enum SchedulerErrorCode implements GlobalErrorCode { DUPLICATE_ANONYMOUS_STATE(HttpStatus.CONFLICT, "현재 익명 상태와 동일한 상태로 변경할 수 없습니다."), - DUPLICATE_POST_STATUS(HttpStatus.CONFLICT, "현재 게시글 상태와 동일한 상태로 변경할 수 없습니다."); + DUPLICATE_POST_STATUS(HttpStatus.CONFLICT, "현재 게시글 상태와 동일한 상태로 변경할 수 없습니다."), + SCHEDULER_INTERRUPT_ERROR(HttpStatus.BAD_REQUEST,"스케줄러 실행중 에러가 발생했습니다"); private final HttpStatus httpStatus; private final String message; From 18ec10832a93be883aaee61941f5ab2137b7c313 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 17 Jul 2025 12:50:52 +0900 Subject: [PATCH 0860/1002] =?UTF-8?q?refactor:=20BestPost=20->=20BestServi?= =?UTF-8?q?ce=20=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20-=20=EA=B0=81=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EB=82=B4=20redisBestService=20=ED=98=B8=EC=B6=9C=20Be?= =?UTF-8?q?st=20Domain=EC=9C=BC=EB=A1=9C=20=EC=B1=85=EC=9E=84=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/best/BestService.java | 46 ++++ .../service/CommentCommandService.java | 9 +- .../comment/service/CommentService.java | 210 ------------------ .../reply/service/ReplyCommandService.java | 8 +- .../reply/service/ReplyCommentService.java | 210 ------------------ .../domain/post/service/BestPostService.java | 58 ----- .../domain/post/service/PostQueryService.java | 48 +++- 7 files changed, 95 insertions(+), 494 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java new file mode 100644 index 00000000..e1308cdc --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java @@ -0,0 +1,46 @@ +package inu.codin.codin.domain.post.domain.best; + +import inu.codin.codin.infra.redis.service.RedisBestService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BestService { + private final RedisBestService redisBestService; + private final BestRepository bestRepository; + + // [BestService] - Top 3 베스트 postId 목록 반환 + public List getTop3BestPostIds() { + Map bestPosts = redisBestService.getBests(); + return new ArrayList<>(bestPosts.keySet()); // 빈 리스트 반환 가능 + } + + // [BestService] - BestEntity 페이지 반환 + public Page getBestEntities(int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + return bestRepository.findAll(pageRequest); + } + + // Redis에서 특정 베스트 게시물 삭제 + public void deleteBestPost(String postId) { + redisBestService.deleteBest(postId); + } + + // [BestService] - 베스트 점수 적용 처리 래핑 + public void applyBestScore(ObjectId postId) { + redisBestService.applyBestScore(1, postId); + log.info("베스트 점수 적용. PostId: {}", postId); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index 0681bb8d..74557283 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -8,9 +8,9 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.infra.redis.service.RedisBestService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -22,10 +22,11 @@ public class CommentCommandService { private final CommentRepository commentRepository; private final NotificationService notificationService; - private final RedisBestService redisBestService; private final PostCommandService postCommandService; private final PostQueryService postQueryService; private final CommentQueryService commentQueryService; + private final BestService bestService; + // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { @@ -38,7 +39,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { commentRepository.save(comment); postCommandService.handleCommentCreation(post, userId); - redisBestService.applyBestScore(1, postId); + bestService.applyBestScore(postId); log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); @@ -77,7 +78,7 @@ public void softDeleteComment(String id) { commentRepository.save(comment); postCommandService.decreaseCommentCount(post); - redisBestService.applyBestScore(-1, postId); +// bestService.applyBestScore( postId); log.info("삭제된 commentId: {}", commentId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java deleted file mode 100644 index 3b599700..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ /dev/null @@ -1,210 +0,0 @@ -//package inu.codin.codin.domain.post.domain.comment.service; -// -//import inu.codin.codin.common.security.util.SecurityUtils; -//import inu.codin.codin.common.util.ObjectIdUtil; -//import inu.codin.codin.domain.like.entity.LikeType; -//import inu.codin.codin.domain.like.service.LikeService; -//import inu.codin.codin.domain.notification.service.NotificationService; -//import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; -//import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; -//import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -//import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO.UserInfo; -//import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -//import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -//import inu.codin.codin.domain.post.domain.comment.exception.CommentException; -//import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; -//import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -//import inu.codin.codin.domain.post.dto.UserDto; -//import inu.codin.codin.domain.post.entity.PostAnonymous; -//import inu.codin.codin.domain.post.entity.PostEntity; -//import inu.codin.codin.domain.post.exception.PostErrorCode; -//import inu.codin.codin.domain.post.exception.PostException; -//import inu.codin.codin.domain.post.repository.PostRepository; -//import inu.codin.codin.domain.post.service.PostCommandService; -//import inu.codin.codin.domain.post.service.PostQueryService; -//import inu.codin.codin.domain.user.entity.UserEntity; -//import inu.codin.codin.domain.user.repository.UserRepository; -//import inu.codin.codin.infra.redis.service.RedisBestService; -//import inu.codin.codin.infra.s3.S3Service; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.bson.types.ObjectId; -//import org.springframework.stereotype.Service; -// -//import java.util.Collections; -//import java.util.List; -//import java.util.Map; -//import java.util.stream.Collectors; -// -//@Service -//@RequiredArgsConstructor -//@Slf4j -//public class CommentService { -// -// private final CommentRepository commentRepository; -// private final UserRepository userRepository; -// -// private final LikeService likeService; -// private final ReplyCommentService replyCommentService; -// private final NotificationService notificationService; -// private final RedisBestService redisBestService; -// private final PostCommandService postCommandService; -// private final PostQueryService postQueryService; -// private final S3Service s3Service; -// -// -// /** -// * Command Method -// */ -// -// // 댓글 추가 -// public void addComment(String id, CommentCreateRequestDTO requestDTO) { -// -// ObjectId postId = ObjectIdUtil.toObjectId(id); -// PostEntity post = postQueryService.findPostById(postId); -// -// ObjectId userId = SecurityUtils.getCurrentUserId(); -// -// CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); -// commentRepository.save(comment); -// -// postCommandService.handleCommentCreation(post, userId); -// redisBestService.applyBestScore(1, postId); -// -// log.info("댓글 추가완료 postId: {} commentId : {}", postId, comment.get_id()); -// if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByComment(post.getPostCategory(), post.getUserId(), post.get_id().toString(), comment.getContent()); -// -// } -// -// public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { -// log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); -// -// ObjectId commentId = ObjectIdUtil.toObjectId(id); -// CommentEntity comment = findCommentById(commentId); -// -// ObjectId userId = SecurityUtils.getCurrentUserId(); -// SecurityUtils.validateUser(userId); -// -// -// comment.updateComment(requestDTO.getContent()); -// commentRepository.save(comment); -// -// log.info("댓글 업데이트 완료. commentId: {}", commentId); -// -// } -// -// // 댓글 삭제 (Soft Delete) -// public void softDeleteComment(String id) { -// ObjectId commentId = ObjectIdUtil.toObjectId(id); -// CommentEntity comment = findCommentById(commentId); -// -// SecurityUtils.validateUser(comment.getUserId()); -// -// ObjectId postId = comment.getPostId(); -// PostEntity post = postQueryService.findPostById(postId); -// -// // 댓글 Soft Delete 처리 -// comment.delete(); -// commentRepository.save(comment); -// -// postCommandService.decreaseCommentCount(post); -// redisBestService.applyBestScore(-1, postId); -// -// log.info("삭제된 commentId: {}", commentId); -// } -// -// /** -// * Query Method -// */ -// -// /** -// * 특정 게시물의 댓글 및 대댓글 조회 -// */ -// public List getCommentsByPostId(String id) { -// // 1. 입력 검증 및 게시물 조회 -// ObjectId postId = ObjectIdUtil.toObjectId(id); -// PostEntity post = postQueryService.findPostById(postId); -// -// // 2. 댓글 목록 조회 -// List comments = commentRepository.findByPostId(postId); -// if (comments.isEmpty()) { -// return Collections.emptyList(); -// } -// -// // 3. 사용자 정보 맵 생성 -// Map userMap = createUserMap(comments); -// String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); -// -// // 4. 댓글 DTO 변환 -// return comments.stream() -// .map(comment -> buildCommentResponseDTO(post.getAnonymous(), comment, userMap, defaultImageUrl)) -// .collect(Collectors.toList()); -// } -// -// -// -// /** -// * 댓글 작성자들의 사용자 정보 맵 생성 -// */ -// private Map createUserMap(List comments) { -// List userIdsInOrder = comments.stream() -// .map(CommentEntity::getUserId) -// .toList(); -// -// List distinctIds = userIdsInOrder.stream() -// .distinct() -// .toList(); -// -// return userRepository.findAllById(distinctIds) -// .stream() -// .collect(Collectors.toMap( -// UserEntity::get_id, -// user -> user -// )); -// } -// -// /** -// * 댓글 응답 DTO 생성 -// */ -// private CommentResponseDTO buildCommentResponseDTO( -// PostAnonymous postAnonymous, -// CommentEntity comment, -// Map userMap, -// String defaultImageUrl) { -// -// int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, comment.getUserId()); -// -// UserEntity user = userMap.get(comment.getUserId()); -// -// // 댓글용 사용자 DTO 생성 -// UserDto commentUserDto = UserDto.ofComment(comment, user, anonNum ,defaultImageUrl); -// -// return CommentResponseDTO.commentOf( -// comment, -// commentUserDto, -// replyCommentService.getRepliesByCommentId(postAnonymous, comment.get_id()), -// likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), -// getUserInfoAboutComment(comment.get_id()) -// ); -// } -// -// -// public UserInfo getUserInfoAboutComment(ObjectId commentId) { -// ObjectId userId = SecurityUtils.getCurrentUserId(); -// return UserInfo.builder() -// .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) -// .build(); -// } -// -// /** -// * -// * @param commentId -// * @return validated PostEntity -// */ -// public CommentEntity findCommentById(ObjectId commentId) { -// return commentRepository.findByIdAndNotDeleted(commentId) -// .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND)); -// } -// -// -//} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java index 71d30d19..3c62bf4d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; @@ -12,7 +13,6 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.infra.redis.service.RedisBestService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -29,7 +29,7 @@ public class ReplyCommandService { private final PostQueryService postQueryService; private final NotificationService notificationService; - private final RedisBestService redisBestService; + private final BestService bestService; private final CommentQueryService commentQueryService; private final ReplyQueryService replyQueryService; @@ -48,7 +48,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { replyCommentRepository.save(reply); postCommandService.handleCommentCreation(post, userId); - redisBestService.applyBestScore(1, post.get_id()); + bestService.applyBestScore(post.get_id()); log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); @@ -82,7 +82,7 @@ public void softDeleteReply(String id) { replyCommentRepository.save(reply); postCommandService.decreaseCommentCount(post); - redisBestService.applyBestScore(-1, post.get_id()); +// bestService.applyBestScore(post.get_id()); log.info("대댓글 성공적 삭제 replyId: {}", reply.get_id()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java deleted file mode 100644 index 5d31fe10..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ /dev/null @@ -1,210 +0,0 @@ -//package inu.codin.codin.domain.post.domain.reply.service; -// -//import inu.codin.codin.common.exception.NotFoundException; -//import inu.codin.codin.common.security.util.SecurityUtils; -//import inu.codin.codin.common.util.ObjectIdUtil; -//import inu.codin.codin.domain.like.entity.LikeType; -//import inu.codin.codin.domain.like.service.LikeService; -//import inu.codin.codin.domain.notification.service.NotificationService; -//import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -//import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -//import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; -//import inu.codin.codin.domain.post.domain.comment.exception.CommentException; -//import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -//import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; -//import inu.codin.codin.domain.post.domain.comment.service.CommentService; -//import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -//import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -//import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -//import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -//import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; -//import inu.codin.codin.domain.post.domain.reply.exception.ReplyErrorCode; -//import inu.codin.codin.domain.post.dto.UserDto; -//import inu.codin.codin.domain.post.entity.PostAnonymous; -//import inu.codin.codin.domain.post.entity.PostEntity; -//import inu.codin.codin.domain.post.exception.PostErrorCode; -//import inu.codin.codin.domain.post.exception.PostException; -//import inu.codin.codin.domain.post.repository.PostRepository; -//import inu.codin.codin.domain.post.service.PostCommandService; -//import inu.codin.codin.domain.post.service.PostQueryService; -//import inu.codin.codin.domain.user.entity.UserEntity; -//import inu.codin.codin.domain.user.repository.UserRepository; -//import inu.codin.codin.domain.user.service.UserService; -//import inu.codin.codin.infra.redis.service.RedisBestService; -//import inu.codin.codin.infra.s3.S3Service; -//import jakarta.validation.Valid; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.bson.types.ObjectId; -//import org.springframework.stereotype.Service; -// -//import java.util.Collections; -//import java.util.List; -//import java.util.Map; -//import java.util.Set; -// -//import java.util.stream.Collectors; -// -//@Service -//@RequiredArgsConstructor -//@Slf4j -//public class ReplyCommentService { -// -// private final ReplyCommentRepository replyCommentRepository; -// private final UserRepository userRepository; -// private final PostCommandService postCommandService; -// private final PostQueryService postQueryService; -// -// private final LikeService likeService; -// private final NotificationService notificationService; -// private final RedisBestService redisBestService; -// private final S3Service s3Service; -// -// -// /** -// *Command Method -// */ -// // 대댓글 추가 -// public void addReply(String id, ReplyCreateRequestDTO requestDTO) { -// ObjectId commentId = ObjectIdUtil.toObjectId(id); -// CommentEntity comment = commentQueryService.findCommentById(commentId); -// -// PostEntity post = postQueryService.findPostById(comment.getPostId()); -// -// ObjectId userId = SecurityUtils.getCurrentUserId(); -// -// -// ReplyCommentEntity reply = ReplyCommentEntity.create(commentId, userId, requestDTO); -// replyCommentRepository.save(reply); -// -// postCommandService.handleCommentCreation(post, userId); -// redisBestService.applyBestScore(1, post.get_id()); -// -// log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", -// reply.get_id(), post.get_id(), post.getCommentCount()); -// if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); -// } -// -// public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { -// -// ObjectId replyId = new ObjectId(id); -// ReplyCommentEntity reply = findReplyById(replyId); -// -// reply.updateReply(requestDTO.getContent()); -// replyCommentRepository.save(reply); -// -// log.info("대댓글 수정 완료 - replyId: {}", replyId); -// -// } -// -// // 대댓글 삭제 (Soft Delete) -// public void softDeleteReply(String id) { -// ObjectId replyId = new ObjectId(id); -// ReplyCommentEntity reply = findReplyById(replyId); -// -// SecurityUtils.validateUser(reply.getUserId()); -// -// ObjectId commentId = reply.getCommentId(); -// CommentEntity comment = commentQueryService.findCommentById(commentId); -// -// ObjectId postId = comment.getPostId(); -// PostEntity post = postQueryService.findPostById(comment.getPostId()); -// -// // 대댓글 삭제 -// reply.delete(); -// replyCommentRepository.save(reply); -// -// postCommandService.decreaseCommentCount(post); -// redisBestService.applyBestScore(-1, postId); -// -// log.info("대댓글 성공적 삭제 replyId: {}", replyId); -// } -// -// /** -// * Query Method -// */ -// -// /** -// * 특정 댓글의 대댓글 조회 -// */ -// public List getRepliesByCommentId(PostAnonymous postAnonymous, ObjectId commentId) { -// // 1. 대댓글 목록 조회 -// List replies = replyCommentRepository.findByCommentId(commentId); -// if (replies.isEmpty()) { -// return Collections.emptyList(); -// } -// -// // 2. 사용자 정보 맵 생성 -// Map userMap = createUserMapFromReplies(replies); -// String defaultImageUrl = s3Service.getDefaultProfileImageUrl(); -// -// // 3. 대댓글 DTO 변환 -// return replies.stream() -// .map(reply -> buildReplyResponseDTO(reply, postAnonymous, userMap, defaultImageUrl)) -// .toList(); -// } -// -// /** -// * 대댓글 작성자들의 사용자 정보 맵 생성 -// */ -// private Map createUserMapFromReplies(List replies) { -// List userIdsInOrder = replies.stream() -// .map(ReplyCommentEntity::getUserId) -// .toList(); -// -// List distinctIds = userIdsInOrder.stream() -// .distinct() -// .toList(); -// -// return userRepository.findAllById(distinctIds) -// .stream() -// .collect(Collectors.toMap( -// UserEntity::get_id, -// user -> user -// )); -// } -// -// /** -// * 대댓글 응답 DTO 생성 -// */ -// private CommentResponseDTO buildReplyResponseDTO( -// ReplyCommentEntity reply, -// PostAnonymous postAnonymous, -// Map userMap, -// String defaultImageUrl) { -// -// ObjectId userId = reply.getUserId(); -// UserEntity user = userMap.get(userId); -// int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); -// -// UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); -// -// return CommentResponseDTO.replyOf( -// reply, -// replyUserDto, -// likeService.getLikeCount(LikeType.REPLY, reply.get_id()), -// getUserInfoAboutReply(reply.get_id()) -// ); -// } -// -// public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { -// ObjectId userId = SecurityUtils.getCurrentUserId(); -// return CommentResponseDTO.UserInfo.builder() -// .isLike(likeService.isLiked(LikeType.COMMENT, replyId, userId)) -// .build(); -// } -// -// /** -// * -// * @param replyId -// * @return validated PostEntity -// */ -// public ReplyCommentEntity findReplyById(ObjectId replyId) { -// return replyCommentRepository.findByIdAndNotDeleted(replyId) -// .orElseThrow(() -> new ReplyException(ReplyErrorCode.REPLY_NOT_FOUND)); -// } -// -// -// -// -//} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java deleted file mode 100644 index 35c81479..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/BestPostService.java +++ /dev/null @@ -1,58 +0,0 @@ -package inu.codin.codin.domain.post.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.post.domain.best.BestEntity; -import inu.codin.codin.domain.post.domain.best.BestRepository; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.exception.PostErrorCode; -import inu.codin.codin.infra.redis.service.RedisBestService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@Slf4j -@Service -@RequiredArgsConstructor -public class BestPostService { - private final RedisBestService redisBestService; - private final BestRepository bestRepository; - private final PostRepository postRepository; - - // [BestService] - Top 3 베스트 게시물 조회 - public List getTop3BestPostsInternal() { - Map posts = redisBestService.getBests(); - return posts.entrySet().stream() - .map(post -> postRepository.findByIdAndNotDeleted(new ObjectId(post.getKey())) - .orElseGet(() -> { - redisBestService.deleteBest(post.getKey()); - return null; - })) - .filter(Objects::nonNull) - .toList(); - } - - // [BestService] - 베스트 게시물 페이지 조회 - public Page getBestPostsInternal(int pageNumber) { - PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page bests = bestRepository.findAll(pageRequest); - return bests.map(bestEntity -> postRepository.findByIdAndNotDeleted(bestEntity.getPostId()) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND))); - } - - // [BestService] - 베스트 점수 적용 처리 - public void applyBestScoreIfNeeded(PostEntity post) { - redisBestService.applyBestScore(1, post.get_id()); - log.info("베스트 점수 적용. PostId: {}", post.get_id()); - } - -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index e308492a..7ea5bc99 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -1,9 +1,12 @@ package inu.codin.codin.domain.post.service; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; import inu.codin.codin.domain.post.domain.poll.service.PollService; import inu.codin.codin.domain.post.dto.UserDto; @@ -31,6 +34,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Objects; import java.util.Optional; @Slf4j @@ -44,7 +48,7 @@ public class PostQueryService private final PollService pollService; private final BlockService blockService; private final PostInteractionService postInteractionService; - private final BestPostService bestPostService; + private final BestService bestService; private final ScrapService scrapService; private final LikeService likeService; private final S3Service s3Service; @@ -74,8 +78,7 @@ public List getPostListResponseDtos(List po * 게시물 상세 조회 */ public PostPageItemResponseDTO getPostWithDetail(String postId) { - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> new PostException(PostErrorCode.POST_NOT_FOUND)); + PostEntity post = findPostById(ObjectIdUtil.toObjectId(postId)); ObjectId userId = SecurityUtils.getCurrentUserId(); postInteractionService.increaseHits(post, userId); return toPageItemDTO(post); @@ -108,19 +111,48 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { * Top 3 베스트 게시물 조회 (불변 리스트) */ public List getTop3BestPosts() { - List bestPosts = bestPostService.getTop3BestPostsInternal(); + List bestPostIds = bestService.getTop3BestPostIds(); + + List validPosts = bestPostIds.stream() + .map(postId -> { + try { + return findPostById(ObjectIdUtil.toObjectId(postId)); + } catch (PostException e) { + bestService.deleteBestPost(postId); // 검증 실패시 삭제 + return null; + } + }) + .filter(Objects::nonNull) + .toList(); + log.info("Top 3 베스트 게시물 반환."); - return getPostListResponseDtos(bestPosts); + return getPostListResponseDtos(validPosts); } /** * 베스트 게시물 페이지 조회 */ public PostPageResponse getBestPosts(int pageNumber) { - Page page = bestPostService.getBestPostsInternal(pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); - } + Page bestEntities = bestService.getBestEntities(pageNumber); + + List validPosts = bestEntities.getContent().stream() + .map(bestEntity -> { + try { + return findPostById(bestEntity.getPostId()); + } catch (PostException e) { + bestService.deleteBestPost(bestEntity.getPostId().toString()); + return null; + } + }) + .filter(Objects::nonNull) + .toList(); + return PostPageResponse.of( + getPostListResponseDtos(validPosts), + bestEntities.getTotalPages() - 1, + bestEntities.hasNext() ? bestEntities.getPageable().getPageNumber() + 1 : -1 + ); + } // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) From 85ce34875195c0bd50f0f1f81de52e181d863758 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 20 Jul 2025 20:09:31 +0900 Subject: [PATCH 0861/1002] =?UTF-8?q?fix=20:=20accessToken=EC=9D=80=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=EB=A1=9C,=20refreshToken=EC=9D=80=20?= =?UTF-8?q?=EC=BF=A0=ED=82=A4=EB=A1=9C=20=EA=B4=80=EB=A6=AC.=20=EA=B7=B8?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20Swagger=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 7 +-- .../codin/common/config/SwaggerConfig.java | 11 ++-- .../filter/JwtAuthenticationFilter.java | 19 +++++- .../common/security/jwt/JwtTokenProvider.java | 6 +- .../codin/common/security/jwt/JwtUtils.java | 25 +++----- .../common/security/service/JwtService.java | 59 ++++++------------- 6 files changed, 53 insertions(+), 74 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 409b872c..aa453e36 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -197,7 +197,8 @@ public CorsConfigurationSource corsConfigurationSource() { "Content-Type", "X-Requested-With", "Accept", - "Cache-Control" + "Cache-Control", + "X-Refresh-Token" )); config.setExposedHeaders(List.of("Authorization")); config.setMaxAge(3600L); @@ -216,10 +217,6 @@ public CorsConfigurationSource corsConfigurationSource() { // Admin 권한 URL private static final String[] ADMIN_AUTH_PATHS = { "/v3/api/test4", - "/swagger-ui/**", - "/v3/api-docs/**", - "/v3/api-docs", - "/swagger-resources/**" }; // Manager 권한 URL diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java index 6aa69a4d..bbea2bf9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SwaggerConfig.java @@ -33,18 +33,19 @@ public OpenAPI customOpenAPI() { // Bearer Auth 설정 SecurityScheme securityScheme = new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) - .in(SecurityScheme.In.COOKIE) - .name("access_token"); + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .description("Bearer ... 을 통한 Authorization 진행"); // Bearer Auth를 사용하는 Security Requirement 설정 SecurityRequirement securityRequirement = new SecurityRequirement() - .addList("cookieAuth"); + .addList("bearerAuth"); return new OpenAPI() .info(info) .security(List.of(securityRequirement)) - .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) .servers(List.of( new Server().url("http://localhost:8080").description("Local Server"), // Local Server new Server().url(BASEURL+"/api").description("Production Server"), // Production Server diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index b45e9737..71ab23b4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -16,6 +16,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Arrays; /** * JWT 토큰을 검증하여 인증하는 필터 @@ -29,6 +30,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final PermitAllProperties permitAllProperties; private final AntPathMatcher pathMatcher = new AntPathMatcher(); + private final String [] SWAGGER_AUTH_PATHS = { + "/swagger-ui/**", + "/v3/api-docs/**", + "/v3/api-docs", + "/swagger-resources/**" + }; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -39,11 +47,16 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String accessToken = jwtUtils.getAccessToken(request); + String token = null; + if (Arrays.stream(SWAGGER_AUTH_PATHS).anyMatch(url -> pathMatcher.match(url, requestURI))) { + token = jwtUtils.getRefreshToken(request); + } else { + token = jwtUtils.getAccessToken(request); + } // Access Token이 있는 경우 - if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { - setAuthentication(accessToken); + if (token != null && jwtTokenProvider.validateToken(token)) { + setAuthentication(token); } else { SecurityContextHolder.clearContext(); } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index ae4345d4..344f9c47 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -101,16 +101,16 @@ public TokenDto createToken(Authentication authentication) { /** * 토큰 유효성 검사 (토큰 변조, 만료) - * @param accessToken + * @param token * @return true: 유효한 토큰, false: 유효하지 않은 토큰 */ - public boolean validateAccessToken(String accessToken) { + public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .setAllowedClockSkewSeconds(60) .build() - .parseClaimsJws(accessToken); + .parseClaimsJws(token); return true; } catch (ExpiredJwtException e) { // 토큰 만료 log.error("[validateAccessToken] 토큰 만료 : {}", e.getMessage()); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index a31e9a9f..8035cd95 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -8,39 +8,32 @@ @Component public class JwtUtils { + private final String REFRESH_TOKEN = "x-refresh-token"; + private final String ACCESS_TOKEN = "Authorization"; + /** - * 쿠키에서 Access 토큰 추출 - * HTTP Cookies : "access_token" : "..." + * 헤더에서 Access 토큰 추출 + * HTTP Header : "Authorization" : "Bearer ..." * @return (null, 빈 문자열)의 경우 null 반환 */ public String getAccessToken(HttpServletRequest request) { -// String bearerToken = request.getHeader("Authorization"); - String bearerToken = null; - if (request.getCookies() != null){ - for (Cookie cookie : request. getCookies()){ - if ("access_token".equals(cookie.getName())){ - bearerToken = cookie.getValue(); - break; - } - } - } - + String bearerToken = request.getHeader(ACCESS_TOKEN); if (StringUtils.hasText(bearerToken)) { - return bearerToken; + return bearerToken.substring(7); } return null; } /** * 쿠키에서 Refresh 토큰 추출 - * HTTP Cookies : "refresh_token" : "..." + * HTTP Cookies : "x-refresh-token" : "..." * @return RefreshToken이 없는 경우 null 반환 */ public String getRefreshToken(HttpServletRequest request) { String refreshToken = null; if (request.getCookies() != null){ for (Cookie cookie : request. getCookies()){ - if ("refresh_token".equals(cookie.getName())){ + if (REFRESH_TOKEN.equals(cookie.getName())){ refreshToken = cookie.getValue(); break; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 17e81668..e2b47eff 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -36,6 +36,10 @@ public class JwtService { @Value("${server.domain}") private String BASERURL; + private final String REFRESH_TOKEN = "x-refresh-token"; + private final String ACCESS_TOKEN = "Authorization"; + private final String ACCESS_TOKEN_PREFIX = "Bearer "; + /** * 최초 로그인 시 Access Token, Refresh Token 발급 * @param response @@ -95,19 +99,9 @@ private void createBothToken(HttpServletResponse response) { JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); // Authorization 헤더에 Access Token 추가 - response.setHeader("Authorization", "Bearer " + newToken.getAccessToken()); - - // todo: access token 헤더 방식으로 전환 - Cookie jwtCookie = new Cookie("access_token", newToken.getAccessToken()); - jwtCookie.setHttpOnly(true); // JavaScript에서 접근 불가 - jwtCookie.setSecure(true); // HTTPS 환경에서만 전송 - jwtCookie.setPath("/"); // 모든 요청에 포함 - jwtCookie.setMaxAge(30 * 60); // 30분 유지 - jwtCookie.setDomain(BASERURL.split("//")[1]); - jwtCookie.setAttribute("SameSite", "None"); - response.addCookie(jwtCookie); - - Cookie newRefreshToken = new Cookie("x-refresh-token", newToken.getRefreshToken()); + response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); + + Cookie newRefreshToken = new Cookie(REFRESH_TOKEN, newToken.getRefreshToken()); newRefreshToken.setHttpOnly(true); newRefreshToken.setSecure(true); newRefreshToken.setPath("/"); @@ -116,16 +110,6 @@ private void createBothToken(HttpServletResponse response) { newRefreshToken.setAttribute("SameSite", "None"); response.addCookie(newRefreshToken); - // todo: refresh token 명칭 변경 - Cookie refreshCookie = new Cookie("refresh_token", newToken.getRefreshToken()); - refreshCookie.setHttpOnly(true); - refreshCookie.setSecure(true); - refreshCookie.setPath("/"); - refreshCookie.setMaxAge(10 * 24 * 60 * 60); // 10일 - refreshCookie.setDomain(BASERURL.split("//")[1]); - refreshCookie.setAttribute("SameSite", "None"); - response.addCookie(refreshCookie); - log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); } @@ -144,14 +128,7 @@ public void deleteToken(HttpServletResponse response) { } private void deleteCookie(HttpServletResponse response) { - Cookie jwtCookie = new Cookie("access_token", ""); - jwtCookie.setMaxAge(0); // 쿠키 삭제 - jwtCookie.setHttpOnly(true); - jwtCookie.setSecure(true); - jwtCookie.setPath("/"); // 쿠키가 적용될 경로 - response.addCookie(jwtCookie); - - Cookie refreshCookie = new Cookie("refresh_token", ""); + Cookie refreshCookie = new Cookie(REFRESH_TOKEN, ""); refreshCookie.setHttpOnly(true); refreshCookie.setSecure(true); refreshCookie.setPath("/"); @@ -163,17 +140,15 @@ public void setAuthentication(ServletServerHttpRequest serverHttpRequest){ String accessToken = jwtUtils.getAccessToken(serverHttpRequest.getServletRequest()); // Access Token이 있는 경우 - if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) { - if (jwtTokenProvider.validateAccessToken(accessToken)) { - String email = jwtTokenProvider.getUsername(accessToken); - UserDetails userDetails = userDetailsService.loadUserByUsername(email); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } + if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) { + String email = jwtTokenProvider.getUsername(accessToken); + UserDetails userDetails = userDetailsService.loadUserByUsername(email); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); } } else { SecurityContextHolder.clearContext(); From 5f01c41dcc2dd50faf197daccf83dfdc7b7097ff Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 20 Jul 2025 22:07:30 +0900 Subject: [PATCH 0862/1002] =?UTF-8?q?feat=20:=20release=20yml=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 50cfe48f..a80fba3d 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 50cfe48f2b60416ef75f21fc3cc58c570bc84f4b +Subproject commit a80fba3d671aec0185603bb38c3a788e51ea3b6c From 47ba3878aecc919c815f359edffb8851d8a92825 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 20 Jul 2025 22:32:04 +0900 Subject: [PATCH 0863/1002] =?UTF-8?q?fix=20:=20mongodb,=20redis=20?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index a80fba3d..702d2304 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit a80fba3d671aec0185603bb38c3a788e51ea3b6c +Subproject commit 702d2304a09e494c436784b0a09c99617c7dd190 From c4677c73367f69d559d16e57e99ee2ad4cc9ec78 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 20 Jul 2025 22:41:06 +0900 Subject: [PATCH 0864/1002] =?UTF-8?q?fix=20:=20mongodb,=20redis=20port=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 702d2304..4851d942 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 702d2304a09e494c436784b0a09c99617c7dd190 +Subproject commit 4851d942c5227e1566d3bbba678ec7345cfb9050 From bc62d9277b44e95f273a47200e85b7af1f6b6cff Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 21 Jul 2025 01:47:23 +0900 Subject: [PATCH 0865/1002] =?UTF-8?q?feat:=20JWT=20Token=EC=97=90=20userId?= =?UTF-8?q?=20Claims=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/jwt/JwtTokenProvider.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index 344f9c47..8796a97e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.codin.domain.user.security.CustomUserDetails; import inu.codin.codin.infra.redis.RedisStorageService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; @@ -57,6 +58,9 @@ public TokenDto createToken(Authentication authentication) { .reduce((auth1, auth2) -> auth1 + "," + auth2) .orElse(""); + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + String userId = userDetails.getId().toHexString(); + // 토큰 만료시간 설정 Date now = new Date(); Date accessTokenExpiration = new Date(now.getTime() + Long.parseLong(this.ACCESS_TOKEN_EXPIRATION) * 1000); @@ -66,6 +70,7 @@ public TokenDto createToken(Authentication authentication) { String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) + .claim("userId", userId) .setIssuedAt(now) .setExpiration(accessTokenExpiration) .signWith(SECRET_KEY, SignatureAlgorithm.HS512) @@ -74,6 +79,7 @@ public TokenDto createToken(Authentication authentication) { String refreshToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) + .claim("userId", userId) .setIssuedAt(now) .setExpiration(refreshTokenExpiration) .signWith(SECRET_KEY, SignatureAlgorithm.HS512) From 7e985dae07d5b2449d3559bfb69c1865f6c2acef Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 21 Jul 2025 02:45:21 +0900 Subject: [PATCH 0866/1002] =?UTF-8?q?perf:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?DTO,=20dateTime=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/NotificationListResponseDto.java | 12 +++++++----- .../repository/NotificationRepository.java | 4 +++- .../notification/service/NotificationService.java | 5 ++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java index ea8ac8fb..af70572a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/NotificationListResponseDto.java @@ -6,26 +6,27 @@ import lombok.Getter; import org.springframework.data.annotation.Id; +import java.time.LocalDateTime; + @Getter public class NotificationListResponseDto { + @Id @NotBlank private String id; - private String targetId; - private String title; - private String message; - + private LocalDateTime dateTime; private boolean isRead = false; @Builder - public NotificationListResponseDto(String id, String targetId, String title, String message, boolean isRead) { + public NotificationListResponseDto(String id, String targetId, String title, String message, LocalDateTime dateTime, boolean isRead) { this.id = id; this.targetId = targetId; this.title = title; this.message = message; + this.dateTime = dateTime; this.isRead = isRead; } @@ -35,6 +36,7 @@ public static NotificationListResponseDto of(NotificationEntity notificationEnti .title(notificationEntity.getTitle()) .message(notificationEntity.getMessage()) .targetId(notificationEntity.getTargetId().toString()) + .dateTime(notificationEntity.getCreatedAt()) .isRead(notificationEntity.isRead()) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java index 075b06cb..2f6817ae 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/repository/NotificationRepository.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.notification.entity.NotificationEntity; import org.bson.types.ObjectId; +import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -13,5 +14,6 @@ public interface NotificationRepository extends MongoRepository findAllByUserId(ObjectId userId); + @Query("{ 'userId': ?0, 'isRead': false, deletedAt: null }") + List findAllByUserId(ObjectId userId, Sort sort); } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 07e05bd1..45eedc5a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -204,7 +205,9 @@ public List getNotification() { ObjectId userId = SecurityUtils.getCurrentUserId(); userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다")); - List notifications = notificationRepository.findAllByUserId(userId); + + Sort sort = Sort.by(Sort.Direction.DESC, "createdAt"); + List notifications = notificationRepository.findAllByUserId(userId, sort); return notifications.stream() .map(NotificationListResponseDto::of) .toList(); From 45397714f5c71b34fb9ee63360a05be3bcd39ff1 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 21 Jul 2025 02:46:01 +0900 Subject: [PATCH 0867/1002] =?UTF-8?q?remove:=20FeignConfig=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 미사용해서 제거합니다. ㅎㅎ --- .../inu/codin/codin/common/config/FeignConfig.java | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java deleted file mode 100644 index 38d348e8..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/config/FeignConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package inu.codin.codin.common.config; - -import feign.Client; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class FeignConfig { - - @Bean - public Client feignClient(){ - return new Client.Default(null, null); - } -} From 56a3f956d4e96e71b4aaac84b2147381bdb80400 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Thu, 24 Jul 2025 02:08:04 +0900 Subject: [PATCH 0868/1002] =?UTF-8?q?refactor=20:=20LikeType=EC=97=90=20Le?= =?UTF-8?q?ctures=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=A8=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20likeTypeId=20=EB=A5=BC=20ObjectId=EC=97=90?= =?UTF-8?q?=EC=84=9C=20String=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/review/service/ReviewService.java | 4 +- .../like/controller/LikeController.java | 2 +- .../codin/domain/like/entity/LikeEntity.java | 4 +- .../codin/domain/like/entity/LikeType.java | 3 +- .../exception/LikeCreateFailException.java | 7 --- .../exception/LikeRemoveFailException.java | 7 --- .../like/repository/LikeRepository.java | 8 ++-- .../domain/like/service/LikeService.java | 46 +++++++++++-------- .../comment/service/CommentService.java | 4 +- .../reply/service/ReplyCommentService.java | 4 +- .../domain/post/service/PostService.java | 4 +- .../domain/user/service/UserService.java | 2 +- .../infra/redis/service/RedisLikeService.java | 25 +++++----- 13 files changed, 59 insertions(+), 61 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index e4dd991d..22f7fdeb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -85,8 +85,8 @@ public ReviewPageResponse getListOfReviews(String lectureId, int page) { ObjectId userId = SecurityUtils.getCurrentUserId(); return ReviewPageResponse.of(reviewPage.stream() .map(review -> ReviewListResposneDto.of(review, - likeService.isLiked(LikeType.REVIEW, review.get_id(), userId), - likeService.getLikeCount(LikeType.REVIEW, review.get_id()))).toList(), + likeService.isLiked(LikeType.REVIEW, review.get_id().toString(), userId), + likeService.getLikeCount(LikeType.REVIEW, review.get_id().toString()))).toList(), reviewPage.getTotalPages() -1, reviewPage.hasNext()? reviewPage.getPageable().getPageNumber() + 1: -1); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 29bc860d..1004ce8d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -22,7 +22,7 @@ public class LikeController { private final LikeService likeService; - @Operation(summary = "게시물, 댓글, 대댓글, 수강후기 좋아요 토글") + @Operation(summary = "게시물, 댓글, 대댓글, 수강후기, 과목 좋아요 토글") @PostMapping public ResponseEntity> toggleLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { String message = likeService.toggleLike(likeRequestDto); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java index f6beb2e9..2b4dd358 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java @@ -12,12 +12,12 @@ public class LikeEntity extends BaseTimeEntity { @Id private ObjectId _id; - private ObjectId likeTypeId; // 게시글, 댓글, 대댓글의 ID + private String likeTypeId; // 게시글, 댓글, 대댓글의 ID private LikeType likeType; // 엔티티 타입 (post, comment, reply) private ObjectId userId; // 좋아요를 누른 사용자 ID @Builder - public LikeEntity(ObjectId likeTypeId, LikeType likeType, ObjectId userId) { + public LikeEntity(String likeTypeId, LikeType likeType, ObjectId userId) { this.likeTypeId = likeTypeId; this.likeType = likeType; this.userId = userId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java index 3c404894..c93ce911 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeType.java @@ -7,7 +7,8 @@ public enum LikeType { POST("게시물"), COMMENT("댓글"), REPLY("대댓글"), - REVIEW("수강 후기"); + REVIEW("수강 후기"), + LECTURE("과목"); private final String description; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java deleted file mode 100644 index a4ed09bb..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeCreateFailException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.like.exception; - -public class LikeCreateFailException extends RuntimeException{ - public LikeCreateFailException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java deleted file mode 100644 index 26130191..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/exception/LikeRemoveFailException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.like.exception; - -public class LikeRemoveFailException extends RuntimeException { - public LikeRemoveFailException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java index 2bc0fd2c..02f8d1b5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java @@ -14,10 +14,8 @@ public interface LikeRepository extends MongoRepository { // 특정 엔티티(게시글/댓글/대댓글)의 좋아요 개수 조회 - int countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId likeTypeId); - int countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, ObjectId likeTypeId); - boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, ObjectId id, ObjectId userId); - - Optional findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, ObjectId likeTypeId, ObjectId userId); + int countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(LikeType likeType, String likeTypeId); + boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, String id, ObjectId userId); + Optional findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String likeTypeId, ObjectId userId); Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 541fbd5c..5e892c3e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -37,7 +37,7 @@ public class LikeService { public String toggleLike(LikeRequestDto likeRequestDto) { - ObjectId likeId = new ObjectId(likeRequestDto.getId()); + String likeId = likeRequestDto.getId(); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 @@ -46,7 +46,7 @@ public String toggleLike(LikeRequestDto likeRequestDto) { return getResult(likeRequestDto, like, likeId, userId); } - private String getResult(LikeRequestDto likeRequestDto, Optional like, ObjectId likeId, ObjectId userId) { + private String getResult(LikeRequestDto likeRequestDto, Optional like, String likeId, ObjectId userId) { if (like.isPresent()){ if (like.get().getDeletedAt() == null) { removeLike(like.get()); @@ -61,7 +61,10 @@ private String getResult(LikeRequestDto likeRequestDto, Optional lik } } - public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId){ + /* + 총 좋아요 개수는 Redis에, 누가 눌렀는지는 DB에 저장 + */ + public void addLike(LikeType likeType, String likeId, ObjectId userId){ if (redisHealthChecker.isRedisAvailable()) { redisLikeService.addLike(likeType.name(), likeId); log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}, userId: {}", likeType, likeId, userId); @@ -73,16 +76,13 @@ public void addLike(LikeType likeType, ObjectId likeId, ObjectId userId){ .userId(userId) .build()); if (likeType == LikeType.POST) { - redisBestService.applyBestScore(1, likeId); + redisBestService.applyBestScore(1, new ObjectId(likeId)); log.info("Redis에 Best Score 적용 - postId: {}", likeId); } } public void restoreLike(LikeEntity like) { - if (redisHealthChecker.isRedisAvailable()) { - redisLikeService.addLike(like.getLikeType().name(), like.getLikeTypeId()); - log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}", like.getLikeType(), like.getLikeTypeId()); - } + ifRedisAvailableAddLike(like.getLikeType(), like.getLikeTypeId()); like.recreatedAt(); like.restore(); @@ -91,6 +91,13 @@ public void restoreLike(LikeEntity like) { } + private void ifRedisAvailableAddLike(LikeType likeType, String likeId) { + if (redisHealthChecker.isRedisAvailable()) { + redisLikeService.addLike(likeType.name(), likeId); + log.info("Redis에 좋아요 추가 - likeType: {}, likeId: {}", likeType, likeId); + } + } + public void removeLike(LikeEntity like) { if (redisHealthChecker.isRedisAvailable()) { redisLikeService.removeLike(like.getLikeType().name(), like.getLikeTypeId()); @@ -101,24 +108,27 @@ public void removeLike(LikeEntity like) { log.info("좋아요 삭제 완료 - likeId: {}, userId: {}", like.get_id(), like.getUserId()); } - public int getLikeCount(LikeType entityType, ObjectId entityId) { + public int getLikeCount(LikeType entityType, String entityId) { + //Redis가 사용 가능하면 Redis 시도 Object redisResult = null; if (redisHealthChecker.isRedisAvailable()) { redisResult = redisLikeService.getLikeCount(entityType.name(), entityId); + if (redisResult != null) + return Integer.parseInt(String.valueOf(redisResult)); } - if (redisResult == null){ - recoveryLike(entityType, entityId); - return likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); - } else return Integer.parseInt(String.valueOf(redisResult)); + + //Redis가 꺼져 있거나 cache가 없을 경우 -> DB 조회 + int likeCount = likeRepository.countByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); + recoveryLike(entityType, entityId, likeCount); + return likeCount; } @Async - protected void recoveryLike(LikeType entityType, ObjectId entityId) { - int likeCount = likeRepository.countAllByLikeTypeAndLikeTypeIdAndDeletedAtIsNull(entityType, entityId); - redisLikeService.recoveryLike(entityType, entityId, likeCount); + protected void recoveryLike(LikeType entityType, String entityId, int likeCount) { + redisLikeService.recoveryLike(entityType.name(), entityId, likeCount); } - public boolean isLiked(LikeType likeType, ObjectId likeTypeId, ObjectId userId){ + public boolean isLiked(LikeType likeType, String likeTypeId, ObjectId userId){ return likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeTypeId, userId); } @@ -133,7 +143,7 @@ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(id) .orElseThrow(() ->new NotFoundException("수강 후기를 찾을 수 없습니다")); - + //todo LECTURE } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java index 428b5492..1d5e3265 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentService.java @@ -123,7 +123,7 @@ public List getCommentsByPostId(String id) { } return CommentResponseDTO.commentOf(comment, nickname, userImageUrl, replyCommentService.getRepliesByCommentId(post.getAnonymous(), comment.get_id()), - likeService.getLikeCount(LikeType.COMMENT, comment.get_id()), + likeService.getLikeCount(LikeType.COMMENT, comment.get_id().toString()), getUserInfoAboutComment(comment.get_id())); }) .toList(); @@ -146,7 +146,7 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { public UserInfo getUserInfoAboutComment(ObjectId commentId) { ObjectId userId = SecurityUtils.getCurrentUserId(); return UserInfo.builder() - .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) + .isLike(likeService.isLiked(LikeType.COMMENT, commentId.toString(), userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java index 91adfc63..5651cd94 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommentService.java @@ -120,7 +120,7 @@ public List getRepliesByCommentId(PostAnonymous postAnonymou userImageUrl = reply.isAnonymous()? defaultImageUrl: userMap.get(reply.getUserId()).imageUrl(); } return CommentResponseDTO.replyOf(reply, nickname, userImageUrl, List.of(), - likeService.getLikeCount(LikeType.REPLY, reply.get_id()), // 대댓글 좋아요 수 + likeService.getLikeCount(LikeType.REPLY, reply.get_id().toString()), // 대댓글 좋아요 수 getUserInfoAboutReply(reply.get_id())); }).toList(); } @@ -129,7 +129,7 @@ public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); //log.info("대댓글 userInfo - replyId: {}, userId: {}", replyId, userId); return CommentResponseDTO.UserInfo.builder() - .isLike(likeService.isLiked(LikeType.REPLY, replyId, userId)) + .isLike(likeService.isLiked(LikeType.REPLY, replyId.toString(), userId)) .build(); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 3d129c9c..28ef716f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -161,7 +161,7 @@ private PostDetailResponseDTO createPostDetailResponse(PostEntity post) { //Post 관련 인자 처리 - int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id()); + int likeCount = likeService.getLikeCount(LikeType.POST, post.get_id().toString()); int scrapCount = scrapService.getScrapCount(post.get_id()); int hitsCount = hitsService.getHitsCount(post.get_id()); int commentCount = post.getCommentCount(); @@ -274,7 +274,7 @@ public void deletePostImage(String postId, String imageUrl) { public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ return UserInfo.builder() - .isLike(likeService.isLiked(LikeType.POST, postId, currentUserId)) + .isLike(likeService.isLiked(LikeType.POST, postId.toString(), currentUserId)) .isScrap(scrapService.isPostScraped(postId, currentUserId)) .isMine(postUserId.equals(currentUserId)) .build(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 620aaf24..a17f5b37 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -77,7 +77,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i log.info("[좋아요 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); List postUserLike = likePage.getContent().stream() - .map(likeEntity -> postRepository.findByIdAndNotDeleted(likeEntity.getLikeTypeId()) + .map(likeEntity -> postRepository.findByIdAndNotDeleted(new ObjectId(likeEntity.getLikeTypeId())) .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) .toList(); log.info("[좋아요 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", likePage.getTotalPages(), likePage.hasNext()); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java index 9968b92a..38343d51 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisLikeService.java @@ -1,10 +1,8 @@ package inu.codin.codin.infra.redis.service; -import inu.codin.codin.domain.like.entity.LikeType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -15,15 +13,15 @@ @Slf4j public class RedisLikeService { /** - * Redis 기반 Like 관리 Service + * Redis 기반 Like 관리 Service, TTL = 1DAYS */ private final RedisTemplate redisTemplate; private static final String LIKE_KEY=":likes:"; //Like - public void addLike(String entityType, ObjectId entityId) { - String redisKey = entityType + LIKE_KEY + entityId; + public void addLike(String entityType, String entityId) { + String redisKey = makeRedisKey(entityType, entityId); if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) redisTemplate.opsForValue().increment(redisKey); else { @@ -32,13 +30,13 @@ public void addLike(String entityType, ObjectId entityId) { redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); } - public void removeLike(String entityType, ObjectId entityId) { - String redisKey = entityType + LIKE_KEY + entityId; + public void removeLike(String entityType, String entityId) { + String redisKey = makeRedisKey(entityType, entityId); redisTemplate.opsForValue().decrement(redisKey, 1); } - public Object getLikeCount(String entityType, ObjectId entityId) { - String redisKey = entityType + LIKE_KEY + entityId; + public Object getLikeCount(String entityType, String entityId) { + String redisKey = makeRedisKey(entityType, entityId); if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))){ redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); return redisTemplate.opsForValue().get(redisKey); @@ -46,9 +44,14 @@ public Object getLikeCount(String entityType, ObjectId entityId) { } - public void recoveryLike(LikeType entityType, ObjectId entityId, int likeCount) { - String redisKey = entityType + LIKE_KEY + entityId; + public void recoveryLike(String entityType, String entityId, int likeCount) { + String redisKey = makeRedisKey(entityType, entityId); redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); redisTemplate.opsForValue().set(redisKey, String.valueOf(likeCount)); } + + private static String makeRedisKey(String entityType, String entityId) { + return entityType + LIKE_KEY + entityId; + } + } From 36912766464de423c42127769796a278226216b7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 25 Jul 2025 01:31:29 +0900 Subject: [PATCH 0869/1002] =?UTF-8?q?feat=20:=20feign=20=ED=86=B5=EC=8B=A0?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20controller=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20ResponseType=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/controller/LikeController.java | 33 +++++++++++--- .../domain/like/dto/LikeResponseType.java | 12 +++++ .../domain/like/service/LikeService.java | 44 +++++++++++-------- 3 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeResponseType.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 1004ce8d..7b22a132 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -2,17 +2,17 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.dto.LikeResponseType; +import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; 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; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/likes") @@ -22,12 +22,31 @@ public class LikeController { private final LikeService likeService; - @Operation(summary = "게시물, 댓글, 대댓글, 수강후기, 과목 좋아요 토글") + @Operation(summary = "POST(게시물), COMMENT(댓글), REPLY(대댓글) 좋아요 토글", + description = "LikeType = POST, COMMENT, REPLY, LECTURE, REVIEW
" + + "id = 좋아요를 누를 entity의 pk
" + + "외부 서버에서 LECTURE(과목), REVIEW(수강 후기) 좋아요 기능으로 사용") @PostMapping public ResponseEntity> toggleLike(@RequestBody @Valid LikeRequestDto likeRequestDto) { - String message = likeService.toggleLike(likeRequestDto); + LikeResponseType message = likeService.toggleLike(likeRequestDto); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(201, message, null)); + .body(new SingleResponse<>(201, "좋아요가 " + message.getDescription() + "되었습니다.", message.toString())); } + @Hidden + @GetMapping + public Integer getLikeCount(@RequestParam("likeType") LikeType likeType, + @RequestParam("id") String id) { + return likeService.getLikeCount(likeType, id); + } + + @Hidden + @GetMapping("/user") + public Boolean isUserLiked(@RequestParam("likeType") LikeType likeType, + @RequestParam("id") String id, + @RequestParam("userId") String userId) { + return likeService.isLiked(likeType, id, userId); + } + + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeResponseType.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeResponseType.java new file mode 100644 index 00000000..814c8d43 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikeResponseType.java @@ -0,0 +1,12 @@ +package inu.codin.codin.domain.like.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LikeResponseType { + ADD("추가"), RECOVER("복구"), REMOVE("삭제"); + + private final String description; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 5e892c3e..f6e2fc49 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -2,8 +2,8 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.lecture.domain.review.repository.ReviewRepository; import inu.codin.codin.domain.like.dto.LikeRequestDto; +import inu.codin.codin.domain.like.dto.LikeResponseType; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; @@ -29,35 +29,34 @@ public class LikeService { private final PostRepository postRepository; private final CommentRepository commentRepository; private final ReplyCommentRepository replyCommentRepository; - private final ReviewRepository reviewRepository; private final RedisLikeService redisLikeService; private final RedisBestService redisBestService; private final RedisHealthChecker redisHealthChecker; - public String toggleLike(LikeRequestDto likeRequestDto) { + public LikeResponseType toggleLike(LikeRequestDto likeRequestDto) { String likeId = likeRequestDto.getId(); ObjectId userId = SecurityUtils.getCurrentUserId(); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 Optional like = likeRepository.findByLikeTypeAndLikeTypeIdAndUserId(likeRequestDto.getLikeType(), likeId, userId); - return getResult(likeRequestDto, like, likeId, userId); + return controlLike(likeRequestDto, like, likeId, userId); } - private String getResult(LikeRequestDto likeRequestDto, Optional like, String likeId, ObjectId userId) { + private LikeResponseType controlLike(LikeRequestDto likeRequestDto, Optional like, String likeId, ObjectId userId) { if (like.isPresent()){ if (like.get().getDeletedAt() == null) { removeLike(like.get()); - return "좋아요가 삭제되었습니다."; + return LikeResponseType.REMOVE; } else { restoreLike(like.get()); //좋아요가 존재하는데 삭제된 상태 - return "좋아요가 복구되었습니다"; + return LikeResponseType.RECOVER; } } else { addLike(likeRequestDto.getLikeType(), likeId, userId); - return "좋아요가 추가되었습니다."; + return LikeResponseType.ADD; } } @@ -132,19 +131,26 @@ public boolean isLiked(LikeType likeType, String likeTypeId, ObjectId userId){ return likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeTypeId, userId); } + public boolean isLiked(LikeType likeType, String likeTypeId, String userId){ + return likeRepository.existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(likeType, likeTypeId, new ObjectId(userId)); + } + private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ - ObjectId id = new ObjectId(likeRequestDto.getId()); - switch(likeRequestDto.getLikeType()){ - case POST -> postRepository.findByIdAndNotDeleted(id) - .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); - case REPLY -> replyCommentRepository.findByIdAndNotDeleted(id) - .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); - case COMMENT -> commentRepository.findByIdAndNotDeleted(id) - .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); - case REVIEW -> reviewRepository.findBy_idAndDeletedAtIsNull(id) - .orElseThrow(() ->new NotFoundException("수강 후기를 찾을 수 없습니다")); - //todo LECTURE + LikeType likeType = likeRequestDto.getLikeType(); + if (likeType.equals(LikeType.LECTURE) || likeType.equals(LikeType.REVIEW)){ + String likeTypeId = likeRequestDto.getId(); + } else { + ObjectId id = new ObjectId(likeRequestDto.getId()); + switch(likeType){ + case POST -> postRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("게시글을 찾을 수 없습니다.")); + case REPLY -> replyCommentRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("대댓글을 찾을 수 없습니다.")); + case COMMENT -> commentRepository.findByIdAndNotDeleted(id) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); + } } + } From e389a2b279db8cf2906adbe99c0fde702d5e64ce Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 25 Jul 2025 01:59:04 +0900 Subject: [PATCH 0870/1002] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98=20Dto=20=ED=95=99?= =?UTF-8?q?=EB=B2=88=20=EC=B6=94=EA=B0=80=20#231?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/user/dto/response/UserInfoResponseDto.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java index 3e165775..1c729088 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java @@ -11,15 +11,17 @@ public class UserInfoResponseDto { private String _id; private String email; + private String studentId; private String name; private String profileImageUrl; private String nickname; private Department department; @Builder - public UserInfoResponseDto(String _id, String email, String name, String profileImageUrl, String nickname, Department department) { + public UserInfoResponseDto(String _id, String email, String studentId, String name, String profileImageUrl, String nickname, Department department) { this._id = _id; this.email = email; + this.studentId = studentId; this.name = name; this.profileImageUrl = profileImageUrl; this.nickname = nickname; @@ -30,6 +32,7 @@ public static UserInfoResponseDto of(UserEntity user) { return UserInfoResponseDto.builder() ._id(user.get_id().toString()) .email(user.getEmail()) + .studentId(user.getStudentId()) .name(user.getName()) .profileImageUrl(user.getProfileImageUrl()) .nickname(user.getNickname()) From 9ea7d0f9c96cea26e65c533533fa1d58d13f9d19 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 25 Jul 2025 02:31:23 +0900 Subject: [PATCH 0871/1002] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=ED=8C=85=20=EC=B0=B8=EC=97=AC=EC=9E=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98,=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20#231?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...cketingParticipationInfoUpdateRequest.java | 17 ++++++++++ ...serTicketingParticipationInfoResponse.java | 31 +++++++++++++++++++ .../codin/domain/user/entity/UserEntity.java | 11 +++++++ .../domain/user/service/UserService.java | 28 +++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java new file mode 100644 index 00000000..e84f774e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.user.dto.request; + +import inu.codin.codin.common.dto.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@Schema(description = "유저 티켓팅 수령자 정보 Update Dto") +public class UserTicketingParticipationInfoUpdateRequest { + + private Department department; + private String studentId; + private String name; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java new file mode 100644 index 00000000..543f55ed --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java @@ -0,0 +1,31 @@ +package inu.codin.codin.domain.user.dto.response; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.user.entity.UserEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@Schema(description = "유저 티켓팅 수령자 정보 반환 Dto") +public class UserTicketingParticipationInfoResponse { + + @Schema(description = "user id", example = "1111") + private String _id; + @Schema(description = "Enum Department 소속 정보", example = "COMPUTER_SCI") + private Department department; + @Schema(description = "학번", example = "202501111") + private String studentId; + @Schema(description = "이름", example = "홍길동") + private String name; + + public static UserTicketingParticipationInfoResponse of(UserEntity user) { + return UserTicketingParticipationInfoResponse.builder() + ._id(user.get_id().toString()) + .department(user.getDepartment()) + .studentId(user.getStudentId()) + .name(user.getName()) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index eed72fd9..218a8787 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.domain.notification.entity.NotificationPreference; +import inu.codin.codin.domain.user.dto.request.UserTicketingParticipationInfoUpdateRequest; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -103,4 +104,14 @@ public void activation() { public void updateTotalSuspensionEndDate(LocalDateTime totalSuspensionEndDate){ this.totalSuspensionEndDate = totalSuspensionEndDate; } + + /** + * studentId(학번), name(실제 이름), department(소속) 정보 업데이트 + * @param updateRequest 유저 티켓팅 참여 정보 업데이트 request dto + */ + public void updateParticipationInfo(UserTicketingParticipationInfoUpdateRequest updateRequest) { + this.studentId = updateRequest.getStudentId(); + this.name = updateRequest.getName(); + this.department = updateRequest.getDepartment(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 620aaf24..199be381 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -15,7 +15,9 @@ import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; +import inu.codin.codin.domain.user.dto.request.UserTicketingParticipationInfoUpdateRequest; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; +import inu.codin.codin.domain.user.dto.response.UserTicketingParticipationInfoResponse; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.domain.user.repository.UserRepository; @@ -186,6 +188,32 @@ public void updateUserProfile(MultipartFile profileImage) { log.info("[프로필 이미지 업데이트 성공] 사용자 ID: {}, 프로필 이미지 URL: {}", userId, profileImageUrl); } + /** + * 유저 티켓팅 수령 정보 반환 + * @return UserTicketingParticipationInfoResponse 유저의 학번, 이름, 소속 Dto 반환 + */ + public UserTicketingParticipationInfoResponse getUserTicketingParticipationInfo() { + return UserTicketingParticipationInfoResponse.of( + userRepository.findByUserId(SecurityUtils.getCurrentUserId()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다."))); + } + + /** + * 유저 티켓팅 수령 정보 수정 (생성) + * @return UserTicketingParticipationInfoResponse 유저의 학번, 이름, 소속 Dto 반환 + */ + public UserTicketingParticipationInfoResponse updateUserTicketingParticipationInfo(UserTicketingParticipationInfoUpdateRequest updateRequest) { + UserEntity userEntity = userRepository.findByUserId(SecurityUtils.getCurrentUserId()) + .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); + + userEntity.updateParticipationInfo(updateRequest); + userEntity = userRepository.save(userEntity); + return UserTicketingParticipationInfoResponse.of(userEntity); + } + + /** + * 유저 도메인 상호작용 조회 Enum Class + */ public enum InteractionType { LIKE, SCRAP, COMMENT } From b0a1673e85edd0c7228e28f3eb1af361564bfb85 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 25 Jul 2025 15:24:41 +0900 Subject: [PATCH 0872/1002] =?UTF-8?q?feat=20:=20LikeType=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=A2=8B=EC=95=84=EC=9A=94=EB=A5=BC=20=EB=88=84=EB=A5=B8=20?= =?UTF-8?q?Id=20=EB=AA=A8=EB=91=90=20=EB=B0=98=ED=99=98=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/controller/LikeController.java | 10 +++++++++- .../codin/codin/domain/like/dto/LikedResponseDto.java | 8 ++++++++ .../codin/domain/like/repository/LikeRepository.java | 6 ++++++ .../codin/codin/domain/like/service/LikeService.java | 6 +++++- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikedResponseDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 7b22a132..bbadbb28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.like.dto.LikeResponseType; +import inu.codin.codin.domain.like.dto.LikedResponseDto; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import io.swagger.v3.oas.annotations.Hidden; @@ -14,6 +15,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/likes") @RequiredArgsConstructor @@ -48,5 +51,10 @@ public Boolean isUserLiked(@RequestParam("likeType") LikeType likeType, return likeService.isLiked(likeType, id, userId); } - + @Hidden + @GetMapping("/list") + public List getLikedId(@RequestParam("likeType") LikeType likeType, + @RequestParam("userId") String userId) { + return likeService.getLikedId(likeType, userId); + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikedResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikedResponseDto.java new file mode 100644 index 00000000..38a124ef --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/dto/LikedResponseDto.java @@ -0,0 +1,8 @@ +package inu.codin.codin.domain.like.dto; + +import lombok.Getter; + +@Getter +public class LikedResponseDto { + private String likeTypeId; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java index 02f8d1b5..1976c8b9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java @@ -1,13 +1,16 @@ package inu.codin.codin.domain.like.repository; +import inu.codin.codin.domain.like.dto.LikedResponseDto; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -18,4 +21,7 @@ public interface LikeRepository extends MongoRepository { boolean existsByLikeTypeAndLikeTypeIdAndUserIdAndDeletedAtIsNull(LikeType likeType, String id, ObjectId userId); Optional findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String likeTypeId, ObjectId userId); Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); + + @Query(value = "{ 'likeType': ?0, 'userId': ?1 }", fields = "{ 'likeTypeId': 1, '_id': 0 }") + List findLikeTypeIdByLikeTypeAndUserId(LikeType likeType, ObjectId userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index f6e2fc49..e6ffaa41 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.dto.LikeRequestDto; import inu.codin.codin.domain.like.dto.LikeResponseType; +import inu.codin.codin.domain.like.dto.LikedResponseDto; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; @@ -19,6 +20,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Optional; @Service @@ -153,5 +155,7 @@ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ } - + public List getLikedId(LikeType likeType, String userId) { + return likeRepository.findLikeTypeIdByLikeTypeAndUserId(likeType, new ObjectId(userId)); + } } \ No newline at end of file From 207a99a380e2209b65f5ead6c724158980969592 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 27 Jul 2025 17:37:47 +0900 Subject: [PATCH 0873/1002] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=ED=8C=85=20=EC=B0=B8=EC=97=AC=EC=9E=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98,=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#231?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 414a9afc..6b92ff97 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -3,7 +3,9 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; +import inu.codin.codin.domain.user.dto.request.UserTicketingParticipationInfoUpdateRequest; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; +import inu.codin.codin.domain.user.dto.response.UserTicketingParticipationInfoResponse; import inu.codin.codin.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -74,7 +76,7 @@ public ResponseEntity> deleteUser(HttpServletResponse response } @Operation( - summary = "유저 정보 반환" + summary = "유저 정보 반환 - [Ticketing API Feign]" ) @GetMapping public ResponseEntity> getUserInfo(){ @@ -101,4 +103,24 @@ public ResponseEntity> updateUserProfile(@RequestPart(value = return ResponseEntity.ok() .body(new SingleResponse<>(200, "유저 사진 수정 완료", null)); } + + @Operation( + summary = "유저 티켓팅 수령자 정보 반환 (학번, 이름, 소속) - [Ticketing]" + ) + @GetMapping("/ticketing-participation") + public ResponseEntity> getUserTicketingParticipationInfo() { + return ResponseEntity.ok().body(new SingleResponse<>(200, "유저 티켓팅 수령 정보 반환 완료", + userService.getUserTicketingParticipationInfo())); + } + + @Operation( + summary = "유저 티켓팅 수령자 정보 수정(생성) (학번, 이름, 소속) - [Ticketing]" + ) + @PutMapping("/ticketing-participation") + public ResponseEntity> updateUserTicketingParticipationInfo( + @RequestBody UserTicketingParticipationInfoUpdateRequest updateRequest + ) { + return ResponseEntity.ok().body(new SingleResponse<>(200, "유저 티켓팅 수령 정보 수정 완료", + userService.updateUserTicketingParticipationInfo(updateRequest))); + } } \ No newline at end of file From e50aa941dfb6c533bc972fb91c8498bd4bd1190b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 27 Jul 2025 20:51:27 +0900 Subject: [PATCH 0874/1002] =?UTF-8?q?docs=20:=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=EB=A5=BC=20=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=B6=80=EB=B6=84=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/like/service/LikeService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index e6ffaa41..b591e842 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -139,9 +139,7 @@ public boolean isLiked(LikeType likeType, String likeTypeId, String userId){ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ LikeType likeType = likeRequestDto.getLikeType(); - if (likeType.equals(LikeType.LECTURE) || likeType.equals(LikeType.REVIEW)){ - String likeTypeId = likeRequestDto.getId(); - } else { + if (likeType.equals(LikeType.POST) || likeType.equals(LikeType.REPLY) || likeType.equals(LikeType.COMMENT)) { ObjectId id = new ObjectId(likeRequestDto.getId()); switch(likeType){ case POST -> postRepository.findByIdAndNotDeleted(id) @@ -151,8 +149,12 @@ private void isEntityNotDeleted(LikeRequestDto likeRequestDto){ case COMMENT -> commentRepository.findByIdAndNotDeleted(id) .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다.")); } + } //LECTURE, REVIEW는 외부 서버에서 사용하므로 삭제 여부 확인하지 않음 + else if (likeType.equals(LikeType.LECTURE) || likeType.equals(LikeType.REVIEW)) { + return; + } else { + throw new NotFoundException("지원하지 않는 좋아요 타입입니다."); } - } public List getLikedId(LikeType likeType, String userId) { From 4757bddc29de90d8a91f832f801c97a6dca5508b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 27 Jul 2025 21:13:11 +0900 Subject: [PATCH 0875/1002] =?UTF-8?q?refactor=20:=20=EA=B3=A0=EC=A0=95?= =?UTF-8?q?=EB=90=9C=20score=20=EC=A0=90=EC=88=98=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/infra/redis/service/RedisBestService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java index dde685d1..287c0209 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/redis/service/RedisBestService.java @@ -36,6 +36,7 @@ public class RedisBestService { private final BestRepository bestRepository; private final PostRepository postRepository; private final String BEST_KEY = "post:top3"; + private final int SCORE_THRESHOLD = 4; /** * 이전 1시간 동안의 가장 score가 높은 게시글 N개를 반환, DB 조회가 안된다면 삭제 @@ -62,7 +63,7 @@ public Map delicatedBestsScheduler(int N) { deleteBest(postId); continue; } - if (score >= 4) { + if (score >= SCORE_THRESHOLD) { result.put(postId, score); // 만약 Best 게시글에 포함되어 있지 않다면 저장 } @@ -159,7 +160,7 @@ public void applyBestScore(int score, ObjectId postId){ */ private void updateBests(String redisKey, String postId){ Double score = redisTemplate.opsForZSet().score(redisKey, postId); - if (score < 4) return; //총 점수가 4점 미만이면 Best 게시글로 취급하지 않음 + if (score < SCORE_THRESHOLD) return; //총 점수가 4점 미만이면 Best 게시글로 취급하지 않음 Set> minEntry = redisTemplate.opsForZSet().rangeWithScores(BEST_KEY, 0, 0); if (minEntry!=null && !minEntry.isEmpty()){ From 0acffb32ceb985b571b52b0487d92b1dec480ae0 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 27 Jul 2025 21:28:27 +0900 Subject: [PATCH 0876/1002] =?UTF-8?q?refactor=20:=20import=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/report/service/ReportService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index e4b6fe7f..fec0c421 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -2,13 +2,13 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.comment.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentService; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; From 0387ba434f595e729a8f1b3a4f4178689a4e422a Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 27 Jul 2025 21:28:27 +0900 Subject: [PATCH 0877/1002] =?UTF-8?q?refactor=20:=20import=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 50cfe48f..2290aade 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 50cfe48f2b60416ef75f21fc3cc58c570bc84f4b +Subproject commit 2290aadec7f5705f09ddf0bfd033d056b3fa8f10 From 5b6b763bc8c09f7a16539c77b2a653824a010e95 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 27 Jul 2025 21:32:13 +0900 Subject: [PATCH 0878/1002] refactor : update resources --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 2290aade..4851d942 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 2290aadec7f5705f09ddf0bfd033d056b3fa8f10 +Subproject commit 4851d942c5227e1566d3bbba678ec7345cfb9050 From 7c3e7af55b68bff54194684539271a944f5575e9 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Sun, 27 Jul 2025 21:45:43 +0900 Subject: [PATCH 0879/1002] =?UTF-8?q?fix=20:=20=EC=83=9D=EC=84=B1=EC=9E=90?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20fie?= =?UTF-8?q?ld=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/user/entity/UserEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index e2b22dd3..fbe6b3fc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -46,7 +46,7 @@ public class UserEntity extends BaseTimeEntity { private NotificationPreference notificationPreference = new NotificationPreference(); @Builder - public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, Boolean undergraduate, UserRole role, UserStatus status) { + public UserEntity(String email, String password, String studentId, String name, String nickname, String profileImageUrl, Department department, String college, UserRole role, UserStatus status) { this.email = email; this.password = password; this.studentId = studentId; From 5ab6ee97ad428f700354250f52e782d80e26c257 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 28 Jul 2025 20:41:11 +0900 Subject: [PATCH 0880/1002] =?UTF-8?q?fix=20:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=EC=97=90=EC=84=9C=20=EC=9D=B8=EC=A6=9D=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=BF=A0=ED=82=A4=20=EC=82=BD=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/security/service/JwtService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index e2b47eff..8d19257a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -101,6 +101,15 @@ private void createBothToken(HttpServletResponse response) { // Authorization 헤더에 Access Token 추가 response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); + Cookie newAccessToken = new Cookie("accessToken", newToken.getRefreshToken()); + newAccessToken.setHttpOnly(true); + newAccessToken.setSecure(true); + newAccessToken.setPath("/"); + newAccessToken.setMaxAge(10 * 24 * 60 * 60); // 10일 + newAccessToken.setDomain(BASERURL.split("//")[1]); + newAccessToken.setAttribute("SameSite", "None"); + response.addCookie(newAccessToken); + Cookie newRefreshToken = new Cookie(REFRESH_TOKEN, newToken.getRefreshToken()); newRefreshToken.setHttpOnly(true); newRefreshToken.setSecure(true); From 712ebf1aee8906a7769284649a49d8e163da3f7c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 28 Jul 2025 20:45:42 +0900 Subject: [PATCH 0881/1002] =?UTF-8?q?fix=20:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EC=B8=A1=EC=97=90=EC=84=9C=20=EC=9D=B8=EC=A6=9D=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=BF=A0=ED=82=A4=20=EC=82=BD=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/JwtService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 8d19257a..9bfcd235 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -101,7 +101,7 @@ private void createBothToken(HttpServletResponse response) { // Authorization 헤더에 Access Token 추가 response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); - Cookie newAccessToken = new Cookie("accessToken", newToken.getRefreshToken()); + Cookie newAccessToken = new Cookie("accessToken", newToken.getAccessToken()); newAccessToken.setHttpOnly(true); newAccessToken.setSecure(true); newAccessToken.setPath("/"); From 5d96b2c9d78a8f380b26a53730b804957d0118e4 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 28 Jul 2025 21:35:13 +0900 Subject: [PATCH 0882/1002] =?UTF-8?q?fix=20:=20reissue=20=EC=8B=9C=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=EB=90=9C=20accessToken=EC=9D=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20refreshToken=EA=B3=BC=20username=20=EB=B9=84?= =?UTF-8?q?=EA=B5=90=ED=95=98=EC=97=AC=20=EC=9D=B8=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 2 +- .../common/security/jwt/JwtTokenProvider.java | 1 - .../common/security/service/JwtService.java | 24 +++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 7bff17a3..510fe140 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -92,7 +92,7 @@ public ResponseEntity logout(HttpServletResponse response) { @Operation(summary = "토큰 재발급") @PostMapping("/reissue") public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { - jwtService.reissueToken(request, response); + jwtService.checkRefreshTokenAndReissue(request, response); return ResponseEntity.ok().body(new SingleResponse<>(200, "토큰 재발급 성공", null)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index 8796a97e..784f1eb9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -98,7 +98,6 @@ public TokenDto createToken(Authentication authentication) { Long.parseLong(this.REFRESH_TOKEN_EXPIRATION) ); - return TokenDto.builder() .accessToken(accessToken) .refreshToken(refreshToken) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 9bfcd235..166ab527 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -7,6 +7,8 @@ import inu.codin.codin.common.security.jwt.JwtUtils; import inu.codin.codin.domain.user.security.CustomUserDetailsService; import inu.codin.codin.infra.redis.RedisStorageService; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -54,7 +56,7 @@ public void createToken(HttpServletResponse response) { * @param request * @param response */ - public void reissueToken(HttpServletRequest request, HttpServletResponse response) { + public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletResponse response) { String refreshToken = jwtUtils.getRefreshToken(request); if (refreshToken == null) { @@ -63,6 +65,8 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons } String username = jwtTokenProvider.getUsername(refreshToken); + + validateRefreshTokenWithAccessToken(request, username, refreshToken); UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 @@ -75,6 +79,22 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons reissueToken(refreshToken, response); } + //만료된 accessToken과 username을 비교 + private void validateRefreshTokenWithAccessToken(HttpServletRequest request, String username, String refreshToken) { + String accessToken = jwtUtils.getAccessToken(request); + String accessUsername; + try { + accessUsername = jwtTokenProvider.getUsername(accessToken); + } catch (ExpiredJwtException e) { + Claims expiredClaims = e.getClaims(); + accessUsername = expiredClaims.getSubject(); + } + if (!accessUsername.equals(username)) { + log.error("[reissueToken] Access Token의 username과 Refresh Token가 일치하지 않습니다. : access - {}, refresh - {}", accessToken, refreshToken); + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Access Token의 username과 Refresh Token가 일치하지 않습니다.."); + } + } + /** * Refresh Token을 이용하여 Access Token, Refresh Token 재발급 * @param refreshToken @@ -119,7 +139,7 @@ private void createBothToken(HttpServletResponse response) { newRefreshToken.setAttribute("SameSite", "None"); response.addCookie(newRefreshToken); - log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); + log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}, Refresh : {}",authentication.getName(), newToken.getAccessToken(), newToken.getRefreshToken()); } /** From 4644e03f4bd6236ee2de7b3dde0b7643b860876b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 28 Jul 2025 22:26:10 +0900 Subject: [PATCH 0883/1002] =?UTF-8?q?fix=20:=20access=EC=99=80=20refresh?= =?UTF-8?q?=EB=A5=BC=20type=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=9D=B8=EC=A6=9D=20&=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 8 ++- .../security/exception/SecurityErrorCode.java | 3 +- .../filter/JwtAuthenticationFilter.java | 32 +++-------- .../common/security/jwt/JwtTokenProvider.java | 17 +++++- .../common/security/service/JwtService.java | 53 +++++++++++++------ .../stomp/HttpHandShakeInterceptor.java | 2 +- 6 files changed, 64 insertions(+), 51 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index aa453e36..310477a3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -4,8 +4,7 @@ import inu.codin.codin.common.dto.PermitAllProperties; import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; -import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.common.security.service.JwtService; import inu.codin.codin.common.security.service.oauth2.AppleOAuth2UserService; import inu.codin.codin.common.security.service.oauth2.CustomOAuth2UserService; import inu.codin.codin.common.security.util.*; @@ -52,9 +51,8 @@ @RequiredArgsConstructor public class SecurityConfig { - private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; - private final JwtUtils jwtUtils; + private final JwtService jwtService; private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; private final CustomOAuth2UserService customOAuth2UserService; @@ -117,7 +115,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( - new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService, jwtUtils, permitAllProperties), + new JwtAuthenticationFilter(jwtService, permitAllProperties), UsernamePasswordAuthenticationFilter.class ) // 예외 처리 필터 추가 diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java b/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java index 019968f7..b6f5b5d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java @@ -13,7 +13,8 @@ public enum SecurityErrorCode { INVALID_SIGNATURE("SEC_004", "잘못된 토큰 서명입니다."), ACCESS_DENIED("SEC_005", "접근 권한이 없습니다."), ACCOUNT_LOCKED("SEC_006", "계정이 잠겼습니다. 관리자에게 문의하세요."), - INVALID_CREDENTIALS("SEC_007", "잘못된 인증 정보입니다."); + INVALID_CREDENTIALS("SEC_007", "잘못된 인증 정보입니다."), + INVALID_TYPE("SEC_008", "잘못된 토큰 타입입니다."); private final String errorCode; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 71ab23b4..e8087769 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -1,17 +1,13 @@ package inu.codin.codin.common.security.filter; import inu.codin.codin.common.dto.PermitAllProperties; -import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; -import inu.codin.codin.common.security.jwt.JwtUtils; +import inu.codin.codin.common.security.service.JwtService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; @@ -24,9 +20,7 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtTokenProvider jwtTokenProvider; - private final UserDetailsService userDetailsService; - private final JwtUtils jwtUtils; + private final JwtService jwtService; private final PermitAllProperties permitAllProperties; private final AntPathMatcher pathMatcher = new AntPathMatcher(); @@ -49,32 +43,18 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String token = null; if (Arrays.stream(SWAGGER_AUTH_PATHS).anyMatch(url -> pathMatcher.match(url, requestURI))) { - token = jwtUtils.getRefreshToken(request); + token = jwtService.getRefreshToken(request); } else { - token = jwtUtils.getAccessToken(request); + token = jwtService.getAccessToken(request); } // Access Token이 있는 경우 - if (token != null && jwtTokenProvider.validateToken(token)) { - setAuthentication(token); + if (token != null) { + jwtService.getUserDetailsAndSetAuthentication(token); } else { SecurityContextHolder.clearContext(); } filterChain.doFilter(request, response); } - - private void setAuthentication(String token) { - String username = jwtTokenProvider.getUsername(token); - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } - - } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index 784f1eb9..2cee57fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -71,6 +71,7 @@ public TokenDto createToken(Authentication authentication) { .setSubject(authentication.getName()) .claim("auth", authorities) .claim("userId", userId) + .claim("type", "access") .setIssuedAt(now) .setExpiration(accessTokenExpiration) .signWith(SECRET_KEY, SignatureAlgorithm.HS512) @@ -78,8 +79,7 @@ public TokenDto createToken(Authentication authentication) { String refreshToken = Jwts.builder() .setSubject(authentication.getName()) - .claim("auth", authorities) - .claim("userId", userId) + .claim("type", "refresh") .setIssuedAt(now) .setExpiration(refreshTokenExpiration) .signWith(SECRET_KEY, SignatureAlgorithm.HS512) @@ -133,6 +133,10 @@ public boolean validateToken(String token) { */ public boolean validateRefreshToken(String refreshToken) { try { + if (getType(refreshToken) == null || !getType(refreshToken).equals("refresh")) { + log.warn("[validateRefreshToken] Refresh Token이 아님"); + return false; + } // Redis에 저장된 Refresh Token과 비교 String storedRefreshToken = redisStorageService.getStoredRefreshToken(getClaims(refreshToken).getSubject()); if (storedRefreshToken == null) { @@ -158,6 +162,15 @@ public String getUsername(String token) { return getClaims(token).getSubject(); } + public String getType(String token) { + return getClaims(token).get("type", String.class); + } + + public boolean validType(String token, String type) { + String tokenType = getType(token); + return tokenType != null && tokenType.equals(type); + } + @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public static class TokenDto { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 166ab527..9298ff18 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -57,15 +57,13 @@ public void createToken(HttpServletResponse response) { * @param response */ public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletResponse response) { - String refreshToken = jwtUtils.getRefreshToken(request); - + String refreshToken = getRefreshToken(request); if (refreshToken == null) { log.error("[reissueToken] Refresh Token이 없습니다."); throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 없습니다."); } String username = jwtTokenProvider.getUsername(refreshToken); - validateRefreshTokenWithAccessToken(request, username, refreshToken); UserDetails userDetails = userDetailsService.loadUserByUsername(username); @@ -81,7 +79,7 @@ public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletR //만료된 accessToken과 username을 비교 private void validateRefreshTokenWithAccessToken(HttpServletRequest request, String username, String refreshToken) { - String accessToken = jwtUtils.getAccessToken(request); + String accessToken = getAccessToken(request); String accessUsername; try { accessUsername = jwtTokenProvider.getUsername(accessToken); @@ -165,23 +163,46 @@ private void deleteCookie(HttpServletResponse response) { response.addCookie(refreshCookie); } - public void setAuthentication(ServletServerHttpRequest serverHttpRequest){ - String accessToken = jwtUtils.getAccessToken(serverHttpRequest.getServletRequest()); + public void setAuthentication(HttpServletRequest request){ + String accessToken = getAccessToken(request); // Access Token이 있는 경우 - if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) { - String email = jwtTokenProvider.getUsername(accessToken); - UserDetails userDetails = userDetailsService.loadUserByUsername(email); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } + if (accessToken != null) { + getUserDetailsAndSetAuthentication(accessToken); } else { SecurityContextHolder.clearContext(); throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); } } + + public void getUserDetailsAndSetAuthentication(String token) { + jwtTokenProvider.validateToken(token); + String email = jwtTokenProvider.getUsername(token); + UserDetails userDetails = userDetailsService.loadUserByUsername(email); + + // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 + if (userDetails != null) { + // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) + JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + + public String getAccessToken(HttpServletRequest request) { + String accessToken = jwtUtils.getAccessToken(request); + if (!jwtTokenProvider.validType(accessToken, "access")) { + log.error("[getAccessToken] Access Token이 아닙니다. : {}", accessToken); + throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Access Token이 아닙니다."); + } + return accessToken; + } + + public String getRefreshToken(HttpServletRequest request) { + String refreshToken = jwtUtils.getRefreshToken(request); + if (!jwtTokenProvider.validType(refreshToken, "refresh")) { + log.error("[getRefreshToken] Refresh Token이 아닙니다. : {}", refreshToken); + throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Refresh Token이 아닙니다."); + } + return refreshToken; + } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java index 20397ac7..9c8e1c8a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java @@ -25,7 +25,7 @@ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse res ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; try { - jwtService.setAuthentication(serverHttpRequest); + jwtService.setAuthentication(serverHttpRequest.getServletRequest()); } catch (MessageDeliveryException e) { throw new MessageDeliveryException("[Chatting] Jwt로 인한 메세지 전송 오류입니다."); } catch (MalformedJwtException e) { From d943430d04217e62751e9314ee34d34dc2a5234c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Mon, 28 Jul 2025 22:43:53 +0900 Subject: [PATCH 0884/1002] =?UTF-8?q?docs=20:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/JwtService.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 9298ff18..c78b69ce 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -16,7 +16,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; @@ -64,7 +63,7 @@ public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletR } String username = jwtTokenProvider.getUsername(refreshToken); - validateRefreshTokenWithAccessToken(request, username, refreshToken); + validateRefreshTokenWithAccessToken(request, username); UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 @@ -78,7 +77,7 @@ public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletR } //만료된 accessToken과 username을 비교 - private void validateRefreshTokenWithAccessToken(HttpServletRequest request, String username, String refreshToken) { + private void validateRefreshTokenWithAccessToken(HttpServletRequest request, String username) { String accessToken = getAccessToken(request); String accessUsername; try { @@ -88,8 +87,8 @@ private void validateRefreshTokenWithAccessToken(HttpServletRequest request, Str accessUsername = expiredClaims.getSubject(); } if (!accessUsername.equals(username)) { - log.error("[reissueToken] Access Token의 username과 Refresh Token가 일치하지 않습니다. : access - {}, refresh - {}", accessToken, refreshToken); - throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Access Token의 username과 Refresh Token가 일치하지 않습니다.."); + log.error("[reissueToken] Access Token의 username과 Refresh Token가 일치하지 않습니다."); + throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Access Token의 username과 Refresh Token가 일치하지 않습니다."); } } @@ -100,7 +99,7 @@ private void validateRefreshTokenWithAccessToken(HttpServletRequest request, Str */ public void reissueToken(String refreshToken, HttpServletResponse response) { if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { - log.error("[reissueToken] Refresh Token이 유효하지 않습니다. : {}", refreshToken); + log.error("[reissueToken] Refresh Token이 유효하지 않습니다."); throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 유효하지 않습니다."); } @@ -137,7 +136,7 @@ private void createBothToken(HttpServletResponse response) { newRefreshToken.setAttribute("SameSite", "None"); response.addCookie(newRefreshToken); - log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}, Refresh : {}",authentication.getName(), newToken.getAccessToken(), newToken.getRefreshToken()); + log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); } /** @@ -191,7 +190,7 @@ public void getUserDetailsAndSetAuthentication(String token) { public String getAccessToken(HttpServletRequest request) { String accessToken = jwtUtils.getAccessToken(request); if (!jwtTokenProvider.validType(accessToken, "access")) { - log.error("[getAccessToken] Access Token이 아닙니다. : {}", accessToken); + log.error("[getAccessToken] Access Token이 아닙니다."); throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Access Token이 아닙니다."); } return accessToken; @@ -200,7 +199,7 @@ public String getAccessToken(HttpServletRequest request) { public String getRefreshToken(HttpServletRequest request) { String refreshToken = jwtUtils.getRefreshToken(request); if (!jwtTokenProvider.validType(refreshToken, "refresh")) { - log.error("[getRefreshToken] Refresh Token이 아닙니다. : {}", refreshToken); + log.error("[getRefreshToken] Refresh Token이 아닙니다."); throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Refresh Token이 아닙니다."); } return refreshToken; From 0d00b0b5d9ff2fe9ac8d0a2f824f8fc703c4995d Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 3 Aug 2025 22:49:40 +0900 Subject: [PATCH 0885/1002] =?UTF-8?q?refactor:=20access=20token=20cookie?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 6 +++--- .../codin/codin/common/security/jwt/JwtUtils.java | 15 +++++++++++++++ .../codin/common/security/service/JwtService.java | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 7bff17a3..2d1a1b62 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -30,8 +30,9 @@ public class AuthController { private final AuthSessionService authSessionService; @GetMapping("/google") - public ResponseEntity> googleLogin(HttpServletResponse response, - @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { + public ResponseEntity> googleLogin( + HttpServletResponse response, + @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { authSessionService.setSession(redirect_url); response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() @@ -76,7 +77,6 @@ public ResponseEntity> completeUserProfile(@RequestPart @Valid @PostMapping("/login") public ResponseEntity> portalSignUp(@RequestBody @Valid SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { authCommonService.login(signUpAndLoginRequestDto, response); - return ResponseEntity.ok() .body(new SingleResponse<>(200, "로그인 성공", "기존 유저 로그인 완료")); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index 8035cd95..ee5357cb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -14,6 +14,8 @@ public class JwtUtils { /** * 헤더에서 Access 토큰 추출 * HTTP Header : "Authorization" : "Bearer ..." + * + * + 쿠키에서 Authorization 쿠키에서 추출 * @return (null, 빈 문자열)의 경우 null 반환 */ public String getAccessToken(HttpServletRequest request) { @@ -21,6 +23,19 @@ public String getAccessToken(HttpServletRequest request) { if (StringUtils.hasText(bearerToken)) { return bearerToken.substring(7); } + + // todo: 쿠키 추출 방식 추후 제거 + if (request.getCookies() != null){ + for (Cookie cookie : request. getCookies()){ + if ("x-access-token".equals(cookie.getName())){ + bearerToken = cookie.getValue(); + break; + } + } + } + if (StringUtils.hasText(bearerToken)) { + return bearerToken; + } return null; } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 8d19257a..fa4581ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -101,11 +101,11 @@ private void createBothToken(HttpServletResponse response) { // Authorization 헤더에 Access Token 추가 response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); - Cookie newAccessToken = new Cookie("accessToken", newToken.getRefreshToken()); + Cookie newAccessToken = new Cookie("x-access-token", newToken.getAccessToken()); newAccessToken.setHttpOnly(true); newAccessToken.setSecure(true); newAccessToken.setPath("/"); - newAccessToken.setMaxAge(10 * 24 * 60 * 60); // 10일 + newAccessToken.setMaxAge(30 * 60); // 10분 newAccessToken.setDomain(BASERURL.split("//")[1]); newAccessToken.setAttribute("SameSite", "None"); response.addCookie(newAccessToken); From 9331ee91d19290f0c1ce308c5873254db7ee8e00 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 3 Aug 2025 22:55:21 +0900 Subject: [PATCH 0886/1002] =?UTF-8?q?comment:=20JWT=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/security/jwt/JwtUtils.java | 2 +- .../inu/codin/codin/common/security/service/JwtService.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java index ee5357cb..37358bbc 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java @@ -15,7 +15,7 @@ public class JwtUtils { * 헤더에서 Access 토큰 추출 * HTTP Header : "Authorization" : "Bearer ..." * - * + 쿠키에서 Authorization 쿠키에서 추출 + * + 쿠키에서 x-access-token 쿠키에서 추출 * @return (null, 빈 문자열)의 경우 null 반환 */ public String getAccessToken(HttpServletRequest request) { diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 264e5b85..635eb72c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -119,6 +119,7 @@ private void createBothToken(HttpServletResponse response) { // Authorization 헤더에 Access Token 추가 response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); + // todo: x-access-token 쿠키에 Access Token 추가 - 추후 제거 Cookie newAccessToken = new Cookie("x-access-token", newToken.getAccessToken()); newAccessToken.setHttpOnly(true); newAccessToken.setSecure(true); @@ -128,6 +129,7 @@ private void createBothToken(HttpServletResponse response) { newAccessToken.setAttribute("SameSite", "None"); response.addCookie(newAccessToken); + // x-rfresh-token 쿠키에 Refresh Token 추가 Cookie newRefreshToken = new Cookie(REFRESH_TOKEN, newToken.getRefreshToken()); newRefreshToken.setHttpOnly(true); newRefreshToken.setSecure(true); @@ -137,7 +139,7 @@ private void createBothToken(HttpServletResponse response) { newRefreshToken.setAttribute("SameSite", "None"); response.addCookie(newRefreshToken); - log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}",authentication.getName(), newToken.getAccessToken()); + log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}", authentication.getName(), newToken.getAccessToken()); } /** From 956208ad0a52e4b7e54f8ce5fe583a4a5e72ebb3 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 4 Aug 2025 13:29:30 +0900 Subject: [PATCH 0887/1002] =?UTF-8?q?fix:=20BCryptPasswordEncoder=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 Admin, Test 계정 로그인이 비밀번호를 통한 검증을 사용하지 않고 있었음. 해당 오류 수정 --- .../codin/common/config/SecurityConfig.java | 1 + .../security/controller/AuthController.java | 1 - .../security/service/AuthCommonService.java | 17 +++++++++++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 310477a3..5d348f7e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -168,6 +168,7 @@ public RoleHierarchy roleHierarchy() { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + @Bean public AuthorizationRequestRepository authorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index b75f9f12..3fc22dae 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -79,7 +79,6 @@ public ResponseEntity> portalSignUp(@RequestBody @Valid SignUp authCommonService.login(signUpAndLoginRequestDto, response); return ResponseEntity.ok() .body(new SingleResponse<>(200, "로그인 성공", "기존 유저 로그인 완료")); - } @Operation(summary = "로그아웃") diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 0fd30873..2bf3d76b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,16 +2,17 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.service.AbstractAuthService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; +import inu.codin.codin.common.security.service.AbstractAuthService; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -24,9 +25,11 @@ @Slf4j public class AuthCommonService extends AbstractAuthService { + private final PasswordEncoder passwordEncoder; - public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService) { + public AuthCommonService(UserRepository userRepository, S3Service s3Service, JwtService jwtService, UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { super(userRepository, s3Service, jwtService, userDetailsService); + this.passwordEncoder = passwordEncoder; } public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { @@ -71,8 +74,14 @@ public LocalDateTime getSuspensionEndDate(OAuth2User oAuth2User){ } public void login(SignUpAndLoginRequestDto signUpAndLoginRequestDto, HttpServletResponse response) { - Optional user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()); - if (user.isPresent()) { + UserEntity user = userRepository.findByEmail(signUpAndLoginRequestDto.getEmail()) + .orElseThrow(() -> new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다.")); + if (user.getPassword() == null) { + log.info("[login] 비밀번호가 존재하지 않는 유저 접근, email={}", user.getEmail()); + throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); + } + // 비밀번호 검증 + if (passwordEncoder.matches(signUpAndLoginRequestDto.getPassword(), user.getPassword())) { issueJwtToken(signUpAndLoginRequestDto.getEmail(), response); } else { throw new UserCreateFailException("아이디 혹은 비밀번호를 잘못 입력하였습니다."); From 5e1a8668059cb6de9032151dda68a637dc7c31c7 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Wed, 6 Aug 2025 22:15:35 +0900 Subject: [PATCH 0888/1002] =?UTF-8?q?refactor:=20IllegalArgumentException?= =?UTF-8?q?=20ExceptionHandling=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/exception/GlobalExceptionHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 0aea23b7..04bf9d48 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -103,4 +103,10 @@ protected ResponseEntity handleOAuth2AuthenticationException( .body(new ExceptionResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.value())); } + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value())); + } + } From 4c4e62a37355e556db6e0709a8aeaca32869bd25 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Thu, 7 Aug 2025 04:08:53 +0900 Subject: [PATCH 0889/1002] =?UTF-8?q?fix:=20=EC=84=9C=EB=B2=84=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20IllegalArgumentException=20=ED=95=84=ED=84=B0=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 에러 JWT String argument cannot be null or empty 가 지속적으로 스택 트레이스 형태로 로깅 --- .../codin/common/security/filter/ExceptionHandlerFilter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java index 64cf4ca7..1b5e5167 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java @@ -31,8 +31,10 @@ public class ExceptionHandlerFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { filterChain.doFilter(request, response); + } catch (IllegalArgumentException e) { + log.warn("[doFilterInternal] IllegalArgumentException msg: {}", e.getMessage()); } catch (Exception e) { - log.error("[doFilterInternal] Exception in ExceptionHandlerFilter: ", e); + log.warn("[doFilterInternal] Exception in ExceptionHandlerFilter: {}", e.getMessage()); sendErrorResponse(response, SecurityErrorCode.INVALID_TOKEN); } } From 3b0edfc7f492b85cbd5f2357bfaacba9a9112375 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 10 Aug 2025 00:15:00 +0900 Subject: [PATCH 0890/1002] =?UTF-8?q?fix:=20Access=20Cookie=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20240=20=EC=8B=9C=EA=B0=84=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 4851d942..0d766494 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 4851d942c5227e1566d3bbba678ec7345cfb9050 +Subproject commit 0d7664943fa2de27deb1a3c56eaf76d43097dfa1 From f7dfb621b28577b359a21cd3c8d5661ebd817c2e Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 10 Aug 2025 22:20:52 +0900 Subject: [PATCH 0891/1002] =?UTF-8?q?fix:=20Feign=20=EC=9A=94=EC=B2=AD=20R?= =?UTF-8?q?ateLimiting=20=EC=95=A0=EB=9F=AC=20=EB=B0=9C=EC=83=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/Bucket4jRateLimitApp.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java index 4fd385ef..19fd425d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java @@ -6,17 +6,18 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -@Configuration -@RequiredArgsConstructor -public class Bucket4jRateLimitApp implements WebMvcConfigurer { - - private final RateLimitInterceptor interceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry. - addInterceptor(interceptor). - addPathPatterns("/**") - .excludePathPatterns("/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "/webjars/**"); - } -} +// Lecture API - 좋아요 개수 Feign 요청으로 인한 RateLimiting 에러로 주석화 +//@Configuration +//@RequiredArgsConstructor +//public class Bucket4jRateLimitApp implements WebMvcConfigurer { +// +// private final RateLimitInterceptor interceptor; +// +// @Override +// public void addInterceptors(InterceptorRegistry registry) { +// registry. +// addInterceptor(interceptor). +// addPathPatterns("/**") +// .excludePathPatterns("/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "/webjars/**"); +// } +//} From 84bca3a9e427d41eea98aee2cc3fff31ddbbcc1a Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 10 Aug 2025 22:32:38 +0900 Subject: [PATCH 0892/1002] =?UTF-8?q?fix:=20=EC=BF=A0=ED=82=A4=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=EC=A6=9D=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20Access=20Token=20=EB=A7=8C=EB=A3=8C=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/JwtService.java | 2 +- codin-core/src/main/resources | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 635eb72c..05c440d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -124,7 +124,7 @@ private void createBothToken(HttpServletResponse response) { newAccessToken.setHttpOnly(true); newAccessToken.setSecure(true); newAccessToken.setPath("/"); - newAccessToken.setMaxAge(30 * 60); // 10분 + newAccessToken.setMaxAge(10 * 24 * 60 * 60); // 10일 newAccessToken.setDomain(BASERURL.split("//")[1]); newAccessToken.setAttribute("SameSite", "None"); response.addCookie(newAccessToken); diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 0d766494..6664d5e9 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 0d7664943fa2de27deb1a3c56eaf76d43097dfa1 +Subproject commit 6664d5e906b797da6664a866bf89a9622afa1f11 From 9042d3a7d1557901da167ca7e10fa6ca01c11f23 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 18 Aug 2025 16:49:24 +0900 Subject: [PATCH 0893/1002] =?UTF-8?q?feat:=20=EB=A6=AC=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=9D=B5=EB=AA=85=EC=9D=98=20=EC=86=8C=EB=A6=AC?= =?UTF-8?q?=ED=95=A8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voice/controller/VoiceController.java | 94 +++++++++++++++ .../voice/dto/VoiceBoxAnswerRequest.java | 13 ++ .../voice/dto/VoiceBoxCreateRequest.java | 20 ++++ .../voice/dto/VoiceBoxDetailResponse.java | 57 +++++++++ .../voice/dto/VoiceBoxPageResponse.java | 32 +++++ .../post/domain/voice/entity/VoiceEntity.java | 74 ++++++++++++ .../voice/repository/VoiceRepository.java | 30 +++++ .../domain/voice/service/VoiceService.java | 111 ++++++++++++++++++ 8 files changed, 431 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java new file mode 100644 index 00000000..53a63c79 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java @@ -0,0 +1,94 @@ +package inu.codin.codin.domain.post.domain.voice.controller; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxAnswerRequest; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxCreateRequest; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxDetailResponse; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxPageResponse; +import inu.codin.codin.domain.post.domain.voice.service.VoiceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/voice-box") +@Tag(name = "Voice Box API", description = "[리다지인] 익명의 소리함 API") +@RequiredArgsConstructor +public class VoiceController { + + private final VoiceService voiceService; + + // 1. 소리함 생성 메서드 + @Operation(summary = "익명 질문 생성") + @PostMapping + public ResponseEntity> createVoiceBox( + @RequestBody VoiceBoxCreateRequest request + ) { + return ResponseEntity.status(HttpStatus.CREATED).body(new SingleResponse<>(201, "익명 목소리 생성 완료", + voiceService.createVoiceBox(request))); + } + + // 2. 익명 소리함 반환 리스트 + @Operation(summary = "답변된 소리함 리스트 반환") + @GetMapping + public ResponseEntity> getVoiceBoxListByDepartment( + @RequestParam Department department, + @RequestParam("page") @NotNull int pageNumber + ) { + return ResponseEntity.ok().body(new SingleResponse<>(200, "답변된 소리함 리스트 반환 성공", + voiceService.getVoiceBoxListByDepartment(department, pageNumber))); + } + + // 3. 소리함 내용 공감 메서드 + @Operation(summary = "소리함 내용 공감 토글(On/Off)") + @PostMapping("/vote/{boxId}") + public ResponseEntity> toggleVoiceBox( + @PathVariable("boxId") String boxId, + @RequestParam @NotNull Boolean positive + ) { + voiceService.toggleVoiceBox(boxId, positive); + return ResponseEntity.ok().body(new SingleResponse<>(200, "소리함 내용 공감 토글(On/Off) 성공", null)); + } + + // 4. 답변되지 않은 소리함 내용 반환 + @Operation(summary = "[학생회] 답변되지 않은 소리함 리스트 반환") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") + @GetMapping("/not-answered") + public ResponseEntity> getAllNotAnsweredList( + @RequestParam Department department, + @RequestParam("page") @NotNull int pageNumber + ) { + return ResponseEntity.ok().body(new SingleResponse<>(200, "[학생회] 답변되지 않은 소리함 리스트 반환 성공", + voiceService.getAllNotAnsweredList(department, pageNumber))); + } + + // 5. 소리함 내용 답변 추가 + @Operation(summary = "[학생회] 답변되지 않은 소리함 내용에 답변 추가") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") + @PostMapping("/not-answered/{boxId}") + public ResponseEntity> addAnswer( + @PathVariable("boxId") String boxId, + @RequestBody VoiceBoxAnswerRequest request + ) { + return ResponseEntity.ok().body(new SingleResponse<>(200, "[학생회] 답변되지 않은 소리함 내용에 답변 추가", + voiceService.addAnswer(boxId, request.getAnswer()))); + } + + // 6. 질문 삭제 + @Operation(summary = "[관리자] 소리함 질문 삭제") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") + @DeleteMapping("/{boxId}") + public ResponseEntity> deleteVoiceBox( + @PathVariable String boxId + ) { + voiceService.deleteVoiceBox(boxId); + return ResponseEntity.ok().body(new SingleResponse<>(200, "[관리자] 소리함 질문 삭제", null)); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java new file mode 100644 index 00000000..c2f70c52 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.post.domain.voice.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class VoiceBoxAnswerRequest { + + @Schema(description = "답변 내용", example = "학회비를 납부하면 다양한 혜택을 받을 수 있습니다.") + @NotBlank(message = "답변 내용은 필수입니다.") + private String answer; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java new file mode 100644 index 00000000..08d26492 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.post.domain.voice.dto; + +import inu.codin.codin.common.dto.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class VoiceBoxCreateRequest { + + @Schema(description = "질문 학과", example = "COMPUTER_SCI") + @NotNull(message = "학과 정보는 필수입니다.") + private Department department; + + @Schema(description = "질문", example = "학회비 낸 사람은 얼마나 이득인가요?") + @NotBlank(message = "질문 내용은 필수입니다.") + private String question; + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java new file mode 100644 index 00000000..7eede589 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java @@ -0,0 +1,57 @@ +package inu.codin.codin.domain.post.domain.voice.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; +import lombok.Builder; +import lombok.Getter; +import org.bson.types.ObjectId; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@Builder +public class VoiceBoxDetailResponse { + + private String boxId; + + private Department department; + + private List positiveVoteIds = new ArrayList<>(); + + private List oppositeVoteIds = new ArrayList<>(); + + private String question; + + private String answer; + + private Boolean isUserInPositive; + private Boolean isUserInOpposite; + + private Integer userCountPositive; + private Integer userCountOpposite; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + private LocalDateTime createdAt; + + public static VoiceBoxDetailResponse of(VoiceEntity voiceEntity) { + return VoiceBoxDetailResponse.builder() + .boxId(voiceEntity.getId().toHexString()) + .department(voiceEntity.getDepartment()) + .positiveVoteIds(voiceEntity.getPositiveVoteIds().stream().map(ObjectId::toString).collect(Collectors.toList())) + .oppositeVoteIds(voiceEntity.getOppositeVoteIds().stream().map(ObjectId::toString).collect(Collectors.toList())) + .question(voiceEntity.getQuestion()) + .answer(voiceEntity.getAnswer()) + .isUserInPositive(voiceEntity.getPositiveVoteIds() == null ? null : voiceEntity.getPositiveVoteIds().contains(SecurityUtils.getCurrentUserId())) + .isUserInOpposite(voiceEntity.getOppositeVoteIds() == null ? null : voiceEntity.getOppositeVoteIds().contains(SecurityUtils.getCurrentUserId())) + .userCountPositive(voiceEntity.getPositiveVoteIds() == null ? null : voiceEntity.getPositiveVoteIds().size()) + .userCountOpposite(voiceEntity.getOppositeVoteIds() == null ? null : voiceEntity.getOppositeVoteIds().size()) + .createdAt(voiceEntity.getCreatedAt()) + .build(); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java new file mode 100644 index 00000000..6ccfb1b7 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.post.domain.voice.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class VoiceBoxPageResponse { + + private List contents = new ArrayList<>(); + private long lastPage; + private long nextPage; + + private VoiceBoxPageResponse(List contents, long lastPage, long nextPage) { + this.contents = contents; + this.lastPage = lastPage; + this.nextPage = nextPage; + } + + public static VoiceBoxPageResponse of(List postPaging, long totalElements, long nextPage) { + return VoiceBoxPageResponse.newPagingHasNext(postPaging, totalElements, nextPage); + } + + private static VoiceBoxPageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { + return new VoiceBoxPageResponse(posts, totalElements, nextPage); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java new file mode 100644 index 00000000..6a99e721 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java @@ -0,0 +1,74 @@ +package inu.codin.codin.domain.post.domain.voice.entity; + +import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.dto.Department; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.ArrayList; +import java.util.List; + +@Document(collection = "voice_boxes") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class VoiceEntity extends BaseTimeEntity { + + @Id + private ObjectId id; + + private Department department; + + private List positiveVoteIds = new ArrayList<>(); + + private List oppositeVoteIds = new ArrayList<>(); + + private String question; + + private String answer; + + @Builder + public VoiceEntity(Department department, List positiveVoteIds, List oppositeVoteIds, String question, String answer) { + this.department = department; + this.positiveVoteIds = positiveVoteIds; + this.oppositeVoteIds = oppositeVoteIds; + this.question = question; + this.answer = answer; + } + + /** + * 긍정에 투표 토글 메서드 (중복 X) + * @param userId 투표 유저 ObjectId + */ + public void votePositiveToggle(ObjectId userId) { + if (positiveVoteIds.contains(userId)) { + positiveVoteIds.remove(userId); + } else if (oppositeVoteIds.contains(userId)) { + oppositeVoteIds.remove(userId); + } else { + positiveVoteIds.add(userId); + } + } + + /** + * 부정에 투표 토글 메서드 (중복 X) + * @param userId 투표 유저 ObjectId + */ + public void voteOppositeToggle(ObjectId userId) { + if (positiveVoteIds.contains(userId)) { + positiveVoteIds.remove(userId); + } else if (oppositeVoteIds.contains(userId)) { + oppositeVoteIds.remove(userId); + } else { + oppositeVoteIds.add(userId); + } + } + + public void updateAnswer(String answer) { + this.answer = answer; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java new file mode 100644 index 00000000..df60a673 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java @@ -0,0 +1,30 @@ +package inu.codin.codin.domain.post.domain.voice.repository; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface VoiceRepository extends MongoRepository { + + // 특정 학과의 보이스박스 페이징 조회 (삭제되지 않은 것만) + @Query("{'department': ?0, 'answer': {$ne: null}, 'deletedAt': null}") + Page findAnsweredByDepartmentAndNotDeleted(Department department, Pageable pageable); + + // 답변이 없는 보이스박스 페이징 조회 (삭제되지 않은 것만) + @Query("{'answer': null, 'deletedAt': null}") + Page findByDepartmentAndAnswerIsNullAndNotDeleted(Department department, Pageable pageable); + + // ID로 삭제되지 않은 보이스박스 조회 + @Query("{'_id': ?0, 'deletedAt': null}") + Optional findByIdAndNotDeleted(ObjectId id); + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java new file mode 100644 index 00000000..9a03eb93 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java @@ -0,0 +1,111 @@ +package inu.codin.codin.domain.post.domain.voice.service; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxCreateRequest; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxDetailResponse; +import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxPageResponse; +import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; +import inu.codin.codin.domain.post.domain.voice.repository.VoiceRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class VoiceService { + + private final VoiceRepository voiceRepository; + private static final int PAGE_SIZE = 20; + + public VoiceBoxDetailResponse createVoiceBox(VoiceBoxCreateRequest voiceBoxCreateRequest) { + VoiceEntity voiceEntity = VoiceEntity.builder() + .question(voiceBoxCreateRequest.getQuestion()) + .department(voiceBoxCreateRequest.getDepartment()) + .build(); + + return VoiceBoxDetailResponse.of(voiceRepository.save(voiceEntity)); + } + + public VoiceBoxPageResponse getVoiceBoxListByDepartment(Department department, int pageNumber) { + Pageable pageable = PageRequest.of(pageNumber, PAGE_SIZE, Sort.by("createdAt").descending()); + Page voiceEntities = voiceRepository.findAnsweredByDepartmentAndNotDeleted(department, pageable); + + return getVoiceBoxPageResponse(pageNumber, voiceEntities); + } + + public void toggleVoiceBox(String boxId, Boolean positive) { + if (!ObjectIdUtil.isValid(boxId)) { + throw new IllegalArgumentException("유효하지 않은 ID 형식입니다."); + } + + ObjectId objectId = ObjectIdUtil.toObjectId(boxId); + VoiceEntity voiceEntity = voiceRepository.findByIdAndNotDeleted(objectId) + .orElseThrow(() -> new IllegalArgumentException("익명의 소리함 질문을 찾을 수 없습니다.")); + + ObjectId currentUserId = SecurityUtils.getCurrentUserId(); + + if (positive) { + voiceEntity.votePositiveToggle(currentUserId); + } else { + voiceEntity.voteOppositeToggle(currentUserId); + } + + voiceRepository.save(voiceEntity); + } + + public VoiceBoxPageResponse getAllNotAnsweredList(Department department, int pageNumber) { + Pageable pageable = PageRequest.of(pageNumber, PAGE_SIZE, Sort.by("createdAt").descending()); + Page notAnsweredVoices = voiceRepository.findByDepartmentAndAnswerIsNullAndNotDeleted(department, pageable); + + return getVoiceBoxPageResponse(pageNumber, notAnsweredVoices); + } + + public VoiceBoxDetailResponse addAnswer(String boxId, String answer) { + if (!ObjectIdUtil.isValid(boxId)) { + throw new IllegalArgumentException("유효하지 않은 ID 형식입니다."); + } + + ObjectId objectId = ObjectIdUtil.toObjectId(boxId); + VoiceEntity voiceEntity = voiceRepository.findByIdAndNotDeleted(objectId) + .orElseThrow(() -> new IllegalArgumentException("익명의 소리함 질문을 찾을 수 없습니다.")); + + voiceEntity.updateAnswer(answer); + + VoiceEntity savedEntity = voiceRepository.save(voiceEntity); + return VoiceBoxDetailResponse.of(savedEntity); + } + + public void deleteVoiceBox(String boxId) { + if (!ObjectIdUtil.isValid(boxId)) { + throw new IllegalArgumentException("유효하지 않은 ID 형식입니다."); + } + + ObjectId objectId = ObjectIdUtil.toObjectId(boxId); + VoiceEntity voiceEntity = voiceRepository.findByIdAndNotDeleted(objectId) + .orElseThrow(() -> new IllegalArgumentException("익명의 소리함 질문을 찾을 수 없습니다.")); + + voiceEntity.delete(); + voiceRepository.save(voiceEntity); + } + + private static VoiceBoxPageResponse getVoiceBoxPageResponse(int pageNumber, Page voiceEntityPage) { + List responses = voiceEntityPage.stream() + .map(VoiceBoxDetailResponse::of) + .collect(Collectors.toList()); + + long totalElements = voiceEntityPage.getTotalElements(); + long nextPage = voiceEntityPage.hasNext() ? pageNumber + 1 : -1; + + return VoiceBoxPageResponse.of(responses, totalElements, nextPage); + } +} + From 8c20e274abf7d06d6e32c49b6d8b4ec18b07a18c Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 18 Aug 2025 19:26:11 +0900 Subject: [PATCH 0894/1002] =?UTF-8?q?fix:=20PR=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=20#238?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voice/controller/VoiceController.java | 5 ++-- .../voice/dto/VoiceBoxDetailResponse.java | 10 -------- .../voice/dto/VoiceBoxPageResponse.java | 8 +++---- .../post/domain/voice/entity/VoiceEntity.java | 24 ++++++++----------- .../voice/repository/VoiceRepository.java | 2 +- .../domain/voice/service/VoiceService.java | 4 ++-- 6 files changed, 20 insertions(+), 33 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java index 53a63c79..9c367763 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.post.domain.voice.service.VoiceService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -28,7 +29,7 @@ public class VoiceController { @Operation(summary = "익명 질문 생성") @PostMapping public ResponseEntity> createVoiceBox( - @RequestBody VoiceBoxCreateRequest request + @RequestBody @Valid VoiceBoxCreateRequest request ) { return ResponseEntity.status(HttpStatus.CREATED).body(new SingleResponse<>(201, "익명 목소리 생성 완료", voiceService.createVoiceBox(request))); @@ -74,7 +75,7 @@ public ResponseEntity> getAllNotAnsweredLis @PostMapping("/not-answered/{boxId}") public ResponseEntity> addAnswer( @PathVariable("boxId") String boxId, - @RequestBody VoiceBoxAnswerRequest request + @RequestBody @Valid VoiceBoxAnswerRequest request ) { return ResponseEntity.ok().body(new SingleResponse<>(200, "[학생회] 답변되지 않은 소리함 내용에 답변 추가", voiceService.addAnswer(boxId, request.getAnswer()))); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java index 7eede589..7d79b9f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java @@ -6,12 +6,8 @@ import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; import lombok.Builder; import lombok.Getter; -import org.bson.types.ObjectId; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; @Getter @Builder @@ -21,10 +17,6 @@ public class VoiceBoxDetailResponse { private Department department; - private List positiveVoteIds = new ArrayList<>(); - - private List oppositeVoteIds = new ArrayList<>(); - private String question; private String answer; @@ -42,8 +34,6 @@ public static VoiceBoxDetailResponse of(VoiceEntity voiceEntity) { return VoiceBoxDetailResponse.builder() .boxId(voiceEntity.getId().toHexString()) .department(voiceEntity.getDepartment()) - .positiveVoteIds(voiceEntity.getPositiveVoteIds().stream().map(ObjectId::toString).collect(Collectors.toList())) - .oppositeVoteIds(voiceEntity.getOppositeVoteIds().stream().map(ObjectId::toString).collect(Collectors.toList())) .question(voiceEntity.getQuestion()) .answer(voiceEntity.getAnswer()) .isUserInPositive(voiceEntity.getPositiveVoteIds() == null ? null : voiceEntity.getPositiveVoteIds().contains(SecurityUtils.getCurrentUserId())) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java index 6ccfb1b7..2cbb2d49 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java @@ -21,12 +21,12 @@ private VoiceBoxPageResponse(List contents, long lastPag this.nextPage = nextPage; } - public static VoiceBoxPageResponse of(List postPaging, long totalElements, long nextPage) { - return VoiceBoxPageResponse.newPagingHasNext(postPaging, totalElements, nextPage); + public static VoiceBoxPageResponse of(List postPaging, long lastPage, long nextPage) { + return VoiceBoxPageResponse.newPagingHasNext(postPaging, lastPage, nextPage); } - private static VoiceBoxPageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { - return new VoiceBoxPageResponse(posts, totalElements, nextPage); + private static VoiceBoxPageResponse newPagingHasNext(List posts, long lastPage, long nextPage) { + return new VoiceBoxPageResponse(posts, lastPage, nextPage); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java index 6a99e721..579f94d2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java @@ -34,8 +34,8 @@ public class VoiceEntity extends BaseTimeEntity { @Builder public VoiceEntity(Department department, List positiveVoteIds, List oppositeVoteIds, String question, String answer) { this.department = department; - this.positiveVoteIds = positiveVoteIds; - this.oppositeVoteIds = oppositeVoteIds; + this.positiveVoteIds = positiveVoteIds != null ? positiveVoteIds : new ArrayList<>();; + this.oppositeVoteIds = oppositeVoteIds != null ? oppositeVoteIds : new ArrayList<>();; this.question = question; this.answer = answer; } @@ -45,13 +45,11 @@ public VoiceEntity(Department department, List positiveVoteIds, List Page findAnsweredByDepartmentAndNotDeleted(Department department, Pageable pageable); // 답변이 없는 보이스박스 페이징 조회 (삭제되지 않은 것만) - @Query("{'answer': null, 'deletedAt': null}") + @Query("{'department': ?0, 'answer': null, 'deletedAt': null}") Page findByDepartmentAndAnswerIsNullAndNotDeleted(Department department, Pageable pageable); // ID로 삭제되지 않은 보이스박스 조회 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java index 9a03eb93..dead4914 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java @@ -102,10 +102,10 @@ private static VoiceBoxPageResponse getVoiceBoxPageResponse(int pageNumber, Page .map(VoiceBoxDetailResponse::of) .collect(Collectors.toList()); - long totalElements = voiceEntityPage.getTotalElements(); + long lastPage = voiceEntityPage.getTotalPages(); long nextPage = voiceEntityPage.hasNext() ? pageNumber + 1 : -1; - return VoiceBoxPageResponse.of(responses, totalElements, nextPage); + return VoiceBoxPageResponse.of(responses, lastPage, nextPage); } } From 97ebbaa74c55e6740a2837b5e76292349f96166c Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 15:46:56 +0900 Subject: [PATCH 0895/1002] =?UTF-8?q?refactor=20:=20voice=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=9D=84=20post=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voice/controller/VoiceController.java | 12 ++++++------ .../domain => }/voice/dto/VoiceBoxAnswerRequest.java | 2 +- .../domain => }/voice/dto/VoiceBoxCreateRequest.java | 2 +- .../voice/dto/VoiceBoxDetailResponse.java | 4 ++-- .../domain => }/voice/dto/VoiceBoxPageResponse.java | 2 +- .../{post/domain => }/voice/entity/VoiceEntity.java | 2 +- .../voice/repository/VoiceRepository.java | 5 ++--- .../domain => }/voice/service/VoiceService.java | 12 ++++++------ 8 files changed, 20 insertions(+), 21 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/controller/VoiceController.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/dto/VoiceBoxAnswerRequest.java (87%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/dto/VoiceBoxCreateRequest.java (91%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/dto/VoiceBoxDetailResponse.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/dto/VoiceBoxPageResponse.java (94%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/entity/VoiceEntity.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/repository/VoiceRepository.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/{post/domain => }/voice/service/VoiceService.java (91%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/controller/VoiceController.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/controller/VoiceController.java index 9c367763..2e820096 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/controller/VoiceController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/controller/VoiceController.java @@ -1,12 +1,12 @@ -package inu.codin.codin.domain.post.domain.voice.controller; +package inu.codin.codin.domain.voice.controller; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxAnswerRequest; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxCreateRequest; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxDetailResponse; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxPageResponse; -import inu.codin.codin.domain.post.domain.voice.service.VoiceService; +import inu.codin.codin.domain.voice.dto.VoiceBoxAnswerRequest; +import inu.codin.codin.domain.voice.dto.VoiceBoxCreateRequest; +import inu.codin.codin.domain.voice.dto.VoiceBoxDetailResponse; +import inu.codin.codin.domain.voice.dto.VoiceBoxPageResponse; +import inu.codin.codin.domain.voice.service.VoiceService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxAnswerRequest.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxAnswerRequest.java index c2f70c52..654171b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxAnswerRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxAnswerRequest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.voice.dto; +package inu.codin.codin.domain.voice.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxCreateRequest.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxCreateRequest.java index 08d26492..c7828345 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxCreateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxCreateRequest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.voice.dto; +package inu.codin.codin.domain.voice.dto; import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxDetailResponse.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxDetailResponse.java index 7d79b9f3..3ca9030f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxDetailResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxDetailResponse.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.post.domain.voice.dto; +package inu.codin.codin.domain.voice.dto; import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; +import inu.codin.codin.domain.voice.entity.VoiceEntity; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxPageResponse.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxPageResponse.java index 2cbb2d49..fc2a7004 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/dto/VoiceBoxPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxPageResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.voice.dto; +package inu.codin.codin.domain.voice.dto; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/entity/VoiceEntity.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/entity/VoiceEntity.java index 579f94d2..2b8d54b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/entity/VoiceEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/entity/VoiceEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.voice.entity; +package inu.codin.codin.domain.voice.entity; import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.common.dto.Department; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/repository/VoiceRepository.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/repository/VoiceRepository.java index 0d24578f..b01c93de 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/repository/VoiceRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/repository/VoiceRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.domain.voice.repository; +package inu.codin.codin.domain.voice.repository; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; +import inu.codin.codin.domain.voice.entity.VoiceEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -9,7 +9,6 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/voice/service/VoiceService.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java rename to codin-core/src/main/java/inu/codin/codin/domain/voice/service/VoiceService.java index dead4914..d8e39f99 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/voice/service/VoiceService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/voice/service/VoiceService.java @@ -1,13 +1,13 @@ -package inu.codin.codin.domain.post.domain.voice.service; +package inu.codin.codin.domain.voice.service; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxCreateRequest; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxDetailResponse; -import inu.codin.codin.domain.post.domain.voice.dto.VoiceBoxPageResponse; -import inu.codin.codin.domain.post.domain.voice.entity.VoiceEntity; -import inu.codin.codin.domain.post.domain.voice.repository.VoiceRepository; +import inu.codin.codin.domain.voice.dto.VoiceBoxCreateRequest; +import inu.codin.codin.domain.voice.dto.VoiceBoxDetailResponse; +import inu.codin.codin.domain.voice.dto.VoiceBoxPageResponse; +import inu.codin.codin.domain.voice.entity.VoiceEntity; +import inu.codin.codin.domain.voice.repository.VoiceRepository; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; From 50e6710e17d9f730ca25a6fa9b5f1ac2ea49bd50 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 18:08:03 +0900 Subject: [PATCH 0896/1002] =?UTF-8?q?refactor=20:=20voice=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voice/controller/VoiceController.java | 12 ++++++------ .../{ => board}/voice/dto/VoiceBoxAnswerRequest.java | 2 +- .../{ => board}/voice/dto/VoiceBoxCreateRequest.java | 2 +- .../voice/dto/VoiceBoxDetailResponse.java | 4 ++-- .../{ => board}/voice/dto/VoiceBoxPageResponse.java | 2 +- .../domain/{ => board}/voice/entity/VoiceEntity.java | 2 +- .../voice/repository/VoiceRepository.java | 4 ++-- .../{ => board}/voice/service/VoiceService.java | 12 ++++++------ 8 files changed, 20 insertions(+), 20 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/controller/VoiceController.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/dto/VoiceBoxAnswerRequest.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/dto/VoiceBoxCreateRequest.java (92%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/dto/VoiceBoxDetailResponse.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/dto/VoiceBoxPageResponse.java (95%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/entity/VoiceEntity.java (97%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/repository/VoiceRepository.java (90%) rename codin-core/src/main/java/inu/codin/codin/domain/{ => board}/voice/service/VoiceService.java (91%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/controller/VoiceController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/controller/VoiceController.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java index 2e820096..50de9eec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/controller/VoiceController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java @@ -1,12 +1,12 @@ -package inu.codin.codin.domain.voice.controller; +package inu.codin.codin.domain.board.voice.controller; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.voice.dto.VoiceBoxAnswerRequest; -import inu.codin.codin.domain.voice.dto.VoiceBoxCreateRequest; -import inu.codin.codin.domain.voice.dto.VoiceBoxDetailResponse; -import inu.codin.codin.domain.voice.dto.VoiceBoxPageResponse; -import inu.codin.codin.domain.voice.service.VoiceService; +import inu.codin.codin.domain.board.voice.service.VoiceService; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxAnswerRequest; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxCreateRequest; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxDetailResponse; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxPageResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxAnswerRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxAnswerRequest.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxAnswerRequest.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxAnswerRequest.java index 654171b6..712f9441 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxAnswerRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxAnswerRequest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.voice.dto; +package inu.codin.codin.domain.board.voice.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxCreateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxCreateRequest.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java index c7828345..de0e1e04 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxCreateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.voice.dto; +package inu.codin.codin.domain.board.voice.dto; import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxDetailResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxDetailResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java index 3ca9030f..9876527b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxDetailResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.voice.dto; +package inu.codin.codin.domain.board.voice.dto; import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.voice.entity.VoiceEntity; +import inu.codin.codin.domain.board.voice.entity.VoiceEntity; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxPageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxPageResponse.java similarity index 95% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxPageResponse.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxPageResponse.java index fc2a7004..3a088640 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/dto/VoiceBoxPageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxPageResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.voice.dto; +package inu.codin.codin.domain.board.voice.dto; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/entity/VoiceEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/entity/VoiceEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java index 2b8d54b2..387bae28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/entity/VoiceEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.voice.entity; +package inu.codin.codin.domain.board.voice.entity; import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.common.dto.Department; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/repository/VoiceRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/repository/VoiceRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java index b01c93de..2ce6feaf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/repository/VoiceRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.voice.repository; +package inu.codin.codin.domain.board.voice.repository; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.domain.voice.entity.VoiceEntity; +import inu.codin.codin.domain.board.voice.entity.VoiceEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java similarity index 91% rename from codin-core/src/main/java/inu/codin/codin/domain/voice/service/VoiceService.java rename to codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java index d8e39f99..973781ba 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/voice/service/VoiceService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java @@ -1,13 +1,13 @@ -package inu.codin.codin.domain.voice.service; +package inu.codin.codin.domain.board.voice.service; import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; -import inu.codin.codin.domain.voice.dto.VoiceBoxCreateRequest; -import inu.codin.codin.domain.voice.dto.VoiceBoxDetailResponse; -import inu.codin.codin.domain.voice.dto.VoiceBoxPageResponse; -import inu.codin.codin.domain.voice.entity.VoiceEntity; -import inu.codin.codin.domain.voice.repository.VoiceRepository; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxCreateRequest; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxDetailResponse; +import inu.codin.codin.domain.board.voice.dto.VoiceBoxPageResponse; +import inu.codin.codin.domain.board.voice.entity.VoiceEntity; +import inu.codin.codin.domain.board.voice.repository.VoiceRepository; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; From 38dd1f7a1fa1db1f26c1ee62589c7287148a562e Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 18:09:05 +0900 Subject: [PATCH 0897/1002] =?UTF-8?q?feat=20:=20=ED=95=99=EA=B3=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=ED=8C=90=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20CRUD=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=EC=9D=98=20PostEntity=EC=97=90=20=EC=97=B0?= =?UTF-8?q?=EA=B3=84=ED=95=98=EC=97=AC=20=EC=82=AC=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notice/controller/NoticeController.java | 137 ++++++++++++++++ .../request/NoticeCreateUpdateRequestDTO.java | 17 ++ .../dto/response/NoticeDetailResponseDto.java | 112 +++++++++++++ .../dto/response/NoticeListResponseDto.java | 4 + .../dto/response/NoticePageResponse.java | 32 ++++ .../notice/exception/NoticeErrorCode.java | 26 +++ .../notice/exception/NoticeException.java | 10 ++ .../notice/repository/NoticeRepository.java | 24 +++ .../board/notice/service/NoticeService.java | 152 ++++++++++++++++++ 9 files changed, 514 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/request/NoticeCreateUpdateRequestDTO.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java new file mode 100644 index 00000000..62a00413 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java @@ -0,0 +1,137 @@ +package inu.codin.codin.domain.board.notice.controller; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; +import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; +import inu.codin.codin.domain.board.notice.dto.response.NoticePageResponse; +import inu.codin.codin.domain.board.notice.service.NoticeService; +import inu.codin.codin.domain.post.service.PostService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/notice") +@Tag(name = "Notice API", description = "[리다지인] 게시판 공지사항 API") +@RequiredArgsConstructor +public class NoticeController { + + private final NoticeService noticeService; + private final PostService postService; + + /* + =================== + ROLE_USER 사용한 API (조회 기능) + =================== + */ + + /* + * 학과별 공지사항 조회, + * Post 기능과 동일하지만 EXTRACURRICULAR_INNER, DEPARTMENT_NOTICE 카테고리만 조회 + */ + @Operation( + summary = "학과별 공지사항 조회", + description = "공지사항의 postCategory 중 EXTRACURRICULAR_INNER는 크롤링된 학과 홈페이지 공지사항
" + + "DEPARTMENT_NOTICE는 직접 수기로 작성한 공지사항
" + ) + @GetMapping("/category") + public ResponseEntity> getAllNotices( + @RequestParam("department") @NotNull Department department, + @RequestParam("page") @NotNull int pageNumber) { + NoticePageResponse noticepages= noticeService.getAllNotices(department, pageNumber); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "학과별 공지사항 조회 성공", noticepages)); + } + + + @Operation( + summary = "해당 공지사항 상세 조회" + ) + @GetMapping("/{postId}") + public ResponseEntity> getNoticesWithDetail(@PathVariable String postId) { + NoticeDetailResponseDto notice = noticeService.getNoticesWithDetail(postId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "공지사항 상세 조회 성공", notice)); + } + + + /* + =================== + ROLE_MANAGER, ROLE_ADMIN 사용한 API (작성, 수정, 삭제 기능) + =================== + */ + + @Operation( + summary = "공지사항 작성", + description = "작성하는 글쓴이의 Department에 맞춰 title에 prefix가 자동으로 삽입
" + + "COMPUTER_SCI -> [컴공]
" + + "COMPUTER_SCI_NIGHT -> [컴공]
" + + "IT_COLLEGE -> [정보대]
" + + "INFO_COMM -> [정통]
" + + "EMBEDDED -> [임베]
" + + "공지사항 작성 시 이미지 첨부 가능, 이미지가 없으면 빈 리스트로 처리" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> createNotice( + @RequestPart("noticeContent") @Valid NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, + @RequestPart(value = "noticeImages", required = false) List noticeImages) { + + // postImages가 null이면 빈 리스트로 처리 + if (noticeImages == null) noticeImages = List.of(); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new SingleResponse<>(201, "공지사항이 작성되었습니다.", noticeService.createNotice(noticeCreateUpdateRequestDTO, noticeImages))); + } + + @Operation( + summary = "공지사항 내용 수정 및 이미지 수정&추가", + description = "공지사항의 내용 수정, 이미지 추가 가능.
" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> updatePostContent( + @PathVariable String postId, + @RequestPart("noticeContent") @Valid NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, + @RequestPart(value = "noticeImages", required = false) List noticeImages) { + + noticeService.updateNoticeContent(postId, noticeCreateUpdateRequestDTO, noticeImages); + return ResponseEntity.status(HttpStatus.OK) + .body(new SingleResponse<>(200, "게시물 내용이 수정되었습니다.", null)); + } + + @Operation( + summary = "공지사항 이미지 삭제" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @DeleteMapping("/{postId}/images") + public ResponseEntity> deleteNoticeImage( + @PathVariable String postId, + @RequestParam String imageUrl) { + + postService.deletePostImage(postId, imageUrl); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "공지사항 이미지가 삭제되었습니다.", null)); + } + + @Operation( + summary = "공지사항 삭제 (Soft Delete)" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @DeleteMapping("/{postId}") + public ResponseEntity> softDeleteNotice(@PathVariable String postId) { + postService.softDeletePost(postId); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "공지사항이 삭제되었습니다.", null)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/request/NoticeCreateUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/request/NoticeCreateUpdateRequestDTO.java new file mode 100644 index 00000000..6f3c7556 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/request/NoticeCreateUpdateRequestDTO.java @@ -0,0 +1,17 @@ +package inu.codin.codin.domain.board.notice.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class NoticeCreateUpdateRequestDTO { + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private String content; +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java new file mode 100644 index 00000000..dca8c5b5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java @@ -0,0 +1,112 @@ +package inu.codin.codin.domain.board.notice.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +public class NoticeDetailResponseDto { + @Schema(description = "게시물 ID", example = "111111") + @NotBlank + private final String _id; + + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private final String userId; + + @Schema(description = "게시물 종류", example = "구해요") + @NotBlank + private final PostCategory postCategory; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private final String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private final String content; + + @Schema(description = "유저 nickname 익명시 익명으로 표시됨") + private final String nickname; + + @Schema(description = "유저 이미지 url", example = "https://~") + private final String userImageUrl; + + @Schema(description = "게시물 내 이미지 url , blank 가능", example = "example/1231") + private final List postImageUrl; + + @Schema(description = "게시물 익명 여부 default = true (익명)", example = "true") + @NotNull + private final boolean isAnonymous; + + @Schema(description = "스크랩 count", example = "0") + private final int scrapCount; + + @Schema(description = "조회수", example = "0") + private final int hits; + + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") + private final LocalDateTime createdAt; + + @Schema(description = "해당 게시글에 대한 유저 반응 여부") + private final UserInfo userInfo; + + public NoticeDetailResponseDto(String userId, String _id, String title, String content, String nickname , PostCategory postCategory, String userImageUrl, List < String > postImageUrls, + boolean isAnonymous, int scrapCount, int hits, LocalDateTime createdAt, UserInfo userInfo){ + this.userId = userId; + this._id = _id; + this.title = title; + this.content = content; + this.nickname = nickname; + this.postCategory = postCategory; + this.userImageUrl = userImageUrl; + this.postImageUrl = postImageUrls; + this.isAnonymous = isAnonymous; + this.scrapCount = scrapCount; + this.hits = hits; + this.createdAt = createdAt; + this.userInfo = userInfo; + } + + @Getter + public static class UserInfo { + private final boolean isScrap; + private final boolean isMine; + + @Builder + public UserInfo(boolean isScrap, boolean isMine) { + this.isScrap = isScrap; + this.isMine = isMine; + } + } + + public static NoticeDetailResponseDto of(PostEntity post, String nickname, String userImageUrl, int scrapCount, int hitsCount, UserInfo userInfo) { + return new NoticeDetailResponseDto( + post.getUserId().toString(), + post.get_id().toString(), + post.getTitle(), + post.getContent(), + nickname, + post.getPostCategory(), + userImageUrl, + post.getPostImageUrls(), + post.isAnonymous(), + scrapCount, + hitsCount, + post.getCreatedAt(), + userInfo); + } + + +} + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java new file mode 100644 index 00000000..2611c878 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java @@ -0,0 +1,4 @@ +package inu.codin.codin.domain.board.notice.dto.response; + +public class NoticeListResponseDto { +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java new file mode 100644 index 00000000..93eb4fc9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java @@ -0,0 +1,32 @@ +package inu.codin.codin.domain.board.notice.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class NoticePageResponse { + + private List contents = new ArrayList<>(); + private long lastPage; + private long nextPage; + + private NoticePageResponse(List contents, long lastPage, long nextPage) { + this.contents = contents; + this.lastPage = lastPage; + this.nextPage = nextPage; + } + + public static NoticePageResponse of(List postPaging, long totalElements, long nextPage) { + return NoticePageResponse.newPagingHasNext(postPaging, totalElements, nextPage); + } + + private static NoticePageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { + return new NoticePageResponse(posts, totalElements, nextPage); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java new file mode 100644 index 00000000..1d2a726e --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java @@ -0,0 +1,26 @@ +package inu.codin.codin.domain.board.notice.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum NoticeErrorCode implements GlobalErrorCode { + NOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "공지사항을 찾을 수 없습니다."), + NOTICE_ACCESS_DENIED(HttpStatus.FORBIDDEN, "공지사항을 작성할 수 없습니다."),; + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java new file mode 100644 index 00000000..7dff6002 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java @@ -0,0 +1,10 @@ +package inu.codin.codin.domain.board.notice.exception; + +import inu.codin.codin.common.exception.GlobalException; + +public class NoticeException extends GlobalException { + + public NoticeException(NoticeErrorCode errorCode) { + super(errorCode); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java new file mode 100644 index 00000000..4c13bd8a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java @@ -0,0 +1,24 @@ +package inu.codin.codin.domain.board.notice.repository; + +import inu.codin.codin.domain.post.entity.PostEntity; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface NoticeRepository extends MongoRepository { + @Query("{'deletedAt': null, " + + "'postStatus': { $in: ['ACTIVE'] }, " + + "'title': { $regex: ?0 }," + + "'postCategory': { $in: ?1 }}") + Page getNoticesByCategory(String department, List postCategories, PageRequest pageRequest); + + @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") + Optional findByIdAndNotDeleted(ObjectId Id); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java new file mode 100644 index 00000000..7ed710b8 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -0,0 +1,152 @@ +package inu.codin.codin.domain.board.notice.service; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; +import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; +import inu.codin.codin.domain.board.notice.dto.response.NoticePageResponse; +import inu.codin.codin.domain.board.notice.exception.NoticeErrorCode; +import inu.codin.codin.domain.board.notice.exception.NoticeException; +import inu.codin.codin.domain.board.notice.repository.NoticeRepository; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +@Service +@RequiredArgsConstructor +public class NoticeService { + + private final NoticeRepository noticeRepository; + private final UserRepository userRepository; + + private final ScrapService scrapService; + private final HitsService hitsService; + private final S3Service s3Service; + + /** + * 공지사항 페이징 조회 + * @param department 학과 + * @param pageNumber 페이지 번호 (0부터 시작) + * @return NoticePageResponse 공지사항 페이지 응답 + */ + public NoticePageResponse getAllNotices(Department department, int pageNumber) { + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + List postCategories = List.of( + PostCategory.EXTRACURRICULAR_INNER.name(), + PostCategory.DEPARTMENT_NOTICE.name() + ); + String regex = "^\\[" + Pattern.quote(department.getAbbreviation()) + "\\]"; + Page notices = noticeRepository.getNoticesByCategory(regex, postCategories, pageRequest); + return NoticePageResponse.of(getNoticeDetailResponse(notices.getContent()), notices.getTotalPages() - 1, notices.hasNext() ? notices.getPageable().getPageNumber() + 1 : -1); + } + + /** + * 공지사항 상세 조회 + * @param postId 공지사항 ID + * @return NoticeDetailResponseDto 공지사항 상세 정보 + */ + public NoticeDetailResponseDto getNoticesWithDetail(String postId) { + PostEntity notice = noticeRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(() -> new NoticeException(NoticeErrorCode.NOTICE_NOT_FOUND)); + return getNoticeWithDetail(notice); + } + + /** + * 공지사항 작성 + * @param noticeCreateUpdateRequestDTO 공지사항 작성 요청 DTO + * @param noticeImages 공지사항 이미지 파일 리스트 + * @return Map 공지사항 ID를 포함한 응답 + */ + public Map createNotice(NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, List noticeImages) { + List imageUrls = s3Service.handleImageUpload(noticeImages); + ObjectId userId = SecurityUtils.getCurrentUserId(); + + validateUserAndPost(userId); + UserEntity user = getUserEntity(userId); + String prefixOfTitle = "[" + user.getDepartment().getAbbreviation() + "]"; //작성자의 학과에 따라 title의 prefix를 붙여줌 + + PostEntity noticeEntity = PostEntity.builder() + .userId(userId) + .title(prefixOfTitle + noticeCreateUpdateRequestDTO.getTitle()) + .content(noticeCreateUpdateRequestDTO.getContent()) + .postImageUrls(imageUrls) + .isAnonymous(false) //공지사항은 항상 실명으로 업로드 + .postCategory(PostCategory.DEPARTMENT_NOTICE) + .postStatus(PostStatus.ACTIVE) + .build(); + noticeRepository.save(noticeEntity); + Map response = new HashMap<>(); + response.put("postId", noticeEntity.get_id().toString()); + return response; + } + + /** + * 공지사항 내용 수정 + * @param postId 공지사항 ID + * @param noticeCreateUpdateRequestDTO 공지사항 수정 요청 DTO + * @param noticeImages 공지사항 이미지 파일 리스트 + */ + public void updateNoticeContent(String postId, NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, List noticeImages) { + PostEntity post = noticeRepository.findByIdAndNotDeleted(new ObjectId(postId)) + .orElseThrow(()-> new NoticeException(NoticeErrorCode.NOTICE_NOT_FOUND)); + + validateUserAndPost(post.getUserId()); + + List imageUrls = s3Service.handleImageUpload(noticeImages); + post.updateNotice(noticeCreateUpdateRequestDTO.getTitle(), noticeCreateUpdateRequestDTO.getContent(), imageUrls); + noticeRepository.save(post); + } + + private List getNoticeDetailResponse(List content) { + return content.stream() + .map(this::getNoticeWithDetail) + .toList(); + } + + private NoticeDetailResponseDto getNoticeWithDetail(PostEntity post) { + UserEntity user = getUserEntity(post.getUserId()); + int scrapCount = scrapService.getScrapCount(post.get_id()); + int hitsCount = hitsService.getHitsCount(post.get_id()); + NoticeDetailResponseDto.UserInfo userInfo = getUserInfoAboutNotice(post.getUserId(), post.get_id()); + + return NoticeDetailResponseDto.of(post, user.getNickname(), user.getProfileImageUrl(), scrapCount, hitsCount, userInfo); + } + + private UserEntity getUserEntity(ObjectId userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.")); + } + + private NoticeDetailResponseDto.UserInfo getUserInfoAboutNotice(ObjectId postUserId, ObjectId postId){ + ObjectId userId = SecurityUtils.getCurrentUserId(); + return NoticeDetailResponseDto.UserInfo.builder() + .isScrap(scrapService.isPostScraped(postId, userId)) + .isMine(postUserId.equals(userId)) + .build(); + } + + private void validateUserAndPost(ObjectId postUserId) { + if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER)) { + throw new NoticeException(NoticeErrorCode.NOTICE_ACCESS_DENIED); + } + SecurityUtils.validateUser(postUserId); + } +} From 1113b3a54d80d0103f252db06ce3092d0982d034 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 18:09:37 +0900 Subject: [PATCH 0898/1002] =?UTF-8?q?feat=20:=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=EC=9D=98=20Post=20=EB=A1=9C=EC=A7=81=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD(Notice)=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20=EC=97=B0=EA=B3=84=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/dto/Department.java | 15 ++++++++------- .../codin/domain/post/entity/PostCategory.java | 2 ++ .../codin/domain/post/entity/PostEntity.java | 6 ++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java index 2ad60cc4..841b1ed4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java @@ -11,15 +11,16 @@ @Slf4j public enum Department { - IT_COLLEGE("정보기술대학"), - COMPUTER_SCI("컴퓨터공학부"), - COMPUTER_SCI_NIGHT("컴퓨터공학부(야)"), - INFO_COMM("정보통신공학과"), - EMBEDDED("임베디드시스템공학과"), - STAFF("교직원"), - OTHERS("타과대"); + IT_COLLEGE("정보기술대학", "정보대"), + COMPUTER_SCI("컴퓨터공학부", "컴공"), + COMPUTER_SCI_NIGHT("컴퓨터공학부(야)", "컴공"), + INFO_COMM("정보통신공학과", "정통"), + EMBEDDED("임베디드시스템공학과", "임베"), + STAFF("교직원", "교직원"), + OTHERS("타과대", "타대"); private final String description; + private final String abbreviation; @JsonCreator public static Department fromDescription(String description) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java index 0b66a8e7..5e984886 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostCategory.java @@ -21,6 +21,8 @@ public enum PostCategory { EXTRACURRICULAR_OUTER("비교과_교외"), EXTRACURRICULAR_INNER("비교과_교내"), + DEPARTMENT_NOTICE("학과_공지사항"), + POLL("설문조사"), BOOKS_SELL("중고책_판매중"), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 0577197a..1dbfb8b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -52,6 +52,12 @@ public PostEntity(ObjectId _id, ObjectId userId, PostCategory postCategory, Stri this.postStatus = postStatus; } + public void updateNotice(String title, String content, List postImageUrls) { + this.title = title; + this.content = content; + this.postImageUrls.addAll(postImageUrls); + } + public void updatePostContent(String content, List postImageUrls) { this.content = content; this.postImageUrls = postImageUrls; From 89c9f701eb309dec270a60f7f21c201c00397159 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 22:50:46 +0900 Subject: [PATCH 0899/1002] =?UTF-8?q?docs=20:=20=EC=A0=9C=ED=9C=B4?= =?UTF-8?q?=EC=97=85=EC=B2=B4=20API=20Swagger=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/info/controller/PartnerController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java index c25a2ba8..f4726da1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -22,13 +22,13 @@ @RestController @RequiredArgsConstructor @RequestMapping("/info/partner") -@Tag(name = "Partner API", description = "Partner CRUD") +@Tag(name = "Partner API", description = "제휴업체 API") public class PartnerController { private final PartnerService partnerService; @Operation( - summary = "Partner 썸네일 리스트 반환" + summary = "제휴업체 썸네일 리스트 반환" ) @GetMapping public ResponseEntity> getPartnerList() { @@ -37,7 +37,7 @@ public ResponseEntity> getPartnerList() { } @Operation( - summary = "Partner 상세 내역 반환" + summary = "제휴업체 상세 내역 반환" ) @GetMapping("/{id}") public ResponseEntity> getPartnerDetails(@PathVariable("id") String partnerId) { @@ -47,7 +47,7 @@ public ResponseEntity> getPartnerDetai @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation( - summary = "[ADMIN, MANAGER] Partner 추가" + summary = "[ADMIN, MANAGER] 제휴업체 추가" ) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createPartner(@RequestPart("partnerInfo") @Valid PartnerCreateRequestDto partnerCreateRequestDto, @@ -60,7 +60,7 @@ public ResponseEntity createPartner(@RequestPart("partnerInfo") @Valid Partne @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") @Operation( - summary = "[ADMIN, MANAGER] Partner 삭제" + summary = "[ADMIN, MANAGER] 제휴업체 삭제" ) @DeleteMapping("/{id}") public ResponseEntity deletePartner(@PathVariable("id") String partnerId) { From d48015f82414c5ad608b4802df0603921315688b Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 22:52:27 +0900 Subject: [PATCH 0900/1002] =?UTF-8?q?docs=20:=20=EC=95=8C=EB=A6=BC=20API?= =?UTF-8?q?=20Swagger=20=EC=84=A4=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notification/controller/NotificationController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java index d0baaaa0..ea0dff79 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.notification.service.NotificationService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -14,6 +15,7 @@ @RestController @RequestMapping("/notification") @RequiredArgsConstructor +@Tag(name = "Notification API", description = "알림 API") public class NotificationController { private final NotificationService notificationService; From 1d6c27b1f4f032870f6a7963731ece8a9944c260 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:15:31 +0900 Subject: [PATCH 0901/1002] =?UTF-8?q?feat=20:=20=EA=B3=B5=EC=A7=80?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=9D=98=20Res?= =?UTF-8?q?ponse=20Dto=EB=A5=BC=20=EB=94=B0=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notice/controller/NoticeController.java | 8 +-- .../dto/response/NoticeListResponseDto.java | 65 +++++++++++++++++++ .../dto/response/NoticePageResponse.java | 8 +-- .../board/notice/service/NoticeService.java | 12 +++- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java index 62a00413..de7832ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java @@ -46,12 +46,10 @@ public class NoticeController { "DEPARTMENT_NOTICE는 직접 수기로 작성한 공지사항
" ) @GetMapping("/category") - public ResponseEntity> getAllNotices( - @RequestParam("department") @NotNull Department department, - @RequestParam("page") @NotNull int pageNumber) { - NoticePageResponse noticepages= noticeService.getAllNotices(department, pageNumber); + public ResponseEntity> getAllNotices(@RequestParam("department") @NotNull Department department, + @RequestParam("page") @NotNull int pageNumber) { return ResponseEntity.ok() - .body(new SingleResponse<>(200, "학과별 공지사항 조회 성공", noticepages)); + .body(new SingleResponse<>(200, "학과별 공지사항 조회 성공", noticeService.getAllNotices(department, pageNumber))); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java index 2611c878..8c793625 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java @@ -1,4 +1,69 @@ package inu.codin.codin.domain.board.notice.dto.response; +import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter public class NoticeListResponseDto { + @Schema(description = "게시물 ID", example = "111111") + @NotBlank + private final String _id; + + @Schema(description = "유저 ID", example = "111111") + @NotBlank + private final String userId; + + @Schema(description = "게시물 종류", example = "구해요") + @NotBlank + private final PostCategory postCategory; + + @Schema(description = "게시물 제목", example = "Example") + @NotBlank + private final String title; + + @Schema(description = "게시물 내용", example = "example content") + @NotBlank + private final String content; + + @Schema(description = "유저 nickname 익명시 익명으로 표시됨") + private final String nickname; + + @Schema(description = "게시물 내 대표 이미지 하나 url , null 가능", example = "example/1231") + private final String postImageUrl; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @Schema(description = "생성 일자", example = "2024-12-02 20:10:18") + private final LocalDateTime createdAt; + + @Builder + public NoticeListResponseDto(String _id, String userId, PostCategory postCategory, String title, String content, String nickname, String postImageUrl, LocalDateTime createdAt) { + this._id = _id; + this.userId = userId; + this.postCategory = postCategory; + this.title = title; + this.content = content; + this.nickname = nickname; + this.postImageUrl = postImageUrl; + this.createdAt = createdAt; + } + + public static NoticeListResponseDto of(PostEntity postEntity, String nickname) { + return NoticeListResponseDto.builder() + ._id(postEntity.get_id().toString()) + .userId(String.valueOf(postEntity.getUserId())) + .postCategory(postEntity.getPostCategory()) + .title(postEntity.getTitle()) + .content(postEntity.getContent()) + .nickname(nickname) + .postImageUrl(postEntity.getPostImageUrls() != null && !postEntity.getPostImageUrls().isEmpty() ? postEntity.getPostImageUrls().get(0) : null) + .createdAt(postEntity.getCreatedAt()) + .build(); + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java index 93eb4fc9..ae2952d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticePageResponse.java @@ -11,21 +11,21 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class NoticePageResponse { - private List contents = new ArrayList<>(); + private List contents = new ArrayList<>(); private long lastPage; private long nextPage; - private NoticePageResponse(List contents, long lastPage, long nextPage) { + private NoticePageResponse(List contents, long lastPage, long nextPage) { this.contents = contents; this.lastPage = lastPage; this.nextPage = nextPage; } - public static NoticePageResponse of(List postPaging, long totalElements, long nextPage) { + public static NoticePageResponse of(List postPaging, long totalElements, long nextPage) { return NoticePageResponse.newPagingHasNext(postPaging, totalElements, nextPage); } - private static NoticePageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { + private static NoticePageResponse newPagingHasNext(List posts, long totalElements, long nextPage) { return new NoticePageResponse(posts, totalElements, nextPage); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java index 7ed710b8..5fefcb1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -4,6 +4,7 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; +import inu.codin.codin.domain.board.notice.dto.response.NoticeListResponseDto; import inu.codin.codin.domain.board.notice.dto.response.NoticePageResponse; import inu.codin.codin.domain.board.notice.exception.NoticeErrorCode; import inu.codin.codin.domain.board.notice.exception.NoticeException; @@ -18,6 +19,7 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -31,6 +33,7 @@ import java.util.regex.Pattern; @Service +@Slf4j @RequiredArgsConstructor public class NoticeService { @@ -55,7 +58,7 @@ public NoticePageResponse getAllNotices(Department department, int pageNumber) { ); String regex = "^\\[" + Pattern.quote(department.getAbbreviation()) + "\\]"; Page notices = noticeRepository.getNoticesByCategory(regex, postCategories, pageRequest); - return NoticePageResponse.of(getNoticeDetailResponse(notices.getContent()), notices.getTotalPages() - 1, notices.hasNext() ? notices.getPageable().getPageNumber() + 1 : -1); + return NoticePageResponse.of(getNoticeListResponse(notices.getContent()), notices.getTotalPages() - 1, notices.hasNext() ? notices.getPageable().getPageNumber() + 1 : -1); } /** @@ -115,9 +118,12 @@ public void updateNoticeContent(String postId, NoticeCreateUpdateRequestDTO noti noticeRepository.save(post); } - private List getNoticeDetailResponse(List content) { + private List getNoticeListResponse(List content) { return content.stream() - .map(this::getNoticeWithDetail) + .map(post -> { + UserEntity user = getUserEntity(post.getUserId()); + return NoticeListResponseDto.of(post, user.getNickname()); + }) .toList(); } From 2bc2fac67fff7bc06ff1ccb11421b4d5b22f7efd Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:16:07 +0900 Subject: [PATCH 0902/1002] =?UTF-8?q?feat=20:=20GlobalExceptionHandler?= =?UTF-8?q?=EC=97=90=20Notice,=20Question=20Exception=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 18 ++++++++++++++++++ .../notice/exception/NoticeException.java | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 04bf9d48..ca48f6cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -4,6 +4,10 @@ import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; +import inu.codin.codin.domain.board.notice.exception.NoticeErrorCode; +import inu.codin.codin.domain.board.notice.exception.NoticeException; +import inu.codin.codin.domain.board.question.QuestionErrorCode; +import inu.codin.codin.domain.board.question.QuestionException; import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; import inu.codin.codin.domain.chat.exception.ChatRoomException; import inu.codin.codin.domain.chat.exception.ChattingErrorCode; @@ -109,4 +113,18 @@ public ResponseEntity handleIllegalArgumentException(IllegalA .body(new ExceptionResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value())); } + @ExceptionHandler(QuestionException.class) + protected ResponseEntity handleQuestionException(QuestionException e) { + QuestionErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + + @ExceptionHandler(NoticeException.class) + protected ResponseEntity handleNoticeException(NoticeException e) { + NoticeErrorCode code = e.getErrorCode(); + return ResponseEntity.status(code.httpStatus()) + .body(new ExceptionResponse(code.message(), code.httpStatus().value())); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java index 7dff6002..4eb4e3f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java @@ -1,10 +1,13 @@ package inu.codin.codin.domain.board.notice.exception; import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; +@Getter public class NoticeException extends GlobalException { - + private final NoticeErrorCode errorCode; public NoticeException(NoticeErrorCode errorCode) { super(errorCode); + this.errorCode = errorCode; } } From 2c67650d4f874b1a2a7fa95bf9fe8454efc346b1 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:24:16 +0900 Subject: [PATCH 0903/1002] =?UTF-8?q?feat=20:=20=EC=9E=90=EC=A3=BC=20?= =?UTF-8?q?=EB=AC=BB=EB=8A=94=20=EC=A7=88=EB=AC=B8(Question)=20CRUD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/QuestionController.java | 83 +++++++++++++++++++ .../QuestionCreateUpdateRequestDto.java | 23 +++++ .../dto/response/QuestionResponseDto.java | 20 +++++ .../board/question/entity/QuestionEntity.java | 45 ++++++++++ .../question/exception/QuestionErrorCode.java | 23 +++++ .../question/exception/QuestionException.java | 13 +++ .../repository/QuestionRepository.java | 14 ++++ .../question/service/QuestionService.java | 53 ++++++++++++ 8 files changed, 274 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java new file mode 100644 index 00000000..b8e0d6f2 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java @@ -0,0 +1,83 @@ +package inu.codin.codin.domain.board.question.controller; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.response.ListResponse; +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.board.question.dto.request.QuestionCreateUpdateRequestDto; +import inu.codin.codin.domain.board.question.dto.response.QuestionResponseDto; +import inu.codin.codin.domain.board.question.service.QuestionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/question") +@Tag(name = "Question API", description = "[리다지인] 게시판 자주 묻는 질문 API") +@RequiredArgsConstructor +public class QuestionController { + + private final QuestionService questionService; + + /* + =================== + ROLE_USER 사용한 API (조회) + =================== + */ + + @Operation( + summary = "자주 묻는 질문 조회" + ) + @GetMapping + public ResponseEntity> getAllQuestions(@RequestParam(value = "department") Department department) { + List questions = questionService.getAllQuestions(department); + return ResponseEntity.ok() + .body(new ListResponse<>(200, "자주 묻는 질문 조회 성공", questions)); + } + + /* + =================== + ROLE_MANAGER, ROLE_ADMIN 사용한 API (작성, 수정, 삭제) + =================== + */ + + @Operation( + summary = "자주 묻는 질문 작성" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PostMapping + public ResponseEntity> createQuestion(@RequestBody QuestionCreateUpdateRequestDto requestDto) { + questionService.createQuestion(requestDto); + return ResponseEntity.status(201) + .body(new SingleResponse<>(201, "자주 묻는 질문 작성 성공", null)); + } + + + @Operation( + summary = "자주 묻는 질문 수정" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PutMapping("/{questionId}") + public ResponseEntity> updateQuestion(@PathVariable("questionId") String id, + @RequestBody QuestionCreateUpdateRequestDto requestDto) { + questionService.updateQuestion(id, requestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "자주 묻는 질문 수정 성공", null)); + } + + @Operation( + summary = "자주 묻는 질문 삭제" + ) + @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @DeleteMapping("/{questionId}") + public ResponseEntity> deleteQuestion(@PathVariable("questionId") String id) { + questionService.deleteQuestion(id); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "자주 묻는 질문 삭제 성공", null)); + } + +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java new file mode 100644 index 00000000..ddf38262 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.board.question.dto.request; + +import inu.codin.codin.common.dto.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class QuestionCreateUpdateRequestDto { + + @Schema(description = "질문 내용", example = "코딘에 들어가려면 어떻게 해야하나요?") + @NotBlank + private String question; + + @Schema(description = "답변 내용", example = "지원서를 작성하면 됩니다.") + @NotBlank + private String answer; + + @Schema(description = "답변 내용", example = "COMPUTER_SCI") + @NotNull + private Department department; +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java new file mode 100644 index 00000000..41f9917d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java @@ -0,0 +1,20 @@ +package inu.codin.codin.domain.board.question.dto.response; + +import inu.codin.codin.domain.board.question.entity.QuestionEntity; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class QuestionResponseDto { + + private String question; + private String answer; + + public static QuestionResponseDto of(QuestionEntity questionEntity) { + return new QuestionResponseDto( + questionEntity.getQuestion(), + questionEntity.getAnswer() + ); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java new file mode 100644 index 00000000..ea8d9d2d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java @@ -0,0 +1,45 @@ +package inu.codin.codin.domain.board.question.entity; + +import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.dto.Department; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Getter +@Document(collection = "often_asked_questions") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class QuestionEntity extends BaseTimeEntity { + + @Id + private ObjectId _id; + + private String question; + + private String answer; + + private Department department; + + @Builder + public QuestionEntity(String question, String answer, Department department) { + this.question = question; + this.answer = answer; + this.department = department; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + + public void setQuestion(String question) { + this.question = question; + } + + public void setDepartment(Department department) { + this.department = department; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java new file mode 100644 index 00000000..156ebf8c --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java @@ -0,0 +1,23 @@ +package inu.codin.codin.domain.board.question.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum QuestionErrorCode implements GlobalErrorCode { + QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "질문을 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java new file mode 100644 index 00000000..47f166e6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.board.question.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; +@Getter +public class QuestionException extends GlobalException { + + private final QuestionErrorCode errorCode; + public QuestionException(QuestionErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java new file mode 100644 index 00000000..75ebcdf4 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java @@ -0,0 +1,14 @@ +package inu.codin.codin.domain.board.question.repository; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.board.question.entity.QuestionEntity; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface QuestionRepository extends MongoRepository { + List findAllByDepartment(Department department); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java new file mode 100644 index 00000000..653b603b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java @@ -0,0 +1,53 @@ +package inu.codin.codin.domain.board.question.service; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.board.question.dto.request.QuestionCreateUpdateRequestDto; +import inu.codin.codin.domain.board.question.dto.response.QuestionResponseDto; +import inu.codin.codin.domain.board.question.entity.QuestionEntity; +import inu.codin.codin.domain.board.question.exception.QuestionErrorCode; +import inu.codin.codin.domain.board.question.exception.QuestionException; +import inu.codin.codin.domain.board.question.repository.QuestionRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class QuestionService { + + private final QuestionRepository questionRepository; + + public List getAllQuestions(Department department) { + return questionRepository.findAllByDepartment(department) + .stream().map(QuestionResponseDto::of).toList(); + } + + public void createQuestion(QuestionCreateUpdateRequestDto requestDto) { + QuestionEntity questionEntity = QuestionEntity.builder() + .question(requestDto.getQuestion()) + .answer(requestDto.getAnswer()) + .department(requestDto.getDepartment()) + .build(); + questionRepository.save(questionEntity); + } + + + public void updateQuestion(String id, QuestionCreateUpdateRequestDto requestDto) { + QuestionEntity questionEntity = questionRepository.findById(new ObjectId(id)) + .orElseThrow(() -> new QuestionException(QuestionErrorCode.QUESTION_NOT_FOUND)); + + questionEntity.setQuestion(requestDto.getQuestion()); + questionEntity.setAnswer(requestDto.getAnswer()); + + questionRepository.save(questionEntity); + } + + public void deleteQuestion(String id) { + QuestionEntity questionEntity = questionRepository.findById(new ObjectId(id)) + .orElseThrow(() -> new QuestionException(QuestionErrorCode.QUESTION_NOT_FOUND)); + + questionRepository.delete(questionEntity); + } +} From f72e0f1347266d1a184304e3e8b7f913de087e7f Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:24:50 +0900 Subject: [PATCH 0904/1002] =?UTF-8?q?refactor=20:=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?import=20=EB=AC=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/common/exception/GlobalExceptionHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index ca48f6cd..cb936191 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -6,8 +6,8 @@ import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.board.notice.exception.NoticeErrorCode; import inu.codin.codin.domain.board.notice.exception.NoticeException; -import inu.codin.codin.domain.board.question.QuestionErrorCode; -import inu.codin.codin.domain.board.question.QuestionException; +import inu.codin.codin.domain.board.question.exception.QuestionErrorCode; +import inu.codin.codin.domain.board.question.exception.QuestionException; import inu.codin.codin.domain.chat.exception.ChatRoomErrorCode; import inu.codin.codin.domain.chat.exception.ChatRoomException; import inu.codin.codin.domain.chat.exception.ChattingErrorCode; From 1de493f0635c88a5ca86db6b1bf9b06044540d61 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:27:01 +0900 Subject: [PATCH 0905/1002] =?UTF-8?q?fix=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=EA=B0=80=20=EB=B0=B0=EC=97=B4=20=ED=98=95?= =?UTF-8?q?=ED=83=9C=EB=A1=9C=20=EC=B6=9C=EB=A0=A5=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=EB=A5=BC=20Format=EC=9D=84=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=ED=95=B4=EC=A4=8C=EC=9C=BC=EB=A1=9C=EC=8D=A8=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/NotificationListResponseDto.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java index 9b6396f4..caa313f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/response/NotificationListResponseDto.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.notification.dto.response; +import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.notification.entity.NotificationEntity; import jakarta.validation.constraints.NotBlank; import lombok.Builder; @@ -11,13 +12,15 @@ @Getter public class NotificationListResponseDto { - @Id @NotBlank private String id; private String targetId; private String title; private String message; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime dateTime; + private boolean isRead = false; @Builder From d0cb6255b04221a2412b759b237acf2bd46788a8 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:28:11 +0900 Subject: [PATCH 0906/1002] =?UTF-8?q?feat=20:=20Response=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EA=B0=92=EC=97=90=20id=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/question/dto/response/QuestionResponseDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java index 41f9917d..985700bf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/response/QuestionResponseDto.java @@ -8,11 +8,13 @@ @AllArgsConstructor public class QuestionResponseDto { + private String id; private String question; private String answer; public static QuestionResponseDto of(QuestionEntity questionEntity) { return new QuestionResponseDto( + questionEntity.get_id().toString(), questionEntity.getQuestion(), questionEntity.getAnswer() ); From 7c0ab0188ddb25ba94368aeb8064ce60a691d877 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 19 Aug 2025 23:42:56 +0900 Subject: [PATCH 0907/1002] =?UTF-8?q?feat=20:=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=ED=95=99=EA=B3=BC(department)?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/notice/exception/NoticeErrorCode.java | 3 ++- .../board/notice/exception/NoticeException.java | 1 + .../board/notice/service/NoticeService.java | 15 +++++++++++---- .../board/question/entity/QuestionEntity.java | 15 +++++---------- .../question/exception/QuestionErrorCode.java | 3 ++- .../board/question/service/QuestionService.java | 15 +++++++++++---- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java index 1d2a726e..f19fbe6e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java @@ -9,7 +9,8 @@ @RequiredArgsConstructor public enum NoticeErrorCode implements GlobalErrorCode { NOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "공지사항을 찾을 수 없습니다."), - NOTICE_ACCESS_DENIED(HttpStatus.FORBIDDEN, "공지사항을 작성할 수 없습니다."),; + NOTICE_ACCESS_DENIED(HttpStatus.FORBIDDEN, "공지사항을 작성할 수 없습니다."), + INVALID_DEPARTMENT(HttpStatus.BAD_REQUEST, "공지사항을 작성하는 유저의 학과가 유효하지 않습니다."),; private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java index 4eb4e3f0..73a8e483 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java @@ -5,6 +5,7 @@ @Getter public class NoticeException extends GlobalException { + private final NoticeErrorCode errorCode; public NoticeException(NoticeErrorCode errorCode) { super(errorCode); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java index 5fefcb1d..11dca23f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -51,6 +51,7 @@ public class NoticeService { * @return NoticePageResponse 공지사항 페이지 응답 */ public NoticePageResponse getAllNotices(Department department, int pageNumber) { + validateDepartment(department); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); List postCategories = List.of( PostCategory.EXTRACURRICULAR_INNER.name(), @@ -84,6 +85,8 @@ public Map createNotice(NoticeCreateUpdateRequestDTO noticeCreat validateUserAndPost(userId); UserEntity user = getUserEntity(userId); + + validateDepartment(user.getDepartment()); String prefixOfTitle = "[" + user.getDepartment().getAbbreviation() + "]"; //작성자의 학과에 따라 title의 prefix를 붙여줌 PostEntity noticeEntity = PostEntity.builder() @@ -119,12 +122,16 @@ public void updateNoticeContent(String postId, NoticeCreateUpdateRequestDTO noti } private List getNoticeListResponse(List content) { - return content.stream() - .map(post -> { + return content.stream().map(post -> { UserEntity user = getUserEntity(post.getUserId()); return NoticeListResponseDto.of(post, user.getNickname()); - }) - .toList(); + }).toList(); + } + + private void validateDepartment(Department department) { + if (!(department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.EMBEDDED) || department.equals(Department.IT_COLLEGE))) { + throw new NoticeException(NoticeErrorCode.INVALID_DEPARTMENT); + } } private NoticeDetailResponseDto getNoticeWithDetail(PostEntity post) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java index ea8d9d2d..b36ee990 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.domain.board.question.dto.request.QuestionCreateUpdateRequestDto; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -31,15 +32,9 @@ public QuestionEntity(String question, String answer, Department department) { this.department = department; } - public void setAnswer(String answer) { - this.answer = answer; - } - - public void setQuestion(String question) { - this.question = question; - } - - public void setDepartment(Department department) { - this.department = department; + public void updateQuestion(QuestionCreateUpdateRequestDto requestDto) { + this.answer = requestDto.getAnswer(); + this.question = requestDto.getQuestion(); + this.department = requestDto.getDepartment(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java index 156ebf8c..cc1e750d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java @@ -6,7 +6,8 @@ @RequiredArgsConstructor public enum QuestionErrorCode implements GlobalErrorCode { - QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "질문을 찾을 수 없습니다."); + QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "질문을 찾을 수 없습니다."), + INVALID_DEPARTMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 학과입니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java index 653b603b..882e8f94 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java @@ -20,11 +20,14 @@ public class QuestionService { private final QuestionRepository questionRepository; public List getAllQuestions(Department department) { + validateDepartment(department); return questionRepository.findAllByDepartment(department) .stream().map(QuestionResponseDto::of).toList(); } public void createQuestion(QuestionCreateUpdateRequestDto requestDto) { + validateDepartment(requestDto.getDepartment()); + QuestionEntity questionEntity = QuestionEntity.builder() .question(requestDto.getQuestion()) .answer(requestDto.getAnswer()) @@ -35,19 +38,23 @@ public void createQuestion(QuestionCreateUpdateRequestDto requestDto) { public void updateQuestion(String id, QuestionCreateUpdateRequestDto requestDto) { + validateDepartment(requestDto.getDepartment()); QuestionEntity questionEntity = questionRepository.findById(new ObjectId(id)) .orElseThrow(() -> new QuestionException(QuestionErrorCode.QUESTION_NOT_FOUND)); - questionEntity.setQuestion(requestDto.getQuestion()); - questionEntity.setAnswer(requestDto.getAnswer()); - + questionEntity.updateQuestion(requestDto); questionRepository.save(questionEntity); } public void deleteQuestion(String id) { QuestionEntity questionEntity = questionRepository.findById(new ObjectId(id)) .orElseThrow(() -> new QuestionException(QuestionErrorCode.QUESTION_NOT_FOUND)); - questionRepository.delete(questionEntity); } + + private void validateDepartment(Department department) { + if (!(department.equals(Department.COMPUTER_SCI) || department.equals(Department.INFO_COMM) || department.equals(Department.EMBEDDED) || department.equals(Department.IT_COLLEGE))) { + throw new QuestionException(QuestionErrorCode.INVALID_DEPARTMENT); + } + } } From 9b3866198132c797c5ed0e14f69e62cf2da08bc8 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Wed, 20 Aug 2025 00:34:08 +0900 Subject: [PATCH 0908/1002] =?UTF-8?q?refactor:=20=EC=B5=9C=EC=83=81?= =?UTF-8?q?=EC=9C=84=20Exception=20Handling=EC=8B=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/exception/GlobalExceptionHandler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 04bf9d48..a0bb07b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -25,8 +25,12 @@ public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) protected ResponseEntity handleException(Exception e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(new ExceptionResponse(e.getMessage(), HttpStatus.NOT_FOUND.value())); + log.warn("[Exception] Class: {}, Error Message : {}, Stack Trace: {}", + e.getClass().getSimpleName(), + e.getMessage(), + e.getStackTrace()[0].toString()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ExceptionResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value())); } @ExceptionHandler(GlobalException.class) From dcef4f0a00648bc0005e1a3660c6c92abedbb933 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 20 Aug 2025 00:34:12 +0900 Subject: [PATCH 0909/1002] =?UTF-8?q?fix=20:=20@PreAuthorize=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/notice/controller/NoticeController.java | 10 +++++----- .../board/question/controller/QuestionController.java | 6 +++--- .../codin/domain/info/controller/LabController.java | 6 +++--- .../codin/domain/info/controller/OfficeController.java | 8 ++++---- .../domain/info/controller/PartnerController.java | 4 ++-- .../domain/info/controller/ProfessorController.java | 6 +++--- .../lecture/controller/LectureUploadController.java | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java index de7832ab..21e8a99c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java @@ -23,7 +23,7 @@ @RestController @RequestMapping("/notice") -@Tag(name = "Notice API", description = "[리다지인] 게시판 공지사항 API") +@Tag(name = "Notice API", description = "[리디자인] 게시판 공지사항 API") @RequiredArgsConstructor public class NoticeController { @@ -80,7 +80,7 @@ public ResponseEntity> getNoticesWithDet "EMBEDDED -> [임베]
" + "공지사항 작성 시 이미지 첨부 가능, 이미지가 없으면 빈 리스트로 처리" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> createNotice( @RequestPart("noticeContent") @Valid NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, @@ -96,7 +96,7 @@ public ResponseEntity> createNotice( summary = "공지사항 내용 수정 및 이미지 수정&추가", description = "공지사항의 내용 수정, 이미지 추가 가능.
" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> updatePostContent( @PathVariable String postId, @@ -111,7 +111,7 @@ public ResponseEntity> updatePostContent( @Operation( summary = "공지사항 이미지 삭제" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @DeleteMapping("/{postId}/images") public ResponseEntity> deleteNoticeImage( @PathVariable String postId, @@ -125,7 +125,7 @@ public ResponseEntity> deleteNoticeImage( @Operation( summary = "공지사항 삭제 (Soft Delete)" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @DeleteMapping("/{postId}") public ResponseEntity> softDeleteNotice(@PathVariable String postId) { postService.softDeletePost(postId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java index b8e0d6f2..133dca28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java @@ -48,7 +48,7 @@ public ResponseEntity> getAllQuestions(@Reques @Operation( summary = "자주 묻는 질문 작성" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PostMapping public ResponseEntity> createQuestion(@RequestBody QuestionCreateUpdateRequestDto requestDto) { questionService.createQuestion(requestDto); @@ -60,7 +60,7 @@ public ResponseEntity> createQuestion(@RequestBody QuestionCre @Operation( summary = "자주 묻는 질문 수정" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PutMapping("/{questionId}") public ResponseEntity> updateQuestion(@PathVariable("questionId") String id, @RequestBody QuestionCreateUpdateRequestDto requestDto) { @@ -72,7 +72,7 @@ public ResponseEntity> updateQuestion(@PathVariable("questionI @Operation( summary = "자주 묻는 질문 삭제" ) - @PreAuthorize("hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @DeleteMapping("/{questionId}") public ResponseEntity> deleteQuestion(@PathVariable("questionId") String id) { questionService.deleteQuestion(id); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java index 8875a931..f07fdcc0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java @@ -37,7 +37,7 @@ public ResponseEntity> getAllLab(){ .body(new ListResponse<>(200, "연구실 리스트 반환 성공", labService.getAllLab())); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 새로운 연구실 등록") @PostMapping public ResponseEntity> createLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto){ @@ -46,7 +46,7 @@ public ResponseEntity> createLab(@RequestBody @Valid LabCreate .body(new SingleResponse<>(201, "새로운 LAB 등록 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 연구실 정보 수정") @PutMapping(value = "/{id}") public ResponseEntity> updateLab(@RequestBody @Valid LabCreateUpdateRequestDto labCreateUpdateRequestDto, @PathVariable("id") String id){ @@ -55,7 +55,7 @@ public ResponseEntity> updateLab(@RequestBody @Valid LabCreate .body(new SingleResponse<>(200, "LAB 정보 수정 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 연구실 삭제") @DeleteMapping(value = "/{id}") public ResponseEntity> deleteLab(@PathVariable("id") String id){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java index 952eb0cc..6dad90d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java @@ -30,7 +30,7 @@ public ResponseEntity> getOfficeByDepartment(@PathVariable("de .body(new SingleResponse<>(200, "학과별 사무실 정보 반환 성공", officeService.getOfficeByDepartment(department))); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 정보 수정") @PatchMapping(value = "/{department}") public ResponseEntity> updateOffice(@PathVariable("department") Department department, @RequestBody @Valid OfficeUpdateRequestDto officeUpdateRequestDto){ @@ -39,7 +39,7 @@ public ResponseEntity> updateOffice(@PathVariable("department" .body(new SingleResponse<>(200, "Office 정보 수정 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 추가") @PatchMapping(value = "/{department}/member") public ResponseEntity> createOfficeMember(@PathVariable("department") Department department, @@ -49,7 +49,7 @@ public ResponseEntity> createOfficeMember(@PathVariable("depar .body(new SingleResponse<>(201, "Office Member 추가 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 수정") @PatchMapping(value = "/{department}/member/{num}") public ResponseEntity> updateOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num, @@ -59,7 +59,7 @@ public ResponseEntity> updateOfficeMember(@PathVariable("depar .body(new SingleResponse<>(200, "Office Member 정보 수정 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 학과사무실 직원 정보 삭제") @DeleteMapping(value = "/{department}/member/{num}") public ResponseEntity deleteOfficeMember(@PathVariable("department") Department department, @PathVariable("num") @Min(0) int num){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java index f4726da1..b35d48e5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -45,7 +45,7 @@ public ResponseEntity> getPartnerDetai .body(new SingleResponse<>(200, "Partner 상세 내열 반환 성공", partnerService.getPartnerDetails(partnerId))); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation( summary = "[ADMIN, MANAGER] 제휴업체 추가" ) @@ -58,7 +58,7 @@ public ResponseEntity createPartner(@RequestPart("partnerInfo") @Valid Partne .body(new SingleResponse<>(201, "Partner 생성 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation( summary = "[ADMIN, MANAGER] 제휴업체 삭제" ) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java index b5910248..4e7a92e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java @@ -37,7 +37,7 @@ public ResponseEntity> getProfesso .body(new SingleResponse<>(200, "교수 썸네일 반환 성공", professorService.getProfessorThumbnail(id))); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 새로운 교수 정보 생성") @PostMapping public ResponseEntity> createProfessor(@RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ @@ -46,7 +46,7 @@ public ResponseEntity> createProfessor(@RequestBody @Valid Pro .body(new SingleResponse<>(200, "새로운 교수 정보 생성 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 교수 정보 수정") @PatchMapping(value = "/{id}") public ResponseEntity> updateProfessor(@PathVariable("id") String id, @RequestBody @Valid ProfessorCreateUpdateRequestDto professorCreateUpdateRequestDto){ @@ -55,7 +55,7 @@ public ResponseEntity> updateProfessor(@PathVariable("id") Str .body(new SingleResponse<>(200, "교수 정보 수정 완료", null)); } - @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @Operation(summary = "[ADMIN, MANAGER] 교수 정보 삭제") @DeleteMapping(value = "/{id}") public ResponseEntity> deleteProfessor(@PathVariable("id") String id){ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java index ea327b8b..3933edca 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java @@ -28,7 +28,7 @@ public class LectureUploadController { description = "강의 내역서(엑셀 파일) 이름을 '년도-학기'로 설정하여 업로드 ex) 24-1.xlsx, 24-2.xlsx" ) @PostMapping(value = "/lectures", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") public ResponseEntity> uploadNewSemesterLectures(@RequestParam("excelFile") MultipartFile file) { lectureUploadService.uploadNewSemesterLectures(file); return ResponseEntity.status(HttpStatus.CREATED) @@ -41,7 +41,7 @@ public ResponseEntity> uploadNewSemesterLectures(@RequestParam description = "강의 내역서(엑셀 파일) 이름을 '년도-학기'로 설정하여 업로드 ex) 24-1.xlsx, 24-2.xlsx" ) @PostMapping(value = "/rooms", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") public ResponseEntity uploadNewSemesterRooms(@RequestParam("excelFile") MultipartFile file) { lectureUploadService.uploadNewSemesterRooms(file); return ResponseEntity.status(HttpStatus.CREATED) From 0ab71f76021fa3732fcb799380d582efbf0c7812 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 20 Aug 2025 00:36:01 +0900 Subject: [PATCH 0910/1002] =?UTF-8?q?fix=20:=20noticeImages=EA=B0=80=20nul?= =?UTF-8?q?l=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/notice/controller/NoticeController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java index 21e8a99c..04268dc0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java @@ -87,7 +87,7 @@ public ResponseEntity> createNotice( @RequestPart(value = "noticeImages", required = false) List noticeImages) { // postImages가 null이면 빈 리스트로 처리 - if (noticeImages == null) noticeImages = List.of(); + if (noticeImages == null || noticeImages.isEmpty()) noticeImages = List.of(); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "공지사항이 작성되었습니다.", noticeService.createNotice(noticeCreateUpdateRequestDTO, noticeImages))); } @@ -97,12 +97,13 @@ public ResponseEntity> createNotice( description = "공지사항의 내용 수정, 이미지 추가 가능.
" ) @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") - @PatchMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PutMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> updatePostContent( @PathVariable String postId, @RequestPart("noticeContent") @Valid NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, @RequestPart(value = "noticeImages", required = false) List noticeImages) { + if (noticeImages == null || noticeImages.isEmpty()) noticeImages = List.of(); noticeService.updateNoticeContent(postId, noticeCreateUpdateRequestDTO, noticeImages); return ResponseEntity.status(HttpStatus.OK) .body(new SingleResponse<>(200, "게시물 내용이 수정되었습니다.", null)); From b6b26825c936f7c6523c83eda3ff3ea1483638b3 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Wed, 20 Aug 2025 00:51:32 +0900 Subject: [PATCH 0911/1002] =?UTF-8?q?refactor=20:=20PR=20=EC=BD=94?= =?UTF-8?q?=EB=A9=98=ED=8A=B8=20=EB=82=B4=EC=9A=A9=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EB=B0=8F=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/notice/controller/NoticeController.java | 3 ++- .../notice/dto/response/NoticeDetailResponseDto.java | 2 +- .../board/notice/dto/response/NoticeListResponseDto.java | 3 ++- .../domain/board/notice/repository/NoticeRepository.java | 7 +++++-- .../codin/domain/board/notice/service/NoticeService.java | 8 ++++---- .../board/question/controller/QuestionController.java | 5 +++-- .../dto/request/QuestionCreateUpdateRequestDto.java | 2 +- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java index 04268dc0..9d442b4f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java @@ -94,7 +94,8 @@ public ResponseEntity> createNotice( @Operation( summary = "공지사항 내용 수정 및 이미지 수정&추가", - description = "공지사항의 내용 수정, 이미지 추가 가능.
" + description = "공지사항의 내용 수정, 이미지 추가 가능.
" + + "새로 추가된 이미지만 넣어서 API 요청 필수 (없으면 빈 리스트), 삭제의 경우 별도의 API로 처리" ) @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PutMapping(value = "/{postId}/content", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java index dca8c5b5..57132837 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeDetailResponseDto.java @@ -23,7 +23,7 @@ public class NoticeDetailResponseDto { private final String userId; @Schema(description = "게시물 종류", example = "구해요") - @NotBlank + @NotNull private final PostCategory postCategory; @Schema(description = "게시물 제목", example = "Example") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java index 8c793625..1ff82d3c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/dto/response/NoticeListResponseDto.java @@ -5,6 +5,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; @@ -21,7 +22,7 @@ public class NoticeListResponseDto { private final String userId; @Schema(description = "게시물 종류", example = "구해요") - @NotBlank + @NotNull private final PostCategory postCategory; @Schema(description = "게시물 제목", example = "Example") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java index 4c13bd8a..4d0c3913 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/repository/NoticeRepository.java @@ -1,23 +1,26 @@ package inu.codin.codin.domain.board.notice.repository; +import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; @Repository public interface NoticeRepository extends MongoRepository { @Query("{'deletedAt': null, " + "'postStatus': { $in: ['ACTIVE'] }, " + - "'title': { $regex: ?0 }," + + "'title': ?0," + "'postCategory': { $in: ?1 }}") - Page getNoticesByCategory(String department, List postCategories, PageRequest pageRequest); + Page getNoticesByCategory(Pattern prefixPattern, List postCategories, Pageable pageable); @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(ObjectId Id); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java index 11dca23f..fc6fca1d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -53,11 +53,11 @@ public class NoticeService { public NoticePageResponse getAllNotices(Department department, int pageNumber) { validateDepartment(department); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - List postCategories = List.of( - PostCategory.EXTRACURRICULAR_INNER.name(), - PostCategory.DEPARTMENT_NOTICE.name() + List postCategories = List.of( + PostCategory.EXTRACURRICULAR_INNER, + PostCategory.DEPARTMENT_NOTICE ); - String regex = "^\\[" + Pattern.quote(department.getAbbreviation()) + "\\]"; + Pattern regex = Pattern.compile("^" + Pattern.quote("[" + department.getAbbreviation() + "]")); Page notices = noticeRepository.getNoticesByCategory(regex, postCategories, pageRequest); return NoticePageResponse.of(getNoticeListResponse(notices.getContent()), notices.getTotalPages() - 1, notices.hasNext() ? notices.getPageable().getPageNumber() + 1 : -1); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java index 133dca28..d9b9b328 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java @@ -8,6 +8,7 @@ import inu.codin.codin.domain.board.question.service.QuestionService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -50,7 +51,7 @@ public ResponseEntity> getAllQuestions(@Reques ) @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PostMapping - public ResponseEntity> createQuestion(@RequestBody QuestionCreateUpdateRequestDto requestDto) { + public ResponseEntity> createQuestion(@RequestBody @Valid QuestionCreateUpdateRequestDto requestDto) { questionService.createQuestion(requestDto); return ResponseEntity.status(201) .body(new SingleResponse<>(201, "자주 묻는 질문 작성 성공", null)); @@ -63,7 +64,7 @@ public ResponseEntity> createQuestion(@RequestBody QuestionCre @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") @PutMapping("/{questionId}") public ResponseEntity> updateQuestion(@PathVariable("questionId") String id, - @RequestBody QuestionCreateUpdateRequestDto requestDto) { + @RequestBody @Valid QuestionCreateUpdateRequestDto requestDto) { questionService.updateQuestion(id, requestDto); return ResponseEntity.ok() .body(new SingleResponse<>(200, "자주 묻는 질문 수정 성공", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java index ddf38262..66065957 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java @@ -17,7 +17,7 @@ public class QuestionCreateUpdateRequestDto { @NotBlank private String answer; - @Schema(description = "답변 내용", example = "COMPUTER_SCI") + @Schema(description = "학과", example = "COMPUTER_SCI") @NotNull private Department department; } From b5c0efae210d9030ec369403e446f4715785e9ce Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 17:39:52 +0900 Subject: [PATCH 0912/1002] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B5=AC=EC=A1=B0=EB=A5=BC=20ErrorCode=20?= =?UTF-8?q?+=20PollException=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EB=B3=84=20=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/poll/exception/PollDuplicateVoteException.java | 7 ------- .../domain/post/domain/poll/exception/PollErrorCode.java | 1 - .../domain/poll/exception/PollOptionChoiceException.java | 7 ------- .../post/domain/poll/exception/PollTimeFailException.java | 8 -------- 4 files changed, 23 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java deleted file mode 100644 index 819be7cf..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollDuplicateVoteException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.post.domain.poll.exception; - -public class PollDuplicateVoteException extends RuntimeException { - public PollDuplicateVoteException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java index 4da8d44e..f1b95f32 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java @@ -7,7 +7,6 @@ @RequiredArgsConstructor public enum PollErrorCode implements GlobalErrorCode { POLL_NOT_FOUND(HttpStatus.NOT_FOUND, "투표 정보를 찾을 수 없습니다."), - POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."), POLL_FINISHED(HttpStatus.BAD_REQUEST, "이미 종료된 투표입니다."), POLL_DUPLICATED(HttpStatus.CONFLICT, "이미 투표하셨습니다."), MULTIPLE_CHOICE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "복수 선택이 허용되지 않은 투표입니다."), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java deleted file mode 100644 index b9ec960a..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollOptionChoiceException.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codin.domain.post.domain.poll.exception; - -public class PollOptionChoiceException extends RuntimeException{ - public PollOptionChoiceException(String message) { - super(message); - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java deleted file mode 100644 index ba4cce7f..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollTimeFailException.java +++ /dev/null @@ -1,8 +0,0 @@ -package inu.codin.codin.domain.post.domain.poll.exception; - - -public class PollTimeFailException extends RuntimeException{ - public PollTimeFailException(String message) { - super(message); - } -} From 1d3aa2f7eaa642738ad87f1a5072c74372ce6ed6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 17:40:26 +0900 Subject: [PATCH 0913/1002] =?UTF-8?q?refactor:=20post=20<->=20poll=20?= =?UTF-8?q?=EA=B0=84=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=88=9C=ED=99=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20(poll=20CQRS)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../poll/controller/PollController.java | 10 ++--- .../post/domain/poll/entity/PollEntity.java | 7 ++-- ...llService.java => PollCommandService.java} | 32 +++++---------- .../domain/poll/service/PollQueryService.java | 41 +++++++++++++++++++ .../domain/post/service/PostQueryService.java | 8 ++-- 5 files changed, 64 insertions(+), 34 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/{PollService.java => PollCommandService.java} (85%) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollQueryService.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index f1924f4a..e6c1909c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; -import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.domain.post.domain.poll.service.PollCommandService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -18,14 +18,14 @@ @RequiredArgsConstructor public class PollController { - private final PollService pollService; + private final PollCommandService pollCommandService; @Operation(summary = "투표 생성") @PostMapping public ResponseEntity> createPoll( @Valid @RequestBody PollCreateRequestDTO pollRequestDTO) { - pollService.createPoll(pollRequestDTO); + pollCommandService.createPoll(pollRequestDTO); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(201, "투표 생성 완료", null)); } @@ -36,7 +36,7 @@ public ResponseEntity> votingPoll( @PathVariable String postId, @Valid @RequestBody PollVotingRequestDTO pollRequestDTO) { - pollService.votingPoll(postId, pollRequestDTO); + pollCommandService.votingPoll(postId, pollRequestDTO); return ResponseEntity.status(HttpStatus.CREATED) .body(new SingleResponse<>(200, "투표 실시 완료", null)); } @@ -44,7 +44,7 @@ public ResponseEntity> votingPoll( @Operation(summary = "투표 취소") @DeleteMapping("/voting/{postId}") public ResponseEntity> deleteVoting(@PathVariable String postId){ - pollService.deleteVoting(postId); + pollCommandService.deleteVoting(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "투표 취소 완료", null)); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index affbc611..5ae43661 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -2,7 +2,8 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; +import inu.codin.codin.domain.post.domain.poll.exception.PollErrorCode; +import inu.codin.codin.domain.post.domain.poll.exception.PollException; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -48,14 +49,14 @@ public PollEntity(ObjectId postId, List pollOptions, //각 옵션의 투표 수 증가 public void vote(int optionIndex) { if (optionIndex < 0 || optionIndex >= this.pollOptions.size()) { - throw new PollOptionChoiceException("잘못된 선택지입니다."); + throw new PollException(PollErrorCode.INVALID_OPTION); } this.pollVotesCounts.set(optionIndex, this.pollVotesCounts.get(optionIndex) + 1); } public void deleteVote(int optionIndex){ if (this.pollVotesCounts.get(optionIndex) - 1 < 0) - throw new PollOptionChoiceException("올바르지 않은 선택지입니다."); + throw new PollException(PollErrorCode.INVALID_OPTION); this.pollVotesCounts.set(optionIndex, this.pollVotesCounts.get(optionIndex) - 1); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index 66b4b014..b54fd090 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -5,14 +5,16 @@ import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; -import inu.codin.codin.domain.post.domain.poll.exception.PollException; import inu.codin.codin.domain.post.domain.poll.exception.PollErrorCode; +import inu.codin.codin.domain.post.domain.poll.exception.PollException; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; -import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.exception.PostErrorCode; +import inu.codin.codin.domain.post.exception.PostException; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostQueryService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -20,16 +22,16 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; + @Service @RequiredArgsConstructor @Slf4j -public class PollService { +public class PollCommandService { - private final PostRepository postRepository; private final PollRepository pollRepository; private final PollVoteRepository pollVoteRepository; + private final PostRepository postRepository; @Transactional public void createPoll(PollCreateRequestDTO pollRequestDTO) { @@ -66,7 +68,7 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> { log.warn("투표 실패 - 게시글 없음 - postId: {}", postId); - return new PollException(PollErrorCode.POST_NOT_FOUND); + return new PostException(PostErrorCode.POST_NOT_FOUND); }); PollEntity poll = pollRepository.findByPostId(post.get_id()) @@ -122,7 +124,7 @@ public void deleteVoting(String postId) { PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) .orElseThrow(() -> { log.warn("투표 취소 실패 - 게시글 없음 - postId: {}", postId); - return new PollException(PollErrorCode.POST_NOT_FOUND); + return new PostException(PostErrorCode.POST_NOT_FOUND); }); PollEntity poll = pollRepository.findByPostId(post.get_id()) @@ -151,18 +153,4 @@ public void deleteVoting(String postId) { pollVoteRepository.delete(pollVote); log.info("투표 취소 완료 - pollId: {}, userId: {}", poll.get_id(), userId); } - - public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> new PollException(PollErrorCode.POLL_NOT_FOUND)); - long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); - List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) - .map(PollVoteEntity::getSelectedOptions) - .orElse(Collections.emptyList()); - boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); - boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); - return PollInfoResponseDTO.of( - poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), - poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); - } -} \ No newline at end of file +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollQueryService.java new file mode 100644 index 00000000..190342b6 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollQueryService.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.post.domain.poll.service; + +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.exception.PollErrorCode; +import inu.codin.codin.domain.post.domain.poll.exception.PollException; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.entity.PostEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class PollQueryService { + + private final PollRepository pollRepository; + private final PollVoteRepository pollVoteRepository; + + public PollInfoResponseDTO getPollInfo(PostEntity post, ObjectId userId) { + PollEntity poll = pollRepository.findByPostId(post.get_id()) + .orElseThrow(() -> new PollException(PollErrorCode.POLL_NOT_FOUND)); + long totalParticipants = pollVoteRepository.countByPollId(poll.get_id()); + List userVotes = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) + .map(PollVoteEntity::getSelectedOptions) + .orElse(Collections.emptyList()); + boolean pollFinished = poll.getPollEndTime() != null && LocalDateTime.now().isAfter(poll.getPollEndTime()); + boolean hasUserVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); + return PollInfoResponseDTO.of( + poll.getPollOptions(), poll.getPollEndTime(), poll.isMultipleChoice(), + poll.getPollVotesCounts(), userVotes, totalParticipants, hasUserVoted, pollFinished); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 7ea5bc99..255119dc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -8,7 +8,7 @@ import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import inu.codin.codin.domain.post.domain.poll.service.PollService; +import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; @@ -45,7 +45,7 @@ public class PostQueryService private final PostRepository postRepository; private final UserRepository userRepository; - private final PollService pollService; + private final PollQueryService pollQueryService; private final BlockService blockService; private final PostInteractionService postInteractionService; private final BestService bestService; @@ -166,7 +166,7 @@ private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo); if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = pollService.getPollInfo(post, userId); + PollInfoResponseDTO pollInfo = pollQueryService.getPollInfo(post, userId); return PostPageItemResponseDTO.of(postDTO, pollInfo); } else { return PostPageItemResponseDTO.of(postDTO, null); @@ -181,7 +181,7 @@ private UserDto resolveUserProfile(PostEntity post) { } // [유저 프로필] - 게시물에 대한 유저정보 추출 - public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ + private UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ return UserInfo.of( likeService.isLiked(LikeType.POST, postId, currentUserId), scrapService.isPostScraped(postId, currentUserId), From 8f3e8c800916f466aa61b86eae0b4b6e8003f755 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 18:04:48 +0900 Subject: [PATCH 0914/1002] =?UTF-8?q?refactor:=20Return=20Type=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/poll/controller/PollController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index e6c1909c..d5d7dc3a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -22,7 +22,7 @@ public class PollController { @Operation(summary = "투표 생성") @PostMapping - public ResponseEntity> createPoll( + public ResponseEntity> createPoll( @Valid @RequestBody PollCreateRequestDTO pollRequestDTO) { pollCommandService.createPoll(pollRequestDTO); @@ -32,7 +32,7 @@ public ResponseEntity> createPoll( @Operation(summary = "투표 실시") @PostMapping("/voting/{postId}") - public ResponseEntity> votingPoll( + public ResponseEntity> votingPoll( @PathVariable String postId, @Valid @RequestBody PollVotingRequestDTO pollRequestDTO) { @@ -43,7 +43,7 @@ public ResponseEntity> votingPoll( @Operation(summary = "투표 취소") @DeleteMapping("/voting/{postId}") - public ResponseEntity> deleteVoting(@PathVariable String postId){ + public ResponseEntity> deleteVoting(@PathVariable String postId){ pollCommandService.deleteVoting(postId); return ResponseEntity.ok() .body(new SingleResponse<>(200, "투표 취소 완료", null)); From dfc13d0e7da776114b6eed6d2fbc2f2c25c3d76a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 18:05:25 +0900 Subject: [PATCH 0915/1002] =?UTF-8?q?refactor:=20assignAnonymousNumber()?= =?UTF-8?q?=EB=8A=94=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A7=8C=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B3=A0,=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=A0=80=EC=9E=A5=EC=9D=80=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EC=9E=90(handleCommentCreation)=EC=97=90=EC=84=9C=20=20?= =?UTF-8?q?=ED=95=9C=20=EB=B2=88=20=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/service/PostCommandService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index a55b438e..2891038b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -150,7 +150,6 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { anonymous.setWriter(userId); } else {anonymous.setAnonNumber(userId); } - postRepository.save(post); log.info("익명 번호 할당. PostId: {}, UserId: {}", post.get_id(), userId); } From c3568ab82010ec1ce6fef54cbd65e81216148419 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 19:14:59 +0900 Subject: [PATCH 0916/1002] =?UTF-8?q?refactor:=20post,comment=20UserInfo?= =?UTF-8?q?=20=EA=B3=B5=EC=9A=A9=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @JsonInclude(Include.NON_NULL) 적용하여 불필요한 필드 응답 제외 --- .../dto/response/CommentResponseDTO.java | 11 +---- .../comment/service/CommentQueryService.java | 10 ++--- .../reply/service/ReplyQueryService.java | 9 ++-- .../codin/codin/domain/post/dto/UserInfo.java | 43 ++++++++++++++----- .../domain/post/service/PostQueryService.java | 2 +- 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 2af63ce9..998fe32b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -4,6 +4,7 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -124,14 +125,4 @@ public CommentResponseDTO repliesFrom(List upd .build(); } - @Getter - public static class UserInfo { - private final boolean isLike; - - @Builder - public UserInfo(boolean isLike) { - this.isLike = isLike; - } - } - } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index e0a837ce..db9587ad 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostQueryService; @@ -111,13 +112,12 @@ private CommentResponseDTO buildCommentResponseDTO( } - public CommentResponseDTO.UserInfo getUserInfoAboutComment(ObjectId commentId) { + public UserInfo getUserInfoAboutComment(ObjectId commentId) { ObjectId userId = SecurityUtils.getCurrentUserId(); - return CommentResponseDTO.UserInfo.builder() - .isLike(likeService.isLiked(LikeType.COMMENT, commentId, userId)) - .build(); + return UserInfo.ofComment( + likeService.isLiked(LikeType.COMMENT, commentId, userId) + ); } - /** * * @param commentId diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java index 86b60cc1..b23739e0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.user.entity.UserEntity; @@ -103,11 +104,11 @@ private CommentResponseDTO buildReplyResponseDTO( ); } - public CommentResponseDTO.UserInfo getUserInfoAboutReply(ObjectId replyId) { + public UserInfo getUserInfoAboutReply(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); - return CommentResponseDTO.UserInfo.builder() - .isLike(likeService.isLiked(LikeType.COMMENT, replyId, userId)) - .build(); + return UserInfo.ofComment( + likeService.isLiked(LikeType.COMMENT, replyId, userId) + ); } /** diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java index 38df0b68..84e4b003 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserInfo.java @@ -1,26 +1,47 @@ package inu.codin.codin.domain.post.dto; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter public class UserInfo { - private final boolean isLike; - private final boolean isScrap; - private final boolean isMine; + + @Schema(description = "좋아요 여부", example = "true") + private final Boolean like; + + //해당 값이 null일 경우 직렬화(JSON 변환) 시 필드 제외 + @Schema(description = "스크랩 여부", example = "false", nullable = true) + @JsonInclude(Include.NON_NULL) + private final Boolean scrap; + + //해당 값이 null일 경우 직렬화(JSON 변환) 시 필드 제외 + @Schema(description = "내가 쓴 글여부", example = "false", nullable = true) + @JsonInclude(Include.NON_NULL) + private final Boolean mine; @Builder - private UserInfo(boolean isLike, boolean isScrap, boolean isMine) { - this.isLike = isLike; - this.isScrap = isScrap; - this.isMine = isMine; + private UserInfo(Boolean like, Boolean scrap, Boolean mine) { + this.like = like; + this.scrap = scrap; + this.mine = mine; + } + + public static UserInfo ofPost(boolean like, boolean scrap, boolean mine) { + return UserInfo.builder() + .like(like) + .scrap(scrap) + .mine(mine) + .build(); } - public static UserInfo of(boolean isLike, boolean isScrap, boolean isMine) { + public static UserInfo ofComment(boolean like) { return UserInfo.builder() - .isLike(isLike) - .isScrap(isScrap) - .isMine(isMine) + .like(like) + .scrap(null) + .mine(null) .build(); } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 255119dc..9ddbbedb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -182,7 +182,7 @@ private UserDto resolveUserProfile(PostEntity post) { // [유저 프로필] - 게시물에 대한 유저정보 추출 private UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ - return UserInfo.of( + return UserInfo.ofPost( likeService.isLiked(LikeType.POST, postId, currentUserId), scrapService.isPostScraped(postId, currentUserId), postUserId.equals(currentUserId) From c74de2d72aabddcd7fac84416a7961d59df60ea5 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 20:31:20 +0900 Subject: [PATCH 0917/1002] =?UTF-8?q?refactor:=20entity=EB=82=B4=20JsonFor?= =?UTF-8?q?mat=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20pollopiton=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=8B=9C=20=EC=83=88=EB=A1=9C=EC=9A=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D(=EB=B0=A9=EC=96=B4=EC=A0=81=20?= =?UTF-8?q?=EB=B3=B5=EC=82=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/domain/poll/entity/PollEntity.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 5ae43661..7a1bd2f1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -24,10 +24,9 @@ public class PollEntity extends BaseTimeEntity { private ObjectId _id; private ObjectId postId; // PostEntity와의 관계를 유지하기 위한 필드 - private List pollOptions = new ArrayList<>(); // 설문조사 선택지 - private List pollVotesCounts = new ArrayList<>(); // 선택지별 투표 수 + private final List pollOptions; // 설문조사 선택지 + private final List pollVotesCounts; // 선택지별 투표 수 - @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "Asia/Seoul") // JSON 직렬화/역직렬화 시 포맷 지정 private LocalDateTime pollEndTime; // 설문조사 종료 시간 private boolean multipleChoice; // 복수 선택 가능 여부 @@ -36,7 +35,7 @@ public class PollEntity extends BaseTimeEntity { public PollEntity(ObjectId postId, List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice) { this.postId = postId; - this.pollOptions = pollOptions; + this.pollOptions = new ArrayList<>(pollOptions); // pollVotesCounts가 null일 경우, pollOptions의 크기만큼 0으로 초기화 this.pollVotesCounts = new ArrayList<>(Collections.nCopies(this.pollOptions.size(), 0)); From f7a93f1be1615cf4b154e1a6a3600b1f552ed073 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 20 Aug 2025 20:31:59 +0900 Subject: [PATCH 0918/1002] =?UTF-8?q?refactor=20:=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EA=B5=AC=EC=A1=B0(CQRS)=20=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=AC=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/CommentServiceTest.java | 232 ---------- ...eTest.java => PostCommandServiceTest.java} | 327 ++++++++----- .../post/PostInteractionServiceTest.java | 222 +++++++++ .../domain/post/PostQueryServiceTest.java | 435 ++++++++++++++++++ .../domain/post/ReplyCommentServiceTest.java | 231 ---------- .../domain/post/domain/HitsServiceTest.java | 80 ---- .../domain/post/domain/PollServiceTest.java | 199 -------- .../post/domain/best/BestServiceTest.java | 219 +++++++++ .../comment/CommentCommandServiceTest.java | 235 ++++++++++ .../comment/CommentQueryServiceTest.java | 320 +++++++++++++ .../post/domain/hits/HitsServiceTest.java | 270 +++++++++++ .../domain/poll/PollCommandServiceTest.java | 364 +++++++++++++++ .../domain/poll/PollQueryServiceTest.java | 291 ++++++++++++ .../domain/reply/ReplyCommandServiceTest.java | 261 +++++++++++ .../domain/reply/ReplyQueryServiceTest.java | 279 +++++++++++ 15 files changed, 3096 insertions(+), 869 deletions(-) delete mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java rename codin-core/src/test/java/inu/codin/codin/domain/post/{PostServiceTest.java => PostCommandServiceTest.java} (51%) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java delete mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java delete mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java delete mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/best/BestServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/HitsServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java deleted file mode 100644 index 41dbcab6..00000000 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/CommentServiceTest.java +++ /dev/null @@ -1,232 +0,0 @@ -package inu.codin.codin.domain.post; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.notification.service.NotificationService; -import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.comment.service.CommentService; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisBestService; -import inu.codin.codin.infra.s3.S3Service; -import org.bson.types.ObjectId; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.MockedStatic; - -import java.util.*; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class CommentServiceTest { - @InjectMocks - private CommentService commentService; - @Mock private PostRepository postRepository; - @Mock private CommentRepository commentRepository; - @Mock private UserRepository userRepository; - @Mock private LikeService likeService; - @Mock private NotificationService notificationService; - @Mock private RedisBestService redisBestService; - @Mock private S3Service s3Service; - @Mock private ReplyCommentService replyCommentService; - private static MockedStatic securityUtilsMock; - - @BeforeEach - void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); - } - @AfterEach - void tearDown() { - securityUtilsMock.close(); - } - - @Test - void 댓글_정상등록_성공() throws Exception { - // Given - String postId = new ObjectId().toString(); - CommentCreateRequestDTO dto = createCommentCreateRequestDTO("내용", true); - PostEntity post = createPostEntity(); - ObjectId userId = new ObjectId(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(commentRepository.save(any())).willAnswer(inv -> { - CommentEntity entity = inv.getArgument(0); - java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); - idField.setAccessible(true); - idField.set(entity, new ObjectId()); - return entity; - }); - given(postRepository.save(any())).willReturn(post); - willDoNothing().given(redisBestService).applyBestScore(anyInt(), any()); - willDoNothing().given(notificationService).sendNotificationMessageByComment(any(), any(), any(), any()); - // When - commentService.addComment(postId, dto); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(CommentEntity.class); - verify(commentRepository).save(captor.capture()); - CommentEntity saved = captor.getValue(); - assertThat(saved.getContent()).isEqualTo("내용"); - assertThat(saved.isAnonymous()).isTrue(); - assertThat(saved.getPostId()).isNotNull(); - assertThat(saved.getUserId()).isEqualTo(userId); - } - - @Test - void 댓글_등록_게시글없음_예외() throws Exception { - // Given - String postId = new ObjectId().toString(); - CommentCreateRequestDTO dto = createCommentCreateRequestDTO("내용", true); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> commentService.addComment(postId, dto)).isInstanceOf(NotFoundException.class); - } - - @Test - void 댓글_소프트삭제_성공() throws Exception { - // Given - String commentId = new ObjectId().toString(); - CommentEntity comment = createCommentEntity(); - given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(comment)); - securityUtilsMock.when(() -> SecurityUtils.validateUser(any())).thenAnswer(invocation -> null); - given(commentRepository.save(any())).willReturn(comment); - // When - commentService.softDeleteComment(commentId); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(CommentEntity.class); - verify(commentRepository).save(captor.capture()); - CommentEntity deleted = captor.getValue(); - assertThat(deleted.getDeletedAt()).isNotNull(); - } - - @Test - void 댓글_소프트삭제_댓글없음_예외() { - // Given - String commentId = new ObjectId().toString(); - given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> commentService.softDeleteComment(commentId)).isInstanceOf(NotFoundException.class); - } - - @Test - void 게시글별_댓글목록_조회_성공() { - // Given - String postId = new ObjectId().toString(); - ObjectId userId = new ObjectId(); - PostEntity post = createPostEntity(); - CommentEntity comment = CommentEntity.builder() - ._id(new ObjectId()) - .userId(userId) - .postId(new ObjectId()) - .content("내용") - .anonymous(true) - .build(); - List comments = List.of(comment); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(commentRepository.findByPostId(any())).willReturn(comments); - given(s3Service.getDefaultProfileImageUrl()).willReturn("defaultUrl"); - UserEntity user = UserEntity.builder() - .nickname("닉네임") - .profileImageUrl("url") - .build(); - try { - setField(user, "_id", userId); - } catch (Exception e) { - throw new RuntimeException(e); - } - given(userRepository.findAllById(any())).willReturn(List.of(user)); - // When - List result = commentService.getCommentsByPostId(postId); - // Then - assertThat(result).isNotNull(); - assertThat(result).hasSize(1); - assertThat(result.get(0).getContent()).isEqualTo("내용"); - } - - @Test - void 게시글별_댓글목록_조회_게시글없음_예외() { - // Given - String postId = new ObjectId().toString(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> commentService.getCommentsByPostId(postId)).isInstanceOf(NotFoundException.class); - } - - @Test - void 댓글_수정_성공() throws Exception { - // Given - String commentId = new ObjectId().toString(); - CommentUpdateRequestDTO dto = createCommentUpdateRequestDTO("수정내용"); - CommentEntity comment = createCommentEntity(); - given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(comment)); - given(commentRepository.save(any())).willReturn(comment); - // When - commentService.updateComment(commentId, dto); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(CommentEntity.class); - verify(commentRepository).save(captor.capture()); - CommentEntity updated = captor.getValue(); - assertThat(updated.getContent()).isEqualTo("수정내용"); - } - - @Test - void 댓글_수정_댓글없음_예외() throws Exception { - // Given - String commentId = new ObjectId().toString(); - CommentUpdateRequestDTO dto = createCommentUpdateRequestDTO("수정내용"); - given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> commentService.updateComment(commentId, dto)).isInstanceOf(NotFoundException.class); - } - - @Test - void 댓글_유저정보_조회_성공() { - // Given - ObjectId commentId = new ObjectId(); - ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(likeService.isLiked(any(), any(), any())).willReturn(true); - // When - CommentResponseDTO.UserInfo info = commentService.getUserInfoAboutComment(commentId); - // Then - assertThat(info).isNotNull(); - assertThat(info.isLike()).isTrue(); - } - - // --- 리플렉션 기반 DTO/Entity 생성 유틸리티 --- - private PostEntity createPostEntity() { - return PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).build(); - } - private CommentEntity createCommentEntity() { - return CommentEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postId(new ObjectId()).content("내용").anonymous(true).build(); - } - private CommentCreateRequestDTO createCommentCreateRequestDTO(String content, boolean anonymous) throws Exception { - CommentCreateRequestDTO dto = CommentCreateRequestDTO.class.getDeclaredConstructor().newInstance(); - setField(dto, "content", content); - setField(dto, "anonymous", anonymous); - return dto; - } - private CommentUpdateRequestDTO createCommentUpdateRequestDTO(String content) throws Exception { - CommentUpdateRequestDTO dto = CommentUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); - setField(dto, "content", content); - return dto; - } - private void setField(Object target, String field, Object value) throws Exception { - java.lang.reflect.Field f = target.getClass().getDeclaredField(field); - f.setAccessible(true); - f.set(target, value); - } -} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java similarity index 51% rename from codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java rename to codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java index 0f5357d8..d7e38acd 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java @@ -3,24 +3,21 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.exception.JwtException; import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.dto.request.*; -import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.post.service.*; -import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostInteractionService; +import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.user.entity.UserRole; -import inu.codin.codin.domain.user.repository.UserRepository; import org.bson.types.ObjectId; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.*; import org.springframework.web.multipart.MultipartFile; import java.util.*; @@ -30,205 +27,255 @@ import static org.mockito.BDDMockito.*; @ExtendWith(MockitoExtension.class) -class PostServiceTest { +class PostCommandServiceTest { + @InjectMocks private PostCommandService postCommandService; - - @InjectMocks - private PostQueryService postQueryService; - - @InjectMocks - private PostInteractionService postInteractionService; - - @InjectMocks - private BestPostService bestpostService; - + @Mock private PostRepository postRepository; - @Mock private UserRepository userRepository; - @Mock private BlockService blockService; + @Mock private PostInteractionService postInteractionService; + @Mock private PostQueryService postQueryService; + private static AutoCloseable securityUtilsMock; - + @BeforeEach void setUp() { securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); } - + @AfterEach void tearDown() throws Exception { securityUtilsMock.close(); } - + @Test - void 게시글_정상등록_성공() throws Exception { + void createPost_정상등록_성공() throws Exception { // Given PostCreateRequestDTO dto = createPostCreateRequestDTO("제목", "내용", true, PostCategory.COMMUNICATION); List images = new ArrayList<>(); ObjectId userId = new ObjectId(); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willAnswer(inv -> { PostEntity entity = inv.getArgument(0); - java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); - idField.setAccessible(true); - idField.set(entity, new ObjectId()); + setIdField(entity, new ObjectId()); return entity; }); - // When - // Then + + // When & Then assertThatCode(() -> postCommandService.createPost(dto, images)).doesNotThrowAnyException(); + verify(postRepository).save(any(PostEntity.class)); } - + @Test - void 게시글_등록_권한없음_예외() throws Exception { + void createPost_비교과_권한없음_예외() throws Exception { // Given PostCreateRequestDTO dto = createPostCreateRequestDTO("제목", "내용", true, PostCategory.EXTRACURRICULAR); List images = new ArrayList<>(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + ObjectId userId = new ObjectId(); + + given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); - given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); + // When & Then - assertThatThrownBy(() -> postCommandService.createPost(dto, images)).isInstanceOf(JwtException.class); + assertThatThrownBy(() -> postCommandService.createPost(dto, images)) + .isInstanceOf(JwtException.class) + .hasMessageContaining("비교과 게시글에 대한 권한이 없습니다."); } - + @Test - void updatePostContent_success() throws Exception { + void updatePostContent_정상수정_성공() throws Exception { // Given String postId = new ObjectId().toString(); - PostContentUpdateRequestDTO dto = createPostContentUpdateRequestDTO("수정내용"); + PostContentUpdateRequestDTO dto = createPostContentUpdateRequestDTO("수정된 내용"); List images = new ArrayList<>(); PostEntity post = createPostEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + List imageUrls = Arrays.asList("image1.jpg", "image2.jpg"); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); + given(postInteractionService.handleImageUpload(any())).willReturn(imageUrls); given(postRepository.save(any())).willReturn(post); - // When/Then + + // When & Then assertThatCode(() -> postCommandService.updatePostContent(postId, dto, images)).doesNotThrowAnyException(); + verify(postRepository).save(post); } - + @Test - void updatePostContent_notFound() throws Exception { - // Given - String postId = new ObjectId().toString(); - PostContentUpdateRequestDTO dto = createPostContentUpdateRequestDTO("수정내용"); - List images = List.of(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When/Then - assertThatThrownBy(() -> postCommandService.updatePostContent(postId, dto, images)) - .isInstanceOf(NotFoundException.class); - } - - @Test - void updatePostAnonymous_success() throws Exception { + void updatePostAnonymous_정상수정_성공() throws Exception { // Given String postId = new ObjectId().toString(); PostAnonymousUpdateRequestDTO dto = createPostAnonymousUpdateRequestDTO(true); PostEntity post = createPostEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); - // When/Then + + // When & Then assertThatCode(() -> postCommandService.updatePostAnonymous(postId, dto)).doesNotThrowAnyException(); + verify(postRepository).save(post); } - + @Test - void updatePostStatus_success() throws Exception { + void updatePostStatus_정상수정_성공() throws Exception { // Given String postId = new ObjectId().toString(); PostStatusUpdateRequestDTO dto = createPostStatusUpdateRequestDTO(PostStatus.ACTIVE); PostEntity post = createPostEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); - // When/Then + + // When & Then assertThatCode(() -> postCommandService.updatePostStatus(postId, dto)).doesNotThrowAnyException(); + verify(postRepository).save(post); } - + @Test - void getAllPosts_success() { - given(blockService.getBlockedUsers()).willReturn(new ArrayList<>()); - List posts = new ArrayList<>(); - Page page = new PageImpl<>(posts); - given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any())).willReturn(page); - var response = postQueryService.getAllPosts(PostCategory.COMMUNICATION, 0); - assertThat(response).isNotNull(); - assertThat(response.getContents()).isInstanceOf(List.class); + void softDeletePost_정상삭제_성공() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(postRepository.save(any())).willReturn(post); + + // When & Then + assertThatCode(() -> postCommandService.softDeletePost(postId)).doesNotThrowAnyException(); + verify(postRepository).save(post); } - + @Test - void getPostWithDetail_success() { + void deletePostImage_정상삭제_성공() throws Exception { // Given String postId = new ObjectId().toString(); + String imageUrl = "test-image.jpg"; PostEntity post = createPostEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + + given(postQueryService.findPostById(any())).willReturn(post); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - doNothing().when(postInteractionService).increaseHits(any(), any()); - given(postQueryService.getLikeCount(any())).willReturn(0); - given(postQueryService.getScrapCount(any())).willReturn(0); - given(postQueryService.getHitsCount(any())).willReturn(0); - given(userRepository.findById(any())).willReturn(Optional.of(UserEntity.builder().nickname("닉네임").profileImageUrl("url").build())); + doNothing().when(postInteractionService).deletePostImageInternal(any(), any()); + + // When & Then + assertThatCode(() -> postCommandService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); + verify(postInteractionService).deletePostImageInternal(post, imageUrl); + } + + @Test + void handleCommentCreation_정상처리_성공() throws Exception { + // Given + PostEntity post = createPostEntityWithAnonymous(); + ObjectId userId = new ObjectId(); + + given(postRepository.save(any())).willReturn(post); + // When - PostPageItemResponseDTO response = postQueryService.getPostWithDetail(postId); + postCommandService.handleCommentCreation(post, userId); + // Then - assertThat(response).isNotNull(); - assertThat(response.getPost()).isNotNull(); + verify(postRepository).save(post); } - + @Test - void softDeletePost_success() { - // Given- - String postId = new ObjectId().toString(); + void increaseCommentCount_댓글수증가_성공() throws Exception { + // Given PostEntity post = createPostEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - given(postRepository.save(any())).willReturn(post); - // When/Then - assertThatCode(() -> postCommandService.softDeletePost(postId)).doesNotThrowAnyException(); + int initialCount = post.getCommentCount(); + + // When + postCommandService.increaseCommentCount(post); + + // Then + assertThat(post.getCommentCount()).isEqualTo(initialCount + 1); } - + @Test - void deletePostImage_success() throws Exception { + void decreaseCommentCount_댓글수감소_성공() throws Exception { // Given - String postId = new ObjectId().toString(); - String imageUrl = "img.jpg"; - List imageList = new ArrayList<>(List.of(imageUrl)); - PostEntity post = PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).postImageUrls(imageList).build(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); - doNothing().when(postInteractionService).deletePostImageInternal(any(), any()); - - // When/Then - assertThatCode(() -> postCommandService.deletePostImage(postId, imageUrl)).doesNotThrowAnyException(); + PostEntity post = createPostEntityWithComments(); + int initialCount = post.getCommentCount(); + + given(postRepository.save(any())).willReturn(post); + + // When + postCommandService.decreaseCommentCount(post); + + // Then + assertThat(post.getCommentCount()).isEqualTo(initialCount - 1); + verify(postRepository).save(post); } - + @Test - void searchPosts_success() { - given(blockService.getBlockedUsers()).willReturn(new ArrayList<>()); - List posts = new ArrayList<>(); - Page page = new PageImpl<>(posts); - given(postRepository.findAllByKeywordAndDeletedAtIsNull(anyString(), anyList(), any())).willReturn(page); - PostPageResponse response = postQueryService.searchPosts("테스트", 0); - assertThat(response).isNotNull(); + void assignAnonymousNumber_익명아님_처리안함() throws Exception { + // Given + PostEntity post = createPostEntity(); // anonymous = false + ObjectId userId = new ObjectId(); + + // When + postCommandService.assignAnonymousNumber(post, userId); + + // Then + verify(postRepository, never()).save(any()); } - + @Test - void getTop3BestPosts_success() { - given(bestpostService.getTop3BestPostsInternal()).willReturn(new ArrayList<>()); - var result = postQueryService.getTop3BestPosts(); - assertThat(result).isNotNull(); - assertThat(result).isInstanceOf(List.class); + void assignAnonymousNumber_이미할당됨_처리안함() throws Exception { + // Given + PostEntity post = createPostEntityWithAnonymous(); + ObjectId userId = new ObjectId(); + PostAnonymous anonymous = post.getAnonymous(); + anonymous.setAnonNumber(userId); // 이미 할당된 상태로 설정 + + // When + postCommandService.assignAnonymousNumber(post, userId); + + // Then + verify(postRepository, never()).save(any()); } - + @Test - void getBestPosts_success() { - given(bestpostService.getBestPostsInternal(anyInt())).willReturn(new PageImpl<>(new ArrayList<>())); - var result = postQueryService.getBestPosts(0); - assertThat(result).isNotNull(); - assertThat(result.getContents()).isInstanceOf(List.class); + void assignAnonymousNumber_작성자_익명할당_성공() throws Exception { + // Given + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder() + .userId(userId) + .postCategory(PostCategory.COMMUNICATION) + .isAnonymous(true) + .build(); + + // When + postCommandService.assignAnonymousNumber(post, userId); + + // Then + assertThat(post.getAnonymous().hasAnonNumber(userId)).isEqualTo(true); } + + @Test + void assignAnonymousNumber_일반사용자_익명번호할당_성공() throws Exception { + // Given + PostEntity post = createPostEntityWithAnonymous(); + ObjectId userId = new ObjectId(); + + // When + postCommandService.assignAnonymousNumber(post, userId); + + // Then + assertThat(post.getAnonymous().hasAnonNumber(userId)).isEqualTo(true); - // --- 리플렉션 기반 DTO 생성 유틸리티 --- + } + + // Helper methods private PostCreateRequestDTO createPostCreateRequestDTO(String title, String content, boolean anonymous, PostCategory category) throws Exception { PostCreateRequestDTO dto = PostCreateRequestDTO.class.getDeclaredConstructor().newInstance(); setField(dto, "title", title); @@ -237,33 +284,59 @@ private PostCreateRequestDTO createPostCreateRequestDTO(String title, String con setField(dto, "postCategory", category); return dto; } + private PostContentUpdateRequestDTO createPostContentUpdateRequestDTO(String content) throws Exception { PostContentUpdateRequestDTO dto = PostContentUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); setField(dto, "content", content); return dto; } + private PostAnonymousUpdateRequestDTO createPostAnonymousUpdateRequestDTO(boolean anonymous) throws Exception { PostAnonymousUpdateRequestDTO dto = PostAnonymousUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); setField(dto, "anonymous", anonymous); return dto; } + private PostStatusUpdateRequestDTO createPostStatusUpdateRequestDTO(PostStatus status) throws Exception { PostStatusUpdateRequestDTO dto = PostStatusUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); setField(dto, "postStatus", status); return dto; } + private void setField(Object target, String field, Object value) throws Exception { java.lang.reflect.Field f = target.getClass().getDeclaredField(field); f.setAccessible(true); f.set(target, value); } - - // 모든 PostEntity fixture에 _id 세팅 + + private void setIdField(PostEntity entity, ObjectId id) throws Exception { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } + private PostEntity createPostEntity() { return PostEntity.builder() - ._id(new ObjectId()) + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .isAnonymous(false) + .build(); + } + + private PostEntity createPostEntityWithAnonymous() { + return PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .isAnonymous(true) + .build(); + } + + private PostEntity createPostEntityWithComments() { + PostEntity post = PostEntity.builder() .userId(new ObjectId()) .postCategory(PostCategory.COMMUNICATION) .build(); + post.plusCommentCount(); // 댓글 수를 1로 설정 + return post; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java new file mode 100644 index 00000000..9254845a --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java @@ -0,0 +1,222 @@ +package inu.codin.codin.domain.post; + +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostInteractionService; +import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.infra.s3.exception.ImageRemoveException; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class PostInteractionServiceTest { + + @InjectMocks + private PostInteractionService postInteractionService; + + @Mock private S3Service s3Service; + @Mock private PostRepository postRepository; + @Mock private HitsService hitsService; + + @Test + void handleImageUpload_이미지업로드_성공() { + // Given + List images = Arrays.asList( + mock(MultipartFile.class), + mock(MultipartFile.class) + ); + List expectedUrls = Arrays.asList("image1.jpg", "image2.jpg"); + + given(s3Service.handleImageUpload(images)).willReturn(expectedUrls); + + // When + List result = postInteractionService.handleImageUpload(images); + + // Then + assertThat(result).isEqualTo(expectedUrls); + verify(s3Service).handleImageUpload(images); + } + + @Test + void handleImageUpload_빈리스트_빈리스트반환() { + // Given + List emptyImages = new ArrayList<>(); + List emptyUrls = new ArrayList<>(); + + given(s3Service.handleImageUpload(emptyImages)).willReturn(emptyUrls); + + // When + List result = postInteractionService.handleImageUpload(emptyImages); + + // Then + assertThat(result).isEmpty(); + verify(s3Service).handleImageUpload(emptyImages); + } + + @Test + void deletePostImageInternal_정상삭제_성공() { + // Given + String imageUrl = "test-image.jpg"; + PostEntity post = createPostEntityWithImages(Arrays.asList(imageUrl, "other-image.jpg")); + + doNothing().when(s3Service).deleteFile(imageUrl); + given(postRepository.save(any())).willReturn(post); + + // When + assertThatCode(() -> postInteractionService.deletePostImageInternal(post, imageUrl)) + .doesNotThrowAnyException(); + + // Then + verify(s3Service).deleteFile(imageUrl); + verify(postRepository).save(post); + } + + @Test + void deletePostImageInternal_이미지없음_예외() { + // Given + String nonExistentImageUrl = "non-existent.jpg"; + PostEntity post = createPostEntityWithImages(Arrays.asList("image1.jpg", "image2.jpg")); + + // When & Then + assertThatThrownBy(() -> postInteractionService.deletePostImageInternal(post, nonExistentImageUrl)) + .isInstanceOf(PostException.class); + + verify(s3Service, never()).deleteFile(any()); + verify(postRepository, never()).save(any()); + } + + @Test + void deletePostImageInternal_S3삭제실패_예외() { + // Given + String imageUrl = "test-image.jpg"; + PostEntity post = createPostEntityWithImages(Arrays.asList(imageUrl)); + + doThrow(new RuntimeException("S3 삭제 실패")).when(s3Service).deleteFile(imageUrl); + + // When & Then + assertThatThrownBy(() -> postInteractionService.deletePostImageInternal(post, imageUrl)) + .isInstanceOf(ImageRemoveException.class) + .hasMessageContaining("이미지 삭제 중 오류 발생"); + + verify(s3Service).deleteFile(imageUrl); + verify(postRepository, never()).save(any()); + } + + @Test + void increaseHits_조회수증가_성공() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + given(hitsService.validateHits(post.get_id(), userId)).willReturn(false); + doNothing().when(hitsService).addHits(post.get_id(), userId); + + // When + postInteractionService.increaseHits(post, userId); + + // Then + verify(hitsService).validateHits(post.get_id(), userId); + verify(hitsService).addHits(post.get_id(), userId); + } + + @Test + void increaseHits_이미조회함_조회수증가안함() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + given(hitsService.validateHits(post.get_id(), userId)).willReturn(true); + + // When + postInteractionService.increaseHits(post, userId); + + // Then + verify(hitsService).validateHits(post.get_id(), userId); + verify(hitsService, never()).addHits(any(), any()); + } + + @Test + void increaseHits_동일사용자_중복조회방지() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + // 첫 번째 호출: 조회수 증가 + given(hitsService.validateHits(post.get_id(), userId)) + .willReturn(false) // 첫 번째는 증가 + .willReturn(true); // 두 번째는 증가하지 않음 + doNothing().when(hitsService).addHits(post.get_id(), userId); + + // When + postInteractionService.increaseHits(post, userId); // 첫 번째 호출 + postInteractionService.increaseHits(post, userId); // 두 번째 호출 + + // Then + verify(hitsService, times(2)).validateHits(post.get_id(), userId); + verify(hitsService, times(1)).addHits(post.get_id(), userId); // 한 번만 호출 + } + + @Test + void increaseHits_다른사용자_각각증가() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + + given(hitsService.validateHits(any(), any())).willReturn(false); + doNothing().when(hitsService).addHits(any(), any()); + + // When + postInteractionService.increaseHits(post, userId1); + postInteractionService.increaseHits(post, userId2); + + // Then + verify(hitsService).validateHits(post.get_id(), userId1); + verify(hitsService).validateHits(post.get_id(), userId2); + verify(hitsService).addHits(post.get_id(), userId1); + verify(hitsService).addHits(post.get_id(), userId2); + } + + // Helper methods + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdField(post, new ObjectId()); + return post; + } + + private PostEntity createPostEntityWithImages(List imageUrls) { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .postImageUrls(new ArrayList<>(imageUrls)) + .build(); + setIdField(post, new ObjectId()); + return post; + } + + private void setIdField(PostEntity entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java new file mode 100644 index 00000000..3e58cace --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java @@ -0,0 +1,435 @@ +package inu.codin.codin.domain.post; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.block.service.BlockService; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.best.BestEntity; +import inu.codin.codin.domain.post.domain.best.BestService; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostInteractionService; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.*; + +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class PostQueryServiceTest { + + @InjectMocks + private PostQueryService postQueryService; + + @Mock private PostRepository postRepository; + @Mock private UserRepository userRepository; + @Mock private PollQueryService pollQueryService; + @Mock private BlockService blockService; + @Mock private PostInteractionService postInteractionService; + @Mock private BestService bestService; + @Mock private ScrapService scrapService; + @Mock private LikeService likeService; + @Mock private S3Service s3Service; + @Mock private HitsService hitsService; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void getAllPosts_카테고리별조회_성공() { + // Given + List blockedUsers = Arrays.asList(new ObjectId(), new ObjectId()); + List posts = Arrays.asList(createPostEntity(), createPostEntity()); + Page page = new PageImpl<>(posts, PageRequest.of(0, 20), 2); + + given(blockService.getBlockedUsers()).willReturn(blockedUsers); + given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any(PageRequest.class))).willReturn(page); + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + + // When + PostPageResponse response = postQueryService.getAllPosts(PostCategory.COMMUNICATION, 0); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getContents()).hasSize(2); + verify(postRepository).getPostsByCategoryWithBlockedUsers(eq("COMMUNICATION"), eq(blockedUsers), any(PageRequest.class)); + } + + @Test + void getAllPosts_빈결과_빈리스트반환() { + // Given + List blockedUsers = new ArrayList<>(); + Page emptyPage = new PageImpl<>(new ArrayList<>(), PageRequest.of(0, 20), 0); + + given(blockService.getBlockedUsers()).willReturn(blockedUsers); + given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any(PageRequest.class))).willReturn(emptyPage); + + // When + PostPageResponse response = postQueryService.getAllPosts(PostCategory.COMMUNICATION, 0); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getContents()).isEmpty(); + } + + @Test + void getPostWithDetail_정상조회_성공() { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + doNothing().when(postInteractionService).increaseHits(any(), any()); + + // When + PostPageItemResponseDTO response = postQueryService.getPostWithDetail(postId); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getPost()).isNotNull(); + verify(postInteractionService).increaseHits(post, userId); + } + + @Test + void getPostWithDetail_게시물없음_예외() { + // Given + String postId = new ObjectId().toString(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> postQueryService.getPostWithDetail(postId)) + .isInstanceOf(PostException.class); + } + + @Test + void getPostDetailById_정상조회_성공() { + // Given + ObjectId postId = new ObjectId(); + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(postId)).willReturn(Optional.of(post)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + doNothing().when(postInteractionService).increaseHits(any(), any()); + + // When + Optional response = postQueryService.getPostDetailById(postId); + + // Then + assertThat(response).isPresent(); + verify(postInteractionService).increaseHits(post, userId); + } + + @Test + void getPostDetailById_게시물없음_빈Optional() { + // Given + ObjectId postId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(postId)).willReturn(Optional.empty()); + + // When + Optional response = postQueryService.getPostDetailById(postId); + + // Then + assertThat(response).isEmpty(); + verify(postInteractionService, never()).increaseHits(any(), any()); + } + + @Test + void searchPosts_키워드검색_성공() { + // Given + String keyword = "테스트"; + List blockedUsers = new ArrayList<>(); + List posts = Arrays.asList(createPostEntity()); + Page page = new PageImpl<>(posts, PageRequest.of(0, 20), 1); + + given(blockService.getBlockedUsers()).willReturn(blockedUsers); + given(postRepository.findAllByKeywordAndDeletedAtIsNull(anyString(), anyList(), any(PageRequest.class))).willReturn(page); + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + + // When + PostPageResponse response = postQueryService.searchPosts(keyword, 0); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getContents()).hasSize(1); + verify(postRepository).findAllByKeywordAndDeletedAtIsNull(eq(keyword), eq(blockedUsers), any(PageRequest.class)); + } + + @Test + void getTop3BestPosts_정상조회_성공() { + // Given + List bestPostIds = Arrays.asList( + new ObjectId().toString(), + new ObjectId().toString(), + new ObjectId().toString() + ); + + given(bestService.getTop3BestPostIds()).willReturn(bestPostIds); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(createPostEntity())); + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + + // When + List response = postQueryService.getTop3BestPosts(); + + // Then + assertThat(response).isNotNull(); + assertThat(response).hasSize(3); + } + + @Test + void getTop3BestPosts_존재하지않는게시물_자동삭제() { + // Given + String validPostId = new ObjectId().toString(); + String invalidPostId = new ObjectId().toString(); + List bestPostIds = Arrays.asList(validPostId, invalidPostId); + + given(bestService.getTop3BestPostIds()).willReturn(bestPostIds); + given(postRepository.findByIdAndNotDeleted(any())) + .willReturn(Optional.of(createPostEntity())) // 첫 번째 호출은 성공 + .willReturn(Optional.empty()); // 두 번째 호출은 실패 + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + doNothing().when(bestService).deleteBestPost(invalidPostId); + + // When + List response = postQueryService.getTop3BestPosts(); + + // Then + assertThat(response).hasSize(1); + verify(bestService).deleteBestPost(invalidPostId); + } + + @Test + void getBestPosts_페이징조회_성공() { + // Given + int pageNumber = 0; + List bestEntities = Arrays.asList(createBestEntity(), createBestEntity()); + Page page = new PageImpl<>(bestEntities, PageRequest.of(0, 20), 2); + + given(bestService.getBestEntities(pageNumber)).willReturn(page); + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(createPostEntity())); + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + mockUserInteractionServices(); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + + // When + PostPageResponse response = postQueryService.getBestPosts(pageNumber); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getContents()).hasSize(2); + verify(bestService).getBestEntities(pageNumber); + } + + @Test + void findPostById_정상조회_성공() { + // Given + ObjectId postId = new ObjectId(); + PostEntity post = createPostEntity(); + + given(postRepository.findByIdAndNotDeleted(postId)).willReturn(Optional.of(post)); + + // When + PostEntity result = postQueryService.findPostById(postId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(post); + } + + @Test + void findPostById_게시물없음_예외() { + // Given + ObjectId postId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(postId)).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> postQueryService.findPostById(postId)) + .isInstanceOf(PostException.class); + } + + @Test + void getLikeCount_좋아요수조회_성공() { + // Given + PostEntity post = createPostEntity(); + int expectedCount = 5; + + given(likeService.getLikeCount(LikeType.POST, post.get_id())).willReturn(expectedCount); + + // When + int result = postQueryService.getLikeCount(post); + + // Then + assertThat(result).isEqualTo(expectedCount); + verify(likeService).getLikeCount(LikeType.POST, post.get_id()); + } + + @Test + void getScrapCount_스크랩수조회_성공() { + // Given + PostEntity post = createPostEntity(); + int expectedCount = 3; + + given(scrapService.getScrapCount(post.get_id())).willReturn(expectedCount); + + // When + int result = postQueryService.getScrapCount(post); + + // Then + assertThat(result).isEqualTo(expectedCount); + verify(scrapService).getScrapCount(post.get_id()); + } + + @Test + void getHitsCount_조회수조회_성공() { + // Given + PostEntity post = createPostEntity(); + int expectedCount = 10; + + given(hitsService.getHitsCount(post.get_id())).willReturn(expectedCount); + + // When + int result = postQueryService.getHitsCount(post); + + // Then + assertThat(result).isEqualTo(expectedCount); + verify(hitsService).getHitsCount(post.get_id()); + } + + @Test + void getUserAnonymousNumber_익명번호조회_성공() { + // Given + ObjectId userId = new ObjectId(); + PostAnonymous postAnonymous = mock(PostAnonymous.class); + Integer expectedNumber = 1; + + given(postAnonymous.getAnonNumber(userId)).willReturn(expectedNumber); + + // When + Integer result = postQueryService.getUserAnonymousNumber(postAnonymous, userId); + + // Then + assertThat(result).isEqualTo(expectedNumber); + verify(postAnonymous).getAnonNumber(userId); + } + + @Test + void toPageItemDTO_Poll게시물_PollInfo포함() { + // Given + PostEntity pollPost = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.POLL) + .build(); + setIdField(pollPost, new ObjectId()); + ObjectId userId = new ObjectId(); + PollInfoResponseDTO pollInfo = mock(PollInfoResponseDTO.class); + + given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollQueryService.getPollInfo(pollPost, userId)).willReturn(pollInfo); + mockUserInteractionServices(); + + // When + PostPageItemResponseDTO result = postQueryService.getPostListResponseDtos(Arrays.asList(pollPost)).get(0); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getPoll()).isEqualTo(pollInfo); + verify(pollQueryService).getPollInfo(pollPost, userId); + } + + // Helper methods + private void mockUserInteractionServices() { + given(likeService.getLikeCount(any(), any())).willReturn(0); + given(scrapService.getScrapCount(any())).willReturn(0); + given(hitsService.getHitsCount(any())).willReturn(0); + given(likeService.isLiked(any(), any(), any())).willReturn(false); + given(scrapService.isPostScraped(any(), any())).willReturn(false); + } + + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .isAnonymous(false) + .build(); + setIdField(post, new ObjectId()); + return post; + } + + private void setIdField(PostEntity entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } + + private UserEntity createUserEntity() { + return UserEntity.builder() + .nickname("테스트유저") + .profileImageUrl("profile.jpg") + .build(); + } + + private BestEntity createBestEntity() { + return BestEntity.builder() + .postId(new ObjectId()) + .build(); + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java deleted file mode 100644 index 558b25c3..00000000 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/ReplyCommentServiceTest.java +++ /dev/null @@ -1,231 +0,0 @@ -package inu.codin.codin.domain.post; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.like.service.LikeService; -import inu.codin.codin.domain.notification.service.NotificationService; -import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommentService; -import inu.codin.codin.domain.post.entity.PostAnonymous; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.report.repository.ReportRepository; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.redis.service.RedisBestService; -import inu.codin.codin.infra.s3.S3Service; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import org.bson.types.ObjectId; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.MockedStatic; - -import java.util.*; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class ReplyCommentServiceTest { - @InjectMocks - private ReplyCommentService replyCommentService; - @Mock private PostRepository postRepository; - @Mock private ReplyCommentRepository replyCommentRepository; - @Mock private UserRepository userRepository; - @Mock private ReportRepository reportRepository; - @Mock private LikeService likeService; - @Mock private NotificationService notificationService; - @Mock private RedisBestService redisBestService; - @Mock private S3Service s3Service; - @Mock private CommentRepository commentRepository; - private static MockedStatic securityUtilsMock; - - @BeforeEach - void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); - } - @AfterEach - void tearDown() { - securityUtilsMock.close(); - } - - @Test - void 대댓글_정상등록_성공() throws Exception { - // Given - String commentId = new ObjectId().toString(); - ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("내용", true); - CommentEntity comment = createCommentEntity(); - PostEntity post = createPostEntity(); - ObjectId userId = new ObjectId(); - given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(comment)); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(replyCommentRepository.save(any())).willAnswer(inv -> { - ReplyCommentEntity entity = inv.getArgument(0); - java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); - idField.setAccessible(true); - idField.set(entity, new ObjectId()); - return entity; - }); - given(postRepository.save(any())).willReturn(post); - willDoNothing().given(redisBestService).applyBestScore(anyInt(), any()); - willDoNothing().given(notificationService).sendNotificationMessageByReply(any(), any(), any(), any()); - // When - replyCommentService.addReply(commentId, dto); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(ReplyCommentEntity.class); - verify(replyCommentRepository).save(captor.capture()); - ReplyCommentEntity saved = captor.getValue(); - assertThat(saved.getContent()).isEqualTo("내용"); - assertThat(saved.isAnonymous()).isTrue(); - assertThat(saved.getCommentId()).isNotNull(); - assertThat(saved.getUserId()).isEqualTo(userId); - } - - @Test - void 대댓글_등록_댓글없음_예외() throws Exception { - // Given - String commentId = new ObjectId().toString(); - ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("내용", true); - given(commentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> replyCommentService.addReply(commentId, dto)).isInstanceOf(NotFoundException.class); - } - - @Test - void 대댓글_소프트삭제_성공() throws Exception { - // Given - String replyId = new ObjectId().toString(); - ReplyCommentEntity reply = createReplyCommentEntity(); - given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(reply)); - securityUtilsMock.when(() -> SecurityUtils.validateUser(any())).thenAnswer(invocation -> null); - given(replyCommentRepository.save(any())).willReturn(reply); - // When - replyCommentService.softDeleteReply(replyId); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(ReplyCommentEntity.class); - verify(replyCommentRepository).save(captor.capture()); - ReplyCommentEntity deleted = captor.getValue(); - assertThat(deleted.getDeletedAt()).isNotNull(); - } - - @Test - void 대댓글_소프트삭제_댓글없음_예외() { - // Given - String replyId = new ObjectId().toString(); - given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> replyCommentService.softDeleteReply(replyId)).isInstanceOf(NotFoundException.class); - } - - @Test - void 댓글별_대댓글목록_조회_성공() { - // Given - PostAnonymous postAnonymous = Mockito.mock(PostAnonymous.class); - ObjectId userId = new ObjectId(); - ObjectId commentId = new ObjectId(); - ReplyCommentEntity reply = ReplyCommentEntity.builder() - ._id(new ObjectId()) - .userId(userId) - .commentId(commentId) - .content("내용") - .anonymous(true) - .build(); - List replies = List.of(reply); - given(replyCommentRepository.findByCommentId(any())).willReturn(replies); - given(s3Service.getDefaultProfileImageUrl()).willReturn("defaultUrl"); - UserEntity user = UserEntity.builder() - .nickname("닉네임") - .profileImageUrl("url") - .build(); - try { - setField(user, "_id", userId); - } catch (Exception e) { - throw new RuntimeException(e); - } - given(userRepository.findAllById(any())).willReturn(List.of(user)); - // When - List result = replyCommentService.getRepliesByCommentId(postAnonymous, commentId); - // Then - assertThat(result).isNotNull(); - assertThat(result).hasSize(1); - assertThat(result.get(0).getContent()).isEqualTo("내용"); - } - - @Test - void 대댓글_수정_성공() throws Exception { - // Given - String replyId = new ObjectId().toString(); - ReplyUpdateRequestDTO dto = createReplyUpdateRequestDTO("수정내용"); - ReplyCommentEntity reply = createReplyCommentEntity(); - given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(reply)); - given(replyCommentRepository.save(any())).willReturn(reply); - // When - replyCommentService.updateReply(replyId, dto); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(ReplyCommentEntity.class); - verify(replyCommentRepository).save(captor.capture()); - ReplyCommentEntity updated = captor.getValue(); - assertThat(updated.getContent()).isEqualTo("수정내용"); - } - - @Test - void 대댓글_수정_댓글없음_예외() throws Exception { - // Given - String replyId = new ObjectId().toString(); - ReplyUpdateRequestDTO dto = createReplyUpdateRequestDTO("수정내용"); - given(replyCommentRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); - // When & Then - assertThatThrownBy(() -> replyCommentService.updateReply(replyId, dto)).isInstanceOf(NotFoundException.class); - } - - @Test - void 대댓글_유저정보_조회_성공() { - // Given - ObjectId replyId = new ObjectId(); - ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(likeService.isLiked(any(), any(), any())).willReturn(true); - // When - CommentResponseDTO.UserInfo info = replyCommentService.getUserInfoAboutReply(replyId); - // Then - assertThat(info).isNotNull(); - assertThat(info.isLike()).isTrue(); - } - - // --- 리플렉션 기반 DTO/Entity 생성 유틸리티 --- - private PostEntity createPostEntity() { - return PostEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postCategory(PostCategory.COMMUNICATION).build(); - } - private ReplyCommentEntity createReplyCommentEntity() { - return ReplyCommentEntity.builder()._id(new ObjectId()).userId(new ObjectId()).commentId(new ObjectId()).content("내용").anonymous(true).build(); - } - private ReplyCreateRequestDTO createReplyCreateRequestDTO(String content, boolean anonymous) throws Exception { - ReplyCreateRequestDTO dto = ReplyCreateRequestDTO.class.getDeclaredConstructor().newInstance(); - setField(dto, "content", content); - setField(dto, "anonymous", anonymous); - return dto; - } - private ReplyUpdateRequestDTO createReplyUpdateRequestDTO(String content) throws Exception { - ReplyUpdateRequestDTO dto = ReplyUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); - setField(dto, "content", content); - return dto; - } - private void setField(Object target, String field, Object value) throws Exception { - java.lang.reflect.Field f = target.getClass().getDeclaredField(field); - f.setAccessible(true); - f.set(target, value); - } - private CommentEntity createCommentEntity() { - return CommentEntity.builder()._id(new ObjectId()).userId(new ObjectId()).postId(new ObjectId()).content("내용").anonymous(true).build(); - } -} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java deleted file mode 100644 index 85625da3..00000000 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/HitsServiceTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package inu.codin.codin.domain.post.domain; - -import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; -import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import inu.codin.codin.infra.redis.config.RedisHealthChecker; -import inu.codin.codin.infra.redis.service.RedisHitsService; -import org.bson.types.ObjectId; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class HitsServiceTest { - @InjectMocks - private HitsService hitsService; - @Mock private RedisHitsService redisHitsService; - @Mock private RedisHealthChecker redisHealthChecker; - @Mock private HitsRepository hitsRepository; - - @Test - void 게시글_조회수_추가_성공() { - // Given - ObjectId postId = new ObjectId(); - ObjectId userId = new ObjectId(); - given(redisHealthChecker.isRedisAvailable()).willReturn(true); - willDoNothing().given(redisHitsService).addHits(postId); - given(hitsRepository.save(any())).willAnswer(inv -> inv.getArgument(0)); - // When - hitsService.addHits(postId, userId); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(HitsEntity.class); - verify(hitsRepository).save(captor.capture()); - HitsEntity saved = captor.getValue(); - assertThat(saved.getPostId()).isEqualTo(postId); - assertThat(saved.getUserId()).isEqualTo(userId); - } - - @Test - void 게시글_조회여부_판단_성공() { - // Given - ObjectId postId = new ObjectId(); - ObjectId userId = new ObjectId(); - given(hitsRepository.existsByPostIdAndUserId(postId, userId)).willReturn(true); - // When - boolean result = hitsService.validateHits(postId, userId); - // Then - assertThat(result).isTrue(); - } - - @Test - void 게시글_조회수_반환_캐시있음() { - // Given - ObjectId postId = new ObjectId(); - given(redisHealthChecker.isRedisAvailable()).willReturn(true); - given(redisHitsService.getHitsCount(postId)).willReturn("5"); - // When - int result = hitsService.getHitsCount(postId); - // Then - assertThat(result).isEqualTo(5); - } - - @Test - void 게시글_조회수_반환_캐시없음_DB조회() { - // Given - ObjectId postId = new ObjectId(); - given(redisHealthChecker.isRedisAvailable()).willReturn(true); - given(redisHitsService.getHitsCount(postId)).willReturn(null); - given(hitsRepository.countAllByPostId(postId)).willReturn(7); - // When - int result = hitsService.getHitsCount(postId); - // Then - assertThat(result).isEqualTo(7); - } -} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java deleted file mode 100644 index 4d76710b..00000000 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/PollServiceTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package inu.codin.codin.domain.post.domain; - -import inu.codin.codin.common.security.util.SecurityUtils; -import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; -import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; -import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; -import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; -import inu.codin.codin.domain.post.domain.poll.exception.PollDuplicateVoteException; -import inu.codin.codin.domain.post.domain.poll.exception.PollOptionChoiceException; -import inu.codin.codin.domain.post.domain.poll.exception.PollTimeFailException; -import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; -import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; -import inu.codin.codin.domain.post.domain.poll.service.PollService; -import inu.codin.codin.domain.post.entity.PostCategory; -import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.entity.PostStatus; -import inu.codin.codin.domain.post.repository.PostRepository; -import org.bson.types.ObjectId; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.MockedStatic; - -import java.time.LocalDateTime; -import java.util.*; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class PollServiceTest { - @InjectMocks - private PollService pollService; - @Mock private PostRepository postRepository; - @Mock private PollRepository pollRepository; - @Mock private PollVoteRepository pollVoteRepository; - private static MockedStatic securityUtilsMock; - - @BeforeEach - void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); - } - @AfterEach - void tearDown() { - securityUtilsMock.close(); - } - - @Test - void 투표_정상생성_성공() throws Exception { - // Given - PollCreateRequestDTO dto = createPollCreateRequestDTO("제목", "내용", List.of("A", "B"), false, LocalDateTime.now().plusDays(1), true, PostCategory.POLL); - ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(postRepository.save(any())).willAnswer(inv -> { - PostEntity entity = inv.getArgument(0); - java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); - idField.setAccessible(true); - idField.set(entity, new ObjectId()); - return entity; - }); - given(pollRepository.save(any())).willAnswer(inv -> { - PollEntity entity = inv.getArgument(0); - java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); - idField.setAccessible(true); - idField.set(entity, new ObjectId()); - return entity; - }); - // When - pollService.createPoll(dto); - // Then - ArgumentCaptor captor = ArgumentCaptor.forClass(PollEntity.class); - verify(pollRepository).save(captor.capture()); - PollEntity saved = captor.getValue(); - assertThat(saved.getPollOptions()).containsExactly("A", "B"); - assertThat(saved.isMultipleChoice()).isFalse(); - } - - @Test - void 투표_투표하기_정상() { - // Given - String postId = new ObjectId().toString(); - ObjectId postObjId = new ObjectId(postId); - ObjectId pollId = new ObjectId(); - ObjectId userId = new ObjectId(); - PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); - PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); - PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0)); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(pollVoteRepository.existsByPollIdAndUserId(any(), any())).willReturn(false); - given(pollVoteRepository.save(any())).willReturn(PollVoteEntity.builder().pollId(pollId).userId(userId).selectedOptions(List.of(0)).build()); - given(pollRepository.save(any())).willReturn(poll); - // When/Then - assertThatCode(() -> pollService.votingPoll(postId, dto)).doesNotThrowAnyException(); - } - - @Test - void 투표_투표하기_중복투표_예외() { - // Given - String postId = new ObjectId().toString(); - ObjectId postObjId = new ObjectId(postId); - ObjectId pollId = new ObjectId(); - ObjectId userId = new ObjectId(); - PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); - PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); - PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0)); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(pollVoteRepository.existsByPollIdAndUserId(any(), any())).willReturn(true); - // When/Then - assertThatThrownBy(() -> pollService.votingPoll(postId, dto)).isInstanceOf(PollDuplicateVoteException.class); - } - - @Test - void 투표_투표하기_종료된투표_예외() { - // Given - String postId = new ObjectId().toString(); - ObjectId postObjId = new ObjectId(postId); - ObjectId pollId = new ObjectId(); - ObjectId userId = new ObjectId(); - PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); - PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().minusDays(1)).multipleChoice(false).build(); - PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0)); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); - // When/Then - assertThatThrownBy(() -> pollService.votingPoll(postId, dto)).isInstanceOf(PollTimeFailException.class); - } - - @Test - void 투표_투표하기_복수선택불가_예외() { - // Given - String postId = new ObjectId().toString(); - ObjectId postObjId = new ObjectId(postId); - ObjectId pollId = new ObjectId(); - ObjectId userId = new ObjectId(); - PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); - PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); - PollVotingRequestDTO dto = createPollVotingRequestDTO(List.of(0, 1)); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(pollVoteRepository.existsByPollIdAndUserId(any(), any())).willReturn(false); - // When/Then - assertThatThrownBy(() -> pollService.votingPoll(postId, dto)).isInstanceOf(PollOptionChoiceException.class); - } - - @Test - void 투표_투표취소_정상() { - // Given - String postId = new ObjectId().toString(); - ObjectId postObjId = new ObjectId(postId); - ObjectId pollId = new ObjectId(); - ObjectId userId = new ObjectId(); - PostEntity post = PostEntity.builder()._id(postObjId).userId(userId).postCategory(PostCategory.POLL).postStatus(PostStatus.ACTIVE).build(); - PollEntity poll = PollEntity.builder()._id(pollId).postId(postObjId).pollOptions(List.of("A", "B")).pollEndTime(LocalDateTime.now().plusDays(1)).multipleChoice(false).build(); - // 투표가 이미 1회 들어간 상태로 세팅 - poll.getPollVotesCounts().set(0, 1); - PollVoteEntity pollVote = PollVoteEntity.builder().pollId(pollId).userId(userId).selectedOptions(List.of(0)).build(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(pollRepository.findByPostId(any())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(pollVoteRepository.findByPollIdAndUserId(any(), any())).willReturn(Optional.of(pollVote)); - given(pollRepository.save(any())).willReturn(poll); - // When/Then - assertThatCode(() -> pollService.deleteVoting(postId)).doesNotThrowAnyException(); - } - - // --- 리플렉션 기반 DTO 생성 유틸리티 --- - private PollCreateRequestDTO createPollCreateRequestDTO(String title, String content, List options, boolean multipleChoice, LocalDateTime endTime, boolean anonymous, PostCategory category) throws Exception { - PollCreateRequestDTO dto = PollCreateRequestDTO.class.getDeclaredConstructor().newInstance(); - setField(dto, "title", title); - setField(dto, "content", content); - setField(dto, "pollOptions", options); - setField(dto, "multipleChoice", multipleChoice); - setField(dto, "pollEndTime", endTime); - setField(dto, "anonymous", anonymous); - setField(dto, "postCategory", category); - return dto; - } - private PollVotingRequestDTO createPollVotingRequestDTO(List selectedOptions) { - try { - PollVotingRequestDTO dto = PollVotingRequestDTO.class.getDeclaredConstructor().newInstance(); - setField(dto, "selectedOptions", selectedOptions); - return dto; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - private void setField(Object target, String field, Object value) throws Exception { - java.lang.reflect.Field f = target.getClass().getDeclaredField(field); - f.setAccessible(true); - f.set(target, value); - } -} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/best/BestServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/best/BestServiceTest.java new file mode 100644 index 00000000..02bfdfdb --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/best/BestServiceTest.java @@ -0,0 +1,219 @@ +package inu.codin.codin.domain.post.domain.best; + +import inu.codin.codin.infra.redis.service.RedisBestService; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.*; + +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class BestServiceTest { + + @InjectMocks + private BestService bestService; + + @Mock private RedisBestService redisBestService; + @Mock private BestRepository bestRepository; + + @Test + void getTop3BestPostIds_정상조회_성공() { + // Given + Map bestPosts = new LinkedHashMap<>(); + bestPosts.put("postId1", 100.0); + bestPosts.put("postId2", 90.0); + bestPosts.put("postId3", 80.0); + + given(redisBestService.getBests()).willReturn(bestPosts); + + // When + List result = bestService.getTop3BestPostIds(); + + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(3); + assertThat(result).containsExactly("postId1", "postId2", "postId3"); + verify(redisBestService).getBests(); + } + + @Test + void getTop3BestPostIds_빈맵_빈리스트반환() { + // Given + Map emptyBestPosts = new HashMap<>(); + + given(redisBestService.getBests()).willReturn(emptyBestPosts); + + // When + List result = bestService.getTop3BestPostIds(); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEmpty(); + verify(redisBestService).getBests(); + } + + @Test + void getTop3BestPostIds_일부데이터_정상반환() { + // Given + Map bestPosts = new LinkedHashMap<>(); + bestPosts.put("postId1", 50.0); + + given(redisBestService.getBests()).willReturn(bestPosts); + + // When + List result = bestService.getTop3BestPostIds(); + + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + assertThat(result).containsExactly("postId1"); + verify(redisBestService).getBests(); + } + + @Test + void getBestEntities_페이징조회_성공() { + // Given + int pageNumber = 0; + List bestEntities = Arrays.asList( + createBestEntity(), + createBestEntity(), + createBestEntity() + ); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page page = new PageImpl<>(bestEntities, pageRequest, 3); + + given(bestRepository.findAll(any(PageRequest.class))).willReturn(page); + + // When + Page result = bestService.getBestEntities(pageNumber); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getContent()).hasSize(3); + assertThat(result.getTotalElements()).isEqualTo(3); + assertThat(result.getNumber()).isEqualTo(0); + verify(bestRepository).findAll(any(PageRequest.class)); + } + + @Test + void getBestEntities_빈페이지_빈결과반환() { + // Given + int pageNumber = 5; // 존재하지 않는 페이지 + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); + Page emptyPage = new PageImpl<>(new ArrayList<>(), pageRequest, 0); + + given(bestRepository.findAll(any(PageRequest.class))).willReturn(emptyPage); + + // When + Page result = bestService.getBestEntities(pageNumber); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getContent()).isEmpty(); + assertThat(result.getTotalElements()).isEqualTo(0); + verify(bestRepository).findAll(any(PageRequest.class)); + } + + @Test + void deleteBestPost_정상삭제_성공() { + // Given + String postId = new ObjectId().toString(); + + doNothing().when(redisBestService).deleteBest(postId); + + // When & Then + assertThatCode(() -> bestService.deleteBestPost(postId)).doesNotThrowAnyException(); + verify(redisBestService).deleteBest(postId); + } + + @Test + void deleteBestPost_다른postId_정상호출() { + // Given + String postId1 = new ObjectId().toString(); + String postId2 = new ObjectId().toString(); + + doNothing().when(redisBestService).deleteBest(anyString()); + + // When + bestService.deleteBestPost(postId1); + bestService.deleteBestPost(postId2); + + // Then + verify(redisBestService).deleteBest(postId1); + verify(redisBestService).deleteBest(postId2); + verify(redisBestService, times(2)).deleteBest(anyString()); + } + + @Test + void applyBestScore_정상적용_성공() { + // Given + ObjectId postId = new ObjectId(); + + doNothing().when(redisBestService).applyBestScore(anyInt(), any(ObjectId.class)); + + // When & Then + assertThatCode(() -> bestService.applyBestScore(postId)).doesNotThrowAnyException(); + verify(redisBestService).applyBestScore(1, postId); + } + + @Test + void applyBestScore_여러postId_각각적용() { + // Given + ObjectId postId1 = new ObjectId(); + ObjectId postId2 = new ObjectId(); + ObjectId postId3 = new ObjectId(); + + doNothing().when(redisBestService).applyBestScore(anyInt(), any(ObjectId.class)); + + // When + bestService.applyBestScore(postId1); + bestService.applyBestScore(postId2); + bestService.applyBestScore(postId3); + + // Then + verify(redisBestService).applyBestScore(1, postId1); + verify(redisBestService).applyBestScore(1, postId2); + verify(redisBestService).applyBestScore(1, postId3); + verify(redisBestService, times(3)).applyBestScore(eq(1), any(ObjectId.class)); + } + + @Test + void applyBestScore_스코어값확인_항상1() { + // Given + ObjectId postId = new ObjectId(); + + doNothing().when(redisBestService).applyBestScore(anyInt(), any(ObjectId.class)); + + // When + bestService.applyBestScore(postId); + + // Then + verify(redisBestService).applyBestScore(eq(1), eq(postId)); // 스코어는 항상 1 + } + + // Helper methods + private BestEntity createBestEntity() { + BestEntity bestEntity = BestEntity.builder() + .postId(new ObjectId()) + .build(); + setIdFieldSafely(bestEntity, new ObjectId()); + return bestEntity; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java new file mode 100644 index 00000000..4ccd8b21 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java @@ -0,0 +1,235 @@ +package inu.codin.codin.domain.post.domain.comment; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.best.BestService; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.service.CommentCommandService; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class CommentCommandServiceTest { + + @InjectMocks + private CommentCommandService commentCommandService; + + @Mock private CommentRepository commentRepository; + @Mock private NotificationService notificationService; + @Mock private PostCommandService postCommandService; + @Mock private PostQueryService postQueryService; + @Mock private CommentQueryService commentQueryService; + @Mock private BestService bestService; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void addComment_정상생성_성공() throws Exception { + // Given + String postId = new ObjectId().toString(); + CommentCreateRequestDTO dto = createCommentCreateRequestDTO("댓글 내용", false); + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(commentRepository.save(any())).willAnswer(inv -> { + CommentEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + doNothing().when(postCommandService).handleCommentCreation(any(), any()); + doNothing().when(bestService).applyBestScore(any()); + doNothing().when(notificationService).sendNotificationMessageByComment(any(), any(), any(), any()); + + // When & Then + assertThatCode(() -> commentCommandService.addComment(postId, dto)).doesNotThrowAnyException(); + verify(commentRepository).save(any(CommentEntity.class)); + verify(postCommandService).handleCommentCreation(post, userId); + verify(bestService).applyBestScore(any()); + } + + @Test + void addComment_본인게시물_알림미발송() throws Exception { + // Given + String postId = new ObjectId().toString(); + CommentCreateRequestDTO dto = createCommentCreateRequestDTO("댓글 내용", false); + ObjectId userId = new ObjectId(); + PostEntity post = PostEntity.builder() + .userId(userId) // 댓글 작성자와 동일한 userId + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdField(post, new ObjectId()); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(commentRepository.save(any())).willAnswer(inv -> { + CommentEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + doNothing().when(postCommandService).handleCommentCreation(any(), any()); + doNothing().when(bestService).applyBestScore(any()); + + // When + commentCommandService.addComment(postId, dto); + + // Then + verify(notificationService, never()).sendNotificationMessageByComment(any(), any(), any(), any()); + } + + @Test + void addComment_다른사용자게시물_알림발송() throws Exception { + // Given + String postId = new ObjectId().toString(); + CommentCreateRequestDTO dto = createCommentCreateRequestDTO("댓글 내용", false); + ObjectId userId = new ObjectId(); + ObjectId postOwner = new ObjectId(); // 다른 사용자 + PostEntity post = PostEntity.builder() + .userId(postOwner) + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdField(post, new ObjectId()); + + given(postQueryService.findPostById(any())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(commentRepository.save(any())).willAnswer(inv -> { + CommentEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + doNothing().when(postCommandService).handleCommentCreation(any(), any()); + doNothing().when(bestService).applyBestScore(any()); + doNothing().when(notificationService).sendNotificationMessageByComment(any(), any(), any(), any()); + + // When + commentCommandService.addComment(postId, dto); + + // Then + verify(notificationService).sendNotificationMessageByComment( + eq(post.getPostCategory()), + eq(postOwner), + eq(post.get_id().toString()), + eq(dto.getContent()) + ); + } + + @Test + void updateComment_정상수정_성공() throws Exception { + // Given + String commentId = new ObjectId().toString(); + CommentUpdateRequestDTO dto = createCommentUpdateRequestDTO("수정된 내용"); + CommentEntity comment = createCommentEntity(); + ObjectId userId = new ObjectId(); + + given(commentQueryService.findCommentById(any())).willReturn(comment); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + doNothing().when(SecurityUtils.class); + SecurityUtils.validateUser(userId); + given(commentRepository.save(any())).willReturn(comment); + + // When & Then + assertThatCode(() -> commentCommandService.updateComment(commentId, dto)).doesNotThrowAnyException(); + verify(commentRepository).save(comment); + } + + @Test + void softDeleteComment_정상삭제_성공() throws Exception { + // Given + String commentId = new ObjectId().toString(); + CommentEntity comment = createCommentEntity(); + PostEntity post = createPostEntity(); + ObjectId userId = comment.getUserId(); + + given(commentQueryService.findCommentById(any())).willReturn(comment); + doNothing().when(SecurityUtils.class); + SecurityUtils.validateUser(userId); + given(postQueryService.findPostById(comment.getPostId())).willReturn(post); + given(commentRepository.save(any())).willReturn(comment); + doNothing().when(postCommandService).decreaseCommentCount(any()); + + // When & Then + assertThatCode(() -> commentCommandService.softDeleteComment(commentId)).doesNotThrowAnyException(); + verify(commentRepository).save(comment); + verify(postCommandService).decreaseCommentCount(post); + } + + // Helper methods + private CommentCreateRequestDTO createCommentCreateRequestDTO(String content, boolean anonymous) throws Exception { + CommentCreateRequestDTO dto = CommentCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + setField(dto, "anonymous", anonymous); + return dto; + } + + private CommentUpdateRequestDTO createCommentUpdateRequestDTO(String content) throws Exception { + CommentUpdateRequestDTO dto = CommentUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + return dto; + } + + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } + + private void setIdField(Object entity, ObjectId id) throws Exception { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } + + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private CommentEntity createCommentEntity() { + CommentEntity comment = CommentEntity.builder() + .postId(new ObjectId()) + .userId(new ObjectId()) + .content("테스트 댓글") + .anonymous(true) + .build(); + setIdFieldSafely(comment, new ObjectId()); + return comment; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + setIdField(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java new file mode 100644 index 00000000..14a2908d --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java @@ -0,0 +1,320 @@ +package inu.codin.codin.domain.post.domain.comment; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.exception.CommentException; +import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; +import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.dto.UserInfo; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class CommentQueryServiceTest { + + @InjectMocks + private CommentQueryService commentQueryService; + + @Mock private CommentRepository commentRepository; + @Mock private UserRepository userRepository; + @Mock private LikeService likeService; + @Mock private PostQueryService postQueryService; + @Mock private S3Service s3Service; + @Mock private ReplyQueryService replyQueryService; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void getCommentsByPostId_정상조회_성공() { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntityWithAnonymous(); + + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + List comments = Arrays.asList( + createCommentEntityWithUser(userId1), + createCommentEntityWithUser(userId2) + ); + List users = Arrays.asList( + createUserEntityWithId(userId1, "사용자1"), + createUserEntityWithId(userId2, "사용자2") + ); + List replies = new ArrayList<>(); + + given(postQueryService.findPostById(any())).willReturn(post); + given(commentRepository.findByPostId(any())).willReturn(comments); + given(userRepository.findAllById(anyList())).willReturn(users); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); + given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); + given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(5); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), any())).willReturn(false); + + // When + List result = commentQueryService.getCommentsByPostId(postId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(2); + verify(postQueryService).findPostById(any()); + verify(commentRepository).findByPostId(any()); + } + + @Test + void getCommentsByPostId_댓글없음_빈리스트반환() { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + List emptyComments = new ArrayList<>(); + + given(postQueryService.findPostById(any())).willReturn(post); + given(commentRepository.findByPostId(any())).willReturn(emptyComments); + + // When + List result = commentQueryService.getCommentsByPostId(postId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEmpty(); + verify(postQueryService).findPostById(any()); + verify(commentRepository).findByPostId(any()); + verify(userRepository, never()).findAllById(anyList()); + } + + @Test + void getCommentsByPostId_익명게시물_익명번호할당() { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntityWithAnonymous(); + ObjectId userId = new ObjectId(); + CommentEntity comment = createCommentEntityWithUser(userId); + List comments = Arrays.asList(comment); + UserEntity user = createUserEntityWithId(userId, "테스트사용자"); + List users = Arrays.asList(user); + List replies = new ArrayList<>(); + + given(postQueryService.findPostById(any())).willReturn(post); + given(commentRepository.findByPostId(any())).willReturn(comments); + given(userRepository.findAllById(anyList())).willReturn(users); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(2); // 익명 번호 + given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); + given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(3); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), any())).willReturn(true); + + // When + List result = commentQueryService.getCommentsByPostId(postId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + verify(postQueryService).getUserAnonymousNumber(post.getAnonymous(), comment.getUserId()); + } + + @Test + void findCommentById_정상조회_성공() { + // Given + ObjectId commentId = new ObjectId(); + CommentEntity comment = createCommentEntity(); + + given(commentRepository.findByIdAndNotDeleted(commentId)).willReturn(Optional.of(comment)); + + // When + CommentEntity result = commentQueryService.findCommentById(commentId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(comment); + verify(commentRepository).findByIdAndNotDeleted(commentId); + } + + @Test + void findCommentById_댓글없음_예외() { + // Given + ObjectId commentId = new ObjectId(); + + given(commentRepository.findByIdAndNotDeleted(commentId)).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> commentQueryService.findCommentById(commentId)) + .isInstanceOf(CommentException.class); + verify(commentRepository).findByIdAndNotDeleted(commentId); + } + + @Test + void getUserInfoAboutComment_좋아요한댓글_정상반환() { + // Given + ObjectId commentId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(likeService.isLiked(LikeType.COMMENT, commentId, userId)).willReturn(true); + + // When + UserInfo result = commentQueryService.getUserInfoAboutComment(commentId); + + // Then + assertThat(result).isNotNull(); + verify(likeService).isLiked(LikeType.COMMENT, commentId, userId); + } + + @Test + void getUserInfoAboutComment_좋아요안한댓글_정상반환() { + // Given + ObjectId commentId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(likeService.isLiked(LikeType.COMMENT, commentId, userId)).willReturn(false); + + // When + UserInfo result = commentQueryService.getUserInfoAboutComment(commentId); + + // Then + assertThat(result).isNotNull(); + verify(likeService).isLiked(LikeType.COMMENT, commentId, userId); + } + + @Test + void createUserMap_중복사용자_한번만조회() { + // Given + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + List comments = Arrays.asList( + createCommentEntityWithUser(userId1), + createCommentEntityWithUser(userId1), // 동일 사용자 + createCommentEntityWithUser(userId2) + ); + List users = Arrays.asList( + createUserEntityWithId(userId1, "사용자1"), + createUserEntityWithId(userId2, "사용자2") + ); + PostEntity post = createPostEntityWithAnonymous(); + List replies = new ArrayList<>(); + + given(postQueryService.findPostById(any())).willReturn(post); + given(commentRepository.findByPostId(any())).willReturn(comments); + given(userRepository.findAllById(anyList())).willReturn(users); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); + given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); + given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(0); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), any())).willReturn(false); + + // When + List result = commentQueryService.getCommentsByPostId(new ObjectId().toString()); + + // Then + assertThat(result).hasSize(3); + // userId1과 userId2만 조회되어야 함 (중복 제거) + verify(userRepository).findAllById(argThat(iterable -> { + List ids = StreamSupport.stream(iterable.spliterator(), false) + .toList(); + return ids.size() == 2 && ids.contains(userId1) && ids.contains(userId2); + })); + } + + // Helper methods + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .isAnonymous(false) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private PostEntity createPostEntityWithAnonymous() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .isAnonymous(true) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private CommentEntity createCommentEntity() { + CommentEntity comment = CommentEntity.builder() + .postId(new ObjectId()) + .userId(new ObjectId()) + .content("테스트 댓글") + .anonymous(false) + .build(); + setIdFieldSafely(comment, new ObjectId()); + return comment; + } + + private CommentEntity createCommentEntityWithUser(ObjectId userId) { + CommentEntity comment = CommentEntity.builder() + .postId(new ObjectId()) + .userId(userId) + .content("테스트 댓글") + .anonymous(false) + .build(); + setIdFieldSafely(comment, new ObjectId()); + return comment; + } + + private UserEntity createUserEntity(String nickname) { + return UserEntity.builder() + .nickname(nickname) + .profileImageUrl("profile.jpg") + .build(); + } + + private UserEntity createUserEntityWithId(ObjectId id, String nickname) { + UserEntity user = UserEntity.builder() + .nickname(nickname) + .profileImageUrl("profile.jpg") + .build(); + setIdFieldSafely(user, id); + return user; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/HitsServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/HitsServiceTest.java new file mode 100644 index 00000000..b3561ea2 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/hits/HitsServiceTest.java @@ -0,0 +1,270 @@ +package inu.codin.codin.domain.post.domain.hits; + +import inu.codin.codin.domain.post.domain.hits.entity.HitsEntity; +import inu.codin.codin.domain.post.domain.hits.repository.HitsRepository; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.infra.redis.config.RedisHealthChecker; +import inu.codin.codin.infra.redis.service.RedisHitsService; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class HitsServiceTest { + + @InjectMocks + private HitsService hitsService; + + @Mock private RedisHitsService redisHitsService; + @Mock private RedisHealthChecker redisHealthChecker; + @Mock private HitsRepository hitsRepository; + + @Test + void addHits_Redis사용가능_Redis와DB모두저장() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + doNothing().when(redisHitsService).addHits(postId); + given(hitsRepository.save(any(HitsEntity.class))).willReturn(createHitsEntity()); + + // When & Then + assertThatCode(() -> hitsService.addHits(postId, userId)).doesNotThrowAnyException(); + verify(redisHitsService).addHits(postId); + verify(hitsRepository).save(any(HitsEntity.class)); + } + + @Test + void addHits_Redis사용불가_DB만저장() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(redisHealthChecker.isRedisAvailable()).willReturn(false); + given(hitsRepository.save(any(HitsEntity.class))).willReturn(createHitsEntity()); + + // When & Then + assertThatCode(() -> hitsService.addHits(postId, userId)).doesNotThrowAnyException(); + verify(redisHitsService, never()).addHits(any()); + verify(hitsRepository).save(any(HitsEntity.class)); + } + + @Test + void addHits_다른사용자_각각저장() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + doNothing().when(redisHitsService).addHits(postId); + given(hitsRepository.save(any(HitsEntity.class))).willReturn(createHitsEntity()); + + // When + hitsService.addHits(postId, userId1); + hitsService.addHits(postId, userId2); + + // Then + verify(redisHitsService, times(2)).addHits(postId); + verify(hitsRepository, times(2)).save(any(HitsEntity.class)); + } + + @Test + void validateHits_이미조회함_true반환() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(hitsRepository.existsByPostIdAndUserId(postId, userId)).willReturn(true); + + // When + boolean result = hitsService.validateHits(postId, userId); + + // Then + assertThat(result).isTrue(); + verify(hitsRepository).existsByPostIdAndUserId(postId, userId); + } + + @Test + void validateHits_조회안함_false반환() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(hitsRepository.existsByPostIdAndUserId(postId, userId)).willReturn(false); + + // When + boolean result = hitsService.validateHits(postId, userId); + + // Then + assertThat(result).isFalse(); + verify(hitsRepository).existsByPostIdAndUserId(postId, userId); + } + + @Test + void validateHits_다른사용자_각각검증() { + // Given + ObjectId postId = new ObjectId(); + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + + given(hitsRepository.existsByPostIdAndUserId(postId, userId1)).willReturn(true); + given(hitsRepository.existsByPostIdAndUserId(postId, userId2)).willReturn(false); + + // When + boolean result1 = hitsService.validateHits(postId, userId1); + boolean result2 = hitsService.validateHits(postId, userId2); + + // Then + assertThat(result1).isTrue(); + assertThat(result2).isFalse(); + verify(hitsRepository).existsByPostIdAndUserId(postId, userId1); + verify(hitsRepository).existsByPostIdAndUserId(postId, userId2); + } + + @Test + void getHitsCount_Redis캐시히트_Redis값반환() { + // Given + ObjectId postId = new ObjectId(); + String cachedHits = "10"; + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn(cachedHits); + + // When + int result = hitsService.getHitsCount(postId); + + // Then + assertThat(result).isEqualTo(10); + verify(redisHitsService).getHitsCount(postId); + verify(hitsRepository, never()).countAllByPostId(any()); + } + + @Test + void getHitsCount_Redis캐시미스_DB조회후복구() { + // Given + ObjectId postId = new ObjectId(); + int dbHitsCount = 15; + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn(null); // 캐시 미스 + given(hitsRepository.countAllByPostId(postId)).willReturn(dbHitsCount); + doNothing().when(redisHitsService).recoveryHits(postId, dbHitsCount); + + // When + int result = hitsService.getHitsCount(postId); + + // Then + assertThat(result).isEqualTo(15); + verify(redisHitsService).getHitsCount(postId); + verify(hitsRepository, atLeast(1)).countAllByPostId(postId); // @Async recoveryHits도 호출하므로 최소 1번 + // recoveryHits는 @Async이므로 직접 검증하기 어려움 + } + + @Test + void getHitsCount_Redis사용불가_DB조회() { + // Given + ObjectId postId = new ObjectId(); + int dbHitsCount = 7; + + given(redisHealthChecker.isRedisAvailable()).willReturn(false); + given(hitsRepository.countAllByPostId(postId)).willReturn(dbHitsCount); + + // When + int result = hitsService.getHitsCount(postId); + + // Then + assertThat(result).isEqualTo(7); + verify(redisHitsService, never()).getHitsCount(any()); + verify(hitsRepository, atLeast(1)).countAllByPostId(postId); // @Async recoveryHits도 호출하므로 최소 1번 + // recoveryHits는 @Async이므로 직접 검증하기 어려움 + } + + @Test + void getHitsCount_Redis캐시0_정상반환() { + // Given + ObjectId postId = new ObjectId(); + String cachedHits = "0"; + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn(cachedHits); + + // When + int result = hitsService.getHitsCount(postId); + + // Then + assertThat(result).isEqualTo(0); + verify(redisHitsService).getHitsCount(postId); + verify(hitsRepository, never()).countAllByPostId(any()); + } + + @Test + void getHitsCount_DB조회0_정상반환() { + // Given + ObjectId postId = new ObjectId(); + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId)).willReturn(null); + given(hitsRepository.countAllByPostId(postId)).willReturn(0); + + // When + int result = hitsService.getHitsCount(postId); + + // Then + assertThat(result).isEqualTo(0); + verify(hitsRepository, atLeast(1)).countAllByPostId(postId); // @Async recoveryHits도 호출하므로 최소 1번 + } + + @Test + void getHitsCount_여러포스트_각각조회() { + // Given + ObjectId postId1 = new ObjectId(); + ObjectId postId2 = new ObjectId(); + ObjectId postId3 = new ObjectId(); + + given(redisHealthChecker.isRedisAvailable()).willReturn(true); + given(redisHitsService.getHitsCount(postId1)).willReturn("5"); + given(redisHitsService.getHitsCount(postId2)).willReturn("12"); + given(redisHitsService.getHitsCount(postId3)).willReturn("0"); + + // When + int result1 = hitsService.getHitsCount(postId1); + int result2 = hitsService.getHitsCount(postId2); + int result3 = hitsService.getHitsCount(postId3); + + // Then + assertThat(result1).isEqualTo(5); + assertThat(result2).isEqualTo(12); + assertThat(result3).isEqualTo(0); + verify(redisHitsService).getHitsCount(postId1); + verify(redisHitsService).getHitsCount(postId2); + verify(redisHitsService).getHitsCount(postId3); + } + + // Helper methods + private HitsEntity createHitsEntity() { + HitsEntity hitsEntity = HitsEntity.builder() + .postId(new ObjectId()) + .userId(new ObjectId()) + .build(); + setIdFieldSafely(hitsEntity, new ObjectId()); + return hitsEntity; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java new file mode 100644 index 00000000..69f95805 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java @@ -0,0 +1,364 @@ +package inu.codin.codin.domain.post.domain.poll; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; +import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.exception.PollException; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.domain.poll.service.PollCommandService; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.repository.PostRepository; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class PollCommandServiceTest { + + @InjectMocks + private PollCommandService pollCommandService; + + @Mock private PollRepository pollRepository; + @Mock private PollVoteRepository pollVoteRepository; + @Mock private PostRepository postRepository; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void createPoll_정상생성_성공() throws Exception { + // Given + PollCreateRequestDTO dto = createPollCreateRequestDTO( + "투표 제목", + "투표 내용", + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().plusDays(1), + false, + false + ); + ObjectId userId = new ObjectId(); + PostEntity post = createPostEntity(); + PollEntity poll = createPollEntity(); + + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(postRepository.save(any())).willAnswer(inv -> { + PostEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + given(pollRepository.save(any())).willReturn(poll); + + // When & Then + assertThatCode(() -> pollCommandService.createPoll(dto)).doesNotThrowAnyException(); + verify(postRepository).save(any(PostEntity.class)); + verify(pollRepository).save(any(PollEntity.class)); + } + + @Test + void votingPoll_정상투표_성공() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); + PostEntity post = createPostEntity(); + PollEntity poll = mock(PollEntity.class); + ObjectId userId = new ObjectId(); + + // Mock PollEntity 설정 + given(poll.get_id()).willReturn(new ObjectId()); + given(poll.getPollEndTime()).willReturn(LocalDateTime.now().plusDays(1)); + given(poll.isMultipleChoice()).willReturn(false); + given(poll.getPollOptions()).willReturn(Arrays.asList("선택지1", "선택지2")); + doNothing().when(poll).vote(anyInt()); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + given(pollVoteRepository.save(any())).willReturn(createPollVoteEntity()); + given(pollRepository.save(any())).willReturn(poll); + + // When & Then + assertThatCode(() -> pollCommandService.votingPoll(postId, dto)).doesNotThrowAnyException(); + verify(pollVoteRepository).save(any(PollVoteEntity.class)); + verify(pollRepository).save(poll); + verify(poll).vote(0); + } + + @Test + void votingPoll_게시물없음_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) + .isInstanceOf(PostException.class); + } + + @Test + void votingPoll_투표없음_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); + PostEntity post = createPostEntity(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) + .isInstanceOf(PollException.class); + } + + @Test + void votingPoll_투표종료됨_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); + PostEntity post = createPostEntity(); + PollEntity poll = createExpiredPollEntity(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + + // When & Then + assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) + .isInstanceOf(PollException.class); + } + + @Test + void votingPoll_중복투표_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); + PostEntity post = createPostEntity(); + PollEntity poll = createPollEntityWithOptions(); + ObjectId userId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(true); + + // When & Then + assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) + .isInstanceOf(PollException.class); + } + + @Test + void votingPoll_복수선택불가_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0, 1)); // 복수 선택 + PostEntity post = createPostEntity(); + PollEntity poll = createSingleChoicePollEntity(); // 단일 선택만 허용 + ObjectId userId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + + // When & Then + assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) + .isInstanceOf(PollException.class); + } + + @Test + void votingPoll_잘못된선택지_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(5)); // 존재하지 않는 선택지 + PostEntity post = createPostEntity(); + PollEntity poll = createPollEntityWithOptions(); // 2개 선택지만 있음 + ObjectId userId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + + // When & Then + assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) + .isInstanceOf(PollException.class); + } + + @Test + void deleteVoting_정상취소_성공() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + PollEntity poll = mock(PollEntity.class); + ObjectId userId = new ObjectId(); + PollVoteEntity vote = mock(PollVoteEntity.class); + + // Mock PollEntity 설정 + given(poll.get_id()).willReturn(new ObjectId()); + given(poll.getPollEndTime()).willReturn(LocalDateTime.now().plusDays(1)); + doNothing().when(poll).deleteVote(anyInt()); + + // Mock PollVoteEntity 설정 + given(vote.getSelectedOptions()).willReturn(Arrays.asList(0)); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.of(vote)); + given(pollRepository.save(any())).willReturn(poll); + doNothing().when(pollVoteRepository).delete(vote); + + // When & Then + assertThatCode(() -> pollCommandService.deleteVoting(postId)).doesNotThrowAnyException(); + verify(pollRepository).save(poll); + verify(pollVoteRepository).delete(vote); + verify(poll).deleteVote(0); + } + + @Test + void deleteVoting_투표내역없음_예외() throws Exception { + // Given + String postId = new ObjectId().toString(); + PostEntity post = createPostEntity(); + PollEntity poll = createPollEntityWithOptions(); + ObjectId userId = new ObjectId(); + + given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> pollCommandService.deleteVoting(postId)) + .isInstanceOf(PollException.class); + } + + // Helper methods + private PollCreateRequestDTO createPollCreateRequestDTO(String title, String content, List options, + LocalDateTime endTime, boolean multipleChoice, boolean anonymous) throws Exception { + PollCreateRequestDTO dto = PollCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "title", title); + setField(dto, "content", content); + setField(dto, "pollOptions", options); + setField(dto, "pollEndTime", endTime); + setField(dto, "multipleChoice", multipleChoice); + setField(dto, "anonymous", anonymous); + setField(dto, "postCategory", PostCategory.POLL); + return dto; + } + + private PollVotingRequestDTO createPollVotingRequestDTO(List selectedOptions) throws Exception { + PollVotingRequestDTO dto = PollVotingRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "selectedOptions", selectedOptions); + return dto; + } + + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } + + private void setIdField(Object entity, ObjectId id) throws Exception { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } + + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.POLL) + .postStatus(PostStatus.ACTIVE) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private PollEntity createPollEntity() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("선택지1", "선택지2")) + .pollEndTime(LocalDateTime.now().plusDays(1)) + .multipleChoice(false) + .build(); + setIdFieldSafely(poll, new ObjectId()); + return poll; + } + + private PollEntity createPollEntityWithOptions() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("선택지1", "선택지2")) + .pollEndTime(LocalDateTime.now().plusDays(1)) + .multipleChoice(true) + .build(); + setIdFieldSafely(poll, new ObjectId()); + return poll; + } + + private PollEntity createExpiredPollEntity() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("선택지1", "선택지2")) + .pollEndTime(LocalDateTime.now().minusDays(1)) // 과거 시간 + .multipleChoice(false) + .build(); + setIdFieldSafely(poll, new ObjectId()); + return poll; + } + + private PollEntity createSingleChoicePollEntity() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("선택지1", "선택지2")) + .pollEndTime(LocalDateTime.now().plusDays(1)) + .multipleChoice(false) // 단일 선택만 허용 + .build(); + setIdFieldSafely(poll, new ObjectId()); + return poll; + } + + private PollVoteEntity createPollVoteEntity() { + PollVoteEntity vote = PollVoteEntity.builder() + .pollId(new ObjectId()) + .userId(new ObjectId()) + .selectedOptions(Arrays.asList(0)) // 첫 번째 선택지 (유효한 인덱스) + .build(); + setIdFieldSafely(vote, new ObjectId()); + return vote; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + setIdField(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java new file mode 100644 index 00000000..4b845a9d --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java @@ -0,0 +1,291 @@ +package inu.codin.codin.domain.post.domain.poll; + +import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; +import inu.codin.codin.domain.post.domain.poll.exception.PollException; +import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; +import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class PollQueryServiceTest { + + @InjectMocks + private PollQueryService pollQueryService; + + @Mock private PollRepository pollRepository; + @Mock private PollVoteRepository pollVoteRepository; + + @Test + void getPollInfo_정상조회_성공() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + PollEntity poll = createActivePollEntity(); + PollVoteEntity userVote = createPollVoteEntity(); + long totalParticipants = 5L; + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(pollVoteRepository.countByPollId(poll.get_id())).willReturn(totalParticipants); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.of(userVote)); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(true); + + // When + PollInfoResponseDTO result = pollQueryService.getPollInfo(post, userId); + + // Then + assertThat(result).isNotNull(); + verify(pollRepository).findByPostId(post.get_id()); + verify(pollVoteRepository).countByPollId(poll.get_id()); + verify(pollVoteRepository).findByPollIdAndUserId(poll.get_id(), userId); + verify(pollVoteRepository).existsByPollIdAndUserId(poll.get_id(), userId); + } + + @Test + void getPollInfo_투표없음_예외() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> pollQueryService.getPollInfo(post, userId)) + .isInstanceOf(PollException.class); + verify(pollRepository).findByPostId(post.get_id()); + } + + @Test + void getPollInfo_사용자투표안함_빈리스트반환() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + PollEntity poll = createActivePollEntity(); + long totalParticipants = 3L; + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(pollVoteRepository.countByPollId(poll.get_id())).willReturn(totalParticipants); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + + // When + PollInfoResponseDTO result = pollQueryService.getPollInfo(post, userId); + + // Then + assertThat(result).isNotNull(); + verify(pollVoteRepository).findByPollIdAndUserId(poll.get_id(), userId); + verify(pollVoteRepository).existsByPollIdAndUserId(poll.get_id(), userId); + } + + @Test + void getPollInfo_투표종료됨_종료상태반환() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + PollEntity poll = createExpiredPollEntity(); + long totalParticipants = 10L; + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(pollVoteRepository.countByPollId(poll.get_id())).willReturn(totalParticipants); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + + // When + PollInfoResponseDTO result = pollQueryService.getPollInfo(post, userId); + + // Then + assertThat(result).isNotNull(); + verify(pollRepository).findByPostId(post.get_id()); + } + + @Test + void getPollInfo_복수선택투표_정상조회() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + PollEntity poll = createMultipleChoicePollEntity(); + PollVoteEntity userVote = createMultipleVoteEntity(); + long totalParticipants = 8L; + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(pollVoteRepository.countByPollId(poll.get_id())).willReturn(totalParticipants); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.of(userVote)); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(true); + + // When + PollInfoResponseDTO result = pollQueryService.getPollInfo(post, userId); + + // Then + assertThat(result).isNotNull(); + verify(pollRepository).findByPostId(post.get_id()); + verify(pollVoteRepository).countByPollId(poll.get_id()); + } + + @Test + void getPollInfo_투표참가자없음_0반환() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + PollEntity poll = createActivePollEntity(); + long totalParticipants = 0L; + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(pollVoteRepository.countByPollId(poll.get_id())).willReturn(totalParticipants); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + + // When + PollInfoResponseDTO result = pollQueryService.getPollInfo(post, userId); + + // Then + assertThat(result).isNotNull(); + verify(pollVoteRepository).countByPollId(poll.get_id()); + } + + @Test + void getPollInfo_종료시간없음_종료안됨처리() { + // Given + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + PollEntity poll = createPollEntityWithoutEndTime(); + long totalParticipants = 2L; + + given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); + given(pollVoteRepository.countByPollId(poll.get_id())).willReturn(totalParticipants); + given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); + given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); + + // When + PollInfoResponseDTO result = pollQueryService.getPollInfo(post, userId); + + // Then + assertThat(result).isNotNull(); + verify(pollRepository).findByPostId(post.get_id()); + } + + // Helper methods + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.POLL) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private PollEntity createActivePollEntity() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("선택지1", "선택지2", "선택지3")) + .pollEndTime(LocalDateTime.now().plusDays(1)) + .multipleChoice(false) + .build(); + setIdFieldSafely(poll, new ObjectId()); + + // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 + poll.vote(0); // 선택지1에 2표 + poll.vote(0); + poll.vote(1); // 선택지2에 1표 + poll.vote(2); // 선택지3에 2표 + poll.vote(2); + + return poll; + } + + private PollEntity createExpiredPollEntity() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("선택지1", "선택지2")) + .pollEndTime(LocalDateTime.now().minusDays(1)) // 과거 시간 + .multipleChoice(false) + .build(); + setIdFieldSafely(poll, new ObjectId()); + + // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 + for (int i = 0; i < 5; i++) { + poll.vote(0); // 선택지1에 5표 + poll.vote(1); // 선택지2에 5표 + } + + return poll; + } + + private PollEntity createMultipleChoicePollEntity() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("옵션1", "옵션2", "옵션3", "옵션4")) + .pollEndTime(LocalDateTime.now().plusHours(12)) + .multipleChoice(true) // 복수 선택 허용 + .build(); + setIdFieldSafely(poll, new ObjectId()); + + // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 + poll.vote(0); poll.vote(0); poll.vote(0); // 옵션1에 3표 + poll.vote(1); poll.vote(1); // 옵션2에 2표 + poll.vote(2); poll.vote(2); poll.vote(2); poll.vote(2); // 옵션3에 4표 + poll.vote(3); // 옵션4에 1표 + + return poll; + } + + private PollEntity createPollEntityWithoutEndTime() { + PollEntity poll = PollEntity.builder() + .postId(new ObjectId()) + .pollOptions(Arrays.asList("A", "B")) + .pollEndTime(null) // 종료 시간 없음 + .multipleChoice(false) + .build(); + setIdFieldSafely(poll, new ObjectId()); + + // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 + poll.vote(0); // A에 1표 + poll.vote(1); // B에 1표 + + return poll; + } + + private PollVoteEntity createPollVoteEntity() { + PollVoteEntity vote = PollVoteEntity.builder() + .pollId(new ObjectId()) + .userId(new ObjectId()) + .selectedOptions(Arrays.asList(0)) // 첫 번째 선택지 선택 + .build(); + setIdFieldSafely(vote, new ObjectId()); + return vote; + } + + private PollVoteEntity createMultipleVoteEntity() { + PollVoteEntity vote = PollVoteEntity.builder() + .pollId(new ObjectId()) + .userId(new ObjectId()) + .selectedOptions(Arrays.asList(0, 2)) // 복수 선택 + .build(); + setIdFieldSafely(vote, new ObjectId()); + return vote; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java new file mode 100644 index 00000000..eb22495c --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java @@ -0,0 +1,261 @@ +package inu.codin.codin.domain.post.domain.reply; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.notification.service.NotificationService; +import inu.codin.codin.domain.post.domain.best.BestService; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.reply.service.ReplyCommandService; +import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class ReplyCommandServiceTest { + + @InjectMocks + private ReplyCommandService replyCommandService; + + @Mock private ReplyCommentRepository replyCommentRepository; + @Mock private PostCommandService postCommandService; + @Mock private PostQueryService postQueryService; + @Mock private NotificationService notificationService; + @Mock private BestService bestService; + @Mock private CommentQueryService commentQueryService; + @Mock private ReplyQueryService replyQueryService; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void addReply_정상생성_성공() throws Exception { + // Given + String commentId = new ObjectId().toString(); + ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("대댓글 내용", false); + CommentEntity comment = createCommentEntity(); + PostEntity post = createPostEntity(); + ObjectId userId = new ObjectId(); + ReplyCommentEntity reply = createReplyEntity(); + + given(commentQueryService.findCommentById(any())).willReturn(comment); + given(postQueryService.findPostById(comment.getPostId())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(replyCommentRepository.save(any())).willAnswer(inv -> { + ReplyCommentEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + doNothing().when(postCommandService).handleCommentCreation(any(), any()); + doNothing().when(bestService).applyBestScore(any()); + doNothing().when(notificationService).sendNotificationMessageByReply(any(), any(), any(), any()); + + // When & Then + assertThatCode(() -> replyCommandService.addReply(commentId, dto)).doesNotThrowAnyException(); + verify(replyCommentRepository).save(any(ReplyCommentEntity.class)); + verify(postCommandService).handleCommentCreation(post, userId); + verify(bestService).applyBestScore(post.get_id()); + } + + @Test + void addReply_본인게시물_알림미발송() throws Exception { + // Given + String commentId = new ObjectId().toString(); + ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("대댓글 내용", false); + ObjectId userId = new ObjectId(); + CommentEntity comment = createCommentEntity(); + PostEntity post = PostEntity.builder() + .userId(userId) // 대댓글 작성자와 동일한 userId + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdField(post, new ObjectId()); + + given(commentQueryService.findCommentById(any())).willReturn(comment); + given(postQueryService.findPostById(comment.getPostId())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(replyCommentRepository.save(any())).willAnswer(inv -> { + ReplyCommentEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + doNothing().when(postCommandService).handleCommentCreation(any(), any()); + doNothing().when(bestService).applyBestScore(any()); + + // When + replyCommandService.addReply(commentId, dto); + + // Then + verify(notificationService, never()).sendNotificationMessageByReply(any(), any(), any(), any()); + } + + @Test + void addReply_다른사용자게시물_알림발송() throws Exception { + // Given + String commentId = new ObjectId().toString(); + ReplyCreateRequestDTO dto = createReplyCreateRequestDTO("대댓글 내용", false); + ObjectId userId = new ObjectId(); + ObjectId postOwner = new ObjectId(); // 다른 사용자 + ObjectId commentOwner = new ObjectId(); // 댓글 작성자 + CommentEntity comment = CommentEntity.builder() + .postId(new ObjectId()) + .userId(commentOwner) + .content("원댓글") + .anonymous(false) + .build(); + setIdField(comment, new ObjectId()); + PostEntity post = PostEntity.builder() + .userId(postOwner) + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdField(post, new ObjectId()); + + given(commentQueryService.findCommentById(any())).willReturn(comment); + given(postQueryService.findPostById(comment.getPostId())).willReturn(post); + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(replyCommentRepository.save(any())).willAnswer(inv -> { + ReplyCommentEntity entity = inv.getArgument(0); + setIdField(entity, new ObjectId()); + return entity; + }); + doNothing().when(postCommandService).handleCommentCreation(any(), any()); + doNothing().when(bestService).applyBestScore(any()); + doNothing().when(notificationService).sendNotificationMessageByReply(any(), any(), any(), any()); + + // When + replyCommandService.addReply(commentId, dto); + + // Then + verify(notificationService).sendNotificationMessageByReply( + eq(post.getPostCategory()), + eq(commentOwner), + eq(post.get_id().toString()), + eq(dto.getContent()) + ); + } + + @Test + void updateReply_정상수정_성공() throws Exception { + // Given + String replyId = new ObjectId().toString(); + ReplyUpdateRequestDTO dto = createReplyUpdateRequestDTO("수정된 대댓글"); + ReplyCommentEntity reply = createReplyEntity(); + + given(replyQueryService.findReplyById(any())).willReturn(reply); + given(replyCommentRepository.save(any())).willReturn(reply); + + // When & Then + assertThatCode(() -> replyCommandService.updateReply(replyId, dto)).doesNotThrowAnyException(); + verify(replyCommentRepository).save(reply); + } + + @Test + void softDeleteReply_정상삭제_성공() throws Exception { + // Given + String replyId = new ObjectId().toString(); + ReplyCommentEntity reply = createReplyEntity(); + CommentEntity comment = createCommentEntity(); + PostEntity post = createPostEntity(); + ObjectId userId = reply.getUserId(); + + given(replyQueryService.findReplyById(any())).willReturn(reply); + doNothing().when(SecurityUtils.class); + SecurityUtils.validateUser(userId); + given(commentQueryService.findCommentById(reply.getCommentId())).willReturn(comment); + given(postQueryService.findPostById(comment.getPostId())).willReturn(post); + given(replyCommentRepository.save(any())).willReturn(reply); + doNothing().when(postCommandService).decreaseCommentCount(any()); + + // When & Then + assertThatCode(() -> replyCommandService.softDeleteReply(replyId)).doesNotThrowAnyException(); + verify(replyCommentRepository).save(reply); + verify(postCommandService).decreaseCommentCount(post); + } + + // Helper methods + private ReplyCreateRequestDTO createReplyCreateRequestDTO(String content, boolean anonymous) throws Exception { + ReplyCreateRequestDTO dto = ReplyCreateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + setField(dto, "anonymous", anonymous); + return dto; + } + + private ReplyUpdateRequestDTO createReplyUpdateRequestDTO(String content) throws Exception { + ReplyUpdateRequestDTO dto = ReplyUpdateRequestDTO.class.getDeclaredConstructor().newInstance(); + setField(dto, "content", content); + return dto; + } + + private void setField(Object target, String field, Object value) throws Exception { + java.lang.reflect.Field f = target.getClass().getDeclaredField(field); + f.setAccessible(true); + f.set(target, value); + } + + private void setIdField(Object entity, ObjectId id) throws Exception { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } + + private PostEntity createPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private CommentEntity createCommentEntity() { + CommentEntity comment = CommentEntity.builder() + .postId(new ObjectId()) + .userId(new ObjectId()) + .content("테스트 댓글") + .anonymous(false) + .build(); + setIdFieldSafely(comment, new ObjectId()); + return comment; + } + + private ReplyCommentEntity createReplyEntity() { + ReplyCommentEntity reply = ReplyCommentEntity.builder() + .commentId(new ObjectId()) + .userId(new ObjectId()) + .content("테스트 대댓글") + .anonymous(false) + .build(); + setIdFieldSafely(reply, new ObjectId()); + return reply; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + setIdField(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java new file mode 100644 index 00000000..2f640107 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java @@ -0,0 +1,279 @@ +package inu.codin.codin.domain.post.domain.reply; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; +import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; +import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.dto.UserInfo; +import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.*; +import java.util.stream.StreamSupport; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +class ReplyQueryServiceTest { + + @InjectMocks + private ReplyQueryService replyQueryService; + + @Mock private ReplyCommentRepository replyCommentRepository; + @Mock private UserRepository userRepository; + @Mock private PostQueryService postQueryService; + @Mock private LikeService likeService; + @Mock private S3Service s3Service; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void getRepliesByCommentId_정상조회_성공() { + // Given + ObjectId commentId = new ObjectId(); + PostAnonymous postAnonymous = mock(PostAnonymous.class); + + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + List replies = Arrays.asList( + createReplyEntityWithUser(userId1), + createReplyEntityWithUser(userId2) + ); + List users = Arrays.asList( + createUserEntityWithId(userId1, "대댓글사용자1"), + createUserEntityWithId(userId2, "대댓글사용자2") + ); + + given(replyCommentRepository.findByCommentId(commentId)).willReturn(replies); + given(userRepository.findAllById(anyList())).willReturn(users); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); + given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(3); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), any())).willReturn(false); + + // When + List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(2); + verify(replyCommentRepository).findByCommentId(commentId); + verify(userRepository).findAllById(anyList()); + } + + @Test + void getRepliesByCommentId_대댓글없음_빈리스트반환() { + // Given + ObjectId commentId = new ObjectId(); + PostAnonymous postAnonymous = mock(PostAnonymous.class); + List emptyReplies = new ArrayList<>(); + + given(replyCommentRepository.findByCommentId(commentId)).willReturn(emptyReplies); + + // When + List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEmpty(); + verify(replyCommentRepository).findByCommentId(commentId); + verify(userRepository, never()).findAllById(anyList()); + } + + @Test + void getRepliesByCommentId_익명게시물_익명번호할당() { + // Given + ObjectId commentId = new ObjectId(); + PostAnonymous postAnonymous = mock(PostAnonymous.class); + ObjectId userId = new ObjectId(); + ReplyCommentEntity reply = createReplyEntityWithUser(userId); + List replies = Arrays.asList(reply); + UserEntity user = createUserEntityWithId(userId, "익명사용자"); + List users = Arrays.asList(user); + + given(replyCommentRepository.findByCommentId(commentId)).willReturn(replies); + given(userRepository.findAllById(anyList())).willReturn(users); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(postQueryService.getUserAnonymousNumber(postAnonymous, userId)).willReturn(3); // 익명 번호 + given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(2); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), any())).willReturn(true); + + // When + List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + verify(postQueryService).getUserAnonymousNumber(postAnonymous, userId); + } + + @Test + void findReplyById_정상조회_성공() { + // Given + ObjectId replyId = new ObjectId(); + ReplyCommentEntity reply = createReplyEntity(); + + given(replyCommentRepository.findByIdAndNotDeleted(replyId)).willReturn(Optional.of(reply)); + + // When + ReplyCommentEntity result = replyQueryService.findReplyById(replyId); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(reply); + verify(replyCommentRepository).findByIdAndNotDeleted(replyId); + } + + @Test + void findReplyById_대댓글없음_예외() { + // Given + ObjectId replyId = new ObjectId(); + + given(replyCommentRepository.findByIdAndNotDeleted(replyId)).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> replyQueryService.findReplyById(replyId)) + .isInstanceOf(ReplyException.class); + verify(replyCommentRepository).findByIdAndNotDeleted(replyId); + } + + @Test + void getUserInfoAboutReply_좋아요한대댓글_정상반환() { + // Given + ObjectId replyId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(likeService.isLiked(LikeType.COMMENT, replyId, userId)).willReturn(true); + + // When + UserInfo result = replyQueryService.getUserInfoAboutReply(replyId); + + // Then + assertThat(result).isNotNull(); + verify(likeService).isLiked(LikeType.COMMENT, replyId, userId); + } + + @Test + void getUserInfoAboutReply_좋아요안한대댓글_정상반환() { + // Given + ObjectId replyId = new ObjectId(); + ObjectId userId = new ObjectId(); + + given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(likeService.isLiked(LikeType.COMMENT, replyId, userId)).willReturn(false); + + // When + UserInfo result = replyQueryService.getUserInfoAboutReply(replyId); + + // Then + assertThat(result).isNotNull(); + verify(likeService).isLiked(LikeType.COMMENT, replyId, userId); + } + + @Test + void createUserMapFromReplies_중복사용자_한번만조회() { + // Given + ObjectId commentId = new ObjectId(); + PostAnonymous postAnonymous = mock(PostAnonymous.class); + ObjectId userId1 = new ObjectId(); + ObjectId userId2 = new ObjectId(); + List replies = Arrays.asList( + createReplyEntityWithUser(userId1), + createReplyEntityWithUser(userId1), // 동일 사용자 + createReplyEntityWithUser(userId2) + ); + List users = Arrays.asList( + createUserEntityWithId(userId1, "사용자1"), + createUserEntityWithId(userId2, "사용자2") + ); + + given(replyCommentRepository.findByCommentId(commentId)).willReturn(replies); + given(userRepository.findAllById(anyList())).willReturn(users); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); + given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(0); + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), any())).willReturn(false); + + // When + List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); + + // Then + assertThat(result).hasSize(3); + // userId1과 userId2만 조회되어야 함 (중복 제거) + verify(userRepository).findAllById(argThat(iterable -> { + List ids = StreamSupport.stream(iterable.spliterator(), false) + .toList(); + return ids.size() == 2 && ids.contains(userId1) && ids.contains(userId2); + })); + } + + // Helper methods + private ReplyCommentEntity createReplyEntity() { + ReplyCommentEntity reply = ReplyCommentEntity.builder() + .commentId(new ObjectId()) + .userId(new ObjectId()) + .content("테스트 대댓글") + .anonymous(false) + .build(); + setIdFieldSafely(reply, new ObjectId()); + return reply; + } + + private ReplyCommentEntity createReplyEntityWithUser(ObjectId userId) { + ReplyCommentEntity reply = ReplyCommentEntity.builder() + .commentId(new ObjectId()) + .userId(userId) + .content("테스트 대댓글") + .anonymous(false) + .build(); + setIdFieldSafely(reply, new ObjectId()); + return reply; + } + + private UserEntity createUserEntityWithId(ObjectId id, String nickname) { + UserEntity user = UserEntity.builder() + .nickname(nickname) + .profileImageUrl("profile.jpg") + .build(); + setIdFieldSafely(user, id); + return user; + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file From 6e18b952155a4a68992fba1127fe7745f28baada Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 15:33:36 +0900 Subject: [PATCH 0919/1002] =?UTF-8?q?feat:=20Poll=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=9B=90=EC=9E=90=EC=A0=81=20=EC=A6=9D=EA=B0=90=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 투표 수 증가/감소를 MongoDB 원자적 연산으로 모듈화 - Poll, PollVote 정적 팩토리 메서드 추가 - Post 생성 책임을 PostCommandService로 위임 - 검증/조회/증감 로직을 헬퍼 메서드로 분리 --- .../post/domain/poll/entity/PollEntity.java | 34 ++- .../domain/poll/entity/PollVoteEntity.java | 11 +- .../domain/poll/exception/PollErrorCode.java | 4 +- .../poll/repository/PollRepository.java | 11 + .../poll/service/PollCommandService.java | 194 ++++++++++-------- .../dto/request/PostCreateRequestDTO.java | 12 ++ .../post/service/PostCommandService.java | 7 + 7 files changed, 157 insertions(+), 116 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 7a1bd2f1..9da8596b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -1,12 +1,14 @@ package inu.codin.codin.domain.post.domain.poll.entity; -import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.exception.PollErrorCode; import inu.codin.codin.domain.post.domain.poll.exception.PollException; import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -18,44 +20,34 @@ @Document(collection = "polls") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PollEntity extends BaseTimeEntity { @Id - @NotBlank private ObjectId _id; private ObjectId postId; // PostEntity와의 관계를 유지하기 위한 필드 - private final List pollOptions; // 설문조사 선택지 - private final List pollVotesCounts; // 선택지별 투표 수 + private List pollOptions; // 설문조사 선택지 + private List pollVotesCounts; // 선택지별 투표 수 private LocalDateTime pollEndTime; // 설문조사 종료 시간 private boolean multipleChoice; // 복수 선택 가능 여부 - @Builder public PollEntity(ObjectId postId, List pollOptions, LocalDateTime pollEndTime, boolean multipleChoice) { this.postId = postId; this.pollOptions = new ArrayList<>(pollOptions); - - // pollVotesCounts가 null일 경우, pollOptions의 크기만큼 0으로 초기화 this.pollVotesCounts = new ArrayList<>(Collections.nCopies(this.pollOptions.size(), 0)); - this.pollEndTime = pollEndTime; this.multipleChoice = multipleChoice; } - - //각 옵션의 투표 수 증가 - public void vote(int optionIndex) { - if (optionIndex < 0 || optionIndex >= this.pollOptions.size()) { - throw new PollException(PollErrorCode.INVALID_OPTION); - } - this.pollVotesCounts.set(optionIndex, this.pollVotesCounts.get(optionIndex) + 1); - } - - public void deleteVote(int optionIndex){ - if (this.pollVotesCounts.get(optionIndex) - 1 < 0) - throw new PollException(PollErrorCode.INVALID_OPTION); - this.pollVotesCounts.set(optionIndex, this.pollVotesCounts.get(optionIndex) - 1); + public static PollEntity from(ObjectId postId, PollCreateRequestDTO dto) { + return new PollEntity( + postId, + dto.getPollOptions(), + dto.getPollEndTime(), + dto.isMultipleChoice() + ); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java index d424e56e..44c5fc03 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollVoteEntity.java @@ -1,8 +1,9 @@ package inu.codin.codin.domain.post.domain.poll.entity; import jakarta.validation.constraints.NotNull; -import lombok.Builder; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -11,6 +12,7 @@ @Document(collection = "poll_votes") @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PollVoteEntity { @Id private ObjectId _id; @@ -22,7 +24,6 @@ public class PollVoteEntity { @NotNull private List selectedOptions; // 선택한 옵션 인덱스 - @Builder public PollVoteEntity(ObjectId pollId, ObjectId userId, List selectedOptions) { this.pollId = pollId; this.userId = userId; @@ -30,4 +31,8 @@ public PollVoteEntity(ObjectId pollId, ObjectId userId, List selectedOp } -} + public static PollVoteEntity from(ObjectId pollId, ObjectId userId, List selectedOptions) { + return new PollVoteEntity(pollId, userId, selectedOptions); + } + +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java index f1b95f32..cfa6bca7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java @@ -10,7 +10,9 @@ public enum PollErrorCode implements GlobalErrorCode { POLL_FINISHED(HttpStatus.BAD_REQUEST, "이미 종료된 투표입니다."), POLL_DUPLICATED(HttpStatus.CONFLICT, "이미 투표하셨습니다."), MULTIPLE_CHOICE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "복수 선택이 허용되지 않은 투표입니다."), - INVALID_OPTION(HttpStatus.BAD_REQUEST, "잘못된 선택지입니다."); + INVALID_OPTION(HttpStatus.BAD_REQUEST, "잘못된 선택지입니다."), + POLL_VOTE_STATE_CONFLICT(HttpStatus.CONFLICT, "투표 증감 실패."), + POLL_VOTE_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자의 일치하는 투표 내역이 없습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java index 2eb58387..8aa06a0b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java @@ -1,8 +1,12 @@ package inu.codin.codin.domain.post.domain.poll.repository; +import com.mongodb.client.result.UpdateResult; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; +import jakarta.validation.constraints.NotBlank; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.Update; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -11,4 +15,11 @@ public interface PollRepository extends MongoRepository { Optional findByPostId(ObjectId postId); + @Query("{ '_id': ?0 }") + @Update("{ '$inc' : { 'pollVotesCounts.?1' : 1 } }") + UpdateResult incOption(ObjectId pollId, int optionIndex); + + @Query("{ '_id': ?0, 'pollVotesCounts.?1': { $gte: 1 } }") + @Update("{ '$inc' : { 'pollVotesCounts.?1' : -1 } }") + UpdateResult dcrOptionIfPositive(ObjectId pollId, int optionIndex); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index b54fd090..658778c3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -1,6 +1,8 @@ package inu.codin.codin.domain.post.domain.poll.service; +import com.mongodb.client.result.UpdateResult; import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; @@ -9,11 +11,9 @@ import inu.codin.codin.domain.post.domain.poll.exception.PollException; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; +import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.entity.PostEntity; -import inu.codin.codin.domain.post.entity.PostStatus; -import inu.codin.codin.domain.post.exception.PostErrorCode; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,6 +21,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; + import java.time.LocalDateTime; import java.util.List; @@ -31,126 +32,137 @@ public class PollCommandService { private final PollRepository pollRepository; private final PollVoteRepository pollVoteRepository; - private final PostRepository postRepository; + + private final PostCommandService postCommandService; + private final PostQueryService postQueryService; @Transactional public void createPoll(PollCreateRequestDTO pollRequestDTO) { - log.info("투표 생성 요청 - title: {}, userId: {}", pollRequestDTO.getTitle(), SecurityUtils.getCurrentUserId()); - ObjectId userId = SecurityUtils.getCurrentUserId(); + PostCreateRequestDTO postCreateRequestDTO = PostCreateRequestDTO.fromPoll(pollRequestDTO); + ObjectId postId = postCommandService.createPostWithoutImagesAndReturn(postCreateRequestDTO); - // PostEntity 생성 및 저장 - PostEntity postEntity = PostEntity.builder() - .title(pollRequestDTO.getTitle()) - .content(pollRequestDTO.getContent()) - .userId(userId) - .postCategory(pollRequestDTO.getPostCategory()) - .isAnonymous(pollRequestDTO.isAnonymous()) - .postStatus(PostStatus.ACTIVE) - .build(); - postEntity = postRepository.save(postEntity); - log.info("게시글 저장 완료 - postId: {}", postEntity.get_id()); - - // PollEntity 생성 및 저장 - PollEntity pollEntity = PollEntity.builder() - .postId(postEntity.get_id()) - .pollOptions(pollRequestDTO.getPollOptions()) - .pollEndTime(pollRequestDTO.getPollEndTime()) - .multipleChoice(pollRequestDTO.isMultipleChoice()) - .build(); + PollEntity pollEntity = PollEntity.from(postId, pollRequestDTO); pollRepository.save(pollEntity); - log.info("투표 저장 완료 - pollId: {}", pollEntity.get_id()); + + log.info("투표 저장 완료 - postId: {}, pollId: {}", postId, pollEntity.get_id()); } public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { log.info("투표 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> { - log.warn("투표 실패 - 게시글 없음 - postId: {}", postId); - return new PostException(PostErrorCode.POST_NOT_FOUND); - }); - - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> { - log.warn("투표 실패 - 투표 데이터 없음 - postId: {}", postId); - return new PollException(PollErrorCode.POLL_NOT_FOUND); - }); - - if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { - log.warn("투표 실패 - 투표 종료됨 - pollId: {}", poll.get_id()); - throw new PollException(PollErrorCode.POLL_FINISHED); - } - + PollEntity poll = getActivePollByPostId(postId); ObjectId userId = SecurityUtils.getCurrentUserId(); - boolean hasAlreadyVoted = pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId); - if (hasAlreadyVoted) { - log.warn("투표 실패 - 중복 투표 - pollId: {}, userId: {}", poll.get_id(), userId); - throw new PollException(PollErrorCode.POLL_DUPLICATED); - } - List selectedOptions = pollRequestDTO.getSelectedOptions(); - if (!poll.isMultipleChoice() && selectedOptions.size() > 1) { - log.warn("투표 실패 - 복수 선택 허용 안됨 - pollId: {}, userId: {}", poll.get_id(), userId); - throw new PollException(PollErrorCode.MULTIPLE_CHOICE_NOT_ALLOWED); - } + ensureNotDuplicatedVote(poll.get_id(), userId); - for (Integer index : selectedOptions) { - if (index < 0 || index >= poll.getPollOptions().size()) { - log.warn("투표 실패 - 잘못된 선택지 - pollId: {}, optionIndex: {}", poll.get_id(), index); - throw new PollException(PollErrorCode.INVALID_OPTION); - } - } + List selectedOptions = pollRequestDTO.getSelectedOptions(); + // 단일,복수 선택 규칙 + 인덱스 범위 검증 + validateSelections(poll, selectedOptions); - PollVoteEntity vote = PollVoteEntity.builder() - .pollId(poll.get_id()) - .userId(userId) - .selectedOptions(selectedOptions) - .build(); + PollVoteEntity vote = PollVoteEntity.from(poll.get_id(), userId, selectedOptions); pollVoteRepository.save(vote); log.info("투표 기록 저장 완료 - pollId: {}, userId: {}", poll.get_id(), userId); - for (Integer index : selectedOptions) { - poll.vote(index); - log.info("투표 항목 반영 - pollId: {}, optionIndex: {}", poll.get_id(), index); - } - pollRepository.save(poll); + // 카운트 반영 (원자적 증감) + incVote(poll, selectedOptions); log.info("투표 완료 - pollId: {}, userId: {}", poll.get_id(), userId); } public void deleteVoting(String postId) { log.info("투표 취소 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); - PostEntity post = postRepository.findByIdAndNotDeleted(new ObjectId(postId)) - .orElseThrow(() -> { - log.warn("투표 취소 실패 - 게시글 없음 - postId: {}", postId); - return new PostException(PostErrorCode.POST_NOT_FOUND); - }); + PollEntity poll = getActivePollByPostId(postId); + ObjectId userId = SecurityUtils.getCurrentUserId(); - PollEntity poll = pollRepository.findByPostId(post.get_id()) - .orElseThrow(() -> { - log.warn("투표 취소 실패 - 투표 데이터 없음 - postId: {}", postId); - return new PollException(PollErrorCode.POLL_NOT_FOUND); - }); + PollVoteEntity vote = requireUserVote(poll.get_id(), userId); + + // 카운트 롤백 (원자적 증감) + dcrVote(poll, vote.getSelectedOptions()); + pollVoteRepository.delete(vote); + log.info("투표 취소 완료 - pollId: {}, userId: {}", poll.get_id(), userId); + } + + + + // ---- 비즈니스 규칙 검증 / 조회 컨텍스트 ---- + private PollEntity findPollByPostId(ObjectId postId) { + return pollRepository.findByPostId(postId) + .orElseThrow(() -> new PollException(PollErrorCode.POLL_NOT_FOUND)); + } + + private PollEntity getActivePollByPostId(String postId) { + PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); + PollEntity poll = findPollByPostId(post.get_id()); + validatePollActive(poll); + return poll; + } + private void validatePollActive(PollEntity poll) { if (LocalDateTime.now().isAfter(poll.getPollEndTime())) { - log.warn("투표 취소 실패 - 투표 종료됨 - pollId: {}", poll.get_id()); + log.warn("투표 종료됨 - pollId: {}", poll.get_id()); throw new PollException(PollErrorCode.POLL_FINISHED); } + } - ObjectId userId = SecurityUtils.getCurrentUserId(); - PollVoteEntity pollVote = pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId) + private void validateSelections(PollEntity poll, List selected) { + if (!poll.isMultipleChoice() && selected.size() > 1) { + log.warn("복수 선택 허용 안됨 - pollId: {}", poll.get_id()); + throw new PollException(PollErrorCode.MULTIPLE_CHOICE_NOT_ALLOWED); + } + int size = poll.getPollOptions().size(); + for (Integer idx : selected) { + if (idx == null || idx < 0 || idx >= size) { + log.warn("잘못된 선택지 - pollId: {}, optionIndex: {}", poll.get_id(), idx); + throw new PollException(PollErrorCode.INVALID_OPTION); + } + } + } + + private void ensureNotDuplicatedVote(ObjectId pollId, ObjectId userId) { + if (pollVoteRepository.existsByPollIdAndUserId(pollId, userId)) { + log.warn("중복 투표 - pollId: {}, userId: {}", pollId, userId); + throw new PollException(PollErrorCode.POLL_DUPLICATED); + } + } + + private PollVoteEntity requireUserVote(ObjectId pollId, ObjectId userId) { + return pollVoteRepository.findByPollIdAndUserId(pollId, userId) .orElseThrow(() -> { - log.warn("투표 취소 실패 - 유저 투표 내역 없음 - pollId: {}, userId: {}", poll.get_id(), userId); - return new PollException(PollErrorCode.POLL_NOT_FOUND); + log.warn("투표 내역 없음 - pollId: {}, userId: {}", pollId, userId); + return new PollException(PollErrorCode.POLL_VOTE_USER_NOT_FOUND); }); + } - for (Integer index : pollVote.getSelectedOptions()) { - poll.deleteVote(index); - log.info("투표 항목 취소 반영 - pollId: {}, optionIndex: {}", poll.get_id(), index); + // ---- 투표 수 카운트 갱신 ---- + private void incVote(PollEntity poll, List selected) { + final int size = poll.getPollOptions().size(); + selected.forEach(idx -> { + validateIndex(size, idx); + UpdateResult result = pollRepository.incOption(poll.get_id(), idx); + if (result.getModifiedCount() == 0) { + log.warn("투표 증가 실패 - pollId: {}, optionIndex: {}", poll.get_id(), idx); + throw new PollException(PollErrorCode.POLL_VOTE_STATE_CONFLICT); + } + }); + } + + private void dcrVote(PollEntity poll, List selected) { + final int size = poll.getPollOptions().size(); + selected.forEach(idx -> { + validateIndex(size, idx); + UpdateResult result = pollRepository.dcrOptionIfPositive(poll.get_id(), idx); + if (result.getModifiedCount() == 0) { + log.warn("투표 감소 실패 - pollId: {}, optionIndex: {}", poll.get_id(), idx); + throw new PollException(PollErrorCode.POLL_VOTE_STATE_CONFLICT); + } + }); + } + + private void validateIndex(int size, Integer idx) { + if (idx == null || idx < 0 || idx >= size) { + throw new PollException(PollErrorCode.INVALID_OPTION); } - pollRepository.save(poll); - pollVoteRepository.delete(pollVote); - log.info("투표 취소 완료 - pollId: {}, userId: {}", poll.get_id(), userId); } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index 75691cf1..d002c47b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -1,12 +1,15 @@ package inu.codin.codin.domain.post.dto.request; +import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.entity.PostCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.Builder; import lombok.Getter; @Getter +@Builder public class PostCreateRequestDTO { @Schema(description = "게시물 제목", example = "Example") @@ -28,4 +31,13 @@ public class PostCreateRequestDTO { private PostCategory postCategory; //STATUS 필드 - DEFAULT :: ACTIVE + public static PostCreateRequestDTO fromPoll(PollCreateRequestDTO pollCreateRequestDTO) { + return PostCreateRequestDTO.builder() + .postCategory(PostCategory.POLL) + .title(pollCreateRequestDTO.getTitle()) + .content(pollCreateRequestDTO.getContent()) + .anonymous(pollCreateRequestDTO.isAnonymous()) + .build(); + } + } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index 2891038b..14cefd85 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -45,6 +45,13 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List Date: Thu, 21 Aug 2025 15:55:35 +0900 Subject: [PATCH 0920/1002] =?UTF-8?q?refactor:=20Poll=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 엔티티 생성 방식 수정 (빌더 제거 → 생성자/정적 팩토리 사용) - 투표 증가/감소 로직에 MongoDB 원자적 연산 적용 - 서비스 위임 구조 개선 (PostQueryService, PostCommandService 활용) - 예외 처리 로직 보강 (PostException, PollException 분리) - 불필요한 Mock/의존성 제거 및 import 정리 - PollEntity 초기 상태 0 투표 보장, 원자적 연산 기반 상태 관리 - 테스트 코드 전면 수정 및 전체 통과 확인 --- .../domain/poll/PollCommandServiceTest.java | 146 +++++++++--------- .../domain/poll/PollQueryServiceTest.java | 94 +++++------ 2 files changed, 112 insertions(+), 128 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java index 69f95805..6864cbbe 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java @@ -1,19 +1,22 @@ package inu.codin.codin.domain.post.domain.poll; +import com.mongodb.client.result.UpdateResult; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.poll.dto.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; import inu.codin.codin.domain.post.domain.poll.entity.PollVoteEntity; import inu.codin.codin.domain.post.domain.poll.exception.PollException; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.domain.poll.repository.PollRepository; import inu.codin.codin.domain.post.domain.poll.repository.PollVoteRepository; import inu.codin.codin.domain.post.domain.poll.service.PollCommandService; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; -import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostCommandService; +import inu.codin.codin.domain.post.service.PostQueryService; import org.bson.types.ObjectId; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,8 +38,8 @@ class PollCommandServiceTest { @Mock private PollRepository pollRepository; @Mock private PollVoteRepository pollVoteRepository; - @Mock private PostRepository postRepository; - + @Mock private PostCommandService postCommandService; + @Mock private PostQueryService postQueryService; private static AutoCloseable securityUtilsMock; @BeforeEach @@ -60,21 +63,15 @@ void tearDown() throws Exception { false, false ); - ObjectId userId = new ObjectId(); - PostEntity post = createPostEntity(); + ObjectId postId = new ObjectId(); PollEntity poll = createPollEntity(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(postRepository.save(any())).willAnswer(inv -> { - PostEntity entity = inv.getArgument(0); - setIdField(entity, new ObjectId()); - return entity; - }); + given(postCommandService.createPostWithoutImagesAndReturn(any())).willReturn(postId); given(pollRepository.save(any())).willReturn(poll); // When & Then assertThatCode(() -> pollCommandService.createPoll(dto)).doesNotThrowAnyException(); - verify(postRepository).save(any(PostEntity.class)); + verify(postCommandService).createPostWithoutImagesAndReturn(any()); verify(pollRepository).save(any(PollEntity.class)); } @@ -84,28 +81,22 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); PostEntity post = createPostEntity(); - PollEntity poll = mock(PollEntity.class); + PollEntity poll = createActivePollEntity(); ObjectId userId = new ObjectId(); + UpdateResult updateResult = mock(UpdateResult.class); - // Mock PollEntity 설정 - given(poll.get_id()).willReturn(new ObjectId()); - given(poll.getPollEndTime()).willReturn(LocalDateTime.now().plusDays(1)); - given(poll.isMultipleChoice()).willReturn(false); - given(poll.getPollOptions()).willReturn(Arrays.asList("선택지1", "선택지2")); - doNothing().when(poll).vote(anyInt()); - - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); given(pollVoteRepository.save(any())).willReturn(createPollVoteEntity()); - given(pollRepository.save(any())).willReturn(poll); + given(updateResult.getModifiedCount()).willReturn(1L); + given(pollRepository.incOption(poll.get_id(), 0)).willReturn(updateResult); // When & Then assertThatCode(() -> pollCommandService.votingPoll(postId, dto)).doesNotThrowAnyException(); verify(pollVoteRepository).save(any(PollVoteEntity.class)); - verify(pollRepository).save(poll); - verify(poll).vote(0); + verify(pollRepository).incOption(poll.get_id(), 0); } @Test @@ -114,7 +105,7 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.empty()); + given(postQueryService.findPostById(any())).willThrow(new PostException(PostErrorCode.POST_NOT_FOUND)); // When & Then assertThatThrownBy(() -> pollCommandService.votingPoll(postId, dto)) @@ -128,7 +119,7 @@ void tearDown() throws Exception { PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); PostEntity post = createPostEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.empty()); // When & Then @@ -144,7 +135,7 @@ void tearDown() throws Exception { PostEntity post = createPostEntity(); PollEntity poll = createExpiredPollEntity(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); // When & Then @@ -158,10 +149,10 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); PollVotingRequestDTO dto = createPollVotingRequestDTO(Arrays.asList(0)); PostEntity post = createPostEntity(); - PollEntity poll = createPollEntityWithOptions(); + PollEntity poll = createActivePollEntity(); ObjectId userId = new ObjectId(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(true); @@ -180,7 +171,7 @@ void tearDown() throws Exception { PollEntity poll = createSingleChoicePollEntity(); // 단일 선택만 허용 ObjectId userId = new ObjectId(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); @@ -199,7 +190,7 @@ void tearDown() throws Exception { PollEntity poll = createPollEntityWithOptions(); // 2개 선택지만 있음 ObjectId userId = new ObjectId(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); @@ -214,30 +205,24 @@ void tearDown() throws Exception { // Given String postId = new ObjectId().toString(); PostEntity post = createPostEntity(); - PollEntity poll = mock(PollEntity.class); ObjectId userId = new ObjectId(); - PollVoteEntity vote = mock(PollVoteEntity.class); - // Mock PollEntity 설정 - given(poll.get_id()).willReturn(new ObjectId()); - given(poll.getPollEndTime()).willReturn(LocalDateTime.now().plusDays(1)); - doNothing().when(poll).deleteVote(anyInt()); + PollEntity poll = createActivePollEntity(); + PollVoteEntity vote = createPollVoteEntity(); + UpdateResult updateResult = mock(UpdateResult.class); - // Mock PollVoteEntity 설정 - given(vote.getSelectedOptions()).willReturn(Arrays.asList(0)); - - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.of(vote)); - given(pollRepository.save(any())).willReturn(poll); + given(updateResult.getModifiedCount()).willReturn(1L); + given(pollRepository.dcrOptionIfPositive(poll.get_id(), 0)).willReturn(updateResult); doNothing().when(pollVoteRepository).delete(vote); // When & Then assertThatCode(() -> pollCommandService.deleteVoting(postId)).doesNotThrowAnyException(); - verify(pollRepository).save(poll); + verify(pollRepository).dcrOptionIfPositive(poll.get_id(), 0); verify(pollVoteRepository).delete(vote); - verify(poll).deleteVote(0); } @Test @@ -248,7 +233,7 @@ void tearDown() throws Exception { PollEntity poll = createPollEntityWithOptions(); ObjectId userId = new ObjectId(); - given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); + given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); @@ -301,55 +286,66 @@ private PostEntity createPostEntity() { } private PollEntity createPollEntity() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("선택지1", "선택지2")) - .pollEndTime(LocalDateTime.now().plusDays(1)) - .multipleChoice(false) - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().plusDays(1), + false + ); setIdFieldSafely(poll, new ObjectId()); return poll; } private PollEntity createPollEntityWithOptions() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("선택지1", "선택지2")) - .pollEndTime(LocalDateTime.now().plusDays(1)) - .multipleChoice(true) - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().plusDays(1), + true + ); setIdFieldSafely(poll, new ObjectId()); return poll; } private PollEntity createExpiredPollEntity() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("선택지1", "선택지2")) - .pollEndTime(LocalDateTime.now().minusDays(1)) // 과거 시간 - .multipleChoice(false) - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().minusDays(1), // 과거 시간 + false + ); setIdFieldSafely(poll, new ObjectId()); return poll; } private PollEntity createSingleChoicePollEntity() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("선택지1", "선택지2")) - .pollEndTime(LocalDateTime.now().plusDays(1)) - .multipleChoice(false) // 단일 선택만 허용 - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().plusDays(1), + false // 단일 선택만 허용 + ); + setIdFieldSafely(poll, new ObjectId()); + return poll; + } + + private PollEntity createActivePollEntity() { + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().plusDays(1), + false + ); setIdFieldSafely(poll, new ObjectId()); return poll; } private PollVoteEntity createPollVoteEntity() { - PollVoteEntity vote = PollVoteEntity.builder() - .pollId(new ObjectId()) - .userId(new ObjectId()) - .selectedOptions(Arrays.asList(0)) // 첫 번째 선택지 (유효한 인덱스) - .build(); + PollVoteEntity vote = new PollVoteEntity( + new ObjectId(), + new ObjectId(), + Arrays.asList(0) // 첫 번째 선택지 (유효한 인덱스) + ); setIdFieldSafely(vote, new ObjectId()); return vote; } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java index 4b845a9d..483b00fa 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollQueryServiceTest.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; import org.bson.types.ObjectId; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -183,98 +184,85 @@ private PostEntity createPostEntity() { PostEntity post = PostEntity.builder() .userId(new ObjectId()) .postCategory(PostCategory.POLL) + .postStatus(PostStatus.ACTIVE) + .isAnonymous(false) .build(); setIdFieldSafely(post, new ObjectId()); return post; } private PollEntity createActivePollEntity() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("선택지1", "선택지2", "선택지3")) - .pollEndTime(LocalDateTime.now().plusDays(1)) - .multipleChoice(false) - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2", "선택지3"), + LocalDateTime.now().plusDays(1), + false + ); setIdFieldSafely(poll, new ObjectId()); - // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 - poll.vote(0); // 선택지1에 2표 - poll.vote(0); - poll.vote(1); // 선택지2에 1표 - poll.vote(2); // 선택지3에 2표 - poll.vote(2); + // 원자적 연산으로 리팩터링된 PollEntity는 초기 0값으로 생성 return poll; } private PollEntity createExpiredPollEntity() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("선택지1", "선택지2")) - .pollEndTime(LocalDateTime.now().minusDays(1)) // 과거 시간 - .multipleChoice(false) - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("선택지1", "선택지2"), + LocalDateTime.now().minusDays(1), // 과거 시간 + false + ); setIdFieldSafely(poll, new ObjectId()); - // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 - for (int i = 0; i < 5; i++) { - poll.vote(0); // 선택지1에 5표 - poll.vote(1); // 선택지2에 5표 - } + // 원자적 연산으로 리팩터링된 PollEntity는 초기 0값으로 생성 return poll; } private PollEntity createMultipleChoicePollEntity() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("옵션1", "옵션2", "옵션3", "옵션4")) - .pollEndTime(LocalDateTime.now().plusHours(12)) - .multipleChoice(true) // 복수 선택 허용 - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("옵션1", "옵션2", "옵션3", "옵션4"), + LocalDateTime.now().plusHours(12), + true // 복수 선택 허용 + ); setIdFieldSafely(poll, new ObjectId()); - // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 - poll.vote(0); poll.vote(0); poll.vote(0); // 옵션1에 3표 - poll.vote(1); poll.vote(1); // 옵션2에 2표 - poll.vote(2); poll.vote(2); poll.vote(2); poll.vote(2); // 옵션3에 4표 - poll.vote(3); // 옵션4에 1표 + // 원자적 연산으로 리팩터링된 PollEntity는 초기 0값으로 생성 return poll; } private PollEntity createPollEntityWithoutEndTime() { - PollEntity poll = PollEntity.builder() - .postId(new ObjectId()) - .pollOptions(Arrays.asList("A", "B")) - .pollEndTime(null) // 종료 시간 없음 - .multipleChoice(false) - .build(); + PollEntity poll = new PollEntity( + new ObjectId(), + Arrays.asList("A", "B"), + null, // 종료 시간 없음 + false + ); setIdFieldSafely(poll, new ObjectId()); - // 행위로 상태 만들기 - vote() 메서드로 투표 수 쌓기 - poll.vote(0); // A에 1표 - poll.vote(1); // B에 1표 + // 원자적 연산으로 리팩터링된 PollEntity는 초기 0값으로 생성 return poll; } private PollVoteEntity createPollVoteEntity() { - PollVoteEntity vote = PollVoteEntity.builder() - .pollId(new ObjectId()) - .userId(new ObjectId()) - .selectedOptions(Arrays.asList(0)) // 첫 번째 선택지 선택 - .build(); + PollVoteEntity vote = new PollVoteEntity( + new ObjectId(), + new ObjectId(), + Arrays.asList(0) // 첫 번째 선택지 선택 + ); setIdFieldSafely(vote, new ObjectId()); return vote; } private PollVoteEntity createMultipleVoteEntity() { - PollVoteEntity vote = PollVoteEntity.builder() - .pollId(new ObjectId()) - .userId(new ObjectId()) - .selectedOptions(Arrays.asList(0, 2)) // 복수 선택 - .build(); + PollVoteEntity vote = new PollVoteEntity( + new ObjectId(), + new ObjectId(), + Arrays.asList(0, 2) // 복수 선택 + ); setIdFieldSafely(vote, new ObjectId()); return vote; } From 002f3059ba4c004e1ae91bdc082ddb2826588ec6 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 16:39:27 +0900 Subject: [PATCH 0921/1002] =?UTF-8?q?refactor:=20reply=20Domain=20Comment/?= =?UTF-8?q?=20=EC=95=84=EB=9E=98=EB=A1=9C=20=EA=B3=84=EC=B8=B5=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/like/service/LikeService.java | 2 +- .../notification/service/NotificationService.java | 4 ++-- .../comment/dto/response/CommentResponseDTO.java | 2 +- .../reply/controller/ReplyCommentController.java | 8 ++++---- .../request/ReplyAnonnymousUpdateRequestDTO.java | 2 +- .../reply/dto/request/ReplyCreateRequestDTO.java | 2 +- .../reply/dto/request/ReplyUpdateRequestDTO.java | 2 +- .../reply/entity/ReplyCommentEntity.java | 5 ++--- .../reply/exception/ReplyErrorCode.java | 2 +- .../reply/exception/ReplyException.java | 2 +- .../reply/repository/ReplyCommentRepository.java | 4 ++-- .../reply/service/ReplyCommandService.java | 10 +++++----- .../reply/service/ReplyQueryService.java | 10 +++++----- .../comment/service/CommentQueryService.java | 2 +- .../inu/codin/codin/domain/post/dto/UserDto.java | 2 +- .../codin/domain/report/service/ReportService.java | 6 +++--- .../domain/comment/CommentQueryServiceTest.java | 3 +-- .../reply/ReplyCommandServiceTest.java | 14 +++++++------- .../{ => comment}/reply/ReplyQueryServiceTest.java | 10 +++++----- 19 files changed, 45 insertions(+), 47 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/controller/ReplyCommentController.java (84%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java (80%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/dto/request/ReplyCreateRequestDTO.java (86%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/dto/request/ReplyUpdateRequestDTO.java (79%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/entity/ReplyCommentEntity.java (85%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/exception/ReplyErrorCode.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/exception/ReplyException.java (82%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/repository/ReplyCommentRepository.java (76%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/service/ReplyCommandService.java (88%) rename codin-core/src/main/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/service/ReplyQueryService.java (90%) rename codin-core/src/test/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/ReplyCommandServiceTest.java (94%) rename codin-core/src/test/java/inu/codin/codin/domain/post/domain/{ => comment}/reply/ReplyQueryServiceTest.java (96%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 541fbd5c..1321ad3b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -8,7 +8,7 @@ import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.infra.redis.config.RedisHealthChecker; import inu.codin.codin.infra.redis.service.RedisBestService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 07e05bd1..2022ee17 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -8,8 +8,8 @@ import inu.codin.codin.domain.notification.repository.NotificationRepository; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java index 998fe32b..09e68232 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/dto/response/CommentResponseDTO.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.report.dto.response.ReportedCommentDetailResponseDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java similarity index 84% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java index 2d6f67a6..09dd304f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java @@ -1,9 +1,9 @@ -package inu.codin.codin.domain.post.domain.reply.controller; +package inu.codin.codin.domain.post.domain.comment.reply.controller; import inu.codin.codin.common.response.SingleResponse; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommandService; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyCommandService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java similarity index 80% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java index 0e4774f7..f10731b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyAnonnymousUpdateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyCreateRequestDTO.java similarity index 86% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyCreateRequestDTO.java index 0795530f..8904fd39 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyCreateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyUpdateRequestDTO.java similarity index 79% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyUpdateRequestDTO.java index 3d152789..5d035315 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/dto/request/ReplyUpdateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/dto/request/ReplyUpdateRequestDTO.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.dto.request; +package inu.codin.codin.domain.post.domain.comment.reply.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java index 56153764..626ce57a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java @@ -1,8 +1,7 @@ -package inu.codin.codin.domain.post.domain.reply.entity; +package inu.codin.codin.domain.post.domain.comment.reply.entity; import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyCreateRequestDTO; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java index 9f1e7414..3e2f9216 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.exception; +package inu.codin.codin.domain.post.domain.comment.reply.exception; import inu.codin.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java similarity index 82% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java index d4b7911b..15c31f6f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/exception/ReplyException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.exception; +package inu.codin.codin.domain.post.domain.comment.reply.exception; import inu.codin.codin.common.exception.GlobalException; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/repository/ReplyCommentRepository.java similarity index 76% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/repository/ReplyCommentRepository.java index 7be2aeb6..6b038752 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/repository/ReplyCommentRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/repository/ReplyCommentRepository.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.domain.reply.repository; +package inu.codin.codin.domain.post.domain.comment.reply.repository; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index 3c62bf4d..5266ab26 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.domain.reply.service; +package inu.codin.codin.domain.post.domain.comment.reply.service; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; @@ -6,10 +6,10 @@ import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index b23739e0..2eda0660 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -1,13 +1,13 @@ -package inu.codin.codin.domain.post.domain.reply.service; +package inu.codin.codin.domain.post.domain.comment.reply.service; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.exception.ReplyErrorCode; -import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.exception.ReplyErrorCode; +import inu.codin.codin.domain.post.domain.comment.reply.exception.ReplyException; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostAnonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index db9587ad..8bc8c8fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -9,7 +9,7 @@ import inu.codin.codin.domain.post.domain.comment.exception.CommentErrorCode; import inu.codin.codin.domain.post.domain.comment.exception.CommentException; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.dto.UserDto; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostAnonymous; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java index ac54e507..f73f5735 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.dto; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.user.entity.UserEntity; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 6d2d6b89..0903201a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -6,9 +6,9 @@ import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostEntity; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java index 14a2908d..79f9a9df 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java @@ -8,7 +8,7 @@ import inu.codin.codin.domain.post.domain.comment.exception.CommentException; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; -import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; @@ -23,7 +23,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; import static org.assertj.core.api.Assertions.*; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java similarity index 94% rename from codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java rename to codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java index eb22495c..8cff3c06 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java @@ -1,16 +1,16 @@ -package inu.codin.codin.domain.post.domain.reply; +package inu.codin.codin.domain.post.domain.comment.reply; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyCreateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.dto.request.ReplyUpdateRequestDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyCommandService; -import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyCreateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyUpdateRequestDTO; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyCommandService; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostCommandService; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java similarity index 96% rename from codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java rename to codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java index 2f640107..a04d2053 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/reply/ReplyQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java @@ -1,13 +1,13 @@ -package inu.codin.codin.domain.post.domain.reply; +package inu.codin.codin.domain.post.domain.comment.reply; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; -import inu.codin.codin.domain.post.domain.reply.entity.ReplyCommentEntity; -import inu.codin.codin.domain.post.domain.reply.exception.ReplyException; -import inu.codin.codin.domain.post.domain.reply.repository.ReplyCommentRepository; -import inu.codin.codin.domain.post.domain.reply.service.ReplyQueryService; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.exception.ReplyException; +import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.dto.UserInfo; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.service.PostQueryService; From 5453ad8e0875e26f3fddbacd18e6e30d89d430f9 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 17:51:25 +0900 Subject: [PATCH 0922/1002] refactor : RequestDto builder pattern -> constructor --- .../dto/request/PostCreateRequestDTO.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java index 045a0fad..9e7e1c01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/request/PostCreateRequestDTO.java @@ -7,9 +7,10 @@ import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter -@Builder +@NoArgsConstructor public class PostCreateRequestDTO { @Schema(description = "게시물 제목", example = "Example") @@ -29,13 +30,20 @@ public class PostCreateRequestDTO { private PostCategory postCategory; //STATUS 필드 - DEFAULT :: ACTIVE + public PostCreateRequestDTO(String title, String content, boolean anonymous, PostCategory postCategory) { + this.title = title; + this.content = content; + this.anonymous = anonymous; + this.postCategory = postCategory; + } + public static PostCreateRequestDTO fromPoll(PollCreateRequestDTO pollCreateRequestDTO) { - return PostCreateRequestDTO.builder() - .postCategory(PostCategory.POLL) - .title(pollCreateRequestDTO.getTitle()) - .content(pollCreateRequestDTO.getContent()) - .anonymous(pollCreateRequestDTO.isAnonymous()) - .build(); + return new PostCreateRequestDTO( + pollCreateRequestDTO.getTitle(), + pollCreateRequestDTO.getContent(), + pollCreateRequestDTO.isAnonymous(), + PostCategory.POLL + ); } } \ No newline at end of file From 53d6416f9e0224a6a7605bc794d31a028233c8a5 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 17:52:41 +0900 Subject: [PATCH 0923/1002] =?UTF-8?q?fix:=20UpdateRequest=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20(=20Long?= =?UTF-8?q?=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/poll/repository/PollRepository.java | 4 ++-- .../post/domain/poll/service/PollCommandService.java | 8 ++++---- .../post/domain/poll/PollCommandServiceTest.java | 10 +++------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java index 8aa06a0b..3a39e1dd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/repository/PollRepository.java @@ -17,9 +17,9 @@ public interface PollRepository extends MongoRepository { @Query("{ '_id': ?0 }") @Update("{ '$inc' : { 'pollVotesCounts.?1' : 1 } }") - UpdateResult incOption(ObjectId pollId, int optionIndex); + long incOption(ObjectId pollId, int optionIndex); @Query("{ '_id': ?0, 'pollVotesCounts.?1': { $gte: 1 } }") @Update("{ '$inc' : { 'pollVotesCounts.?1' : -1 } }") - UpdateResult dcrOptionIfPositive(ObjectId pollId, int optionIndex); + long dcrOptionIfPositive(ObjectId pollId, int optionIndex); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index 95864421..2b699d56 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -139,8 +139,8 @@ private void incVote(PollEntity poll, List selected) { final int size = poll.getPollOptions().size(); selected.forEach(idx -> { validateIndex(size, idx); - UpdateResult result = pollRepository.incOption(poll.get_id(), idx); - if (result.getModifiedCount() == 0) { + long result = pollRepository.incOption(poll.get_id(), idx); + if (result == 0) { log.warn("투표 증가 실패 - pollId: {}, optionIndex: {}", poll.get_id(), idx); throw new PollException(PollErrorCode.POLL_VOTE_STATE_CONFLICT); } @@ -151,8 +151,8 @@ private void dcrVote(PollEntity poll, List selected) { final int size = poll.getPollOptions().size(); selected.forEach(idx -> { validateIndex(size, idx); - UpdateResult result = pollRepository.dcrOptionIfPositive(poll.get_id(), idx); - if (result.getModifiedCount() == 0) { + long result = pollRepository.dcrOptionIfPositive(poll.get_id(), idx); + if (result == 0) { log.warn("투표 감소 실패 - pollId: {}, optionIndex: {}", poll.get_id(), idx); throw new PollException(PollErrorCode.POLL_VOTE_STATE_CONFLICT); } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java index 82209183..d503c53a 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.post.domain.poll; -import com.mongodb.client.result.UpdateResult; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; @@ -83,15 +82,13 @@ void tearDown() throws Exception { PostEntity post = createPostEntity(); PollEntity poll = createActivePollEntity(); ObjectId userId = new ObjectId(); - UpdateResult updateResult = mock(UpdateResult.class); given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); given(pollVoteRepository.save(any())).willReturn(createPollVoteEntity()); - given(updateResult.getModifiedCount()).willReturn(1L); - given(pollRepository.incOption(poll.get_id(), 0)).willReturn(updateResult); + given(pollRepository.incOption(eq(poll.get_id()), eq(0))).willReturn(1L); // When & Then assertThatCode(() -> pollCommandService.votingPoll(postId, dto)).doesNotThrowAnyException(); @@ -209,14 +206,13 @@ void tearDown() throws Exception { PollEntity poll = createActivePollEntity(); PollVoteEntity vote = createPollVoteEntity(); - UpdateResult updateResult = mock(UpdateResult.class); given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.of(vote)); - given(updateResult.getModifiedCount()).willReturn(1L); - given(pollRepository.dcrOptionIfPositive(poll.get_id(), 0)).willReturn(updateResult); + given(pollRepository.dcrOptionIfPositive(eq(poll.get_id()), eq(0))).willReturn(1L); + doNothing().when(pollVoteRepository).delete(vote); // When & Then From ed8846b9fb187a4e91277016c0ef9150e62b8fdd Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 17:53:01 +0900 Subject: [PATCH 0924/1002] =?UTF-8?q?fix:=20isLiked=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(=20Object=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/comment/CommentQueryServiceTest.java | 6 +++--- .../post/domain/comment/reply/ReplyQueryServiceTest.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java index f88484a8..1dfe7b6f 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java @@ -80,7 +80,7 @@ void tearDown() throws Exception { given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(5); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(), (String) any())).willReturn(false); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), (ObjectId) any())).willReturn(false); // When List result = commentQueryService.getCommentsByPostId(postId); @@ -133,7 +133,7 @@ void tearDown() throws Exception { given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(3); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(), (String) any())).willReturn(true); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), (ObjectId) any())).willReturn(true); // When List result = commentQueryService.getCommentsByPostId(postId); @@ -233,7 +233,7 @@ void tearDown() throws Exception { given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(0); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(), (String) any())).willReturn(false); + given(likeService.isLiked(eq(LikeType.COMMENT), any(), (ObjectId) any())).willReturn(false); // When List result = commentQueryService.getCommentsByPostId(new ObjectId().toString()); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java index e25139ef..fd06e36d 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java @@ -74,7 +74,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(3); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(),(String) any())).willReturn(false); + given(likeService.isLiked(eq(LikeType.COMMENT), any(),(ObjectId) any())).willReturn(false); // When List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); @@ -122,7 +122,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(postAnonymous, userId)).willReturn(3); // 익명 번호 given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(2); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(),(String) any())).willReturn(true); + given(likeService.isLiked(eq(LikeType.COMMENT), any(),(ObjectId) any())).willReturn(true); // When List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); @@ -220,7 +220,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(0); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(),(String) any())).willReturn(false); + given(likeService.isLiked(eq(LikeType.COMMENT), any(),(ObjectId) any())).willReturn(false); // When List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); From 08660e4754eb98b86fa3414ac307872db08376c4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 17:59:43 +0900 Subject: [PATCH 0925/1002] =?UTF-8?q?refactor=20:=20post=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=8B=9C=20postImages=20Entity=20=EB=82=B4=EB=B6=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B9=88=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=BC=EA=B4=84=20=EC=A0=95=EA=B7=9C=ED=99=94=20?= =?UTF-8?q?(non=20-=20null=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 383e9a63..f5bbc911 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -32,7 +32,7 @@ public class PostEntity extends BaseTimeEntity { private String title; @NotBlank private String content; - private List postImageUrls; + private List postImageUrls = new ArrayList<>(); private boolean isAnonymous; @NotNull @@ -62,7 +62,7 @@ public static PostEntity create(ObjectId userId, PostCreateRequestDTO dto, List< dto.getPostCategory(), dto.getTitle(), dto.getContent(), - imageUrls != null ? new ArrayList<>(imageUrls) : new ArrayList<>(), + imageUrls, dto.isAnonymous(), PostStatus.ACTIVE ); From aa70506c55d03565c316e635df113a5f659618d9 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 22:12:39 +0900 Subject: [PATCH 0926/1002] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=20import=20=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/domain/poll/entity/PollEntity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index f879adba..9510bb81 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -2,9 +2,7 @@ import inu.codin.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; -import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.bson.types.ObjectId; From 69ccec22a0201f8445502d50d028b06def974b98 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 21 Aug 2025 22:47:14 +0900 Subject: [PATCH 0927/1002] =?UTF-8?q?refactor:=20UserDto=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=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B0=8F=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ofPost/ofComment/ofReply 메서드 → forPost/forComment/forReply 로 변경하여 의미 명확화 - 삭제/일반 사용자 생성 로직 중복 제거 --- .../reply/service/ReplyQueryService.java | 2 +- .../comment/service/CommentQueryService.java | 2 +- .../codin/codin/domain/post/dto/UserDto.java | 139 ++++++------------ .../domain/post/service/PostQueryService.java | 2 +- 4 files changed, 51 insertions(+), 94 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index 3140e62e..f51aa5e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -94,7 +94,7 @@ private CommentResponseDTO buildReplyResponseDTO( UserEntity user = userMap.get(userId); int anonNum = postQueryService.getUserAnonymousNumber(postAnonymous, reply.getUserId()); - UserDto replyUserDto = UserDto.ofReply(reply, user, anonNum, defaultImageUrl); + UserDto replyUserDto = UserDto.forReply(reply, user, anonNum, defaultImageUrl); return CommentResponseDTO.replyOf( reply, diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index 4ac1cfa7..738a2816 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -100,7 +100,7 @@ private CommentResponseDTO buildCommentResponseDTO( UserEntity user = userMap.get(comment.getUserId()); // 댓글용 사용자 DTO 생성 - UserDto commentUserDto = UserDto.ofComment(comment, user, anonNum ,defaultImageUrl); + UserDto commentUserDto = UserDto.forComment(comment, user, anonNum ,defaultImageUrl); return CommentResponseDTO.commentOf( comment, diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java index f73f5735..eaafe5fb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/UserDto.java @@ -4,12 +4,11 @@ import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.user.entity.UserEntity; -import lombok.Builder; import lombok.Getter; @Getter -@Builder public class UserDto { + private final String nickname; private final String imageUrl; private final boolean deleted; @@ -20,101 +19,59 @@ private UserDto(String nickname, String imageUrl, boolean deleted) { this.deleted = deleted; } - /** - * 게시물용 사용자 DTO 생성 - * - * @param post 게시물 엔티티 - * @param user 사용자 엔티티 - * @param defaultProfileImageUrl 기본 프로필 이미지 URL - * @return 게시물용 사용자 DTO - */ - public static UserDto ofPost(PostEntity post, UserEntity user, String defaultProfileImageUrl) { - if (user.getDeletedAt() != null) { - return ofDeletedUser(user); - } - - if (post.isAnonymous()) { - return UserDto.builder() - .nickname("익명") - .imageUrl(defaultProfileImageUrl) - .deleted(false) - .build(); - } - - return ofNormalUser(user); + /** 게시물용 사용자 DTO */ + public static UserDto forPost(PostEntity post, UserEntity user, String defaultProfileImageUrl) { + if (isDeleted(user)) return deletedUser(user, defaultProfileImageUrl); + if (post.isAnonymous()) { + return of("익명", defaultProfileImageUrl, false); } + return normalUser(user, defaultProfileImageUrl); + } - /** - * 댓글용 사용자 DTO 생성 - * - * @param comment 댓글 엔티티 - * @param user 사용자 엔티티 - * @param anonNum 익명 번호 - * @param defaultProfileImageUrl 기본 프로필 이미지 URL - * @return 댓글용 사용자 DTO - */ - public static UserDto ofComment(CommentEntity comment, UserEntity user, int anonNum, String defaultProfileImageUrl) { - if (user.getDeletedAt() != null) { - return ofDeletedUser(user); - } - - if (comment.isAnonymous()) { - String nickname = anonNum == 0 ? "글쓴이" : "익명" + anonNum; - return UserDto.builder() - .nickname(nickname) - .imageUrl(defaultProfileImageUrl) - .deleted(false) - .build(); - } + /** 댓글용 사용자 DTO */ + public static UserDto forComment(CommentEntity comment, UserEntity user, int anonNum, String defaultProfileImageUrl) { + if (isDeleted(user)) return deletedUser(user, defaultProfileImageUrl); + if (comment.isAnonymous()) { + String nick = anonNickname(anonNum); + return of(nick, defaultProfileImageUrl, false); + } + return normalUser(user, defaultProfileImageUrl); + } - return ofNormalUser(user); + /** 대댓글용 사용자 DTO */ + public static UserDto forReply(ReplyCommentEntity reply, UserEntity user, int anonNum, String defaultProfileImageUrl) { + if (isDeleted(user)) return deletedUser(user, defaultProfileImageUrl); + if (reply.isAnonymous()) { + String nick = anonNickname(anonNum); + return of(nick, defaultProfileImageUrl, false); } + return normalUser(user, defaultProfileImageUrl); + } + + // ---------- Private helpers ---------- + private static boolean isDeleted(UserEntity user) { + return user.getDeletedAt() != null; + } - /** - * 댓글용 사용자 DTO 생성 - * - * @param reply 대댓글 엔티티 - * @param user 사용자 엔티티 - * @param anonNum 익명 번호 - * @param defaultProfileImageUrl 기본 프로필 이미지 URL - * @return 댓글용 사용자 DTO - */ - public static UserDto ofReply(ReplyCommentEntity reply, UserEntity user, int anonNum, String defaultProfileImageUrl) { - if (user.getDeletedAt() != null) { - return ofDeletedUser(user); - } + private static UserDto deletedUser(UserEntity user, String defaultProfileImageUrl) { + String image = withDefault(user.getProfileImageUrl(), defaultProfileImageUrl); + return of(user.getNickname(), image, true); + } - if (reply.isAnonymous()) { - String nickname = (anonNum == 0) ? "글쓴이" : "익명" + anonNum; - return UserDto.builder() - .nickname(nickname) - .imageUrl(defaultProfileImageUrl) - .deleted(false) - .build(); - } + private static UserDto normalUser(UserEntity user, String defaultProfileImageUrl) { + String image = withDefault(user.getProfileImageUrl(), defaultProfileImageUrl); + return of(user.getNickname(), image, false); + } - return ofNormalUser(user); - } + private static String anonNickname(int anonNum) { + return anonNum == 0 ? "글쓴이" : "익명" + anonNum; + } - /** - * 삭제된 사용자 DTO 생성 - */ - public static UserDto ofDeletedUser(UserEntity user) { - return UserDto.builder() - .nickname(user.getNickname()) - .imageUrl(user.getProfileImageUrl()) - .deleted(true) - .build(); - } + private static String withDefault(String value, String defaultValue) { + return (value == null || value.isBlank()) ? defaultValue : value; + } - /** - * 일반 사용자 DTO 생성 - */ - public static UserDto ofNormalUser(UserEntity user) { - return UserDto.builder() - .nickname(user.getNickname()) - .imageUrl(user.getProfileImageUrl()) - .deleted(false) - .build(); - } - } \ No newline at end of file + private static UserDto of(String nickname, String imageUrl, boolean deleted) { + return new UserDto(nickname, imageUrl, deleted); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 3c923806..7436fb54 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -177,7 +177,7 @@ private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { private UserDto resolveUserProfile(PostEntity post) { UserEntity user = userRepository.findById(post.getUserId()) .orElseThrow(() -> new PostException(PostErrorCode.USER_NOT_FOUND)); - return UserDto.ofPost(post, user, s3Service.getDefaultProfileImageUrl()); + return UserDto.forPost(post, user, s3Service.getDefaultProfileImageUrl()); } // [유저 프로필] - 게시물에 대한 유저정보 추출 From fa89281b16869f1cd4f2e3ed742b461155bf189e Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 22 Aug 2025 10:14:55 +0900 Subject: [PATCH 0928/1002] =?UTF-8?q?refactor:=20PostDtoAssembler=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85=EC=9C=BC=EB=A1=9C=20DTO=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PostQueryService 내 PostEntity → DTO 변환 중복 로직을 PostDtoAssembler로 이동 - getTop3BestPosts(), getBestPosts() 등에서 공통 변환 로직을 Assembler 호출로 교체 --- .../domain/post/service/PostDtoAssembler.java | 118 +++++++++ .../domain/post/service/PostQueryService.java | 99 +------- .../domain/post/PostDtoAssemblerTest.java | 239 ++++++++++++++++++ .../domain/post/PostQueryServiceTest.java | 163 +++--------- 4 files changed, 412 insertions(+), 207 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java new file mode 100644 index 00000000..0235a291 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java @@ -0,0 +1,118 @@ +package inu.codin.codin.domain.post.service; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; +import inu.codin.codin.domain.post.dto.UserDto; +import inu.codin.codin.domain.post.dto.UserInfo; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.exception.PostErrorCode; +import inu.codin.codin.domain.post.exception.PostException; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * PostEntity를 다양한 Response DTO로 변환하는 책임을 담당하는 어셈블러 + * CQRS의 Query 측면에서 DTO 조립 로직을 분리하여 단일 책임 원칙을 준수 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class PostDtoAssembler { + + private final UserRepository userRepository; + private final LikeService likeService; + private final ScrapService scrapService; + private final S3Service s3Service; + private final PollQueryService pollQueryService; + private final HitsService hitsService; + + /** + * PostEntity를 PostPageItemResponseDTO로 변환 + * 현재 사용자 기준으로 좋아요/스크랩 상태, Poll 정보 등을 포함한 완전한 DTO 생성 + */ + public PostPageItemResponseDTO toPageItem(PostEntity post, ObjectId currentUserId) { + UserDto userDto = resolveUserProfile(post); + int likeCount = getLikeCount(post); + int scrapCount = getScrapCount(post); + int hitsCount = getHitsCount(post); + int commentCount = post.getCommentCount(); + UserInfo userInfo = getUserInfoAboutPost(currentUserId, post.getUserId(), post.get_id()); + + PostDetailResponseDTO postDTO = PostDetailResponseDTO.of( + post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo + ); + + if (post.getPostCategory() == PostCategory.POLL) { + PollInfoResponseDTO pollInfo = pollQueryService.getPollInfo(post, currentUserId); + return PostPageItemResponseDTO.of(postDTO, pollInfo); + } else { + return PostPageItemResponseDTO.of(postDTO, null); + } + } + + /** + * PostEntity 리스트를 PostPageItemResponseDTO 리스트로 변환 + */ + public List toPageItemList(List posts) { + ObjectId currentUserId = SecurityUtils.getCurrentUserId(); + return posts.stream() + .map(post -> toPageItem(post, currentUserId)) + .toList(); + } + + /** + * 사용자 프로필 정보 결정 (익명/실명, 닉네임/이미지) + */ + private UserDto resolveUserProfile(PostEntity post) { + UserEntity user = userRepository.findById(post.getUserId()) + .orElseThrow(() -> new PostException(PostErrorCode.USER_NOT_FOUND)); + return UserDto.forPost(post, user, s3Service.getDefaultProfileImageUrl()); + } + + /** + * 현재 사용자의 게시물에 대한 상호작용 정보 조회 (좋아요, 스크랩, 작성자 여부) + */ + private UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId) { + return UserInfo.ofPost( + likeService.isLiked(LikeType.POST, postId.toString(), currentUserId), + scrapService.isPostScraped(postId, currentUserId), + postUserId.equals(currentUserId) + ); + } + + /** + * 게시글 좋아요 수 조회 + */ + private int getLikeCount(PostEntity post) { + return likeService.getLikeCount(LikeType.POST, post.get_id().toString()); + } + + /** + * 게시글 스크랩 수 조회 + */ + private int getScrapCount(PostEntity post) { + return scrapService.getScrapCount(post.get_id()); + } + + /** + * 게시글 조회수 조회 + */ + private int getHitsCount(PostEntity post) { + return hitsService.getHitsCount(post.get_id()); + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 7436fb54..f6c9ca14 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -3,16 +3,8 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestService; -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; -import inu.codin.codin.domain.post.dto.UserDto; -import inu.codin.codin.domain.post.dto.UserInfo; -import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; -import inu.codin.codin.domain.post.dto.response.PostDetailResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostAnonymous; @@ -21,10 +13,6 @@ import inu.codin.codin.domain.post.exception.PostException; import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.scrap.service.ScrapService; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.s3.S3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -43,16 +31,10 @@ public class PostQueryService { private final PostRepository postRepository; - private final UserRepository userRepository; - - private final PollQueryService pollQueryService; private final BlockService blockService; private final PostInteractionService postInteractionService; private final BestService bestService; - private final ScrapService scrapService; - private final LikeService likeService; - private final S3Service s3Service; - private final HitsService hitsService; + private final PostDtoAssembler postDtoAssembler; /** * 카테고리별 삭제되지 않은 게시물 목록 조회 * @return PostPageResponse (불변 리스트) @@ -62,17 +44,9 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.getPostsByCategoryWithBlockedUsers(postCategory.toString(), blockedUsersId, pageRequest); log.info("모든 글 반환 성공 Category: {}, Page: {}", postCategory, pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postDtoAssembler.toPageItemList(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } - /** - * 게시물 리스트 DTO 변환 (불변 리스트) - */ - public List getPostListResponseDtos(List posts) { - return posts.stream() - .map(this::toPageItemDTO) - .toList(); - } /** * 게시물 상세 조회 @@ -81,7 +55,7 @@ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = findPostById(ObjectIdUtil.toObjectId(postId)); ObjectId userId = SecurityUtils.getCurrentUserId(); postInteractionService.increaseHits(post, userId); - return toPageItemDTO(post); + return postDtoAssembler.toPageItem(post, userId); } /** @@ -92,7 +66,7 @@ public Optional getPostDetailById(ObjectId postId) { .map(post -> { ObjectId userId = SecurityUtils.getCurrentUserId(); postInteractionService.increaseHits(post, userId); - return toPageItemDTO(post); + return postDtoAssembler.toPageItem(post, userId); }); } @@ -104,7 +78,7 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); - return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postDtoAssembler.toPageItemList(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } /** @@ -126,7 +100,7 @@ public List getTop3BestPosts() { .toList(); log.info("Top 3 베스트 게시물 반환."); - return getPostListResponseDtos(validPosts); + return postDtoAssembler.toPageItemList(validPosts); } /** @@ -148,48 +122,21 @@ public PostPageResponse getBestPosts(int pageNumber) { .toList(); return PostPageResponse.of( - getPostListResponseDtos(validPosts), + postDtoAssembler.toPageItemList(validPosts), bestEntities.getTotalPages() - 1, bestEntities.hasNext() ? bestEntities.getPageable().getPageNumber() + 1 : -1 ); } - - // PostEntity → PostPageItemResponseDTO 변환 (공통 변환 로직) - private PostPageItemResponseDTO toPageItemDTO(PostEntity post) { - UserDto userDto = resolveUserProfile(post); - int likeCount = getLikeCount(post); - int scrapCount = getScrapCount(post); - int hitsCount = getHitsCount(post); - int commentCount = post.getCommentCount(); - ObjectId userId = SecurityUtils.getCurrentUserId(); - UserInfo userInfo = getUserInfoAboutPost(userId, post.getUserId(), post.get_id()); - PostDetailResponseDTO postDTO = PostDetailResponseDTO.of(post, userDto, likeCount, scrapCount, hitsCount, commentCount, userInfo); - if (post.getPostCategory() == PostCategory.POLL) { - PollInfoResponseDTO pollInfo = pollQueryService.getPollInfo(post, userId); - return PostPageItemResponseDTO.of(postDTO, pollInfo); - } else { - return PostPageItemResponseDTO.of(postDTO, null); - } - } - - // [유저 프로필] - 닉네임/이미지 결정 - private UserDto resolveUserProfile(PostEntity post) { - UserEntity user = userRepository.findById(post.getUserId()) - .orElseThrow(() -> new PostException(PostErrorCode.USER_NOT_FOUND)); - return UserDto.forPost(post, user, s3Service.getDefaultProfileImageUrl()); - } - - // [유저 프로필] - 게시물에 대한 유저정보 추출 - private UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId){ - return UserInfo.ofPost( - likeService.isLiked(LikeType.POST, postId.toString(), currentUserId), - scrapService.isPostScraped(postId, currentUserId), - postUserId.equals(currentUserId) - ); + /** + * 게시물 리스트 DTO 변환 (불변 리스트) - 다른 서비스와의 호환성을 위해 유지(userSerice) + * @deprecated 내부적으로는 PostDtoAssembler 사용을 권장 + */ + @Deprecated + public List getPostListResponseDtos(List posts) { + return postDtoAssembler.toPageItemList(posts); } - /** * 유저의 익명 번호 조회 */ @@ -208,22 +155,4 @@ public PostEntity findPostById(ObjectId postId) { } - // [likeService] - 게시글 좋아요 수 조회 - public int getLikeCount(PostEntity post) { - return likeService.getLikeCount(LikeType.POST, post.get_id().toString()); - } - - // [ScrapService] - 게시글 스크랩 수 조회 - public int getScrapCount(PostEntity post) { - return scrapService.getScrapCount(post.get_id()); - } - - // [HitsService] - 게시글 조회수 조회 - public int getHitsCount(PostEntity post) { - return hitsService.getHitsCount(post.get_id()); - } - - - - } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java new file mode 100644 index 00000000..ed3fc973 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java @@ -0,0 +1,239 @@ +package inu.codin.codin.domain.post; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.like.service.LikeService; +import inu.codin.codin.domain.post.domain.hits.service.HitsService; +import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; +import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; +import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; +import inu.codin.codin.domain.post.entity.PostCategory; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.post.service.PostDtoAssembler; +import inu.codin.codin.domain.scrap.service.ScrapService; +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.repository.UserRepository; +import inu.codin.codin.infra.s3.S3Service; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +/** + * PostDtoAssembler 단위 테스트 + * DTO 조립 로직의 다양한 케이스를 검증 + */ +@ExtendWith(MockitoExtension.class) +class PostDtoAssemblerTest { + + @InjectMocks + private PostDtoAssembler postDtoAssembler; + + @Mock private UserRepository userRepository; + @Mock private LikeService likeService; + @Mock private ScrapService scrapService; + @Mock private S3Service s3Service; + @Mock private PollQueryService pollQueryService; + @Mock private HitsService hitsService; + + private static AutoCloseable securityUtilsMock; + + @BeforeEach + void setUp() { + securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + } + + @AfterEach + void tearDown() throws Exception { + securityUtilsMock.close(); + } + + @Test + void toPageItem_일반게시물_정상변환() { + // Given + PostEntity post = createNormalPostEntity(); + ObjectId currentUserId = new ObjectId(); + UserEntity user = createUserEntity(); + + given(userRepository.findById(post.getUserId())).willReturn(Optional.of(user)); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(likeService.getLikeCount(LikeType.POST, post.get_id().toString())).willReturn(5); + given(scrapService.getScrapCount(post.get_id())).willReturn(3); + given(hitsService.getHitsCount(post.get_id())).willReturn(100); + given(likeService.isLiked(LikeType.POST, post.get_id().toString(), currentUserId)).willReturn(true); + given(scrapService.isPostScraped(post.get_id(), currentUserId)).willReturn(false); + + // When + PostPageItemResponseDTO result = postDtoAssembler.toPageItem(post, currentUserId); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getPost()).isNotNull(); + assertThat(result.getPoll()).isNull(); // 일반 게시물은 Poll 정보 없음 + verify(userRepository).findById(post.getUserId()); + verify(likeService).getLikeCount(LikeType.POST, post.get_id().toString()); + verify(scrapService).getScrapCount(post.get_id()); + verify(hitsService).getHitsCount(post.get_id()); + } + + @Test + void toPageItem_투표게시물_Poll정보포함() { + // Given + PostEntity pollPost = createPollPostEntity(); + ObjectId currentUserId = new ObjectId(); + UserEntity user = createUserEntity(); + PollInfoResponseDTO pollInfo = mock(PollInfoResponseDTO.class); + + given(userRepository.findById(pollPost.getUserId())).willReturn(Optional.of(user)); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(likeService.getLikeCount(LikeType.POST, pollPost.get_id().toString())).willReturn(2); + given(scrapService.getScrapCount(pollPost.get_id())).willReturn(1); + given(hitsService.getHitsCount(pollPost.get_id())).willReturn(50); + given(likeService.isLiked(LikeType.POST, pollPost.get_id().toString(), currentUserId)).willReturn(false); + given(scrapService.isPostScraped(pollPost.get_id(), currentUserId)).willReturn(true); + given(pollQueryService.getPollInfo(pollPost, currentUserId)).willReturn(pollInfo); + + // When + PostPageItemResponseDTO result = postDtoAssembler.toPageItem(pollPost, currentUserId); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getPost()).isNotNull(); + assertThat(result.getPoll()).isEqualTo(pollInfo); // Poll 정보 포함 + verify(pollQueryService).getPollInfo(pollPost, currentUserId); + } + + @Test + void toPageItem_익명게시물_익명처리() { + // Given + PostEntity anonymousPost = createAnonymousPostEntity(); + ObjectId currentUserId = new ObjectId(); + UserEntity user = createUserEntity(); + + given(userRepository.findById(anonymousPost.getUserId())).willReturn(Optional.of(user)); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(likeService.getLikeCount(LikeType.POST, anonymousPost.get_id().toString())).willReturn(0); + given(scrapService.getScrapCount(anonymousPost.get_id())).willReturn(0); + given(hitsService.getHitsCount(anonymousPost.get_id())).willReturn(10); + given(likeService.isLiked(LikeType.POST, anonymousPost.get_id().toString(), currentUserId)).willReturn(false); + given(scrapService.isPostScraped(anonymousPost.get_id(), currentUserId)).willReturn(false); + + // When + PostPageItemResponseDTO result = postDtoAssembler.toPageItem(anonymousPost, currentUserId); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getPost()).isNotNull(); + // UserDto.forPost에서 익명 처리 로직이 작동하는지는 UserDto 테스트에서 확인 + verify(userRepository).findById(anonymousPost.getUserId()); + } + + @Test + void toPageItem_삭제된사용자_예외발생() { + // Given + PostEntity post = createNormalPostEntity(); + ObjectId currentUserId = new ObjectId(); + + given(userRepository.findById(post.getUserId())).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> postDtoAssembler.toPageItem(post, currentUserId)) + .isInstanceOf(RuntimeException.class); // PostException이 발생해야 함 + verify(userRepository).findById(post.getUserId()); + } + + @Test + void toPageItemList_여러게시물_정상변환() { + // Given + PostEntity post1 = createNormalPostEntity(); + PostEntity post2 = createPollPostEntity(); + List posts = Arrays.asList(post1, post2); + UserEntity user = createUserEntity(); + + given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(userRepository.findById(any())).willReturn(Optional.of(user)); + given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); + given(likeService.getLikeCount(any(), any())).willReturn(0); + given(scrapService.getScrapCount(any())).willReturn(0); + given(hitsService.getHitsCount(any())).willReturn(0); + given(likeService.isLiked(any(), any(), (ObjectId) any())).willReturn(false); + given(scrapService.isPostScraped(any(), any())).willReturn(false); + given(pollQueryService.getPollInfo(eq(post2), any())).willReturn(mock(PollInfoResponseDTO.class)); + + // When + List results = postDtoAssembler.toPageItemList(posts); + + // Then + assertThat(results).hasSize(2); + assertThat(results.get(0)).isNotNull(); + assertThat(results.get(1)).isNotNull(); + assertThat(results.get(0).getPoll()).isNull(); // 일반 게시물 + assertThat(results.get(1).getPoll()).isNotNull(); // 투표 게시물 + } + + // Helper methods + private PostEntity createNormalPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .title("Test Post") + .content("Test Content") + .postStatus(PostStatus.ACTIVE) + .isAnonymous(false) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private PostEntity createPollPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.POLL) + .title("Test Poll") + .content("Test Poll Content") + .postStatus(PostStatus.ACTIVE) + .isAnonymous(false) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private PostEntity createAnonymousPostEntity() { + PostEntity post = PostEntity.builder() + .userId(new ObjectId()) + .postCategory(PostCategory.COMMUNICATION) + .title("Anonymous Post") + .content("Anonymous Content") + .postStatus(PostStatus.ACTIVE) + .isAnonymous(true) + .build(); + setIdFieldSafely(post, new ObjectId()); + return post; + } + + private UserEntity createUserEntity() { + return UserEntity.builder() + .nickname("testuser") + .build(); + } + + private void setIdFieldSafely(Object entity, ObjectId id) { + try { + java.lang.reflect.Field idField = entity.getClass().getDeclaredField("_id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (Exception e) { + throw new RuntimeException("Failed to set ID field", e); + } + } +} \ No newline at end of file diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java index 319a3f47..75f0eeb1 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java @@ -2,26 +2,20 @@ import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestService; -import inu.codin.codin.domain.post.domain.hits.service.HitsService; -import inu.codin.codin.domain.post.domain.poll.service.PollQueryService; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; import inu.codin.codin.domain.post.dto.response.PostPageResponse; -import inu.codin.codin.domain.post.dto.response.PollInfoResponseDTO; import inu.codin.codin.domain.post.entity.PostAnonymous; +import inu.codin.codin.domain.post.entity.PostStatus; +import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.exception.PostException; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.post.service.PostInteractionService; import inu.codin.codin.domain.post.service.PostQueryService; -import inu.codin.codin.domain.scrap.service.ScrapService; -import inu.codin.codin.domain.user.entity.UserEntity; -import inu.codin.codin.domain.user.repository.UserRepository; -import inu.codin.codin.infra.s3.S3Service; +import inu.codin.codin.domain.post.service.PostDtoAssembler; import org.bson.types.ObjectId; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -42,15 +36,10 @@ class PostQueryServiceTest { private PostQueryService postQueryService; @Mock private PostRepository postRepository; - @Mock private UserRepository userRepository; - @Mock private PollQueryService pollQueryService; @Mock private BlockService blockService; @Mock private PostInteractionService postInteractionService; @Mock private BestService bestService; - @Mock private ScrapService scrapService; - @Mock private LikeService likeService; - @Mock private S3Service s3Service; - @Mock private HitsService hitsService; + @Mock private PostDtoAssembler postDtoAssembler; private static AutoCloseable securityUtilsMock; @@ -71,12 +60,11 @@ void tearDown() throws Exception { List posts = Arrays.asList(createPostEntity(), createPostEntity()); Page page = new PageImpl<>(posts, PageRequest.of(0, 20), 2); + List mockDtoList = Arrays.asList(createMockPostPageItemResponseDTO(), createMockPostPageItemResponseDTO()); + given(blockService.getBlockedUsers()).willReturn(blockedUsers); given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any(PageRequest.class))).willReturn(page); - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(postDtoAssembler.toPageItemList(posts)).willReturn(mockDtoList); // When PostPageResponse response = postQueryService.getAllPosts(PostCategory.COMMUNICATION, 0); @@ -85,16 +73,19 @@ void tearDown() throws Exception { assertThat(response).isNotNull(); assertThat(response.getContents()).hasSize(2); verify(postRepository).getPostsByCategoryWithBlockedUsers(eq("COMMUNICATION"), eq(blockedUsers), any(PageRequest.class)); + verify(postDtoAssembler).toPageItemList(posts); } @Test void getAllPosts_빈결과_빈리스트반환() { // Given List blockedUsers = new ArrayList<>(); - Page emptyPage = new PageImpl<>(new ArrayList<>(), PageRequest.of(0, 20), 0); + List emptyPosts = new ArrayList<>(); + Page emptyPage = new PageImpl<>(emptyPosts, PageRequest.of(0, 20), 0); given(blockService.getBlockedUsers()).willReturn(blockedUsers); given(postRepository.getPostsByCategoryWithBlockedUsers(anyString(), anyList(), any(PageRequest.class))).willReturn(emptyPage); + given(postDtoAssembler.toPageItemList(emptyPosts)).willReturn(new ArrayList<>()); // When PostPageResponse response = postQueryService.getAllPosts(PostCategory.COMMUNICATION, 0); @@ -102,6 +93,7 @@ void tearDown() throws Exception { // Then assertThat(response).isNotNull(); assertThat(response.getContents()).isEmpty(); + verify(postDtoAssembler).toPageItemList(emptyPosts); } @Test @@ -110,12 +102,11 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); PostEntity post = createPostEntity(); ObjectId userId = new ObjectId(); + PostPageItemResponseDTO mockDto = createMockPostPageItemResponseDTO(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); + given(postDtoAssembler.toPageItem(post, userId)).willReturn(mockDto); doNothing().when(postInteractionService).increaseHits(any(), any()); // When @@ -123,8 +114,8 @@ void tearDown() throws Exception { // Then assertThat(response).isNotNull(); - assertThat(response.getPost()).isNotNull(); verify(postInteractionService).increaseHits(post, userId); + verify(postDtoAssembler).toPageItem(post, userId); } @Test @@ -145,12 +136,11 @@ void tearDown() throws Exception { ObjectId postId = new ObjectId(); PostEntity post = createPostEntity(); ObjectId userId = new ObjectId(); + PostPageItemResponseDTO mockDto = createMockPostPageItemResponseDTO(); given(postRepository.findByIdAndNotDeleted(postId)).willReturn(Optional.of(post)); given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); + given(postDtoAssembler.toPageItem(post, userId)).willReturn(mockDto); doNothing().when(postInteractionService).increaseHits(any(), any()); // When @@ -159,6 +149,7 @@ void tearDown() throws Exception { // Then assertThat(response).isPresent(); verify(postInteractionService).increaseHits(post, userId); + verify(postDtoAssembler).toPageItem(post, userId); } @Test @@ -183,13 +174,11 @@ void tearDown() throws Exception { List blockedUsers = new ArrayList<>(); List posts = Arrays.asList(createPostEntity()); Page page = new PageImpl<>(posts, PageRequest.of(0, 20), 1); + List mockDtoList = Arrays.asList(createMockPostPageItemResponseDTO()); given(blockService.getBlockedUsers()).willReturn(blockedUsers); given(postRepository.findAllByKeywordAndDeletedAtIsNull(anyString(), anyList(), any(PageRequest.class))).willReturn(page); - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(postDtoAssembler.toPageItemList(posts)).willReturn(mockDtoList); // When PostPageResponse response = postQueryService.searchPosts(keyword, 0); @@ -198,6 +187,7 @@ void tearDown() throws Exception { assertThat(response).isNotNull(); assertThat(response.getContents()).hasSize(1); verify(postRepository).findAllByKeywordAndDeletedAtIsNull(eq(keyword), eq(blockedUsers), any(PageRequest.class)); + verify(postDtoAssembler).toPageItemList(posts); } @Test @@ -208,13 +198,15 @@ void tearDown() throws Exception { new ObjectId().toString(), new ObjectId().toString() ); + List mockDtoList = Arrays.asList( + createMockPostPageItemResponseDTO(), + createMockPostPageItemResponseDTO(), + createMockPostPageItemResponseDTO() + ); given(bestService.getTop3BestPostIds()).willReturn(bestPostIds); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(createPostEntity())); - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(postDtoAssembler.toPageItemList(anyList())).willReturn(mockDtoList); // When List response = postQueryService.getTop3BestPosts(); @@ -222,6 +214,7 @@ void tearDown() throws Exception { // Then assertThat(response).isNotNull(); assertThat(response).hasSize(3); + verify(postDtoAssembler).toPageItemList(anyList()); } @Test @@ -231,14 +224,13 @@ void tearDown() throws Exception { String invalidPostId = new ObjectId().toString(); List bestPostIds = Arrays.asList(validPostId, invalidPostId); + List mockDtoList = Arrays.asList(createMockPostPageItemResponseDTO()); + given(bestService.getTop3BestPostIds()).willReturn(bestPostIds); given(postRepository.findByIdAndNotDeleted(any())) .willReturn(Optional.of(createPostEntity())) // 첫 번째 호출은 성공 .willReturn(Optional.empty()); // 두 번째 호출은 실패 - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(postDtoAssembler.toPageItemList(anyList())).willReturn(mockDtoList); doNothing().when(bestService).deleteBestPost(invalidPostId); // When @@ -247,6 +239,7 @@ void tearDown() throws Exception { // Then assertThat(response).hasSize(1); verify(bestService).deleteBestPost(invalidPostId); + verify(postDtoAssembler).toPageItemList(anyList()); } @Test @@ -256,12 +249,11 @@ void tearDown() throws Exception { List bestEntities = Arrays.asList(createBestEntity(), createBestEntity()); Page page = new PageImpl<>(bestEntities, PageRequest.of(0, 20), 2); + List mockDtoList = Arrays.asList(createMockPostPageItemResponseDTO(), createMockPostPageItemResponseDTO()); + given(bestService.getBestEntities(pageNumber)).willReturn(page); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(createPostEntity())); - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - mockUserInteractionServices(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(postDtoAssembler.toPageItemList(anyList())).willReturn(mockDtoList); // When PostPageResponse response = postQueryService.getBestPosts(pageNumber); @@ -270,6 +262,7 @@ void tearDown() throws Exception { assertThat(response).isNotNull(); assertThat(response.getContents()).hasSize(2); verify(bestService).getBestEntities(pageNumber); + verify(postDtoAssembler).toPageItemList(anyList()); } @Test @@ -300,54 +293,6 @@ void tearDown() throws Exception { .isInstanceOf(PostException.class); } - @Test - void getLikeCount_좋아요수조회_성공() { - // Given - PostEntity post = createPostEntity(); - int expectedCount = 5; - - given(likeService.getLikeCount(LikeType.POST, post.get_id().toString())).willReturn(expectedCount); - - // When - int result = postQueryService.getLikeCount(post); - - // Then - assertThat(result).isEqualTo(expectedCount); - verify(likeService).getLikeCount(LikeType.POST, post.get_id().toString()); - } - - @Test - void getScrapCount_스크랩수조회_성공() { - // Given - PostEntity post = createPostEntity(); - int expectedCount = 3; - - given(scrapService.getScrapCount(post.get_id())).willReturn(expectedCount); - - // When - int result = postQueryService.getScrapCount(post); - - // Then - assertThat(result).isEqualTo(expectedCount); - verify(scrapService).getScrapCount(post.get_id()); - } - - @Test - void getHitsCount_조회수조회_성공() { - // Given - PostEntity post = createPostEntity(); - int expectedCount = 10; - - given(hitsService.getHitsCount(post.get_id())).willReturn(expectedCount); - - // When - int result = postQueryService.getHitsCount(post); - - // Then - assertThat(result).isEqualTo(expectedCount); - verify(hitsService).getHitsCount(post.get_id()); - } - @Test void getUserAnonymousNumber_익명번호조회_성공() { // Given @@ -365,45 +310,19 @@ void tearDown() throws Exception { verify(postAnonymous).getAnonNumber(userId); } - @Test - void toPageItemDTO_Poll게시물_PollInfo포함() { - // Given - PostEntity pollPost = PostEntity.builder() - .userId(new ObjectId()) - .postCategory(PostCategory.POLL) - .build(); - setIdField(pollPost, new ObjectId()); - ObjectId userId = new ObjectId(); - PollInfoResponseDTO pollInfo = mock(PollInfoResponseDTO.class); - - given(userRepository.findById(any())).willReturn(Optional.of(createUserEntity())); - given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(pollQueryService.getPollInfo(pollPost, userId)).willReturn(pollInfo); - mockUserInteractionServices(); - - // When - PostPageItemResponseDTO result = postQueryService.getPostListResponseDtos(Arrays.asList(pollPost)).get(0); - - // Then - assertThat(result).isNotNull(); - assertThat(result.getPoll()).isEqualTo(pollInfo); - verify(pollQueryService).getPollInfo(pollPost, userId); - } // Helper methods - private void mockUserInteractionServices() { - given(likeService.getLikeCount(any(), any())).willReturn(0); - given(scrapService.getScrapCount(any())).willReturn(0); - given(hitsService.getHitsCount(any())).willReturn(0); - given(likeService.isLiked(any(),any(), (ObjectId) any())).willReturn(false); - given(scrapService.isPostScraped(any(), any())).willReturn(false); + private PostPageItemResponseDTO createMockPostPageItemResponseDTO() { + return PostPageItemResponseDTO.of(null, null); } private PostEntity createPostEntity() { PostEntity post = PostEntity.builder() .userId(new ObjectId()) .postCategory(PostCategory.COMMUNICATION) + .title("Test Post") + .content("Test Content") + .postStatus(PostStatus.ACTIVE) .isAnonymous(false) .build(); setIdField(post, new ObjectId()); From ee8144119a3d10416e16876aadd7e3b1675ec513 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 22 Aug 2025 10:19:45 +0900 Subject: [PATCH 0929/1002] =?UTF-8?q?refactor:=20UserService=20=EC=9D=98?= =?UTF-8?q?=20Post=20DTO=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20PostDtoAssembler=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 PostQueryService#getPostListResponseDtos()는 @Deprecated 처리 ( 외부 호환성용도 유지 ) - UserService 내 deprecate Logic PostDtoAssembler 호출로 대체 --- .../codin/domain/post/service/PostQueryService.java | 9 --------- .../codin/codin/domain/user/service/UserService.java | 11 ++++++----- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index f6c9ca14..60adc2f6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -128,15 +128,6 @@ public PostPageResponse getBestPosts(int pageNumber) { ); } - /** - * 게시물 리스트 DTO 변환 (불변 리스트) - 다른 서비스와의 호환성을 위해 유지(userSerice) - * @deprecated 내부적으로는 PostDtoAssembler 사용을 권장 - */ - @Deprecated - public List getPostListResponseDtos(List posts) { - return postDtoAssembler.toPageItemList(posts); - } - /** * 유저의 익명 번호 조회 */ diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 1fc088db..d39ab051 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.service.PostDtoAssembler; import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; @@ -48,7 +49,7 @@ public class UserService { private final ScrapRepository scrapRepository; private final CommentRepository commentRepository; - private final PostQueryService postQueryService; + private final PostDtoAssembler postDtoAssembler; private final S3Service s3Service; private final JwtService jwtService; @@ -63,7 +64,7 @@ public PostPageResponse getAllUserPosts(int pageNumber) { log.info("[게시글 조회 성공] 조회된 게시글 수: {}, 총 페이지 수: {}", page.getContent().size(), page.getTotalPages()); return PostPageResponse.of( - postQueryService.getPostListResponseDtos(page.getContent()), + postDtoAssembler.toPageItemList(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1 ); @@ -83,7 +84,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) .toList(); log.info("[좋아요 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", likePage.getTotalPages(), likePage.hasNext()); - return PostPageResponse.of(postQueryService.getPostListResponseDtos(postUserLike), likePage.getTotalPages() - 1, likePage.hasNext() ? likePage.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postDtoAssembler.toPageItemList(postUserLike), likePage.getTotalPages() - 1, likePage.hasNext() ? likePage.getPageable().getPageNumber() + 1 : -1); } case SCRAP -> { log.info("[스크랩 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); @@ -93,7 +94,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) .toList(); log.info("[스크랩 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", scrapPage.getTotalPages(), scrapPage.hasNext()); - return PostPageResponse.of(postQueryService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages() - 1, scrapPage.hasNext() ? scrapPage.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postDtoAssembler.toPageItemList(postUserScrap), scrapPage.getTotalPages() - 1, scrapPage.hasNext() ? scrapPage.getPageable().getPageNumber() + 1 : -1); } case COMMENT -> { log.info("[댓글 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); @@ -112,7 +113,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i .stream() .toList(); log.info("[댓글 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", commentPage.getTotalPages(), commentPage.hasNext()); - return PostPageResponse.of(postQueryService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages() - 1, commentPage.hasNext() ? commentPage.getPageable().getPageNumber() + 1 : -1); + return PostPageResponse.of(postDtoAssembler.toPageItemList(postUserComment), commentPage.getTotalPages() - 1, commentPage.hasNext() ? commentPage.getPageable().getPageNumber() + 1 : -1); } default -> { log.warn("[유효하지 않은 상호작용 타입] 유저 ID: {}, 상호작용 타입: {}", userId, interactionType); From 89e0931b68be140073ac8958ab6ccaae960b7b66 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 22 Aug 2025 10:48:43 +0900 Subject: [PATCH 0930/1002] =?UTF-8?q?refactor:=20PostScheduler=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=B6=80=EB=B6=84=20=EB=AA=A8=EB=93=88=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Test code : ModckedConstruction 으로 ProcessBuilder 대신 생성 후 Python Script run 없이 Logic Test --- .../domain/post/schedular/PostsScheduler.java | 54 ++-- .../post/schedular/PostsSchedulerTest.java | 261 ++++++++++++++++++ 2 files changed, 286 insertions(+), 29 deletions(-) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index 15d16b5e..bc364686 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -26,44 +26,40 @@ public class PostsScheduler { @Scheduled(cron = "${schedule.department.cron}", zone = "Asia/Seoul") @Async public void departmentPostsScheduler() { - try { - String fileName = "department.py"; - ProcessBuilder processBuilder = - new ProcessBuilder().inheritIO().command( - PYTHON_DIR, - PATH + fileName - ); - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - log.warn("Exited department python with error code" + exitCode); - if (exitCode == 0) - log.info("[PostsScheduler] 학과 공지사항 업데이트 완료"); - else log.warn("[PostsScheduler] 학과 공지사항 업데이트 실패"); - } catch (IOException | InterruptedException e) { - log.error(e.getMessage(), e.getStackTrace()[0]); - throw new SchedulerException(SchedulerErrorCode.SCHEDULER_INTERRUPT_ERROR); - } - + runPythonScript("department.py", "학과 공지사항"); } @Scheduled(cron = "${schedule.starinu.cron}", zone = "Asia/Seoul") @Async - public void starinuPostsScheduler(){ + public void starinuPostsScheduler() { + runPythonScript("starinu.py", "STARINU 공지사항"); + } + + /** + * Python 스크립트 실행 공통 로직 + * + * @param fileName 실행할 python 파일명 + * @param taskName 로그 출력용 태스크 이름 + */ + private void runPythonScript(String fileName, String taskName) { try { - String fileName = "starinu.py"; ProcessBuilder processBuilder = - new ProcessBuilder().inheritIO().command( - PYTHON_DIR, - PATH + fileName - ); + new ProcessBuilder() + .inheritIO() + .command(PYTHON_DIR, PATH + fileName); + Process process = processBuilder.start(); int exitCode = process.waitFor(); - log.warn("Exited starinu python with error code" + exitCode); - if (exitCode == 0) - log.info("[PostsScheduler] STARINU 공지사항 업데이트 완료"); - else log.warn("[PostsScheduler] STARINU 공지사항 업데이트 실패"); + + log.warn("Exited {} python with error code {}", taskName, exitCode); + if (exitCode == 0) { + log.info("[PostsScheduler] {} 업데이트 완료", taskName); + } else { + log.warn("[PostsScheduler] {} 업데이트 실패", taskName); + } + } catch (IOException | InterruptedException e) { - log.error(e.getMessage(), e.getStackTrace()[0]); + log.error("[PostsScheduler] {} 실행 중 오류: {}", taskName, e.getMessage(), e); throw new SchedulerException(SchedulerErrorCode.SCHEDULER_INTERRUPT_ERROR); } } diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java new file mode 100644 index 00000000..dfd4b189 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java @@ -0,0 +1,261 @@ +package inu.codin.codin.domain.post.schedular; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.lang.reflect.Method; + +import inu.codin.codin.domain.post.schedular.exception.SchedulerException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +class PostsSchedulerTest { + + @InjectMocks + private PostsScheduler postsScheduler; + + @Mock + private Process process; + + private final String TEST_PATH = "/test/path/"; + private final String TEST_PYTHON_DIR = "/usr/bin/python3"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(postsScheduler, "PATH", TEST_PATH); + ReflectionTestUtils.setField(postsScheduler, "PYTHON_DIR", TEST_PYTHON_DIR); + } + + @Test + @DisplayName("학과 공지사항 스케줄러 정상 실행") + void departmentPostsScheduler_Success() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenReturn(process); + })) { + + when(process.waitFor()).thenReturn(0); + + // When + postsScheduler.departmentPostsScheduler(); + + // Then + assertEquals(1, processBuilderMock.constructed().size()); + ProcessBuilder constructedBuilder = processBuilderMock.constructed().get(0); + verify(constructedBuilder).inheritIO(); + verify(constructedBuilder).command(TEST_PYTHON_DIR, TEST_PATH + "department.py"); + verify(constructedBuilder).start(); + verify(process).waitFor(); + } + } + + @Test + @DisplayName("STARINU 공지사항 스케줄러 정상 실행") + void starinuPostsScheduler_Success() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenReturn(process); + })) { + + when(process.waitFor()).thenReturn(0); + + // When + postsScheduler.starinuPostsScheduler(); + + // Then + assertEquals(1, processBuilderMock.constructed().size()); + ProcessBuilder constructedBuilder = processBuilderMock.constructed().get(0); + verify(constructedBuilder).inheritIO(); + verify(constructedBuilder).command(TEST_PYTHON_DIR, TEST_PATH + "starinu.py"); + verify(constructedBuilder).start(); + verify(process).waitFor(); + } + } + + @Test + @DisplayName("Python 스크립트 실행 성공 - 정상 종료 코드") + void runPythonScript_Success_ExitCode0() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenReturn(process); + })) { + + when(process.waitFor()).thenReturn(0); + + // When + Method runPythonScript = PostsScheduler.class.getDeclaredMethod("runPythonScript", String.class, String.class); + runPythonScript.setAccessible(true); + + assertDoesNotThrow(() -> { + runPythonScript.invoke(postsScheduler, "test.py", "테스트 태스크"); + }); + + // Then + verify(process).waitFor(); + } + } + + @Test + @DisplayName("Python 스크립트 실행 실패 - 비정상 종료 코드") + void runPythonScript_Failure_NonZeroExitCode() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenReturn(process); + })) { + + when(process.waitFor()).thenReturn(1); // 비정상 종료 코드 + + // When + Method runPythonScript = PostsScheduler.class.getDeclaredMethod("runPythonScript", String.class, String.class); + runPythonScript.setAccessible(true); + + // 비정상 종료 코드여도 예외가 발생하지 않고 로그만 출력됨 + assertDoesNotThrow(() -> { + runPythonScript.invoke(postsScheduler, "test.py", "테스트 태스크"); + }); + + // Then + verify(process).waitFor(); + } + } + + @Test + @DisplayName("Python 스크립트 실행 중 IOException 발생") + void runPythonScript_IOException() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenThrow(new IOException("테스트 IOException")); + })) { + + // When & Then + Method runPythonScript = PostsScheduler.class.getDeclaredMethod("runPythonScript", String.class, String.class); + runPythonScript.setAccessible(true); + + Exception exception = assertThrows(Exception.class, () -> { + runPythonScript.invoke(postsScheduler, "test.py", "테스트 태스크"); + }); + + // InvocationTargetException이 발생하므로 원인을 확인 + assertTrue(exception.getCause() instanceof SchedulerException); + } + } + + @Test + @DisplayName("Python 스크립트 실행 중 InterruptedException 발생") + void runPythonScript_InterruptedException() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenReturn(process); + })) { + + when(process.waitFor()).thenThrow(new InterruptedException("테스트 InterruptedException")); + + // When & Then + Method runPythonScript = PostsScheduler.class.getDeclaredMethod("runPythonScript", String.class, String.class); + runPythonScript.setAccessible(true); + + Exception exception = assertThrows(Exception.class, () -> { + runPythonScript.invoke(postsScheduler, "test.py", "테스트 태스크"); + }); + + // InvocationTargetException이 발생하므로 원인을 확인 + assertTrue(exception.getCause() instanceof SchedulerException); + } + } + + @Test + @DisplayName("스케줄러 어노테이션 설정 검증") + void validateSchedulerAnnotations() throws Exception { + // Given + Class clazz = PostsScheduler.class; + + // When & Then - departmentPostsScheduler 메서드 어노테이션 검증 + Method departmentMethod = clazz.getMethod("departmentPostsScheduler"); + assertTrue(departmentMethod.isAnnotationPresent(Scheduled.class)); + assertTrue(departmentMethod.isAnnotationPresent(Async.class)); + + Scheduled departmentScheduled = departmentMethod.getAnnotation(Scheduled.class); + assertEquals("${schedule.department.cron}", departmentScheduled.cron()); + assertEquals("Asia/Seoul", departmentScheduled.zone()); + + // starinuPostsScheduler 메서드 어노테이션 검증 + Method starinuMethod = clazz.getMethod("starinuPostsScheduler"); + assertTrue(starinuMethod.isAnnotationPresent(Scheduled.class)); + assertTrue(starinuMethod.isAnnotationPresent(Async.class)); + + Scheduled starinuScheduled = starinuMethod.getAnnotation(Scheduled.class); + assertEquals("${schedule.starinu.cron}", starinuScheduled.cron()); + assertEquals("Asia/Seoul", starinuScheduled.zone()); + } + + @Test + @DisplayName("프로퍼티 값 주입 테스트") + void validatePropertyInjection() { + // Given & When + String path = (String) ReflectionTestUtils.getField(postsScheduler, "PATH"); + String pythonDir = (String) ReflectionTestUtils.getField(postsScheduler, "PYTHON_DIR"); + + // Then + assertEquals(TEST_PATH, path); + assertEquals(TEST_PYTHON_DIR, pythonDir); + } + + @Test + @DisplayName("ProcessBuilder 체이닝 메서드 호출 순서 검증") + void validateProcessBuilderMethodChaining() throws Exception { + // Given + try (MockedConstruction processBuilderMock = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.inheritIO()).thenReturn(mock); + when(mock.command(anyString(), anyString())).thenReturn(mock); + when(mock.start()).thenReturn(process); + })) { + + when(process.waitFor()).thenReturn(0); + + // When + Method runPythonScript = PostsScheduler.class.getDeclaredMethod("runPythonScript", String.class, String.class); + runPythonScript.setAccessible(true); + runPythonScript.invoke(postsScheduler, "test.py", "테스트 태스크"); + + // Then - 메서드 호출 순서 검증 + ProcessBuilder constructedBuilder = processBuilderMock.constructed().get(0); + InOrder inOrder = inOrder(constructedBuilder, process); + inOrder.verify(constructedBuilder).inheritIO(); + inOrder.verify(constructedBuilder).command(TEST_PYTHON_DIR, TEST_PATH + "test.py"); + inOrder.verify(constructedBuilder).start(); + inOrder.verify(process).waitFor(); + } + } +} \ No newline at end of file From 01d3b403bc669da4ea621f470d80862bddcf64f2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 22 Aug 2025 11:24:05 +0900 Subject: [PATCH 0931/1002] =?UTF-8?q?refactor:=20Scheduler=20testcode=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=ED=95=84=EB=93=9C=20static=20final?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/schedular/PostsSchedulerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java index dfd4b189..bd514567 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java @@ -30,8 +30,8 @@ class PostsSchedulerTest { @Mock private Process process; - private final String TEST_PATH = "/test/path/"; - private final String TEST_PYTHON_DIR = "/usr/bin/python3"; + private static final String TEST_PATH = "/test/path/"; + private static final String TEST_PYTHON_DIR = "/usr/bin/python3"; @BeforeEach void setUp() { From 9ec64924233bc7086cc5bfc1a89273a9552f4bba Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 22 Aug 2025 13:21:16 +0900 Subject: [PATCH 0932/1002] =?UTF-8?q?chore:=20TODO=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20PostScheduler=20=EC=99=B8=EB=B6=80=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=84=B8=EC=8A=A4(Python=20script)=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91=20=EC=A0=95=EC=B1=85=20=EC=88=98=EB=A6=BD=20?= =?UTF-8?q?=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/schedular/PostsScheduler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java index bc364686..649e3f28 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java @@ -59,6 +59,9 @@ private void runPythonScript(String fileName, String taskName) { } } catch (IOException | InterruptedException e) { + // TODO: InterruptedException 발생 시 현재 스레드 인터럽트 플래그 복구 필요 + // - Thread.currentThread().interrupt(); 호출 후 적절히 종료 처리 + // - 단순히 예외 변환만 하면 종료/재시작 시 스레드가 정상적으로 중단되지 않을 수 있음 log.error("[PostsScheduler] {} 실행 중 오류: {}", taskName, e.getMessage(), e); throw new SchedulerException(SchedulerErrorCode.SCHEDULER_INTERRUPT_ERROR); } From 70bd39ad23fcbe5de12cc691a9b5e01941912a03 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 24 Aug 2025 22:48:13 +0900 Subject: [PATCH 0933/1002] =?UTF-8?q?feat:=20=EB=A6=AC=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=BA=98=EB=A6=B0=EB=8D=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CalendarControllerImpl.java | 43 ++++++++++ .../swagger/CalendarController.java | 46 +++++++++++ .../calendar/dto/CalendarCreateRequest.java | 41 ++++++++++ .../calendar/dto/CalendarCreateResponse.java | 51 ++++++++++++ .../calendar/dto/CalendarDayResponse.java | 28 +++++++ .../calendar/dto/CalendarMonthResponse.java | 27 +++++++ .../codin/domain/calendar/dto/EventDto.java | 36 +++++++++ .../calendar/entity/CalendarEntity.java | 39 +++++++++ .../calendar/exception/CalendarErrorCode.java | 25 ++++++ .../calendar/exception/CalendarException.java | 15 ++++ .../repository/CalendarRepository.java | 15 ++++ .../calendar/service/CalendarService.java | 80 +++++++++++++++++++ .../department/DepartmentPostController.java | 35 ++++++++ 13 files changed, 481 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarDayResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarMonthResponse.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java new file mode 100644 index 00000000..ef839c35 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java @@ -0,0 +1,43 @@ +package inu.codin.codin.domain.calendar.controller; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.calendar.controller.swagger.CalendarController; +import inu.codin.codin.domain.calendar.dto.CalendarCreateRequest; +import inu.codin.codin.domain.calendar.dto.CalendarCreateResponse; +import inu.codin.codin.domain.calendar.dto.CalendarMonthResponse; +import inu.codin.codin.domain.calendar.service.CalendarService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/calender") +@RequiredArgsConstructor +public class CalendarControllerImpl implements CalendarController { + + private final CalendarService calendarService; + + @GetMapping("/month") + public ResponseEntity> getMonth( + @RequestParam int year, + @RequestParam int month + ) { + return ResponseEntity.ok().body(new SingleResponse<>(200, "캘린더 반환 완료", + calendarService.getMonth(year, month))); + } + + @PostMapping("/events") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") + public ResponseEntity> create(@RequestBody CalendarCreateRequest request) { + return ResponseEntity.status(201).body(new SingleResponse<>(201, "켈린더 이벤트 생성 완료", + calendarService.create(request))); + } + + @DeleteMapping("/{eventId}") + @PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')") + public ResponseEntity> delete(@PathVariable String eventId) { + calendarService.delete(eventId); + return ResponseEntity.accepted().body(new SingleResponse<>(202, "캘린더 이벤트 삭제 완료", null)); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java new file mode 100644 index 00000000..4df65128 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java @@ -0,0 +1,46 @@ +package inu.codin.codin.domain.calendar.controller.swagger; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.calendar.dto.CalendarCreateRequest; +import inu.codin.codin.domain.calendar.dto.CalendarCreateResponse; +import inu.codin.codin.domain.calendar.dto.CalendarMonthResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@Tag(name = "Calendar API", description = "[리디자인] 캘린더 API") +public interface CalendarController { + + @Operation(summary = "월별 캘린더 조회", description = "특정 년도와 월의 캘린더 이벤트를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "캘린더 조회 성공"), + }) + ResponseEntity> getMonth( + @Parameter(description = "년도", example = "2025") @RequestParam int year, + @Parameter(description = "월", example = "8") @RequestParam int month + ); + + @Operation(summary = "캘린더 이벤트 생성", description = "새로운 캘린더 이벤트를 생성합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "이벤트 생성 성공"), + }) + ResponseEntity> create( + @Valid @RequestBody CalendarCreateRequest request + ); + + @Operation(summary = "캘린더 이벤트 삭제", description = "특정 캘린더 이벤트를 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "이벤트 삭제 성공"), + @ApiResponse(responseCode = "404", description = "이벤트를 찾을 수 없음") + }) + ResponseEntity> delete( + @Parameter(description = "이벤트 ID", example = "68ab0ac4ffbcdc7080e8d663") @PathVariable String eventId + ); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java new file mode 100644 index 00000000..0c4ccd02 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java @@ -0,0 +1,41 @@ +package inu.codin.codin.domain.calendar.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.common.dto.Department; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +public class CalendarCreateRequest { + + @Schema(description = "이벤트 시작 날짜", example = "2025-08-23") + @JsonFormat(pattern = "yyyy-MM-dd") + @NotNull + private final LocalDate startDate; + + @Schema(description = "이벤트 종료 날짜", example = "2025-08-29") + @JsonFormat(pattern = "yyyy-MM-dd") + @NotNull + private final LocalDate endDate; + + @Schema(description = "이벤트 내용", example = "테스트 이벤트") + @NotBlank + private final String content; + + @Schema(description = "학과 또는 대학 전체 행사(IT_COLLEAGE로 지정)", example = "COMPUTER_SCI") + @NotNull + private final Department department; + + @Builder + public CalendarCreateRequest(LocalDate startDate, LocalDate endDate, String content, Department department) { + this.startDate = startDate; + this.endDate = endDate; + this.content = content; + this.department = department; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java new file mode 100644 index 00000000..ef4e9c07 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java @@ -0,0 +1,51 @@ +package inu.codin.codin.domain.calendar.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.calendar.entity.CalendarEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +public class CalendarCreateResponse { + + @Schema(description = "이벤트 ID", example = "68ab0ac4ffbcdc7080e8d663") + private final String eventId; + + @Schema(description = "이벤트 시작 날짜", example = "2025-08-23") + @JsonFormat(pattern = "yyyy-MM-dd") + private final LocalDate startDate; + + @Schema(description = "이벤트 종료 날짜", example = "2025-08-29") + @JsonFormat(pattern = "yyyy-MM-dd") + private final LocalDate endDate; + + @Schema(description = "이벤트 내용", example = "테스트 이벤트") + private final String content; + + @Schema(description = "학과 (한글 가능)", example = "COMPUTER_SCI") + private final Department department; + + @Builder + public CalendarCreateResponse(String eventId, LocalDate startDate, LocalDate endDate, String content, Department department) { + this.eventId = eventId; + this.startDate = startDate; + this.endDate = endDate; + this.content = content; + this.department = department; + } + + public static CalendarCreateResponse of(CalendarEntity calendar) { + return CalendarCreateResponse.builder() + .eventId(ObjectIdUtil.toString(calendar.getId())) + .startDate(calendar.getStartDate()) + .endDate(calendar.getEndDate()) + .content(calendar.getContent()) + .department(calendar.getDepartment() != null ? calendar.getDepartment() : null) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarDayResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarDayResponse.java new file mode 100644 index 00000000..5e46b15d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarDayResponse.java @@ -0,0 +1,28 @@ +package inu.codin.codin.domain.calendar.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +public class CalendarDayResponse { + + @Schema(description = "날짜", example = "2025-08-23") + @JsonFormat(pattern = "yyyy-MM-dd") + private final LocalDate date; + + @Schema(description = "해당 날짜의 총 이벤트 수", example = "2") + private final int totalCont; + + @Schema(description = "해당 날짜의 이벤트 목록") + private final List items; + + public CalendarDayResponse(LocalDate date, int totalCont, List items) { + this.date = date; + this.totalCont = totalCont; + this.items = items; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarMonthResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarMonthResponse.java new file mode 100644 index 00000000..b812cf11 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarMonthResponse.java @@ -0,0 +1,27 @@ +package inu.codin.codin.domain.calendar.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +public class CalendarMonthResponse { + + @Schema(description = "년도", example = "2025") + private final int year; + + @Schema(description = "월", example = "8") + private final int month; + + @Schema(description = "해당 월의 일별 이벤트 목록") + private final List days; + + @Builder + public CalendarMonthResponse(int year, int month, List days) { + this.year = year; + this.month = month; + this.days = days; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java new file mode 100644 index 00000000..80f3be68 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java @@ -0,0 +1,36 @@ +package inu.codin.codin.domain.calendar.dto; + +import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.calendar.entity.CalendarEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class EventDto { + + @Schema(description = "이벤트 ID", example = "6214897124abc...") + private final String eventId; + + @Schema(description = "이벤트 내용", example = "테스트 이벤트") + private final String content; + + @Schema(description = "학과", example = "COMPUTER_SCI") + private final Department department; + + @Builder + public EventDto(String eventId, String content, Department department) { + this.eventId = eventId; + this.content = content; + this.department = department; + } + + public static EventDto of(CalendarEntity calendarEntity) { + return EventDto.builder() + .eventId(ObjectIdUtil.toString(calendarEntity.getId())) + .content(calendarEntity.getContent()) + .department(calendarEntity.getDepartment() != null ? calendarEntity.getDepartment() : null) + .build(); + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java new file mode 100644 index 00000000..d9368b2d --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java @@ -0,0 +1,39 @@ +package inu.codin.codin.domain.calendar.entity; + +import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.codin.common.dto.Department; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDate; + +@Getter +@Document(collection = "calendar_events") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CalendarEntity extends BaseTimeEntity { + + @Id + ObjectId id; + + @Indexed(name = "startDate_idx") + LocalDate startDate; + @Indexed(name = "endDate_idx") + LocalDate endDate; + + private String content; + private Department department; + + @Builder + public CalendarEntity(String content, Department department, LocalDate startDate, LocalDate endDate) { + this.content = content; + this.department = department; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java new file mode 100644 index 00000000..1dac8a56 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java @@ -0,0 +1,25 @@ +package inu.codin.codin.domain.calendar.exception; + +import inu.codin.codin.common.exception.GlobalErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum CalendarErrorCode implements GlobalErrorCode { + + CALENDAR_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "달력을 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } +} + diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java new file mode 100644 index 00000000..2613c31a --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.calendar.exception; + +import inu.codin.codin.common.exception.GlobalException; +import lombok.Getter; + +@Getter +public class CalendarException extends GlobalException { + + private final CalendarErrorCode calendarErrorCode; + + public CalendarException(CalendarErrorCode calendarErrorCode) { + super(calendarErrorCode); + this.calendarErrorCode = calendarErrorCode; + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java new file mode 100644 index 00000000..e297a672 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java @@ -0,0 +1,15 @@ +package inu.codin.codin.domain.calendar.repository; + +import inu.codin.codin.domain.calendar.entity.CalendarEntity; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; + +@Repository +public interface CalendarRepository extends MongoRepository { + + List findByEndDateGreaterThanEqualAndStartDateLessThanEqual(LocalDate endDate, LocalDate startDate); +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java new file mode 100644 index 00000000..47f22161 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java @@ -0,0 +1,80 @@ +package inu.codin.codin.domain.calendar.service; + +import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.codin.domain.calendar.dto.*; +import inu.codin.codin.domain.calendar.entity.CalendarEntity; +import inu.codin.codin.domain.calendar.exception.CalendarErrorCode; +import inu.codin.codin.domain.calendar.exception.CalendarException; +import inu.codin.codin.domain.calendar.repository.CalendarRepository; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class CalendarService { + + private final CalendarRepository calendarRepository; + + public CalendarMonthResponse getMonth(int year, int month) { + LocalDate monthStart = LocalDate.of(year, month, 1); + LocalDate monthEnd = monthStart.withDayOfMonth(monthStart.lengthOfMonth()); + + List calendarEventList = calendarRepository + .findByEndDateGreaterThanEqualAndStartDateLessThanEqual(monthStart, monthEnd).stream() + .filter(e -> e.getStartDate() != null && e.getEndDate() != null) + .toList(); + + // 날짜 별로 클램핑 + Map> days = new HashMap<>(); + for (CalendarEntity e : calendarEventList) { + LocalDate startDate = e.getStartDate().isBefore(monthStart) ? monthStart : e.getStartDate(); + LocalDate endDate = e.getEndDate().isAfter(monthEnd) ? monthEnd : e.getEndDate(); + for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { + days.computeIfAbsent(date, k -> new ArrayList<>()).add(e); + } + } + + // 날짜 별로 정렬하기 + List dayResponses = new ArrayList<>(); + for (LocalDate date = monthStart; !date.isAfter(monthEnd); date = date.plusDays(1)) { + List list = new ArrayList<>(days.getOrDefault(date, Collections.emptyList())); + list.sort(Comparator.comparing(CalendarEntity::getStartDate)); + + int total = list.size(); + List events = list.stream() + .map(EventDto::of) + .toList(); + dayResponses.add(new CalendarDayResponse(date, total, events)); + } + return CalendarMonthResponse.builder() + .year(year) + .month(month) + .days(dayResponses) + .build(); + } + + public CalendarCreateResponse create(CalendarCreateRequest request) { + CalendarEntity entity = CalendarEntity.builder() + .content(request.getContent()) + .department(request.getDepartment()) + .startDate(request.getStartDate()) + .endDate(request.getEndDate()) + .build(); + + CalendarEntity savedEntity = calendarRepository.save(entity); + return CalendarCreateResponse.of(savedEntity); + } + + public void delete(String id) { + try { + ObjectId objectId = ObjectIdUtil.toObjectId(id); + calendarRepository.deleteById(objectId); + } catch (Exception e) { + throw new CalendarException(CalendarErrorCode.CALENDAR_EVENT_NOT_FOUND); + } + } +} diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java new file mode 100644 index 00000000..31d7d05b --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java @@ -0,0 +1,35 @@ +//package inu.codin.codin.domain.post.department; +// +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import lombok.RequiredArgsConstructor; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.PostMapping; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RestController; +// +//@RestController +//@RequestMapping("/department/posts") +//@Tag(name = "Department Post API", description = "리디자인 학과별 게시판 API") +//@RequiredArgsConstructor +//public class DepartmentPostController { +// +// @Operation(summary = "공지사항 리스트 반환") +// @GetMapping +// +// @Operation(summary = "공지사항 항목 세부내용 반환") +// @GetMapping +// +// @Operation(summary = "자주묻는 질문 리스트 반환") +// @GetMapping +// +// @Operation(summary = "익명의 소리함 리스트 반환") +// @GetMapping +// +// @Operation(summary = "익명의 소리함 리스트 반환") +// @GetMapping +// +// @Operation(summary = "익명의 소리함 생성") +// @PostMapping +// +//} From 5cb819c2cea8422e50040b9ec4e0e9872bc6dd3a Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 24 Aug 2025 23:04:20 +0900 Subject: [PATCH 0934/1002] =?UTF-8?q?refactor:=20=EC=BA=98=EB=A6=B0?= =?UTF-8?q?=EB=8D=94=20document=20soft=20delete=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/repository/CalendarRepository.java | 6 ++++++ .../domain/calendar/service/CalendarService.java | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java index e297a672..ee2a490b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/repository/CalendarRepository.java @@ -3,13 +3,19 @@ import inu.codin.codin.domain.calendar.entity.CalendarEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.List; +import java.util.Optional; @Repository public interface CalendarRepository extends MongoRepository { + @Query("{ 'endDate': { $gte: ?0 }, 'startDate': { $lte: ?1 }, 'deleted_at': null }") List findByEndDateGreaterThanEqualAndStartDateLessThanEqual(LocalDate endDate, LocalDate startDate); + + @Query("{ '_id': ?0, 'deleted_at': null }") + Optional findByIdAndNotDeleted(ObjectId id); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java index 47f22161..30a51db2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java @@ -70,11 +70,10 @@ public CalendarCreateResponse create(CalendarCreateRequest request) { } public void delete(String id) { - try { - ObjectId objectId = ObjectIdUtil.toObjectId(id); - calendarRepository.deleteById(objectId); - } catch (Exception e) { - throw new CalendarException(CalendarErrorCode.CALENDAR_EVENT_NOT_FOUND); - } + ObjectId objectId = ObjectIdUtil.toObjectId(id); + CalendarEntity calendar = calendarRepository.findByIdAndNotDeleted(objectId) + .orElseThrow(() -> new CalendarException(CalendarErrorCode.CALENDAR_EVENT_NOT_FOUND)); + calendar.delete(); + calendarRepository.save(calendar); } } From 2f6a2b1a1daf1f6d1be60ee3ab3199fff10a515c Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 24 Aug 2025 23:59:03 +0900 Subject: [PATCH 0935/1002] =?UTF-8?q?refactor:=20PR=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20#243?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CalendarControllerImpl.java | 2 +- .../swagger/CalendarController.java | 4 ++- .../codin/domain/calendar/dto/EventDto.java | 2 +- .../calendar/exception/CalendarErrorCode.java | 4 ++- .../calendar/service/CalendarService.java | 11 ++++++ .../department/DepartmentPostController.java | 35 ------------------- 6 files changed, 19 insertions(+), 39 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java index ef839c35..ead43c76 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/calender") +@RequestMapping("/calendar") @RequiredArgsConstructor public class CalendarControllerImpl implements CalendarController { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java index 4df65128..303a79f7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java @@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -24,7 +26,7 @@ public interface CalendarController { }) ResponseEntity> getMonth( @Parameter(description = "년도", example = "2025") @RequestParam int year, - @Parameter(description = "월", example = "8") @RequestParam int month + @Parameter(description = "월", example = "8") @RequestParam @Min(1) @Max(12) int month ); @Operation(summary = "캘린더 이벤트 생성", description = "새로운 캘린더 이벤트를 생성합니다.") diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java index 80f3be68..f6652180 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java @@ -16,7 +16,7 @@ public class EventDto { @Schema(description = "이벤트 내용", example = "테스트 이벤트") private final String content; - @Schema(description = "학과", example = "COMPUTER_SCI") + @Schema(description = "학과 또는 대학 전체 행사(OTHERS로 지정) ", example = "COMPUTER_SCI") private final Department department; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java index 1dac8a56..233528e1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java @@ -7,7 +7,9 @@ @RequiredArgsConstructor public enum CalendarErrorCode implements GlobalErrorCode { - CALENDAR_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "달력을 찾을 수 없습니다."); + CALENDAR_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "캘린더 이벤트를 찾을 수 없습니다."), + DATE_CANNOT_NULL(HttpStatus.BAD_REQUEST, "날짜를 입력해야 합니다."), + DATE_FORMAT_ERROR(HttpStatus.BAD_REQUEST, "날짜 형식이 잘못되었습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java index 30a51db2..73389f3d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java @@ -20,6 +20,10 @@ public class CalendarService { private final CalendarRepository calendarRepository; public CalendarMonthResponse getMonth(int year, int month) { + if (month < 1 || month > 12) { + throw new CalendarException(CalendarErrorCode.DATE_FORMAT_ERROR); + } + LocalDate monthStart = LocalDate.of(year, month, 1); LocalDate monthEnd = monthStart.withDayOfMonth(monthStart.lengthOfMonth()); @@ -58,6 +62,13 @@ public CalendarMonthResponse getMonth(int year, int month) { } public CalendarCreateResponse create(CalendarCreateRequest request) { + if (request.getStartDate() == null || request.getEndDate() == null) { + throw new CalendarException(CalendarErrorCode.DATE_CANNOT_NULL); + } + if (request.getStartDate().isAfter(request.getEndDate())) { + throw new CalendarException(CalendarErrorCode.DATE_FORMAT_ERROR); + } + CalendarEntity entity = CalendarEntity.builder() .content(request.getContent()) .department(request.getDepartment()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java deleted file mode 100644 index 31d7d05b..00000000 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/department/DepartmentPostController.java +++ /dev/null @@ -1,35 +0,0 @@ -//package inu.codin.codin.domain.post.department; -// -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.tags.Tag; -//import lombok.RequiredArgsConstructor; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.PostMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RestController; -// -//@RestController -//@RequestMapping("/department/posts") -//@Tag(name = "Department Post API", description = "리디자인 학과별 게시판 API") -//@RequiredArgsConstructor -//public class DepartmentPostController { -// -// @Operation(summary = "공지사항 리스트 반환") -// @GetMapping -// -// @Operation(summary = "공지사항 항목 세부내용 반환") -// @GetMapping -// -// @Operation(summary = "자주묻는 질문 리스트 반환") -// @GetMapping -// -// @Operation(summary = "익명의 소리함 리스트 반환") -// @GetMapping -// -// @Operation(summary = "익명의 소리함 리스트 반환") -// @GetMapping -// -// @Operation(summary = "익명의 소리함 생성") -// @PostMapping -// -//} From 590f5a1a13a53e16d6efac551bcd47d991850cd7 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Tue, 2 Sep 2025 22:30:08 +0900 Subject: [PATCH 0936/1002] =?UTF-8?q?fix:=20=EC=A2=8B=EC=95=84=EC=9A=94?= =?UTF-8?q?=EA=B0=80=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EA=B0=80=20=EB=B0=98=ED=99=98=EB=90=98=EB=8A=94=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/like/repository/LikeRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java index 1976c8b9..7b92004f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/repository/LikeRepository.java @@ -22,6 +22,6 @@ public interface LikeRepository extends MongoRepository { Optional findByLikeTypeAndLikeTypeIdAndUserId(LikeType likeType, String likeTypeId, ObjectId userId); Page findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(ObjectId userId, LikeType likeType, Pageable pageable); - @Query(value = "{ 'likeType': ?0, 'userId': ?1 }", fields = "{ 'likeTypeId': 1, '_id': 0 }") + @Query(value = "{ 'likeType': ?0, 'userId': ?1, 'deletedAt': null }", fields = "{ 'likeTypeId': 1, '_id': 0 }") List findLikeTypeIdByLikeTypeAndUserId(LikeType likeType, ObjectId userId); } From 7b9473aea618d995bbc4e46db7a79a11047c7c41 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 5 Sep 2025 00:02:45 +0900 Subject: [PATCH 0937/1002] =?UTF-8?q?fix:=20=EA=B2=80=EC=83=89=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=EC=9D=B4=20=ED=91=9C=EC=8B=9C=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/repository/PostRepository.java | 16 +++++++++++----- .../codin/domain/post/service/PostService.java | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 0c3b5003..04b0ccec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -32,11 +32,17 @@ public interface PostRepository extends MongoRepository { - @Query("{ '$or': [ " - + - "{ 'content': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } }, " - + - "{ 'title': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } } ] }") + @Query(""" + { $and: [ + { $or: [ { "deletedAt": null }, { "deletedAt": { $exists: false } } ] }, + {'postStatus': { $in: ['ACTIVE'] }}, + { "userId": { $nin: ?1 } }, + { $or: [ + { "content": { $regex: ?0, $options: "i" } }, + { "title": { $regex: ?0, $options: "i" } } + ]} + ]} + """) Page findAllByKeywordAndDeletedAtIsNull(String keyword, List blockedUsersId, PageRequest pageRequest); boolean existsBy_idAndDeletedAtIsNull(ObjectId id); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 28ef716f..58fdc04f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -132,6 +132,9 @@ private void validateUserAndPost(PostEntity post) { post.getPostCategory().toString().split("_")[0].equals("EXTRACURRICULAR")){ log.error("비교과 게시글에 대한 권한이 없음. PostId: {}", post.get_id()); throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); + } else if (SecurityUtils.getCurrentUserRole().equals(UserRole.ADMIN) || SecurityUtils.getCurrentUserRole().equals(UserRole.MANAGER)) { + // 관리자 또는 어드민 계정은 게시글 권한을 가짐 (e.g. 삭제권한) + return; } SecurityUtils.validateUser(post.getUserId()); } @@ -283,6 +286,7 @@ public UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId public PostPageResponse searchPosts(String keyword, int pageNumber) { // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); + log.info("blockedUsersId: {}", blockedUsersId.size()); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); From a134a12dfd7e21c00af079f4d380eca7d605c3e7 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 5 Sep 2025 00:17:19 +0900 Subject: [PATCH 0938/1002] =?UTF-8?q?fix:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20=EC=9D=B4=EC=8A=A4=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=ED=94=84=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=B4=20=EC=BF=BC=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/domain/post/service/PostService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java index 58fdc04f..4cac1745 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostService.java @@ -44,6 +44,7 @@ import java.time.LocalDateTime; import java.util.*; +import java.util.regex.Pattern; @Slf4j @Service @@ -288,9 +289,11 @@ public PostPageResponse searchPosts(String keyword, int pageNumber) { List blockedUsersId = blockService.getBlockedUsers(); log.info("blockedUsersId: {}", blockedUsersId.size()); + String pattern = Pattern.quote(keyword); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); - log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); + Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(pattern, blockedUsersId, pageRequest); + log.info("키워드 기반 게시물 검색: {}, Page: {}", pattern, pageNumber); return PostPageResponse.of(getPostListResponseDtos(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } From c7d9b0424cb1f97fbb12fee96f0a6f16ec7ec9b6 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 5 Sep 2025 00:40:22 +0900 Subject: [PATCH 0939/1002] =?UTF-8?q?fix:=20=EC=A2=8B=EC=95=84=EC=9A=94/?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=9E=A9=20=ED=95=9C=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=A4=91=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EB=A7=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#243?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/repository/PostRepository.java | 1 + .../domain/user/service/UserService.java | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 0c3b5003..f75672d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -17,6 +17,7 @@ public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(ObjectId Id); + List findBy_idInAndDeletedAtIsNull(List ids); @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 84ca6040..52185cce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -34,7 +34,10 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @@ -78,39 +81,37 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i case LIKE -> { log.info("[좋아요 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); - List postUserLike = likePage.getContent().stream() - .map(likeEntity -> postRepository.findByIdAndNotDeleted(new ObjectId(likeEntity.getLikeTypeId())) - .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) + + List postIds = likePage.getContent().stream() + .map(likeEntity -> new ObjectId(likeEntity.getLikeTypeId())) .toList(); + List postUserLike = postRepository.findBy_idInAndDeletedAtIsNull(postIds); + log.info("[좋아요 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", likePage.getTotalPages(), likePage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages() - 1, likePage.hasNext() ? likePage.getPageable().getPageNumber() + 1 : -1); } case SCRAP -> { log.info("[스크랩 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page scrapPage = scrapRepository.findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(userId, pageRequest); - List postUserScrap = scrapPage.getContent().stream() - .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) + + List postIds = scrapPage.getContent().stream() + .map(ScrapEntity::getPostId) .toList(); + List postUserScrap = postRepository.findBy_idInAndDeletedAtIsNull(postIds); + log.info("[스크랩 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", scrapPage.getTotalPages(), scrapPage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages() - 1, scrapPage.hasNext() ? scrapPage.getPageable().getPageNumber() + 1 : -1); } case COMMENT -> { log.info("[댓글 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page commentPage = commentRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); - List postUserComment = commentPage.getContent().stream() - .map(commentEntity -> postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("유저가 작성한 댓글의 게시글을 찾을 수 없습니다."))) - // 중복 필터링 로직 - .collect(Collectors.toMap( - PostEntity::get_id, // Key: postId - postEntity -> postEntity, // Value: PostEntity - (existing, replacement) -> existing // 중복 발생 시 기존 값 유지 - )) - // 중복 제거된 후 Map에서 PostEntity 추출 - .values() - .stream() + + List commentedPostIds = commentPage.getContent().stream() + .map(CommentEntity::getPostId) + .distinct() // 한 게시글에 여러 댓글을 달았을 경우 중복 제거 .toList(); + List postUserComment = postRepository.findBy_idInAndDeletedAtIsNull(commentedPostIds); + log.info("[댓글 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", commentPage.getTotalPages(), commentPage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages() - 1, commentPage.hasNext() ? commentPage.getPageable().getPageNumber() + 1 : -1); } From 41d1ba1c942d2a0b1ff5e476fe25f967ef714e60 Mon Sep 17 00:00:00 2001 From: X1n9fU Date: Fri, 5 Sep 2025 00:40:22 +0900 Subject: [PATCH 0940/1002] =?UTF-8?q?fix:=20=EC=A2=8B=EC=95=84=EC=9A=94/?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=9E=A9=20=ED=95=9C=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=A4=91=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EB=A7=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#248?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/repository/PostRepository.java | 1 + .../domain/user/service/UserService.java | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 0c3b5003..f75672d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -17,6 +17,7 @@ public interface PostRepository extends MongoRepository { @Query("{'_id': ?0, 'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }}") Optional findByIdAndNotDeleted(ObjectId Id); + List findBy_idInAndDeletedAtIsNull(List ids); @Query("{'deletedAt': null, 'postStatus': { $in: ['ACTIVE'] }, 'userId': ?0 }") Page findAllByUserIdOrderByCreatedAt(ObjectId userId, PageRequest pageRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 84ca6040..52185cce 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -34,7 +34,10 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @@ -78,39 +81,37 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i case LIKE -> { log.info("[좋아요 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page likePage = likeRepository.findAllByUserIdAndLikeTypeAndDeletedAtIsNullOrderByCreatedAt(userId, LikeType.valueOf("POST"), pageRequest); - List postUserLike = likePage.getContent().stream() - .map(likeEntity -> postRepository.findByIdAndNotDeleted(new ObjectId(likeEntity.getLikeTypeId())) - .orElseThrow(() -> new NotFoundException("유저가 좋아요를 누른 게시글을 찾을 수 없습니다."))) + + List postIds = likePage.getContent().stream() + .map(likeEntity -> new ObjectId(likeEntity.getLikeTypeId())) .toList(); + List postUserLike = postRepository.findBy_idInAndDeletedAtIsNull(postIds); + log.info("[좋아요 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", likePage.getTotalPages(), likePage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserLike), likePage.getTotalPages() - 1, likePage.hasNext() ? likePage.getPageable().getPageNumber() + 1 : -1); } case SCRAP -> { log.info("[스크랩 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page scrapPage = scrapRepository.findAllByUserIdAndDeletedAtIsNullOrderByCreatedAt(userId, pageRequest); - List postUserScrap = scrapPage.getContent().stream() - .map(scrapEntity -> postRepository.findByIdAndNotDeleted(scrapEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("유저가 스크랩한 게시글을 찾을 수 없습니다."))) + + List postIds = scrapPage.getContent().stream() + .map(ScrapEntity::getPostId) .toList(); + List postUserScrap = postRepository.findBy_idInAndDeletedAtIsNull(postIds); + log.info("[스크랩 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", scrapPage.getTotalPages(), scrapPage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserScrap), scrapPage.getTotalPages() - 1, scrapPage.hasNext() ? scrapPage.getPageable().getPageNumber() + 1 : -1); } case COMMENT -> { log.info("[댓글 조회 시작] 유저 ID: {}, 타입: {}", userId, interactionType); Page commentPage = commentRepository.findAllByUserIdOrderByCreatedAt(userId, pageRequest); - List postUserComment = commentPage.getContent().stream() - .map(commentEntity -> postRepository.findByIdAndNotDeleted(commentEntity.getPostId()) - .orElseThrow(() -> new NotFoundException("유저가 작성한 댓글의 게시글을 찾을 수 없습니다."))) - // 중복 필터링 로직 - .collect(Collectors.toMap( - PostEntity::get_id, // Key: postId - postEntity -> postEntity, // Value: PostEntity - (existing, replacement) -> existing // 중복 발생 시 기존 값 유지 - )) - // 중복 제거된 후 Map에서 PostEntity 추출 - .values() - .stream() + + List commentedPostIds = commentPage.getContent().stream() + .map(CommentEntity::getPostId) + .distinct() // 한 게시글에 여러 댓글을 달았을 경우 중복 제거 .toList(); + List postUserComment = postRepository.findBy_idInAndDeletedAtIsNull(commentedPostIds); + log.info("[댓글 조회 완료] 총 페이지 수: {}, 다음 페이지 여부: {}", commentPage.getTotalPages(), commentPage.hasNext()); return PostPageResponse.of(postService.getPostListResponseDtos(postUserComment), commentPage.getTotalPages() - 1, commentPage.hasNext() ? commentPage.getPageable().getPageNumber() + 1 : -1); } From 3bd8b7e981e3f96dad2933d4a0b0e6e9ef3b9655 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Mon, 8 Sep 2025 12:15:59 +0900 Subject: [PATCH 0941/1002] =?UTF-8?q?fix:=20fcm=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swagger 명세를 인터페이스로 분리 - Swagger 명세 예시 및 이름 추가 --- ...Controller.java => FcmControllerImpl.java} | 21 ++++++------- .../fcm/controller/swagger/FcmController.java | 30 +++++++++++++++++++ .../fcm/dto/request/FcmTokenRequest.java | 4 +-- 3 files changed, 41 insertions(+), 14 deletions(-) rename codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/{FcmController.java => FcmControllerImpl.java} (66%) create mode 100644 codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java similarity index 66% rename from codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java rename to codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java index a7522bb3..96eac2eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmController.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java @@ -1,28 +1,25 @@ package inu.codin.codin.infra.fcm.controller; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.infra.fcm.controller.swagger.FcmController; import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import inu.codin.codin.infra.fcm.service.FcmService; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.Null; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; 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; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/fcm") -@Tag(name = "FCM API", description = "FCM 토큰 저장 API") @RequiredArgsConstructor -public class FcmController { +public class FcmControllerImpl implements FcmController { private final FcmService fcmService; @PostMapping("/save") - public ResponseEntity sendFcmMessage( + public ResponseEntity> sendFcmMessage( @RequestBody @Valid FcmTokenRequest fcmTokenRequest ) { fcmService.saveFcmToken(fcmTokenRequest); @@ -30,16 +27,16 @@ public ResponseEntity sendFcmMessage( } @PostMapping("/subscribe") - public ResponseEntity subscribeTopic( - @RequestBody String topic + public ResponseEntity> subscribeTopic( + @RequestParam String topic ) { fcmService.subscribeTopic(topic); return ResponseEntity.status(HttpStatus.ACCEPTED).body(new SingleResponse<>(202, "FCM 토픽 구독 성공", null)); } @PostMapping("/unsubscribe") - public ResponseEntity unsubscribeTopic( - @RequestBody String topic + public ResponseEntity> unsubscribeTopic( + @RequestParam String topic ) { fcmService.unsubscribeTopic(topic); return ResponseEntity.status(HttpStatus.ACCEPTED).body(new SingleResponse<>(202, "FCM 토픽 구독 해제 성공", null)); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java new file mode 100644 index 00000000..d8d1eb15 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java @@ -0,0 +1,30 @@ +package inu.codin.codin.infra.fcm.controller.swagger; + +import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Null; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@Tag(name = "FCM API", description = "FCM 토큰 저장 API") +public interface FcmController { + + @Operation(summary = "FCM 토큰 저장", description = "알림 설정을 위해서는 미리 토큰 저장이 필요합니다") + public ResponseEntity> sendFcmMessage( + @RequestBody @Valid FcmTokenRequest fcmTokenRequest + ); + + @Operation(summary = "FCM 토픽 구독", description = "토픽을 구독하여 해당 토픽으로 알림이 옵니다.") + public ResponseEntity> subscribeTopic( + @RequestParam String topic + ); + + @Operation(summary = "FCM 토픽 구독 해제", description = "토픽 구독을 해제합니다.") + public ResponseEntity> unsubscribeTopic( + @RequestParam String topic + ); +} diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java index 97f4bead..ff6dfe89 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/dto/request/FcmTokenRequest.java @@ -9,11 +9,11 @@ @Getter public class FcmTokenRequest { - @Schema(description = "Fcm Token") + @Schema(description = "Fcm Token", example = "FCM 토큰") @NotBlank private String fcmToken; - @Schema(description = "Android, IOS") + @Schema(description = "Android, IOS", example = "디바이스 종류") @NotBlank private String deviceType; From 28c7b2958041df61ae086aa1245e714b8c2e7eb9 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 19 Sep 2025 11:16:43 +0900 Subject: [PATCH 0942/1002] =?UTF-8?q?fix:=20IllegalArgumentException=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=A7=81=20=EC=82=AD=EC=A0=9C=20#259?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 있던 IllegalArgumentException 처리 부분에서 Error Response를 전달하지 않아서 Front 측에서 로그인 데이터가 없는데 빈 data 값을 받게 되는 경우가 발생했습니다. 토큰이 없거나, 만료되었을 때에 아래의 Exception 핸들링에서 처리 가능합니다. --- .../codin/common/security/filter/ExceptionHandlerFilter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java index 1b5e5167..8d356f62 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java @@ -31,8 +31,6 @@ public class ExceptionHandlerFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { filterChain.doFilter(request, response); - } catch (IllegalArgumentException e) { - log.warn("[doFilterInternal] IllegalArgumentException msg: {}", e.getMessage()); } catch (Exception e) { log.warn("[doFilterInternal] Exception in ExceptionHandlerFilter: {}", e.getMessage()); sendErrorResponse(response, SecurityErrorCode.INVALID_TOKEN); From 877eb2f1a170d63bb3b4da2966cb79891f2d59a4 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 19 Sep 2025 11:19:25 +0900 Subject: [PATCH 0943/1002] =?UTF-8?q?fix:=20oauth=20redirect=20suffix=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20#259?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 원래 코드의 의도는 로그인 이후에 홈페이지로 이동하는 것입니다. 하지만, `/vote`와 같은 원래 로그인 전 페이지로 이동하기 어려운 상황이라 변경했습니다. --- .../codin/common/security/util/OAuth2LoginSuccessHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 148e1401..ce23c404 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -61,7 +61,7 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r switch (result) { case LOGIN_SUCCESS -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/main"); + getRedirectStrategy().sendRedirect(request, response, redirectUrl); log.info("{\"code\":200, \"message\":\"정상 로그인 완료: {}\"}", email); } case NEW_USER_REGISTERED -> { From 71f73fa23ef105561824efab97c18863a55e7bec Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 16:46:41 +0900 Subject: [PATCH 0944/1002] =?UTF-8?q?refactor=20:=20Best3=203=EA=B0=9C=20l?= =?UTF-8?q?imit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/domain/best/BestService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java index e1308cdc..cf32e770 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/best/BestService.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Slf4j @Service @@ -23,7 +24,10 @@ public class BestService { // [BestService] - Top 3 베스트 postId 목록 반환 public List getTop3BestPostIds() { Map bestPosts = redisBestService.getBests(); - return new ArrayList<>(bestPosts.keySet()); // 빈 리스트 반환 가능 + return bestPosts.keySet() + .stream() + .limit(3) + .collect(Collectors.toList()); } // [BestService] - BestEntity 페이지 반환 From 5f2225e34b057ff66d0f10323409af4d7cfadfe8 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 17:53:27 +0900 Subject: [PATCH 0945/1002] refactor : Comment Anonymous Boolean -> boolean --- .../codin/domain/post/domain/comment/entity/CommentEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index 8bdcedbc..af42e2d8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -31,7 +31,7 @@ public class CommentEntity extends BaseTimeEntity { private boolean anonymous; @Builder - public CommentEntity(ObjectId postId, ObjectId userId, String content, Boolean anonymous) { + public CommentEntity(ObjectId postId, ObjectId userId, String content, boolean anonymous) { this.postId = postId; this.userId = userId; this.content = content; From 5c204f0aa989fc4909e056e96c6120bdd6a795c4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 17:54:37 +0900 Subject: [PATCH 0946/1002] =?UTF-8?q?refactor=20:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=86=8C=EC=9C=A0=EC=9E=90=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/util/SecurityUtils.java | 6 ++++++ .../comment/service/CommentCommandService.java | 12 ++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 0f11558e..2f984d8a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -46,4 +46,10 @@ public static void validateUser(ObjectId id){ } } + public static void validateOwners(ObjectId currentUserId, ObjectId ownerId) { + validateUser(currentUserId); + if (!ownerId.equals(currentUserId)) { + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "본인 리소스가 아닙니다. "); + } + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index 74557283..cd69be01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -52,9 +52,7 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { ObjectId commentId = ObjectIdUtil.toObjectId(id); CommentEntity comment = commentQueryService.findCommentById(commentId); - ObjectId userId = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateUser(userId); - + assertOwner(comment.getUserId()); comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); @@ -68,7 +66,8 @@ public void softDeleteComment(String id) { ObjectId commentId = ObjectIdUtil.toObjectId(id); CommentEntity comment = commentQueryService.findCommentById(commentId); - SecurityUtils.validateUser(comment.getUserId()); + assertOwner(comment.getUserId()); + ObjectId postId = comment.getPostId(); PostEntity post = postQueryService.findPostById(postId); @@ -82,4 +81,9 @@ public void softDeleteComment(String id) { log.info("삭제된 commentId: {}", commentId); } + + private void assertOwner(ObjectId ownerId) { + ObjectId currentUserId = SecurityUtils.getCurrentUserId(); + SecurityUtils.validateOwners(currentUserId, ownerId); + } } From 2420f239de47ecc22eb29141c538a8cd35e4168c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 18:32:03 +0900 Subject: [PATCH 0947/1002] =?UTF-8?q?refactor=20:=20=EB=8C=80=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=9E=91=EC=84=B1=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=88=98=EC=8B=A0=EC=9E=90=20=EC=A1=B0=EA=B1=B4(post=20->=20co?= =?UTF-8?q?mment)=20=EB=B3=80=EA=B2=BD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/comment/reply/service/ReplyCommandService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index 5266ab26..d8a09eb1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -52,7 +52,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (!userId.equals(comment.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { From a1ee1777b2984efc64bdcad598f6067728f639ce Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 18:45:01 +0900 Subject: [PATCH 0948/1002] =?UTF-8?q?refactor=20:=20comment=20owner=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EC=A7=81=20=EB=AA=A8=EB=91=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentCommandService.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index cd69be01..f682e2a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -49,25 +49,18 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); - ObjectId commentId = ObjectIdUtil.toObjectId(id); - CommentEntity comment = commentQueryService.findCommentById(commentId); - - assertOwner(comment.getUserId()); + CommentEntity comment = assertCommentOwner(id); comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); - log.info("댓글 업데이트 완료. commentId: {}", commentId); + log.info("댓글 업데이트 완료. commentId: {}", comment.get_id()); } // 댓글 삭제 (Soft Delete) public void softDeleteComment(String id) { - ObjectId commentId = ObjectIdUtil.toObjectId(id); - CommentEntity comment = commentQueryService.findCommentById(commentId); - - assertOwner(comment.getUserId()); - + CommentEntity comment = assertCommentOwner(id); ObjectId postId = comment.getPostId(); PostEntity post = postQueryService.findPostById(postId); @@ -79,11 +72,16 @@ public void softDeleteComment(String id) { postCommandService.decreaseCommentCount(post); // bestService.applyBestScore( postId); - log.info("삭제된 commentId: {}", commentId); + log.info("삭제된 commentId: {}", comment.get_id()); } - private void assertOwner(ObjectId ownerId) { + private CommentEntity assertCommentOwner(String commentId) { + ObjectId objectId = ObjectIdUtil.toObjectId(commentId); + CommentEntity comment = commentQueryService.findCommentById(objectId); + ObjectId currentUserId = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateOwners(currentUserId, ownerId); + SecurityUtils.validateOwners(currentUserId, comment.getUserId()); + + return comment; } } From 10697262e202b3b24928d2e2775e33cac84422bc Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 19:37:42 +0900 Subject: [PATCH 0949/1002] =?UTF-8?q?refactor=20:=20post,comment,reply=20?= =?UTF-8?q?=EC=86=8C=EC=9C=A0=EC=9E=90=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=AA=A8=EB=93=88=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reply/service/ReplyCommandService.java | 18 +++---- .../service/CommentCommandService.java | 22 +++----- .../domain/post/security/OwnershipPolicy.java | 45 ++++++++++++++++ .../post/service/PostCommandService.java | 52 +++++++++++-------- 4 files changed, 91 insertions(+), 46 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index d8a09eb1..d6d67e47 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; import jakarta.validation.Valid; @@ -32,6 +33,7 @@ public class ReplyCommandService { private final BestService bestService; private final CommentQueryService commentQueryService; private final ReplyQueryService replyQueryService; + private final OwnershipPolicy ownershipPolicy; /** *Command Method @@ -52,26 +54,22 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - if (!userId.equals(comment.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); } - public void updateReply(String id, @Valid ReplyUpdateRequestDTO requestDTO) { - - ObjectId replyId = ObjectIdUtil.toObjectId(id); - ReplyCommentEntity reply = replyQueryService.findReplyById(replyId); + public void updateReply(String replyId, @Valid ReplyUpdateRequestDTO requestDTO) { + ReplyCommentEntity reply = ownershipPolicy.assertReplyOwner(ObjectIdUtil.toObjectId(replyId)); reply.updateReply(requestDTO.getContent()); replyCommentRepository.save(reply); - log.info("대댓글 수정 완료 - replyId: {}", replyId); + log.info("대댓글 수정 완료 - replyId: {}", reply.get_id()); } // 대댓글 삭제 (Soft Delete) - public void softDeleteReply(String id) { - ReplyCommentEntity reply = replyQueryService.findReplyById(ObjectIdUtil.toObjectId(id)); - - SecurityUtils.validateUser(reply.getUserId()); + public void softDeleteReply(String replyId) { + ReplyCommentEntity reply = ownershipPolicy.assertReplyOwner(ObjectIdUtil.toObjectId(replyId)); CommentEntity comment = commentQueryService.findCommentById(reply.getCommentId()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index f682e2a4..267ff399 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.domain.best.BestService; +import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; import lombok.RequiredArgsConstructor; @@ -24,7 +25,7 @@ public class CommentCommandService { private final NotificationService notificationService; private final PostCommandService postCommandService; private final PostQueryService postQueryService; - private final CommentQueryService commentQueryService; + private final OwnershipPolicy ownershipPolicy; private final BestService bestService; // 댓글 추가 @@ -46,10 +47,10 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { } - public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { - log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", id, requestDTO.getContent()); + public void updateComment(String commentId, CommentUpdateRequestDTO requestDTO) { + log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", commentId, requestDTO.getContent()); - CommentEntity comment = assertCommentOwner(id); + CommentEntity comment = ownershipPolicy.assertCommentOwner(ObjectIdUtil.toObjectId(commentId)); comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); @@ -59,8 +60,8 @@ public void updateComment(String id, CommentUpdateRequestDTO requestDTO) { } // 댓글 삭제 (Soft Delete) - public void softDeleteComment(String id) { - CommentEntity comment = assertCommentOwner(id); + public void softDeleteComment(String commentId) { + CommentEntity comment = ownershipPolicy.assertCommentOwner(ObjectIdUtil.toObjectId(commentId)); ObjectId postId = comment.getPostId(); PostEntity post = postQueryService.findPostById(postId); @@ -75,13 +76,4 @@ public void softDeleteComment(String id) { log.info("삭제된 commentId: {}", comment.get_id()); } - private CommentEntity assertCommentOwner(String commentId) { - ObjectId objectId = ObjectIdUtil.toObjectId(commentId); - CommentEntity comment = commentQueryService.findCommentById(objectId); - - ObjectId currentUserId = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateOwners(currentUserId, comment.getUserId()); - - return comment; - } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java new file mode 100644 index 00000000..b4fffd18 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java @@ -0,0 +1,45 @@ +package inu.codin.codin.domain.post.security; + +import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.service.PostQueryService; +import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; +import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; +import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; +import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; +import lombok.RequiredArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OwnershipPolicy { + + private final PostQueryService postQueryService; + private final CommentQueryService commentQueryService; + private final ReplyQueryService replyQueryService; + + /** 존재 + 소유자 검증 후 엔티티 반환 (실패 시 예외) */ + public PostEntity assertPostOwner(ObjectId postId) { + PostEntity post = postQueryService.findPostById(postId); + validateOwner(post.getUserId()); + return post; + } + + public CommentEntity assertCommentOwner(ObjectId commentId) { + CommentEntity comment = commentQueryService.findCommentById(commentId); + validateOwner(comment.getUserId()); + return comment; + } + + public ReplyCommentEntity assertReplyOwner(ObjectId replyId) { + ReplyCommentEntity reply = replyQueryService.findReplyById(replyId); + validateOwner(reply.getUserId()); + return reply; + } + + private void validateOwner(ObjectId ownerId) { + ObjectId current = SecurityUtils.getCurrentUserId(); + SecurityUtils.validateOwners(current, ownerId); // 불일치 시 예외 + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index 14cefd85..d6ed74c0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -12,6 +12,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.user.entity.UserRole; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -27,7 +28,7 @@ public class PostCommandService { private final PostRepository postRepository; private final PostInteractionService postInteractionService; - private final PostQueryService postQueryService; + private final OwnershipPolicy ownershipPolicy; /** * 게시글 생성 @@ -42,7 +43,8 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { log.info("게시물 수정 시작. PostId: {}", postId); - PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = validateUserAndPost(post.getPostCategory()); + PostEntity post = assertPostOwner(ObjectIdUtil.toObjectId(postId)); + assertCategoryWriteAllowed(post.getPostCategory()); + List imageUrls = postInteractionService.handleImageUpload(postImages); post.updatePostContent(requestDTO.getContent(), imageUrls); postRepository.save(post); - log.info("게시물 수정 성공. UserId: {}, PostId: {}",userId, postId); + log.info("게시물 수정 성공. PostId: {}", postId); } /** * 게시글 익명 설정 수정 */ public void updatePostAnonymous(String postId, PostAnonymousUpdateRequestDTO requestDTO) { - PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = validateUserAndPost(post.getPostCategory()); + PostEntity post = assertPostOwner(ObjectIdUtil.toObjectId(postId)); post.updatePostAnonymous(requestDTO.isAnonymous()); postRepository.save(post); - log.info("게시물 익명 수정 성공. UserId: {}, PostId: {}",userId, postId); + log.info("게시물 익명 수정 성공.PostId: {}", postId); } /** * 게시글 상태 수정 */ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDTO) { - PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = validateUserAndPost(post.getPostCategory()); + PostEntity post = assertPostOwner(ObjectIdUtil.toObjectId(postId)); post.updatePostStatus(requestDTO.getPostStatus()); postRepository.save(post); - log.info("게시물 상태 수정 성공. UserId : {}. PostId: {}, Status: {}", userId, postId, requestDTO.getPostStatus()); + log.info("게시물 상태 수정 성공. PostId: {}, Status: {}", postId, requestDTO.getPostStatus()); } @@ -93,21 +94,19 @@ public void updatePostStatus(String postId, PostStatusUpdateRequestDTO requestDT * 게시물 소프트 삭제 */ public void softDeletePost(String postId) { - PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = validateUserAndPost(post.getPostCategory()); + PostEntity post = assertPostOwner(ObjectIdUtil.toObjectId(postId)); post.delete(); postRepository.save(post); - log.info("게시물 안전 삭제. UserId: {} PostId: {}", userId, postId); + log.info("게시물 안전 삭제. PostId: {}", postId); } /** * 게시물 이미지 삭제 */ public void deletePostImage(String postId, String imageUrl) { - PostEntity post = postQueryService.findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = validateUserAndPost(post.getPostCategory()); + PostEntity post = assertPostOwner(ObjectIdUtil.toObjectId(postId)); postInteractionService.deletePostImageInternal(post, imageUrl); - log.info("게시물 이미지 삭제. UserId: {} PostId: {}", userId, postId); + log.info("게시물 이미지 삭제. PostId: {}", postId); } /** @@ -162,14 +161,25 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { private ObjectId validateUserAndPost(PostCategory postCategory) { + assertCategoryWriteAllowed(postCategory); + ObjectId userId = SecurityUtils.getCurrentUserId(); + SecurityUtils.validateUser(userId); + return userId; + } + + private PostEntity assertPostOwner(ObjectId postId){ + PostEntity post = ownershipPolicy.assertPostOwner(postId); + assertCategoryWriteAllowed(post.getPostCategory()); + return post; + } + + + private void assertCategoryWriteAllowed(PostCategory postCategory) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && - postCategory.toString().split("_")[0].equals("EXTRACURRICULAR")){ - log.error("비교과 게시글에 대한 권한이 없음. userId: {}", userId); + postCategory.toString().split("_")[0].equals("EXTRACURRICULAR")) { throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } - SecurityUtils.validateUser(userId); - return userId; } } From bb528e39cc87cb8dc4ab4ef1b48fd8bc2eaa1575 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 19:50:29 +0900 Subject: [PATCH 0950/1002] =?UTF-8?q?refactor=20:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=82=AD=EC=A0=9C=EC=8B=9C=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=B2=98=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(s3=20<->=20db)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/codin/domain/post/service/PostInteractionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java index c5b4c9c3..4bdebc0b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java @@ -41,10 +41,10 @@ public void deletePostImageInternal(PostEntity post, String imageUrl) { throw new PostException(PostErrorCode.POST_NOT_FOUND); } try { - s3Service.deleteFile(imageUrl); post.removePostImage(imageUrl); postRepository.save(post); log.info("이미지 삭제 성공. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl); + s3Service.deleteFile(imageUrl); } catch (Exception e) { log.error("이미지 삭제 중 오류 발생. PostId: {}, ImageUrl: {}", post.get_id(), imageUrl, e); throw new ImageRemoveException("이미지 삭제 중 오류 발생: " + imageUrl); From 30af42a6c4d1cd860556dfa50ba35ecf00ac057f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 19:53:39 +0900 Subject: [PATCH 0951/1002] =?UTF-8?q?refactor=20:=20poll=20=EC=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=91=EB=8B=B5=20=EC=BD=94=EB=93=9C=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/poll/controller/PollController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index 8cba3d83..5e4cb0a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -38,7 +38,7 @@ public ResponseEntity> votingPoll( pollCommandService.votingPoll(postId, pollRequestDTO); return ResponseEntity.status(HttpStatus.CREATED) - .body(new SingleResponse<>(200, "투표 실시 완료", null)); + .body(new SingleResponse<>(201, "투표 실시 완료", null)); } @Operation(summary = "투표 취소") From 46da0046994803634b1e5475713651400c824430 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 20:04:20 +0900 Subject: [PATCH 0952/1002] =?UTF-8?q?refactor=20:=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=20=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/poll/exception/PollErrorCode.java | 4 ++-- .../domain/post/domain/poll/service/PollCommandService.java | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java index cfa6bca7..1f3b037a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java @@ -12,8 +12,8 @@ public enum PollErrorCode implements GlobalErrorCode { MULTIPLE_CHOICE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "복수 선택이 허용되지 않은 투표입니다."), INVALID_OPTION(HttpStatus.BAD_REQUEST, "잘못된 선택지입니다."), POLL_VOTE_STATE_CONFLICT(HttpStatus.CONFLICT, "투표 증감 실패."), - POLL_VOTE_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자의 일치하는 투표 내역이 없습니다."); - + POLL_VOTE_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자의 일치하는 투표 내역이 없습니다."), + DUPLICATE_SELECTION(HttpStatus.BAD_REQUEST, "중복 선택입니다."); private final HttpStatus httpStatus; private final String message; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index 2b699d56..c6f57e02 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -24,6 +24,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Objects; @Service @RequiredArgsConstructor @@ -110,6 +111,11 @@ private void validateSelections(PollEntity poll, List selected) { log.warn("복수 선택 허용 안됨 - pollId: {}", poll.get_id()); throw new PollException(PollErrorCode.MULTIPLE_CHOICE_NOT_ALLOWED); } + long distinct = selected.stream().filter(Objects::nonNull).distinct().count(); + if (distinct != selected.size()) { + log.warn("중복 선택 금지 - pollId: {}, selections: {}", poll.get_id(), selected); + throw new PollException(PollErrorCode.DUPLICATE_SELECTION); + } int size = poll.getPollOptions().size(); for (Integer idx : selected) { if (idx == null || idx < 0 || idx >= size) { From ef544798f2311aa75ebe73bfed24e8044fd31f5c Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 20:36:56 +0900 Subject: [PATCH 0953/1002] =?UTF-8?q?refactor=20:=20scheduler=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/domain/post/entity/PostEntity.java | 4 ++-- .../post/{schedular => scheduler}/PostsScheduler.java | 6 +++--- .../exception/SchedulerErrorCode.java | 2 +- .../exception/SchedulerException.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename codin-core/src/main/java/inu/codin/codin/domain/post/{schedular => scheduler}/PostsScheduler.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{schedular => scheduler}/exception/SchedulerErrorCode.java (93%) rename codin-core/src/main/java/inu/codin/codin/domain/post/{schedular => scheduler}/exception/SchedulerException.java (85%) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index f5bbc911..6ff338f5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -4,8 +4,8 @@ import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.exception.PostException; -import inu.codin.codin.domain.post.schedular.exception.SchedulerErrorCode; -import inu.codin.codin.domain.post.schedular.exception.SchedulerException; +import inu.codin.codin.domain.post.scheduler.exception.SchedulerErrorCode; +import inu.codin.codin.domain.post.scheduler.exception.SchedulerException; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/PostsScheduler.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/PostsScheduler.java index 649e3f28..022650e2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/PostsScheduler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/PostsScheduler.java @@ -1,7 +1,7 @@ -package inu.codin.codin.domain.post.schedular; +package inu.codin.codin.domain.post.scheduler; -import inu.codin.codin.domain.post.schedular.exception.SchedulerErrorCode; -import inu.codin.codin.domain.post.schedular.exception.SchedulerException; +import inu.codin.codin.domain.post.scheduler.exception.SchedulerErrorCode; +import inu.codin.codin.domain.post.scheduler.exception.SchedulerException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java index 54bcbb7e..46a6bace 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.schedular.exception; +package inu.codin.codin.domain.post.scheduler.exception; import inu.codin.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java similarity index 85% rename from codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java rename to codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java index 0f81030f..68e08457 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/schedular/exception/SchedulerException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.schedular.exception; +package inu.codin.codin.domain.post.scheduler.exception; import inu.codin.codin.common.exception.GlobalException; import lombok.Getter; From 73a74751e306eac644f8c0d6dfec0acca02332cb Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 20:37:17 +0900 Subject: [PATCH 0954/1002] =?UTF-8?q?refactor=20:=20Reply=20=EB=82=B4=20li?= =?UTF-8?q?ke=20=EA=B4=80=EB=A0=A8=20comment=20->=20reply=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/comment/reply/service/ReplyQueryService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index f51aa5e4..3af1e9eb 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -107,7 +107,7 @@ private CommentResponseDTO buildReplyResponseDTO( public UserInfo getUserInfoAboutReply(ObjectId replyId) { ObjectId userId = SecurityUtils.getCurrentUserId(); return UserInfo.ofComment( - likeService.isLiked(LikeType.COMMENT, replyId.toString(), userId) + likeService.isLiked(LikeType.REPLY, replyId.toString(), userId) ); } From 13fda5da4884c55752573108fad24e567156b67b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 20:37:29 +0900 Subject: [PATCH 0955/1002] =?UTF-8?q?refactor=20:=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/PostCommandServiceTest.java | 25 ++++++++++--------- .../post/PostInteractionServiceTest.java | 10 +++++--- .../comment/CommentCommandServiceTest.java | 11 +++++--- .../reply/ReplyCommandServiceTest.java | 12 ++++----- .../comment/reply/ReplyQueryServiceTest.java | 14 +++++------ .../PostsSchedulerTest.java | 4 +-- 6 files changed, 41 insertions(+), 35 deletions(-) rename codin-core/src/test/java/inu/codin/codin/domain/post/{schedular => scheduler}/PostsSchedulerTest.java (98%) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java index d7e38acd..d36079e6 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java @@ -9,6 +9,7 @@ import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.entity.PostStatus; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostInteractionService; import inu.codin.codin.domain.post.service.PostQueryService; @@ -34,8 +35,8 @@ class PostCommandServiceTest { @Mock private PostRepository postRepository; @Mock private PostInteractionService postInteractionService; - @Mock private PostQueryService postQueryService; - + @Mock private OwnershipPolicy ownershipPolicy; + private static AutoCloseable securityUtilsMock; @BeforeEach @@ -93,8 +94,8 @@ void tearDown() throws Exception { List images = new ArrayList<>(); PostEntity post = createPostEntity(); List imageUrls = Arrays.asList("image1.jpg", "image2.jpg"); - - given(postQueryService.findPostById(any())).willReturn(post); + + given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postInteractionService.handleImageUpload(any())).willReturn(imageUrls); @@ -111,8 +112,8 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); PostAnonymousUpdateRequestDTO dto = createPostAnonymousUpdateRequestDTO(true); PostEntity post = createPostEntity(); - - given(postQueryService.findPostById(any())).willReturn(post); + + given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); @@ -128,8 +129,8 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); PostStatusUpdateRequestDTO dto = createPostStatusUpdateRequestDTO(PostStatus.ACTIVE); PostEntity post = createPostEntity(); - - given(postQueryService.findPostById(any())).willReturn(post); + + given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); @@ -144,8 +145,8 @@ void tearDown() throws Exception { // Given String postId = new ObjectId().toString(); PostEntity post = createPostEntity(); - - given(postQueryService.findPostById(any())).willReturn(post); + + given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); @@ -161,8 +162,8 @@ void tearDown() throws Exception { String postId = new ObjectId().toString(); String imageUrl = "test-image.jpg"; PostEntity post = createPostEntity(); - - given(postQueryService.findPostById(any())).willReturn(post); + + given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); doNothing().when(postInteractionService).deletePostImageInternal(any(), any()); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java index 9254845a..eebc9db1 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostInteractionServiceTest.java @@ -102,17 +102,19 @@ class PostInteractionServiceTest { void deletePostImageInternal_S3삭제실패_예외() { // Given String imageUrl = "test-image.jpg"; - PostEntity post = createPostEntityWithImages(Arrays.asList(imageUrl)); + PostEntity post = createPostEntityWithImages(List.of(imageUrl)); - doThrow(new RuntimeException("S3 삭제 실패")).when(s3Service).deleteFile(imageUrl); + doThrow(new RuntimeException("S3 삭제 실패")) + .when(s3Service).deleteFile(imageUrl); // When & Then assertThatThrownBy(() -> postInteractionService.deletePostImageInternal(post, imageUrl)) .isInstanceOf(ImageRemoveException.class) .hasMessageContaining("이미지 삭제 중 오류 발생"); - + + + verify(postRepository, times(1)).save(post); verify(s3Service).deleteFile(imageUrl); - verify(postRepository, never()).save(any()); } @Test diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java index 4ccd8b21..4eb3f9b6 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java @@ -11,6 +11,7 @@ import inu.codin.codin.domain.post.domain.comment.service.CommentQueryService; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; import org.bson.types.ObjectId; @@ -35,6 +36,8 @@ class CommentCommandServiceTest { @Mock private PostQueryService postQueryService; @Mock private CommentQueryService commentQueryService; @Mock private BestService bestService; + + @Mock private OwnershipPolicy ownershipPolicy; private static AutoCloseable securityUtilsMock; @@ -146,8 +149,8 @@ void tearDown() throws Exception { CommentUpdateRequestDTO dto = createCommentUpdateRequestDTO("수정된 내용"); CommentEntity comment = createCommentEntity(); ObjectId userId = new ObjectId(); - - given(commentQueryService.findCommentById(any())).willReturn(comment); + + given(ownershipPolicy.assertCommentOwner(any(ObjectId.class))).willReturn(comment); given(SecurityUtils.getCurrentUserId()).willReturn(userId); doNothing().when(SecurityUtils.class); SecurityUtils.validateUser(userId); @@ -165,8 +168,8 @@ void tearDown() throws Exception { CommentEntity comment = createCommentEntity(); PostEntity post = createPostEntity(); ObjectId userId = comment.getUserId(); - - given(commentQueryService.findCommentById(any())).willReturn(comment); + + given(ownershipPolicy.assertCommentOwner(any(ObjectId.class))).willReturn(comment); doNothing().when(SecurityUtils.class); SecurityUtils.validateUser(userId); given(postQueryService.findPostById(comment.getPostId())).willReturn(post); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java index 8cff3c06..07642e6a 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java @@ -13,6 +13,7 @@ import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; +import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostQueryService; import org.bson.types.ObjectId; @@ -37,8 +38,7 @@ class ReplyCommandServiceTest { @Mock private NotificationService notificationService; @Mock private BestService bestService; @Mock private CommentQueryService commentQueryService; - @Mock private ReplyQueryService replyQueryService; - + @Mock private OwnershipPolicy ownershipPolicy; private static AutoCloseable securityUtilsMock; @BeforeEach @@ -162,8 +162,8 @@ void tearDown() throws Exception { String replyId = new ObjectId().toString(); ReplyUpdateRequestDTO dto = createReplyUpdateRequestDTO("수정된 대댓글"); ReplyCommentEntity reply = createReplyEntity(); - - given(replyQueryService.findReplyById(any())).willReturn(reply); + + given(ownershipPolicy.assertReplyOwner(any(ObjectId.class))).willReturn(reply); given(replyCommentRepository.save(any())).willReturn(reply); // When & Then @@ -179,8 +179,8 @@ void tearDown() throws Exception { CommentEntity comment = createCommentEntity(); PostEntity post = createPostEntity(); ObjectId userId = reply.getUserId(); - - given(replyQueryService.findReplyById(any())).willReturn(reply); + + given(ownershipPolicy.assertReplyOwner(any(ObjectId.class))).willReturn(reply); doNothing().when(SecurityUtils.class); SecurityUtils.validateUser(userId); given(commentQueryService.findCommentById(reply.getCommentId())).willReturn(comment); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java index fd06e36d..d24082f7 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java @@ -74,7 +74,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(3); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(),(ObjectId) any())).willReturn(false); + given(likeService.isLiked(eq(LikeType.REPLY), any(),(ObjectId) any())).willReturn(false); // When List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); @@ -122,7 +122,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(postAnonymous, userId)).willReturn(3); // 익명 번호 given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(2); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(),(ObjectId) any())).willReturn(true); + given(likeService.isLiked(eq(LikeType.REPLY), any(),(ObjectId) any())).willReturn(true); // When List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); @@ -170,14 +170,14 @@ void tearDown() throws Exception { ObjectId userId = new ObjectId(); given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(likeService.isLiked(LikeType.COMMENT, replyId.toString(), userId)).willReturn(true); + given(likeService.isLiked(LikeType.REPLY, replyId.toString(), userId)).willReturn(true); // When UserInfo result = replyQueryService.getUserInfoAboutReply(replyId); // Then assertThat(result).isNotNull(); - verify(likeService).isLiked(LikeType.COMMENT, replyId.toString(), userId); + verify(likeService).isLiked(LikeType.REPLY, replyId.toString(), userId); } @Test @@ -187,14 +187,14 @@ void tearDown() throws Exception { ObjectId userId = new ObjectId(); given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(likeService.isLiked(LikeType.COMMENT, replyId.toString(), userId)).willReturn(false); + given(likeService.isLiked(LikeType.REPLY, replyId.toString(), userId)).willReturn(false); // When UserInfo result = replyQueryService.getUserInfoAboutReply(replyId); // Then assertThat(result).isNotNull(); - verify(likeService).isLiked(LikeType.COMMENT, replyId.toString(), userId); + verify(likeService).isLiked(LikeType.REPLY, replyId.toString(), userId); } @Test @@ -220,7 +220,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(0); given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(likeService.isLiked(eq(LikeType.COMMENT), any(),(ObjectId) any())).willReturn(false); + given(likeService.isLiked(eq(LikeType.REPLY), any(),(ObjectId) any())).willReturn(false); // When List result = replyQueryService.getRepliesByCommentId(postAnonymous, commentId); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/scheduler/PostsSchedulerTest.java similarity index 98% rename from codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java rename to codin-core/src/test/java/inu/codin/codin/domain/post/scheduler/PostsSchedulerTest.java index bd514567..b87e0fd7 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/schedular/PostsSchedulerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/scheduler/PostsSchedulerTest.java @@ -1,4 +1,4 @@ -package inu.codin.codin.domain.post.schedular; +package inu.codin.codin.domain.post.scheduler; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -7,7 +7,7 @@ import java.io.IOException; import java.lang.reflect.Method; -import inu.codin.codin.domain.post.schedular.exception.SchedulerException; +import inu.codin.codin.domain.post.scheduler.exception.SchedulerException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 527d5cd0d3f7dddbe5efc7752a8de8ab0b89cce4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 20:53:06 +0900 Subject: [PATCH 0956/1002] =?UTF-8?q?chore:=20develop=20=EC=B6=A9=EB=8F=8C?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20refactor/post-feat=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=88=98=EB=8F=99=20=EB=B0=98=EC=98=81=20(7b9473a=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/repository/PostRepository.java | 16 +++++++++++----- .../domain/post/service/PostCommandService.java | 9 +++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java index 0c3b5003..04b0ccec 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/repository/PostRepository.java @@ -32,11 +32,17 @@ public interface PostRepository extends MongoRepository { - @Query("{ '$or': [ " - + - "{ 'content': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } }, " - + - "{ 'title': { $regex: ?0, $options: 'i' }, 'userId': { $nin: ?1 } } ] }") + @Query(""" + { $and: [ + { $or: [ { "deletedAt": null }, { "deletedAt": { $exists: false } } ] }, + {'postStatus': { $in: ['ACTIVE'] }}, + { "userId": { $nin: ?1 } }, + { $or: [ + { "content": { $regex: ?0, $options: "i" } }, + { "title": { $regex: ?0, $options: "i" } } + ]} + ]} + """) Page findAllByKeywordAndDeletedAtIsNull(String keyword, List blockedUsersId, PageRequest pageRequest); boolean existsBy_idAndDeletedAtIsNull(ObjectId id); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index d6ed74c0..4e572dc4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -161,6 +161,10 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { private ObjectId validateUserAndPost(PostCategory postCategory) { + if (isPrivileged()) { + // ADMIN / MANAGER 는 카테고리/유저 상태 검증을 통과시킴 + return SecurityUtils.getCurrentUserId(); + } assertCategoryWriteAllowed(postCategory); ObjectId userId = SecurityUtils.getCurrentUserId(); @@ -182,4 +186,9 @@ private void assertCategoryWriteAllowed(PostCategory postCategory) { } } + private boolean isPrivileged() { + UserRole role = SecurityUtils.getCurrentUserRole(); + return role == UserRole.ADMIN || role == UserRole.MANAGER; + } + } From 2eea4dde6df28c6db55e8d910c79d1c516d34ef3 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 20:55:53 +0900 Subject: [PATCH 0957/1002] =?UTF-8?q?chore:=20develop=20=EC=B6=A9=EB=8F=8C?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20refactor/post-feat=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=88=98=EB=8F=99=20=EB=B0=98=EC=98=81=20(a134a12=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/service/PostQueryService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 60adc2f6..3ac7c250 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; @Slf4j @Service @@ -75,9 +76,13 @@ public Optional getPostDetailById(ObjectId postId) { */ public PostPageResponse searchPosts(String keyword, int pageNumber) { List blockedUsersId = blockService.getBlockedUsers(); + log.info("blockedUsersId: {}", blockedUsersId.size()); + + String pattern = Pattern.quote(keyword); + PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); - Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(keyword, blockedUsersId, pageRequest); - log.info("키워드 기반 게시물 검색: {}, Page: {}", keyword, pageNumber); + Page page = postRepository.findAllByKeywordAndDeletedAtIsNull(pattern, blockedUsersId, pageRequest); + log.info("키워드 기반 게시물 검색: {}, Page: {}", pattern, pageNumber); return PostPageResponse.of(postDtoAssembler.toPageItemList(page.getContent()), page.getTotalPages() - 1, page.hasNext() ? page.getPageable().getPageNumber() + 1 : -1); } From 6be807a13135e6f3b30e7efb5656c21138619558 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 21:23:54 +0900 Subject: [PATCH 0958/1002] =?UTF-8?q?refactor=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/domain/post/PostQueryServiceTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java index 75f0eeb1..9c855dd6 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java @@ -24,6 +24,7 @@ import org.springframework.data.domain.*; import java.util.*; +import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -171,13 +172,18 @@ void tearDown() throws Exception { void searchPosts_키워드검색_성공() { // Given String keyword = "테스트"; + String quoted = Pattern.quote(keyword); List blockedUsers = new ArrayList<>(); List posts = Arrays.asList(createPostEntity()); Page page = new PageImpl<>(posts, PageRequest.of(0, 20), 1); List mockDtoList = Arrays.asList(createMockPostPageItemResponseDTO()); given(blockService.getBlockedUsers()).willReturn(blockedUsers); - given(postRepository.findAllByKeywordAndDeletedAtIsNull(anyString(), anyList(), any(PageRequest.class))).willReturn(page); + given(postRepository.findAllByKeywordAndDeletedAtIsNull( + eq(quoted), + eq(blockedUsers), + any(PageRequest.class)) + ).willReturn(page); given(postDtoAssembler.toPageItemList(posts)).willReturn(mockDtoList); // When @@ -186,7 +192,10 @@ void tearDown() throws Exception { // Then assertThat(response).isNotNull(); assertThat(response.getContents()).hasSize(1); - verify(postRepository).findAllByKeywordAndDeletedAtIsNull(eq(keyword), eq(blockedUsers), any(PageRequest.class)); + verify(postRepository).findAllByKeywordAndDeletedAtIsNull( + eq(quoted), + eq(blockedUsers), + any(PageRequest.class)); verify(postDtoAssembler).toPageItemList(posts); } From 65752174a811ebca8dde9626b8474447b7f25f5f Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sat, 20 Sep 2025 21:29:14 +0900 Subject: [PATCH 0959/1002] =?UTF-8?q?refactor=20:=20=EB=8C=80=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=9E=91=EC=84=B1=EC=8B=9C=20=EC=95=8C=EB=9E=8C?= =?UTF-8?q?=EB=8C=80=EC=83=81=20post=20=EC=9E=91=EC=84=B1=EC=9E=90?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/comment/reply/service/ReplyCommandService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index d6d67e47..e735b8b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -54,7 +54,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { log.info("대댓글 추가 완료 - replyId: {}, postId: {}, commentCount: {}", reply.get_id(), post.get_id(), post.getCommentCount()); - if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), comment.getUserId(), post.get_id().toString(), reply.getContent()); + if (!userId.equals(post.getUserId())) notificationService.sendNotificationMessageByReply(post.getPostCategory(), post.getUserId(), post.get_id().toString(), reply.getContent()); } public void updateReply(String replyId, @Valid ReplyUpdateRequestDTO requestDTO) { From 96f976c7ba77fc9bcbe77dfffba44701c25d8445 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sat, 27 Sep 2025 15:17:44 +0900 Subject: [PATCH 0960/1002] =?UTF-8?q?fix:=20redirect=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EC=84=B8=EB=B6=84=ED=99=94=20#259?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 redirect_url을 host와 path로 분리해서 입력 받음 --- .../security/controller/AuthController.java | 7 +++-- .../util/OAuth2LoginSuccessHandler.java | 29 ++++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 3fc22dae..2bc46075 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -32,8 +32,11 @@ public class AuthController { @GetMapping("/google") public ResponseEntity> googleLogin( HttpServletResponse response, - @RequestParam(required = false, value = "redirect_url") String redirect_url) throws IOException { - authSessionService.setSession(redirect_url); + @RequestParam(required = false, value = "redirect_host") String redirect_host, + @RequestParam(required = false, value = "redirect_path") String redirect_path + ) throws IOException { + authSessionService.setSession(redirect_host); + authSessionService.setSession(redirect_path); response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index ce23c404..447db5b4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -55,29 +55,42 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); - String redirectUrl = (String) request.getSession().getAttribute("redirect_url"); - if (Objects.equals(redirectUrl, null)) redirectUrl = BASEURL; - request.getSession().removeAttribute("redirect_url"); + String redirectHost = (String) request.getSession().getAttribute("redirect_host"); + String redirectPath = (String) request.getSession().getAttribute("redirect_path"); + + boolean isPathExists = false; + if (Objects.nonNull(redirectHost)) { + redirectHost = BASEURL; + } + if (Objects.nonNull(redirectPath)) { + isPathExists = true; + } + request.getSession().removeAttribute("redirect_host"); + request.getSession().removeAttribute("redirect_path"); switch (result) { case LOGIN_SUCCESS -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl); + if (isPathExists) { + getRedirectStrategy().sendRedirect(request, response, redirectHost + redirectPath); + } else { + getRedirectStrategy().sendRedirect(request, response, redirectHost + "/main"); + } log.info("{\"code\":200, \"message\":\"정상 로그인 완료: {}\"}", email); } case NEW_USER_REGISTERED -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, redirectHost + "/auth/profile?email=" + email); log.info("{\"code\":201, \"message\":\"신규 회원 등록 완료: {}\"}", email); } case PROFILE_INCOMPLETE -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/auth/profile?email=" + email); + getRedirectStrategy().sendRedirect(request, response, redirectHost + "/auth/profile?email=" + email); log.info("{\"code\":200, \"message\":\"회원 프로필 설정 미완료: {}\"}", email); } case SUSPENDED_USER -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/api/suspends"); + getRedirectStrategy().sendRedirect(request, response, redirectHost + "/api/suspends"); log.info("{\"code\":200, \"message\":\"정지된 회원: {}\"}", email); } default -> { - getRedirectStrategy().sendRedirect(request, response, redirectUrl + "/login"); + getRedirectStrategy().sendRedirect(request, response, redirectHost + "/login"); log.info("{\"code\":500, \"message\":\"알 수 없는 오류 발생: {}\"}", email); } } From 2e3666b7fb35dd3f6882dde0b1fb2c234036b20a Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sat, 27 Sep 2025 15:45:39 +0900 Subject: [PATCH 0961/1002] =?UTF-8?q?fix:=20PR=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95=20#259?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/OAuth2LoginSuccessHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java index 447db5b4..1ce354d3 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginSuccessHandler.java @@ -58,12 +58,12 @@ private void handleLoginResult(HttpServletRequest request, HttpServletResponse r String redirectHost = (String) request.getSession().getAttribute("redirect_host"); String redirectPath = (String) request.getSession().getAttribute("redirect_path"); - boolean isPathExists = false; - if (Objects.nonNull(redirectHost)) { + boolean isPathExists = true; + if (Objects.isNull(redirectHost)) { redirectHost = BASEURL; } - if (Objects.nonNull(redirectPath)) { - isPathExists = true; + if (Objects.isNull(redirectPath)) { + isPathExists = false; } request.getSession().removeAttribute("redirect_host"); request.getSession().removeAttribute("redirect_path"); From 6ac19c1415756b97781df3774da208bb22ddf5cc Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 3 Oct 2025 00:18:47 +0900 Subject: [PATCH 0962/1002] =?UTF-8?q?fix:=20Session=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20#259?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/controller/AuthController.java | 4 ++-- .../codin/common/security/service/AuthSessionService.java | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 2bc46075..8fecb571 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -35,8 +35,8 @@ public ResponseEntity> googleLogin( @RequestParam(required = false, value = "redirect_host") String redirect_host, @RequestParam(required = false, value = "redirect_path") String redirect_path ) throws IOException { - authSessionService.setSession(redirect_host); - authSessionService.setSession(redirect_path); + authSessionService.setRedirectHostSession(redirect_host); + authSessionService.setRedirectPathSession(redirect_path); response.sendRedirect("/api/oauth2/authorization/google"); return ResponseEntity.ok() .body(new SingleResponse<>(200, "google OAuth2 Login Redirect",null)); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java index d019b15c..10f1f5db 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthSessionService.java @@ -12,7 +12,11 @@ public class AuthSessionService { private final HttpSession httpSession; - public void setSession(String redirectUrl) { - if (!Objects.equals(redirectUrl, null)) httpSession.setAttribute("redirect_url", redirectUrl); + public void setRedirectHostSession(String redirectHost) { + if (!Objects.equals(redirectHost, null)) httpSession.setAttribute("redirect_host", redirectHost); + } + + public void setRedirectPathSession(String redirectPath) { + if (!Objects.equals(redirectPath, null)) httpSession.setAttribute("redirect_path", redirectPath); } } From 0e9839bad21be52b172c0b0fe276d7fde66a757b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 5 Oct 2025 00:41:16 +0900 Subject: [PATCH 0963/1002] =?UTF-8?q?feat=20:=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20API=20Permit?= =?UTF-8?q?ALL=20=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 50cfe48f..24f54282 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 50cfe48f2b60416ef75f21fc3cc58c570bc84f4b +Subproject commit 24f54282c0e38026224032037773f17133a5202b From cab7124382e34cc8db7bb1e59b748f2f03050177 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 5 Oct 2025 02:57:41 +0900 Subject: [PATCH 0964/1002] =?UTF-8?q?feat=20:=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=97=86=EC=9D=B4(=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8)=20post?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/util/SecurityUtils.java | 23 ++++++++++++++++++- .../domain/block/service/BlockService.java | 6 +++++ .../post/domain/hits/service/HitsService.java | 3 ++- .../domain/post/service/PostDtoAssembler.java | 13 ++++++----- .../post/service/PostInteractionService.java | 5 ---- .../domain/post/service/PostQueryService.java | 2 +- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 2f984d8a..977514de 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -4,13 +4,16 @@ import inu.codin.codin.common.security.exception.SecurityErrorCode; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.security.CustomUserDetails; +import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; /** * SecurityContext와 관련된 유틸리티 클래스. */ +@Slf4j public class SecurityUtils { /** @@ -20,8 +23,9 @@ public class SecurityUtils { * @throws JwtException 인증 정보가 없는 경우 예외 발생 */ public static ObjectId getCurrentUserId() { + log.info("getCurrentUserId."); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - + log.info("auth={} / principalClass={}", authentication, (authentication!=null ? authentication.getPrincipal().getClass() : null)); if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { throw new JwtException(SecurityErrorCode.ACCESS_DENIED); } @@ -29,6 +33,23 @@ public static ObjectId getCurrentUserId() { return userDetails.getId(); } + + /** + * 현재 인증된 사용자의 ID를 반환 (nullable 안전 버전) + * - 인증이 없거나 익명이면 null 반환 + */ + public static ObjectId getCurrentUserIdOrNull() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !authentication.isAuthenticated() + || authentication instanceof AnonymousAuthenticationToken + || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { + return null; + } + + return userDetails.getId(); + } + public static UserRole getCurrentUserRole(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 974b2eac..0374cac5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -78,9 +78,15 @@ public void unblockUser(String strBlockedUserId) { /** * 현재 유저의 차단된 유저 목록 반환 + * 인증이 없거나 익명이면 빈 리스트 반환 * @return 차단한 유저 목록 (빈 리스트가 제공될 수 있음) */ public List getBlockedUsers() { + ObjectId currentUserId = SecurityUtils.getCurrentUserIdOrNull(); + if (currentUserId == null) { + return List.of(); + } + return blockRepository.findByUserId(SecurityUtils.getCurrentUserId()) .map(BlockEntity::getBlockedUsers) .orElse(List.of()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java index ef3cf220..53e6130a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/service/HitsService.java @@ -44,9 +44,10 @@ public void addHits(ObjectId postId, ObjectId userId){ * 게시글 조회 여부 판단 * @param postId 게시글 _id * @param userId 유저 _id - * @return true : 게시글 조회 유 , false : 게시글 조회 무 + * @return true : 게시글 조회 유 , false : 게시글 조회 무 / 인증X (비로그인) */ public boolean validateHits(ObjectId postId, ObjectId userId) { + if (userId == null) return false; return hitsRepository.existsByPostIdAndUserId(postId, userId); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java index 0235a291..4b21f469 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; import java.util.List; +import java.util.Objects; /** * PostEntity를 다양한 Response DTO로 변환하는 책임을 담당하는 어셈블러 @@ -69,7 +70,7 @@ public PostPageItemResponseDTO toPageItem(PostEntity post, ObjectId currentUserI * PostEntity 리스트를 PostPageItemResponseDTO 리스트로 변환 */ public List toPageItemList(List posts) { - ObjectId currentUserId = SecurityUtils.getCurrentUserId(); + ObjectId currentUserId = SecurityUtils.getCurrentUserIdOrNull(); return posts.stream() .map(post -> toPageItem(post, currentUserId)) .toList(); @@ -86,13 +87,13 @@ private UserDto resolveUserProfile(PostEntity post) { /** * 현재 사용자의 게시물에 대한 상호작용 정보 조회 (좋아요, 스크랩, 작성자 여부) + * - 익명(비로그인)인 경우: liked=false, scraped=false, isOwner=false */ private UserInfo getUserInfoAboutPost(ObjectId currentUserId, ObjectId postUserId, ObjectId postId) { - return UserInfo.ofPost( - likeService.isLiked(LikeType.POST, postId.toString(), currentUserId), - scrapService.isPostScraped(postId, currentUserId), - postUserId.equals(currentUserId) - ); + boolean liked = (currentUserId != null) && likeService.isLiked(LikeType.POST, postId.toString(), currentUserId); + boolean scraped = (currentUserId != null) && scrapService.isPostScraped(postId, currentUserId); + boolean isOwner = Objects.equals(postUserId, currentUserId); + return UserInfo.ofPost(liked, scraped, isOwner); } /** diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java index 4bdebc0b..ff7f810f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java @@ -1,12 +1,7 @@ package inu.codin.codin.domain.post.service; - -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.domain.like.entity.LikeType; -import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; -import inu.codin.codin.domain.scrap.service.ScrapService; import inu.codin.codin.infra.s3.S3Service; import inu.codin.codin.infra.s3.exception.ImageRemoveException; import inu.codin.codin.domain.post.exception.PostException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 3ac7c250..8cf1984e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -54,7 +54,7 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { */ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserIdOrNull(); postInteractionService.increaseHits(post, userId); return postDtoAssembler.toPageItem(post, userId); } From 0334ab165104cd4cf62f2d48a901c267006c24c0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Sun, 5 Oct 2025 03:16:00 +0900 Subject: [PATCH 0965/1002] =?UTF-8?q?chore=20:=20submodule=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 24f54282..602ab284 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 24f54282c0e38026224032037773f17133a5202b +Subproject commit 602ab2842c147fc7bffcb7f137539d0229e86b78 From 4deb47080e6052d5af8858024c9314fb387a7f03 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 6 Oct 2025 12:55:24 +0900 Subject: [PATCH 0966/1002] =?UTF-8?q?fix(security):=20permitAll=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=EC=97=90=EC=84=9C=EB=8F=84=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EC=9D=B4=20=EC=9E=88=EC=9C=BC=EB=A9=B4=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=84=B8=ED=8C=85=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/filter/JwtAuthenticationFilter.java | 14 +++++++++----- .../codin/common/security/service/JwtService.java | 8 +++++++- .../domain/post/domain/hits/entity/HitsEntity.java | 4 +--- .../post/service/PostInteractionService.java | 3 ++- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index e8087769..0238da97 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.AntPathMatcher; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -36,10 +37,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String requestURI = request.getRequestURI(); - if (permitAllProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, requestURI))) { - filterChain.doFilter(request, response); - return; - } + final boolean isPermitAll = permitAllProperties.getUrls().stream() + .anyMatch(url -> pathMatcher.match(url, requestURI)); String token = null; if (Arrays.stream(SWAGGER_AUTH_PATHS).anyMatch(url -> pathMatcher.match(url, requestURI))) { @@ -49,10 +48,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } // Access Token이 있는 경우 - if (token != null) { + if (StringUtils.hasText(token)) { jwtService.getUserDetailsAndSetAuthentication(token); } else { SecurityContextHolder.clearContext(); + + if (isPermitAll) { + filterChain.doFilter(request, response); + return; + } } filterChain.doFilter(request, response); diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 05c440d2..98a9a269 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -16,10 +16,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; /** * JWT 토큰 관련 비즈니스 로직을 처리하는 서비스 @@ -192,6 +192,12 @@ public void getUserDetailsAndSetAuthentication(String token) { public String getAccessToken(HttpServletRequest request) { String accessToken = jwtUtils.getAccessToken(request); + log.info("token: {}", accessToken); + + if (!StringUtils.hasText(accessToken)) { + return null; + } + if (!jwtTokenProvider.validType(accessToken, "access")) { log.error("[getAccessToken] Access Token이 아닙니다."); throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Access Token이 아닙니다."); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java index 6f00726f..c4cbc429 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/entity/HitsEntity.java @@ -14,13 +14,11 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class HitsEntity { - @Id @NotBlank + @Id private ObjectId _id; - @NotBlank private ObjectId userId; - @NotBlank private ObjectId postId; @Builder diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java index ff7f810f..21b939b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostInteractionService.java @@ -47,8 +47,9 @@ public void deletePostImageInternal(PostEntity post, String imageUrl) { } // [HitsService] - 조회수 증가 처리 + // 비로그인(null) → 무조건 증가, 로그인 → 중복 아닐 때만 증가 public void increaseHits(PostEntity post, ObjectId userId) { - if (!hitsService.validateHits(post.get_id(), userId)) { + if (userId==null || !hitsService.validateHits(post.get_id(), userId)) { hitsService.addHits(post.get_id(), userId); log.info("조회수 업데이트. PostId: {}, UserId: {}", post.get_id(), userId); } From 6c45dfbb91f6f764ec00e2fb63b7bbf24c8ed4e5 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Mon, 6 Oct 2025 13:03:52 +0900 Subject: [PATCH 0967/1002] =?UTF-8?q?chore=20:=20log=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/codin/common/security/service/JwtService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 98a9a269..6e419603 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -192,7 +192,6 @@ public void getUserDetailsAndSetAuthentication(String token) { public String getAccessToken(HttpServletRequest request) { String accessToken = jwtUtils.getAccessToken(request); - log.info("token: {}", accessToken); if (!StringUtils.hasText(accessToken)) { return null; From 0b9beceab8a3ffda36a1e087816c6f90b017d1a2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 7 Oct 2025 14:07:59 +0900 Subject: [PATCH 0968/1002] =?UTF-8?q?fix=20:=20=EA=B3=B5=EA=B0=9Capi=20(sw?= =?UTF-8?q?agger,=20auth)=20=EC=97=90=EC=84=9C=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9A=94=EA=B5=AC=20=EC=97=90=EB=9F=AC=20-?= =?UTF-8?q?=20permit=5Fall=20=EC=97=90=EC=84=9C=20public=5Fapi=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/config/SecurityConfig.java | 5 ++++- .../codin/common/dto/PublicApiProperties.java | 16 ++++++++++++++++ .../security/filter/JwtAuthenticationFilter.java | 12 +++++++++++- codin-core/src/main/resources | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 5d348f7e..5d3cafbb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.dto.PermitAllProperties; +import inu.codin.codin.common.dto.PublicApiProperties; import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; import inu.codin.codin.common.security.service.JwtService; @@ -57,6 +58,7 @@ public class SecurityConfig { private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; private final CustomOAuth2UserService customOAuth2UserService; private final PermitAllProperties permitAllProperties; + private final PublicApiProperties publicApiProperties; private final AppleOAuth2UserService appleOAuth2UserService; private final ClientRegistrationRepository clientRegistrationRepository; @@ -81,6 +83,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(permitAllProperties.getUrls().toArray(new String[0])).permitAll() + .requestMatchers(publicApiProperties.getUrls().toArray(new String[0])).permitAll() .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") .requestMatchers(USER_AUTH_PATHS).hasRole("USER") @@ -115,7 +118,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // .httpBasic(Customizer.withDefaults()) // JwtAuthenticationFilter 추가 .addFilterBefore( - new JwtAuthenticationFilter(jwtService, permitAllProperties), + new JwtAuthenticationFilter(jwtService, permitAllProperties, publicApiProperties), UsernamePasswordAuthenticationFilter.class ) // 예외 처리 필터 추가 diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java b/codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java new file mode 100644 index 00000000..14b3d1e5 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java @@ -0,0 +1,16 @@ +package inu.codin.codin.common.dto; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "security.public-api") +public class PublicApiProperties { + private List urls; +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java index 0238da97..e7499d35 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java @@ -1,6 +1,7 @@ package inu.codin.codin.common.security.filter; import inu.codin.codin.common.dto.PermitAllProperties; +import inu.codin.codin.common.dto.PublicApiProperties; import inu.codin.codin.common.security.service.JwtService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -23,6 +24,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtService jwtService; private final PermitAllProperties permitAllProperties; + private final PublicApiProperties publicApiProperties; private final AntPathMatcher pathMatcher = new AntPathMatcher(); private final String [] SWAGGER_AUTH_PATHS = { @@ -40,6 +42,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse final boolean isPermitAll = permitAllProperties.getUrls().stream() .anyMatch(url -> pathMatcher.match(url, requestURI)); + final boolean isPublicApi = publicApiProperties.getUrls().stream() + .anyMatch(url -> pathMatcher.match(url, requestURI)); + + if (isPermitAll) { + filterChain.doFilter(request, response); + return; + } + String token = null; if (Arrays.stream(SWAGGER_AUTH_PATHS).anyMatch(url -> pathMatcher.match(url, requestURI))) { token = jwtService.getRefreshToken(request); @@ -53,7 +63,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } else { SecurityContextHolder.clearContext(); - if (isPermitAll) { + if (isPublicApi) { filterChain.doFilter(request, response); return; } diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 602ab284..7a4f3d3c 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 602ab2842c147fc7bffcb7f137539d0229e86b78 +Subproject commit 7a4f3d3cfcc431add261543a761a86bbe49bba9a From 3d552e927911935d277fc0e88061a098e5a03fcf Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 7 Oct 2025 23:42:29 +0900 Subject: [PATCH 0969/1002] =?UTF-8?q?todo:Comment=20Query=20userId=20Null?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/domain/comment/service/CommentQueryService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index 738a2816..81f36dee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -114,6 +114,8 @@ private CommentResponseDTO buildCommentResponseDTO( public UserInfo getUserInfoAboutComment(ObjectId commentId) { ObjectId userId = SecurityUtils.getCurrentUserId(); + + //todo: userId -> NULL isLiked=false 처리 return UserInfo.ofComment( likeService.isLiked(LikeType.COMMENT, commentId.toString(), userId) ); From 722b07decd6cbdaa5d9f271926b39f400eb1d723 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 7 Oct 2025 23:45:10 +0900 Subject: [PATCH 0970/1002] =?UTF-8?q?fix=20:=20logout=20=EC=8B=9C=20AT=20?= =?UTF-8?q?=EC=BF=A0=ED=82=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/service/JwtService.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 6e419603..fd3c87a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -142,8 +142,10 @@ private void createBothToken(HttpServletResponse response) { log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}", authentication.getName(), newToken.getAccessToken()); } + /** - * 로그아웃 - Refresh Token 삭제 + * 로그아웃 - + * Access,Refresh Token 제거/ 서버측 RT 삭제 */ public void deleteToken(HttpServletResponse response) { // 어차피 JwtAuthenticationFilter 단에서 토큰을 검증하여 인증을 처리하므로 @@ -163,6 +165,15 @@ private void deleteCookie(HttpServletResponse response) { refreshCookie.setPath("/"); refreshCookie.setMaxAge(0); // 7일 response.addCookie(refreshCookie); + log.info("[deleteToken] Refresh Cookie 삭제 완료"); + + Cookie AccessCookie = new Cookie("x-access-token", ""); + AccessCookie.setHttpOnly(true); + AccessCookie.setSecure(true); + AccessCookie.setPath("/"); + AccessCookie.setMaxAge(0); + response.addCookie(AccessCookie); + log.info("[deleteToken] Access Cookie 삭제 완료"); } public void setAuthentication(HttpServletRequest request){ From 1ef011bb515c8ba82a274a9dd460759388fc1756 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 7 Oct 2025 23:55:05 +0900 Subject: [PATCH 0971/1002] =?UTF-8?q?refactor:=20=EB=B9=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=8B=9C=20isLiked=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=3D=20false?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/service/CommentQueryService.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index 81f36dee..fbbc94db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -113,12 +113,14 @@ private CommentResponseDTO buildCommentResponseDTO( public UserInfo getUserInfoAboutComment(ObjectId commentId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = SecurityUtils.getCurrentUserIdOrNull(); - //todo: userId -> NULL isLiked=false 처리 - return UserInfo.ofComment( - likeService.isLiked(LikeType.COMMENT, commentId.toString(), userId) - ); + boolean isLiked = false; + if (userId != null) { + isLiked = likeService.isLiked(LikeType.COMMENT, commentId.toString(), userId); + } + + return UserInfo.ofComment(isLiked); } /** * From 0062aab609fa0bbd7f9444350472373082b7c2e7 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 8 Oct 2025 18:12:27 +0900 Subject: [PATCH 0972/1002] =?UTF-8?q?fix=20:=20Cookie=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EC=8B=9C=20Domain,=20Attribute=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/service/JwtService.java | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index fd3c87a0..9c0280fe 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -36,7 +36,7 @@ public class JwtService { private final CustomUserDetailsService userDetailsService; @Value("${server.domain}") - private String BASERURL; + private String BASEURL; private final String REFRESH_TOKEN = "x-refresh-token"; private final String ACCESS_TOKEN = "Authorization"; @@ -125,7 +125,7 @@ private void createBothToken(HttpServletResponse response) { newAccessToken.setSecure(true); newAccessToken.setPath("/"); newAccessToken.setMaxAge(10 * 24 * 60 * 60); // 10일 - newAccessToken.setDomain(BASERURL.split("//")[1]); + newAccessToken.setDomain(BASEURL.split("//")[1]); newAccessToken.setAttribute("SameSite", "None"); response.addCookie(newAccessToken); @@ -135,7 +135,7 @@ private void createBothToken(HttpServletResponse response) { newRefreshToken.setSecure(true); newRefreshToken.setPath("/"); newRefreshToken.setMaxAge(10 * 24 * 60 * 60); // 10일 - newRefreshToken.setDomain(BASERURL.split("//")[1]); + newRefreshToken.setDomain(BASEURL.split("//")[1]); newRefreshToken.setAttribute("SameSite", "None"); response.addCookie(newRefreshToken); @@ -159,21 +159,37 @@ public void deleteToken(HttpServletResponse response) { } private void deleteCookie(HttpServletResponse response) { + + String domain = BASEURL.replaceFirst("https?://", "").split(":")[0]; + log.info("[deleteCookie] BASEURL={}, domain={}", BASEURL, domain); + Cookie refreshCookie = new Cookie(REFRESH_TOKEN, ""); refreshCookie.setHttpOnly(true); refreshCookie.setSecure(true); refreshCookie.setPath("/"); - refreshCookie.setMaxAge(0); // 7일 + refreshCookie.setDomain(domain); + refreshCookie.setMaxAge(0); + refreshCookie.setAttribute("SameSite","None"); response.addCookie(refreshCookie); - log.info("[deleteToken] Refresh Cookie 삭제 완료"); - - Cookie AccessCookie = new Cookie("x-access-token", ""); - AccessCookie.setHttpOnly(true); - AccessCookie.setSecure(true); - AccessCookie.setPath("/"); - AccessCookie.setMaxAge(0); - response.addCookie(AccessCookie); - log.info("[deleteToken] Access Cookie 삭제 완료"); + + log.info("[deleteCookie] refreshCookie info => name={}, domain={}, path={}, secure={}, httpOnly={}, maxAge={}, sameSite=None", + refreshCookie.getName(), refreshCookie.getDomain(), refreshCookie.getPath(), + refreshCookie.getSecure(), refreshCookie.isHttpOnly(), refreshCookie.getMaxAge()); + + Cookie accessCookie = new Cookie("x-access-token", ""); + accessCookie.setHttpOnly(true); + accessCookie.setSecure(true); + accessCookie.setPath("/"); + accessCookie.setDomain(domain); + accessCookie.setMaxAge(0); + refreshCookie.setAttribute("SameSite","None"); + response.addCookie(accessCookie); + + log.info("[deleteCookie] accessCookie info => name={}, domain={}, path={}, secure={}, httpOnly={}, maxAge={}, sameSite=None", + accessCookie.getName(), accessCookie.getDomain(), accessCookie.getPath(), + accessCookie.getSecure(), accessCookie.isHttpOnly(), accessCookie.getMaxAge()); + + log.info("[deleteToken] Access/Refresh Cookie 삭제 완료"); } public void setAuthentication(HttpServletRequest request){ From 4f0bb21042a01a2598db48f30c6299a912ca4e47 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 8 Oct 2025 19:34:12 +0900 Subject: [PATCH 0973/1002] =?UTF-8?q?fix=20:=20post=20json=20=ED=94=84?= =?UTF-8?q?=EB=A1=A0=ED=8A=B8=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=A4=20-=20=EC=B6=94=ED=9B=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/dto/response/PostPageItemResponseDTO.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java index e753499b..9a618f97 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java @@ -1,10 +1,14 @@ package inu.codin.codin.domain.post.dto.response; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import lombok.Builder; import lombok.Getter; @Getter +@JsonInclude(JsonInclude.Include.NON_NULL) public class PostPageItemResponseDTO { + @JsonUnwrapped // 추후 프론트에 얘기해서 {post:{}, poll:{} 구조로 변경} 현재:{,,,poll:{}} private final PostDetailResponseDTO post; private final PollInfoResponseDTO poll; From 0bc9ec76eead836759c547c2ada462f3ab74984f Mon Sep 17 00:00:00 2001 From: gisu1102 <76506573+gisu1102@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:49:28 +0900 Subject: [PATCH 0974/1002] =?UTF-8?q?Revert=20"fix:=20post=20=EB=8B=A8?= =?UTF-8?q?=EA=B1=B4/=EB=AA=A9=EB=A1=9D=20JSON=EC=9D=84=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=ED=94=84=EB=A1=A0=ED=8A=B8=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=ED=99=94"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/dto/response/PostPageItemResponseDTO.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java index 9a618f97..e753499b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/dto/response/PostPageItemResponseDTO.java @@ -1,14 +1,10 @@ package inu.codin.codin.domain.post.dto.response; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import lombok.Builder; import lombok.Getter; @Getter -@JsonInclude(JsonInclude.Include.NON_NULL) public class PostPageItemResponseDTO { - @JsonUnwrapped // 추후 프론트에 얘기해서 {post:{}, poll:{} 구조로 변경} 현재:{,,,poll:{}} private final PostDetailResponseDTO post; private final PollInfoResponseDTO poll; From fa8236cc724b9282554062ac64d2f5624f7fc78b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 9 Oct 2025 01:57:34 +0900 Subject: [PATCH 0975/1002] chore : yml file update --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 7a4f3d3c..7e18b69a 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 7a4f3d3cfcc431add261543a761a86bbe49bba9a +Subproject commit 7e18b69a67053717cb9133d1bd7e484f0aecc270 From e9f4560d61025a2e2fec665fe1d0e59c2e890d1a Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 9 Oct 2025 02:03:33 +0900 Subject: [PATCH 0976/1002] chore : yml file update --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 7a4f3d3c..7e18b69a 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 7a4f3d3cfcc431add261543a761a86bbe49bba9a +Subproject commit 7e18b69a67053717cb9133d1bd7e484f0aecc270 From 08776401cc412fcfe4816b605bd81fdc25cd176e Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 10 Oct 2025 16:30:54 +0900 Subject: [PATCH 0977/1002] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8,=20=EC=9C=A0=EC=A0=80=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=ED=95=84=EB=93=9C=20=EB=B0=98=ED=99=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 현재 유저가 관리자인지 확인하는 용도이고, 추후 어드민 전용 기능이 생길 것을 감안해 필드 값을 추가 --- .../codin/domain/user/dto/response/UserInfoResponseDto.java | 6 +++++- codin-core/src/main/resources | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java index 1c729088..2e0bdcc1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -16,9 +17,10 @@ public class UserInfoResponseDto { private String profileImageUrl; private String nickname; private Department department; + private UserRole userRole; @Builder - public UserInfoResponseDto(String _id, String email, String studentId, String name, String profileImageUrl, String nickname, Department department) { + public UserInfoResponseDto(String _id, String email, String studentId, String name, String profileImageUrl, String nickname, Department department, UserRole userRole) { this._id = _id; this.email = email; this.studentId = studentId; @@ -26,6 +28,7 @@ public UserInfoResponseDto(String _id, String email, String studentId, String na this.profileImageUrl = profileImageUrl; this.nickname = nickname; this.department = department; + this.userRole = userRole; } public static UserInfoResponseDto of(UserEntity user) { @@ -37,6 +40,7 @@ public static UserInfoResponseDto of(UserEntity user) { .profileImageUrl(user.getProfileImageUrl()) .nickname(user.getNickname()) .department(user.getDepartment()) + .userRole(user.getRole()) .build(); } } diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 7e18b69a..4a5df64c 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 7e18b69a67053717cb9133d1bd7e484f0aecc270 +Subproject commit 4a5df64c750e3948f5be7a98d6f912886fa60ba3 From 88c5b28d9bcb0e5c0ec88dfc7e8f9042d4f386b9 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Fri, 10 Oct 2025 16:40:01 +0900 Subject: [PATCH 0978/1002] =?UTF-8?q?chore:=20resources=20main=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B0=B8=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/src/main/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/resources b/codin-core/src/main/resources index 4a5df64c..7e18b69a 160000 --- a/codin-core/src/main/resources +++ b/codin-core/src/main/resources @@ -1 +1 @@ -Subproject commit 4a5df64c750e3948f5be7a98d6f912886fa60ba3 +Subproject commit 7e18b69a67053717cb9133d1bd7e484f0aecc270 From b7b60d5e568471ff9938a0bb94596b69fad4f2e5 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Sun, 12 Oct 2025 15:59:55 +0900 Subject: [PATCH 0979/1002] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=EA=B0=80=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=ED=8C=85=20=EC=88=98=EB=A0=B9=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=9E=85=EB=A0=A5=EC=8B=9C=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=B4=20null=EB=A1=9C=20=EB=B3=80=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/UserTicketingParticipationInfoUpdateRequest.java | 1 - .../main/java/inu/codin/codin/domain/user/entity/UserEntity.java | 1 - 2 files changed, 2 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java index e84f774e..070267c9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java @@ -12,6 +12,5 @@ public class UserTicketingParticipationInfoUpdateRequest { private Department department; private String studentId; - private String name; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index fbe6b3fc..eca9f5b2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -107,7 +107,6 @@ public void updateTotalSuspensionEndDate(LocalDateTime totalSuspensionEndDate){ */ public void updateParticipationInfo(UserTicketingParticipationInfoUpdateRequest updateRequest) { this.studentId = updateRequest.getStudentId(); - this.name = updateRequest.getName(); this.department = updateRequest.getDepartment(); } } From 10f259f5875ef981bab3ac5b54b5b21bb127f2e4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 14 Oct 2025 15:26:43 +0900 Subject: [PATCH 0980/1002] fix : Reply unlogined --- .../comment/reply/service/ReplyQueryService.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index 3af1e9eb..5de87298 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -105,10 +105,15 @@ private CommentResponseDTO buildReplyResponseDTO( } public UserInfo getUserInfoAboutReply(ObjectId replyId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); - return UserInfo.ofComment( - likeService.isLiked(LikeType.REPLY, replyId.toString(), userId) - ); + ObjectId userId = SecurityUtils.getCurrentUserIdOrNull(); + + boolean isLiked = false; + + if (userId != null) { + isLiked = likeService.isLiked(LikeType.REPLY, replyId.toString(), userId); + } + + return UserInfo.ofComment(isLiked); } /** From abe0ce7dc213813db063bad43207fbe93cf73733 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min Date: Thu, 16 Oct 2025 18:40:13 +0900 Subject: [PATCH 0981/1002] =?UTF-8?q?hotfix:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=90=9C=20=EB=8B=89=EB=84=A4=EC=9E=84,=20?= =?UTF-8?q?=EC=9E=90=EA=B8=B0=20=EC=9E=90=EC=8B=A0=EC=9D=98=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EC=82=AC=EC=9A=A9=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codin/common/security/service/AuthCommonService.java | 3 ++- .../codin/codin/domain/user/repository/UserRepository.java | 3 ++- .../inu/codin/codin/domain/user/service/UserService.java | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 2bf3d76b..a5e4a518 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; +import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; @@ -33,7 +34,7 @@ public AuthCommonService(UserRepository userRepository, S3Service s3Service, Jwt } public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userProfileRequestDto.getNickname()); if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index e65c0b34..ecf6fd70 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -18,7 +18,8 @@ public interface UserRepository extends MongoRepository { @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByEmail(String email); - Optional findByNicknameAndDeletedAtIsNull(String nickname); + @Query("{'_id': {$ne: ?0}, 'nickname': ?1, 'deletedAt': null}") + Optional findByNicknameAndDeletedAtIsNull(ObjectId useId, String nickname); @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") Optional findByEmailAndDisabled(String email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 52801bff..98fccf7a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -30,6 +30,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -148,9 +149,8 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) { - - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); - if (nickNameDuplicate.isPresent()){ + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userNicknameRequestDto.getNickname()); + if (nickNameDuplicate.isPresent() && nickNameDuplicate.get().getNickname().equals( SecurityContextHolder.getContext().getAuthentication().getName())) { throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } From 2e0a70fe91d6c86c4871fd42d0120bfe16f310a5 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min <67214970+doma17@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:45:08 +0900 Subject: [PATCH 0982/1002] Update src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../inu/codin/codin/domain/user/repository/UserRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index ecf6fd70..cfe9ee3f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -19,7 +19,7 @@ public interface UserRepository extends MongoRepository { Optional findByEmail(String email); @Query("{'_id': {$ne: ?0}, 'nickname': ?1, 'deletedAt': null}") - Optional findByNicknameAndDeletedAtIsNull(ObjectId useId, String nickname); + Optional findByNicknameAndDeletedAtIsNull(ObjectId userId, String nickname); @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") Optional findByEmailAndDisabled(String email); From e03b416eb2c165563bd4a1e1734eb861429b0ed3 Mon Sep 17 00:00:00 2001 From: Kwak Byoung Min <67214970+doma17@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:45:21 +0900 Subject: [PATCH 0983/1002] Update src/main/java/inu/codin/codin/domain/user/service/UserService.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../java/inu/codin/codin/domain/user/service/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 98fccf7a..5a9ec711 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -150,7 +150,7 @@ public UserInfoResponseDto getUserInfo() { } public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) { Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userNicknameRequestDto.getNickname()); - if (nickNameDuplicate.isPresent() && nickNameDuplicate.get().getNickname().equals( SecurityContextHolder.getContext().getAuthentication().getName())) { + if (nickNameDuplicate.isPresent()) { throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } From 8cd2d85e16c6f7b22eb33c0d5911480f1841483e Mon Sep 17 00:00:00 2001 From: gisu1102 <76506573+gisu1102@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:33:36 +0900 Subject: [PATCH 0984/1002] =?UTF-8?q?Revert=20"hotfix:=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84,=20=EC=9E=90=EA=B8=B0=20=EC=9E=90=EC=8B=A0?= =?UTF-8?q?=EC=9D=98=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../codin/common/security/service/AuthCommonService.java | 3 +-- .../codin/codin/domain/user/repository/UserRepository.java | 3 +-- .../inu/codin/codin/domain/user/service/UserService.java | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index a5e4a518..2bf3d76b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; @@ -34,7 +33,7 @@ public AuthCommonService(UserRepository userRepository, S3Service s3Service, Jwt } public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userProfileRequestDto.getNickname()); + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index cfe9ee3f..e65c0b34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -18,8 +18,7 @@ public interface UserRepository extends MongoRepository { @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByEmail(String email); - @Query("{'_id': {$ne: ?0}, 'nickname': ?1, 'deletedAt': null}") - Optional findByNicknameAndDeletedAtIsNull(ObjectId userId, String nickname); + Optional findByNicknameAndDeletedAtIsNull(String nickname); @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") Optional findByEmailAndDisabled(String email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 5a9ec711..52801bff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -30,7 +30,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -149,8 +148,9 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) { - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userNicknameRequestDto.getNickname()); - if (nickNameDuplicate.isPresent()) { + + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); + if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } From a7ccdf0863645eb47182fe3b2b99d31eb5dfd8fa Mon Sep 17 00:00:00 2001 From: gisu1102 <76506573+gisu1102@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:33:36 +0900 Subject: [PATCH 0985/1002] =?UTF-8?q?Revert=20"hotfix:=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84,=20=EC=9E=90=EA=B8=B0=20=EC=9E=90=EC=8B=A0?= =?UTF-8?q?=EC=9D=98=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=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 (cherry picked from commit cfd17d3733fe9c9eee331dbb93fee777fea29b09) --- .../codin/common/security/service/AuthCommonService.java | 3 +-- .../codin/codin/domain/user/repository/UserRepository.java | 3 +-- .../inu/codin/codin/domain/user/service/UserService.java | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index a5e4a518..2bf3d76b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -2,7 +2,6 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; -import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; @@ -34,7 +33,7 @@ public AuthCommonService(UserRepository userRepository, S3Service s3Service, Jwt } public void completeUserProfile(UserProfileRequestDto userProfileRequestDto, MultipartFile userImage, HttpServletResponse response) { - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userProfileRequestDto.getNickname()); + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userProfileRequestDto.getNickname()); if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index cfe9ee3f..e65c0b34 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -18,8 +18,7 @@ public interface UserRepository extends MongoRepository { @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['ACTIVE'] }}") Optional findByEmail(String email); - @Query("{'_id': {$ne: ?0}, 'nickname': ?1, 'deletedAt': null}") - Optional findByNicknameAndDeletedAtIsNull(ObjectId userId, String nickname); + Optional findByNicknameAndDeletedAtIsNull(String nickname); @Query("{'email': ?0, 'deletedAt': null, 'status': { $in: ['DISABLED'] }}") Optional findByEmailAndDisabled(String email); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 5a9ec711..52801bff 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -30,7 +30,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -149,8 +148,9 @@ public UserInfoResponseDto getUserInfo() { return UserInfoResponseDto.of(user); } public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) { - Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(SecurityUtils.getCurrentUserId(), userNicknameRequestDto.getNickname()); - if (nickNameDuplicate.isPresent()) { + + Optional nickNameDuplicate = userRepository.findByNicknameAndDeletedAtIsNull(userNicknameRequestDto.getNickname()); + if (nickNameDuplicate.isPresent()){ throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } From d1e5e90ac9ec20f1caf3143482063b601ec1fdab Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Oct 2025 21:56:49 +0900 Subject: [PATCH 0986/1002] feat : user name update --- .../user/controller/UserController.java | 11 +++++++ .../dto/request/UserNameUpdateRequestDto.java | 22 +++++++++++++ .../codin/domain/user/entity/UserEntity.java | 4 +++ .../domain/user/service/UserService.java | 33 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 6b92ff97..4dac0266 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -2,6 +2,7 @@ import inu.codin.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; +import inu.codin.codin.domain.user.dto.request.UserNameUpdateRequestDto; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.request.UserTicketingParticipationInfoUpdateRequest; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; @@ -104,6 +105,16 @@ public ResponseEntity> updateUserProfile(@RequestPart(value = .body(new SingleResponse<>(200, "유저 사진 수정 완료", null)); } + @Operation( + summary = "유저 이름 수정" + ) + @PutMapping("/name") + public ResponseEntity> updateUserName(@RequestBody @Valid UserNameUpdateRequestDto userUpdateRequestDto){ + userService.updateUserName(userUpdateRequestDto); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "유저 이름 수정 완료", null)); + } + @Operation( summary = "유저 티켓팅 수령자 정보 반환 (학번, 이름, 소속) - [Ticketing]" ) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java new file mode 100644 index 00000000..e212bda9 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java @@ -0,0 +1,22 @@ +package inu.codin.codin.domain.user.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +import java.beans.ConstructorProperties; + +@Getter +public class UserNameUpdateRequestDto { + + @Schema(description = "이름", example = "횃불이") + @NotBlank(message = "이름은 비어 있을 수 없습니다.") + @Size(min = 1, max = 10, message = "이름은 1~10자여야 합니다.") + private String name; + + @ConstructorProperties({"name"}) + public UserNameUpdateRequestDto(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index eca9f5b2..2914f8f4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -59,6 +59,10 @@ public UserEntity(String email, String password, String studentId, String name, this.status = status; } + public void updateName(String name) { + this.name = name; + } + public void updateNickname(String nickname) { this.nickname = nickname; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 52801bff..3347a5cc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -14,6 +14,7 @@ import inu.codin.codin.domain.post.service.PostDtoAssembler; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; +import inu.codin.codin.domain.user.dto.request.UserNameUpdateRequestDto; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; import inu.codin.codin.domain.user.dto.request.UserTicketingParticipationInfoUpdateRequest; import inu.codin.codin.domain.user.dto.response.UserInfoResponseDto; @@ -184,6 +185,38 @@ public void updateUserProfile(MultipartFile profileImage) { log.info("[프로필 이미지 업데이트 성공] 사용자 ID: {}, 프로필 이미지 URL: {}", userId, profileImageUrl); } + public void updateUserName(@Valid UserNameUpdateRequestDto request){ + ObjectId userId = SecurityUtils.getCurrentUserId(); + log.info("[유저 실명 수정] 현재 사용자 ID: {}, 요청 이름: {}", userId, request.getName()); + + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> { + log.info("[유저 실명 수정 실패] 유저 정보를 찾을 수 없음. 사용자 ID: {}", userId); + return new NotFoundException("유저 정보를 찾을 수 없습니다."); + }); + + String newName = request.getName().trim(); + + if (newName.isEmpty()) { + throw new IllegalArgumentException("이름은 비어 있을 수 없습니다."); + } + if (newName.length() > 10) { + throw new IllegalArgumentException("이름은 10자 이하여야 합니다."); + } + if (!newName.matches("^[가-힣a-zA-Z]+$")) { + throw new IllegalArgumentException("이름은 한글 또는 영어만 입력 가능합니다."); + } + + if (newName.equals(user.getName())) { + log.info("[유저 실명 수정] 변경 사항 없음. 사용자 ID: {}", userId); + return; + } + + user.updateName(newName); + userRepository.save(user); + + log.info("[유저 실명 수정 성공] 사용자 ID: {}, 변경 이름: {}", userId, newName); + } /** * 유저 티켓팅 수령 정보 반환 * @return UserTicketingParticipationInfoResponse 유저의 학번, 이름, 소속 Dto 반환 From 187c37ad99cead52fa3d2022d68adec5e1c0ef70 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Oct 2025 21:57:24 +0900 Subject: [PATCH 0987/1002] =?UTF-8?q?refactor=20:=20local=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20domain=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/JwtService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 9c0280fe..14e7399b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -119,13 +119,15 @@ private void createBothToken(HttpServletResponse response) { // Authorization 헤더에 Access Token 추가 response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); + String domain = BASEURL.replaceFirst("https?://", "").split(":")[0]; + // todo: x-access-token 쿠키에 Access Token 추가 - 추후 제거 Cookie newAccessToken = new Cookie("x-access-token", newToken.getAccessToken()); newAccessToken.setHttpOnly(true); newAccessToken.setSecure(true); newAccessToken.setPath("/"); newAccessToken.setMaxAge(10 * 24 * 60 * 60); // 10일 - newAccessToken.setDomain(BASEURL.split("//")[1]); + newAccessToken.setDomain(domain); newAccessToken.setAttribute("SameSite", "None"); response.addCookie(newAccessToken); From 898f571ffd8517fc8bc0a4073f0b97417ddee762 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Thu, 16 Oct 2025 21:57:39 +0900 Subject: [PATCH 0988/1002] =?UTF-8?q?refactor=20:=20local=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20domain=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inu/codin/codin/common/security/service/JwtService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java index 14e7399b..18054bc2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java @@ -137,7 +137,7 @@ private void createBothToken(HttpServletResponse response) { newRefreshToken.setSecure(true); newRefreshToken.setPath("/"); newRefreshToken.setMaxAge(10 * 24 * 60 * 60); // 10일 - newRefreshToken.setDomain(BASEURL.split("//")[1]); + newRefreshToken.setDomain(domain); newRefreshToken.setAttribute("SameSite", "None"); response.addCookie(newRefreshToken); From 43b6f74382c3ec0d50ff7f9f59a270dcb96f5466 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 17 Oct 2025 01:07:36 +0900 Subject: [PATCH 0989/1002] =?UTF-8?q?feat=20:=20name=201=EA=B8=80=EC=9E=90?= =?UTF-8?q?=20=EB=8C=80=EC=83=81=20=EB=8B=A8=EC=B2=B4=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 32 +++++++-- .../dto/request/OneCharNameRequestDto.java | 13 ++++ .../service/NotificationService.java | 69 ++++++++++++++++++- .../user/repository/UserRepository.java | 8 +++ 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 codin-core/src/main/java/inu/codin/codin/domain/notification/dto/request/OneCharNameRequestDto.java diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java index ea0dff79..64c3b705 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java @@ -2,15 +2,17 @@ import inu.codin.codin.common.response.ListResponse; import inu.codin.codin.common.response.SingleResponse; +import inu.codin.codin.domain.notification.dto.request.OneCharNameRequestDto; import inu.codin.codin.domain.notification.service.NotificationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +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.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; @RestController @RequestMapping("/notification") @@ -38,4 +40,26 @@ public ResponseEntity> readNotification(@PathVariable String n .body(new SingleResponse<>(200, "알림 읽기 완료", null)); } + @Operation(summary = "이름 1글자 대상 1회성 알림 발송 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("/onceCharName") + public ResponseEntity> sendNameFix( + @RequestBody @Valid OneCharNameRequestDto request) { + + int sent = notificationService.sendOneCharNameFix(request); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "1회성 알림 발송 완료", Map.of("sent", sent))); + } + + @Operation(summary = "이름 1글자 대상 1회성 알림 발송 테스트 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("/test") + public ResponseEntity> sendNameFixTest( + @RequestBody @Valid OneCharNameRequestDto request) { + + int sent = notificationService.sendOneCharNameFixTest(request); + return ResponseEntity.ok() + .body(new SingleResponse<>(200, "1회성 알림 발송 완료", Map.of("sent", sent))); + } + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/request/OneCharNameRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/request/OneCharNameRequestDto.java new file mode 100644 index 00000000..a525d320 --- /dev/null +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/dto/request/OneCharNameRequestDto.java @@ -0,0 +1,13 @@ +package inu.codin.codin.domain.notification.dto.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class OneCharNameRequestDto { + @NotBlank(message = "알림 제목은 필수입니다.") + private String title; + + @NotBlank(message = "알림 내용은 필수입니다.") + private String body; +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index ea21c58e..8a67217b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -3,6 +3,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; +import inu.codin.codin.domain.notification.dto.request.OneCharNameRequestDto; import inu.codin.codin.domain.notification.dto.response.NotificationListResponseDto; import inu.codin.codin.domain.notification.entity.NotificationEntity; import inu.codin.codin.domain.notification.repository.NotificationRepository; @@ -13,6 +14,7 @@ import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.repository.PostRepository; +import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.fcm.dto.FcmMessageTopicDto; import inu.codin.codin.infra.fcm.dto.FcmMessageUserDto; @@ -111,7 +113,12 @@ private void saveNotificationLog(FcmMessageUserDto msgDto, Map d .userId(msgDto.getUserId()) .title(msgDto.getTitle()) .message(msgDto.getBody()) - .targetId(new ObjectId(data.get("id"))) + // id가 존재하고 비어있지 않으면 ObjectId로 변환, 아니면 null + .targetId( + (data != null && data.get("id") != null && !data.get("id").isBlank()) + ? new ObjectId(data.get("id")) + : null + ) .type("push") .priority("high") .build(); @@ -212,4 +219,64 @@ public List getNotification() { .map(NotificationListResponseDto::of) .toList(); } + + public int sendOneCharNameFix(OneCharNameRequestDto oneCharNameRequestDto) { + final String title = oneCharNameRequestDto.getTitle().trim(); + final String body = oneCharNameRequestDto.getBody().trim(); + + List targets = userRepository.findActiveUsersWithOneCharName(); + if (targets.isEmpty()) { + log.info("[이름 1글자 알림] 대상 없음"); + return 0; + } + + Map data = new HashMap<>(); + data.put("route", "/mypage/edit"); // 클릭 시 이동 경로(프론트에서 처리) + + int success = 0; + for (UserEntity u : targets) { + try { + sendFcmMessageToUser(title, body, data, u.get_id()); + success++; + } catch (Exception e) { + log.info("[이름 1글자 알림] 전송 실패 userId={}", u.get_id(), e); + } + } + log.info("[이름 1글자 알림] 전송 완료: total={}, success={}", targets.size(), success); + return success; + } + + public int sendOneCharNameFixTest(OneCharNameRequestDto oneCharNameRequestDto) { + final String title = oneCharNameRequestDto.getTitle().trim(); + final String body = oneCharNameRequestDto.getBody().trim(); + + //이름 1글자 유저 (로그용) + List oneCharTargets = userRepository.findActiveUsersWithOneCharName(); + log.info("[이름 1글자 알림 테스트] 1글자 이름 유저 수: {}", oneCharTargets.size()); + for (UserEntity u : oneCharTargets) { + log.info(" - 대상 후보 (한글자): name={}, userId={}", u.getName(), u.get_id()); + } + + // 실제 발송 대상: ADMIN + List adminTargets = userRepository.findActiveAdmins(); + if (adminTargets.isEmpty()) { + log.info("[이름 1글자 알림 테스트] ADMIN 대상 없음"); + return 0; + } + + Map data = new HashMap<>(); + data.put("route", "/mypage/edit"); // 클릭 시 이동 경로(프론트에서 처리) + + int success = 0; + for (UserEntity u : adminTargets) { + try { + sendFcmMessageToUser(title, body, data, u.get_id()); + success++; + } catch (Exception e) { + log.info("[이름 1글자 알림] 전송 실패 userId={}", u.get_id(), e); + } + } + log.info("[이름 1글자 알림] 전송 완료: total={}, success={}", adminTargets.size(), success); + return success; + } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java index e65c0b34..58d83bc4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/repository/UserRepository.java @@ -30,4 +30,12 @@ public interface UserRepository extends MongoRepository { //정지 종료일(suspensionEndDate)이 현재 날짜보다 이전($lt) @Query("{'status': 'SUSPENDED', 'totalSuspensionEndDate': { $lt: ?0 }}") List findSuspendedUsers(LocalDateTime now); + + // 이름이 정확히 1글자(한글/영문), role=USER, status=ACTIVE + @Query("{ 'role': 'USER', 'status': 'ACTIVE', 'name': { $regex: '^[A-Za-z가-힣]$' } }") + List findActiveUsersWithOneCharName(); + + @Query("{ 'role': 'ADMIN', 'status': 'ACTIVE' }") + List findActiveAdmins(); + } From 221e51f1998567a2cf8e3732ad4b74305b5e4b3b Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 17 Oct 2025 01:08:45 +0900 Subject: [PATCH 0990/1002] test: add UserRepository query test --- .../domain/user/UserRepositoryQueryTest.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 codin-core/src/test/java/inu/codin/codin/domain/user/UserRepositoryQueryTest.java diff --git a/codin-core/src/test/java/inu/codin/codin/domain/user/UserRepositoryQueryTest.java b/codin-core/src/test/java/inu/codin/codin/domain/user/UserRepositoryQueryTest.java new file mode 100644 index 00000000..086c8028 --- /dev/null +++ b/codin-core/src/test/java/inu/codin/codin/domain/user/UserRepositoryQueryTest.java @@ -0,0 +1,118 @@ +package inu.codin.codin.domain.user; + +import inu.codin.codin.domain.user.entity.UserEntity; +import inu.codin.codin.domain.user.entity.UserRole; +import inu.codin.codin.domain.user.entity.UserStatus; +import inu.codin.codin.domain.user.repository.UserRepository; +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.data.mongo.DataMongoTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@Testcontainers +@DataMongoTest +class UserRepositoryQueryTest { + + @Container + static MongoDBContainer mongo = new MongoDBContainer("mongo:7.0"); + + @DynamicPropertySource + static void mongoProps(DynamicPropertyRegistry r) { + r.add("spring.data.mongodb.uri", mongo::getReplicaSetUrl); + // 컬렉션/DB 이름은 엔티티/설정에 따라 자동 + } + + @Autowired + UserRepository userRepository; + + @BeforeEach + void setUp() { + userRepository.deleteAll(); + + // 매칭되어야 하는 케이스 (USER + ACTIVE + 이름 1글자) + userRepository.save(UserEntity.builder() + .email("user1@inu.ac.kr") + .name("김") // 1글자 한글 + .nickname("u1") + .role(UserRole.USER) + .status(UserStatus.ACTIVE) + .build()); + + userRepository.save(UserEntity.builder() + .email("user2@inu.ac.kr") + .name("A") // 1글자 영문 + .nickname("u2") + .role(UserRole.USER) + .status(UserStatus.ACTIVE) + .build()); + + // 매칭되면 안 되는 케이스들 + userRepository.save(UserEntity.builder() + .email("user3@inu.ac.kr") + .name("홍 길동") // 공백 포함 (정규식 불일치) + .nickname("u3") + .role(UserRole.USER) + .status(UserStatus.ACTIVE) + .build()); + + userRepository.save(UserEntity.builder() + .email("user4@inu.ac.kr") + .name("AB") // 2글자 + .nickname("u4") + .role(UserRole.USER) + .status(UserStatus.ACTIVE) + .build()); + + userRepository.save(UserEntity.builder() + .email("user5@inu.ac.kr") + .name("김") // 1글자지만 INACTIVE + .nickname("u5") + .role(UserRole.USER) + .status(UserStatus.SUSPENDED) // 또는 INACTIVE + .build()); + + // ADMIN들 + userRepository.save(UserEntity.builder() + .email("admin1@inu.ac.kr") + .name("관리자") + .nickname("a1") + .role(UserRole.ADMIN) + .status(UserStatus.ACTIVE) + .build()); + + userRepository.save(UserEntity.builder() + .email("admin2@inu.ac.kr") + .name("Admin") + .nickname("a2") + .role(UserRole.ADMIN) + .status(UserStatus.ACTIVE) + .build()); + } + + @Test + void findActiveUsersWithOneCharName_정상동작() { + List result = userRepository.findActiveUsersWithOneCharName(); + // user1(김), user2(A)만 매칭되어야 함 -> 총 2명 + assertThat(result).extracting("email") + .containsExactlyInAnyOrder("user1@inu.ac.kr", "user2@inu.ac.kr"); + assertThat(result).hasSize(2); + } + + @Test + void findActiveAdmins_정상동작() { + List result = userRepository.findActiveAdmins(); + assertThat(result).extracting("email") + .containsExactlyInAnyOrder("admin1@inu.ac.kr", "admin2@inu.ac.kr"); + assertThat(result).hasSize(2); + } +} \ No newline at end of file From 4b377d99c2ac50d9e2e15779db73ce1d2012ef62 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Fri, 17 Oct 2025 13:58:01 +0900 Subject: [PATCH 0991/1002] =?UTF-8?q?chore:=20OAuth2=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20attribute=20=EA=B0=92=20=ED=99=95=EC=9D=B8=EC=9A=A9?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/oauth2/GoogleAuthService.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index 51e75860..f339fb01 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -32,15 +32,21 @@ public GoogleAuthService(UserRepository userRepository, S3Service s3Service, Jwt public AuthResultStatus oauthLogin(OAuth2User oAuth2User, HttpServletResponse response) { // Google에서는 email, family_name, given_name 등 제공됨. Map attributes = oAuth2User.getAttributes(); + log.info("OAuth2User attributes: {}", attributes); + String email = (String) attributes.get("email"); - String name = (String) attributes.get("family_name"); - if (name == null || name.isEmpty()) { - name = (String) attributes.get("name"); - } - String department = (String) attributes.get("given_name"); - if (department == null) { - department = ""; - } + String familyName = (String) attributes.get("family_name"); + String givenName = (String) attributes.get("given_name"); + String fullName = (String) attributes.get("name"); + + String name = (familyName != null && !familyName.isEmpty()) + ? familyName + : (fullName != null ? fullName : ""); + + String department = (givenName != null) ? givenName : ""; + + log.info("OAuth2 login parsed values -> email={}, family_name={}, given_name={}, name={}, department={}", + email, familyName, givenName, fullName, department); log.info("OAuth2 login: email={}, name={}, department={}", email, name, department); From a7d8bd900211a466fd692b2a2a27e1b0a4e3211e Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 2 Dec 2025 14:42:01 +0900 Subject: [PATCH 0992/1002] feat: add name field to UserTicketingParticipationInfoUpdateRequest --- .../codin/common/security/util/SecurityUtils.java | 2 -- .../codin/domain/user/controller/UserController.java | 2 +- .../user/dto/request/UserNameUpdateRequestDto.java | 2 +- .../UserTicketingParticipationInfoUpdateRequest.java | 10 ++++++++++ .../inu/codin/codin/domain/user/entity/UserEntity.java | 1 + .../codin/codin/domain/user/service/UserService.java | 1 + 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java index 977514de..5171b8f6 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java @@ -23,9 +23,7 @@ public class SecurityUtils { * @throws JwtException 인증 정보가 없는 경우 예외 발생 */ public static ObjectId getCurrentUserId() { - log.info("getCurrentUserId."); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - log.info("auth={} / principalClass={}", authentication, (authentication!=null ? authentication.getPrincipal().getClass() : null)); if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { throw new JwtException(SecurityErrorCode.ACCESS_DENIED); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 4dac0266..9fb11b5b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -129,7 +129,7 @@ public ResponseEntity> ge ) @PutMapping("/ticketing-participation") public ResponseEntity> updateUserTicketingParticipationInfo( - @RequestBody UserTicketingParticipationInfoUpdateRequest updateRequest + @Valid @RequestBody UserTicketingParticipationInfoUpdateRequest updateRequest ) { return ResponseEntity.ok().body(new SingleResponse<>(200, "유저 티켓팅 수령 정보 수정 완료", userService.updateUserTicketingParticipationInfo(updateRequest))); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java index e212bda9..0ad5c370 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserNameUpdateRequestDto.java @@ -12,7 +12,7 @@ public class UserNameUpdateRequestDto { @Schema(description = "이름", example = "횃불이") @NotBlank(message = "이름은 비어 있을 수 없습니다.") - @Size(min = 1, max = 10, message = "이름은 1~10자여야 합니다.") + @Size(min = 2, max = 10, message = "이름은 2~10자여야 합니다.") private String name; @ConstructorProperties({"name"}) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java index 070267c9..85fa4360 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java @@ -2,6 +2,8 @@ import inu.codin.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Getter; @@ -10,7 +12,15 @@ @Schema(description = "유저 티켓팅 수령자 정보 Update Dto") public class UserTicketingParticipationInfoUpdateRequest { + @Schema(description = "학과", example = "컴퓨터공학부") private Department department; + + @Schema(description = "학번", example = "2025123456") private String studentId; + @Schema(description = "이름", example = "횃불이") + @NotBlank(message = "이름은 비어 있을 수 없습니다.") + @Size(min = 2, max = 10, message = "이름은 2~10자여야 합니다.") + private String name; + } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index 2914f8f4..bb441c74 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -112,5 +112,6 @@ public void updateTotalSuspensionEndDate(LocalDateTime totalSuspensionEndDate){ public void updateParticipationInfo(UserTicketingParticipationInfoUpdateRequest updateRequest) { this.studentId = updateRequest.getStudentId(); this.department = updateRequest.getDepartment(); + this.name = updateRequest.getName(); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 3347a5cc..76dd912f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -237,6 +237,7 @@ public UserTicketingParticipationInfoResponse updateUserTicketingParticipationIn userEntity.updateParticipationInfo(updateRequest); userEntity = userRepository.save(userEntity); + log.info("User Ticketing Participation Update: Email: {}, Name:{}", userEntity.getEmail(),userEntity.getName()); return UserTicketingParticipationInfoResponse.of(userEntity); } From 0f5f05e713329b2a83b9412d47d1804c42c5fefd Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 16 Dec 2025 18:14:25 +0900 Subject: [PATCH 0993/1002] chore : add codin-auth gradle --- codin-auth/README.md | 0 codin-auth/build.gradle | 7 +++++++ settings.gradle | 1 + 3 files changed, 8 insertions(+) create mode 100644 codin-auth/README.md create mode 100644 codin-auth/build.gradle diff --git a/codin-auth/README.md b/codin-auth/README.md new file mode 100644 index 00000000..e69de29b diff --git a/codin-auth/build.gradle b/codin-auth/build.gradle new file mode 100644 index 00000000..23dab9b0 --- /dev/null +++ b/codin-auth/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'java' +} + +dependencies { + //todo : 의존성 추가 +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 191265be..3129a640 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'codin-backend-repo' include 'codin-core' +include 'codin-auth' include 'codin-ticketing-api' include 'codin-ticketing-sse' include 'codin-lecture-api' \ No newline at end of file From af1461a29fc78d5b0fd9377883e00ee5426ffcda Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 16 Dec 2025 20:34:13 +0900 Subject: [PATCH 0994/1002] =?UTF-8?q?Phase=201:=20Security=20Module=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EA=B8=B0=EC=A1=B4:=20Authorization=20Serv?= =?UTF-8?q?er=20+=20Resource=20Server=20=EC=97=AD=ED=95=A0=EC=9D=84=20?= =?UTF-8?q?=EB=AA=A8=EB=91=90=20=EB=8B=B4=EB=8B=B9=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD:?= =?UTF-8?q?=20Resource=20Server=20=EC=A0=84=EC=9A=A9=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20(codin-security=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88)=20OAuth2=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=8F=20=ED=86=A0=ED=81=B0=20=EB=B0=9C=EA=B8=89=EC=9D=80=20?= =?UTF-8?q?=EB=B3=84=EB=8F=84=EC=9D=98=20codin-auth=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/build.gradle | 15 +- .../codin/common/config/SecurityConfig.java | 251 ++---------------- .../codin/common/config/WebSocketConfig.java | 2 +- .../exception/GlobalExceptionHandler.java | 4 +- .../codin/common/response/ListResponse.java | 1 + .../codin/common/response/SingleResponse.java | 1 + .../security/controller/AuthController.java | 2 +- .../common/security/jwt/JwtTokenProvider.java | 4 +- .../security/service/AuthCommonService.java | 1 + .../common/security/service/JwtService.java | 244 ----------------- .../service/oauth2/AbstractAuthService.java | 16 +- .../service/oauth2/AppleAuthService.java | 2 +- .../service/oauth2/GoogleAuthService.java | 2 +- .../util/CustomAuthenticationEntryPoint.java | 2 +- .../util/OAuth2LoginFailureHandler.java | 4 +- .../common/security/util/SecurityUtils.java | 74 ------ .../stomp/HttpHandShakeInterceptor.java | 6 +- .../domain/block/service/BlockService.java | 2 +- .../board/notice/service/NoticeService.java | 2 +- .../voice/dto/VoiceBoxDetailResponse.java | 2 +- .../board/voice/service/VoiceService.java | 2 +- .../chatroom/service/ChatRoomService.java | 2 +- .../chatting/service/ChattingService.java | 2 +- .../domain/review/service/ReviewService.java | 2 +- .../domain/like/service/LikeService.java | 2 +- .../service/NotificationService.java | 2 +- .../reply/service/ReplyCommandService.java | 2 +- .../reply/service/ReplyQueryService.java | 2 +- .../service/CommentCommandService.java | 2 +- .../comment/service/CommentQueryService.java | 2 +- .../poll/service/PollCommandService.java | 2 +- .../domain/post/security/OwnershipPolicy.java | 2 +- .../post/service/PostCommandService.java | 6 +- .../domain/post/service/PostDtoAssembler.java | 2 +- .../domain/post/service/PostQueryService.java | 2 +- .../domain/report/service/ReportService.java | 2 +- .../domain/scrap/service/ScrapService.java | 2 +- .../domain/user/service/UserService.java | 4 +- .../codin/infra/fcm/service/FcmService.java | 2 +- .../block/service/BlockServiceTest.java | 2 +- .../domain/post/PostCommandServiceTest.java | 4 +- .../domain/post/PostDtoAssemblerTest.java | 2 +- .../domain/post/PostQueryServiceTest.java | 2 +- .../comment/CommentCommandServiceTest.java | 2 +- .../comment/CommentQueryServiceTest.java | 2 +- .../reply/ReplyCommandServiceTest.java | 2 +- .../comment/reply/ReplyQueryServiceTest.java | 2 +- .../domain/poll/PollCommandServiceTest.java | 2 +- codin-security/build.gradle | 62 +++++ .../security/config}/PermitAllProperties.java | 2 +- .../security/config}/PublicApiProperties.java | 2 +- .../config/ResourceServerSecurityConfig.java | 198 ++++++++++++++ .../security/exception/JwtException.java | 3 +- .../security/exception/SecurityErrorCode.java | 2 +- .../filter/ExceptionHandlerFilter.java | 6 +- .../filter/JwtAuthenticationFilter.java | 48 ++-- .../filter/TokenValidationFilter.java | 8 +- .../security/jwt/JwtAuthenticationToken.java | 2 +- .../security/jwt/JwtTokenValidator.java | 12 +- .../inu/codin}/security/jwt/JwtUtils.java | 2 +- .../codin}/security/jwt/TokenUserDetails.java | 2 +- .../security}/response/CommonResponse.java | 2 +- .../security}/response/ExceptionResponse.java | 2 +- .../codin/security/service/JwtService.java | 105 ++++++++ .../codin/security/util/SecurityUtils.java | 105 ++++++++ .../inu/codin}/security/util/TokenUtil.java | 2 +- settings.gradle | 1 + 67 files changed, 618 insertions(+), 649 deletions(-) delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java delete mode 100644 codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java create mode 100644 codin-security/build.gradle rename {codin-core/src/main/java/inu/codin/codin/common/dto => codin-security/src/main/java/inu/codin/security/config}/PermitAllProperties.java (90%) rename {codin-core/src/main/java/inu/codin/codin/common/dto => codin-security/src/main/java/inu/codin/security/config}/PublicApiProperties.java (90%) create mode 100644 codin-security/src/main/java/inu/codin/security/config/ResourceServerSecurityConfig.java rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin}/security/exception/JwtException.java (88%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin}/security/exception/SecurityErrorCode.java (93%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin}/security/filter/ExceptionHandlerFilter.java (92%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin}/security/filter/JwtAuthenticationFilter.java (60%) rename {codin-ticketing-api/src/main/java/inu/codin/codinticketingapi => codin-security/src/main/java/inu/codin}/security/filter/TokenValidationFilter.java (91%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin}/security/jwt/JwtAuthenticationToken.java (94%) rename {codin-ticketing-api/src/main/java/inu/codin/codinticketingapi => codin-security/src/main/java/inu/codin}/security/jwt/JwtTokenValidator.java (83%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin}/security/jwt/JwtUtils.java (97%) rename {codin-ticketing-api/src/main/java/inu/codin/codinticketingapi => codin-security/src/main/java/inu/codin}/security/jwt/TokenUserDetails.java (97%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin/security}/response/CommonResponse.java (87%) rename {codin-core/src/main/java/inu/codin/codin/common => codin-security/src/main/java/inu/codin/security}/response/ExceptionResponse.java (82%) create mode 100644 codin-security/src/main/java/inu/codin/security/service/JwtService.java create mode 100644 codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java rename {codin-ticketing-api/src/main/java/inu/codin/codinticketingapi => codin-security/src/main/java/inu/codin}/security/util/TokenUtil.java (96%) diff --git a/codin-core/build.gradle b/codin-core/build.gradle index 10d82194..60f84ad5 100644 --- a/codin-core/build.gradle +++ b/codin-core/build.gradle @@ -33,14 +33,17 @@ dependencyManagement { } } dependencies { + // Security - codin-security 공통 모듈 사용 + implementation project(':codin-security') + // FCM implementation 'com.google.firebase:firebase-admin:7.3.0' - // JWT Token - implementation 'io.jsonwebtoken:jjwt-api:0.11.2' - implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' - implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' + // JWT Token - codin-security에서 제공되므로 제거 가능 + // implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + // implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' + // implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' + // Security - codin-security에서 제공되므로 제거 가능 + // implementation 'org.springframework.boot:spring-boot-starter-security' // Email implementation 'org.springframework.boot:spring-boot-starter-mail' // Thymeleaf(for email service) diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index 5d3cafbb..ecb01075 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -1,228 +1,27 @@ package inu.codin.codin.common.config; - -import inu.codin.codin.common.dto.PermitAllProperties; -import inu.codin.codin.common.dto.PublicApiProperties; -import inu.codin.codin.common.security.filter.ExceptionHandlerFilter; -import inu.codin.codin.common.security.filter.JwtAuthenticationFilter; -import inu.codin.codin.common.security.service.JwtService; -import inu.codin.codin.common.security.service.oauth2.AppleOAuth2UserService; -import inu.codin.codin.common.security.service.oauth2.CustomOAuth2UserService; -import inu.codin.codin.common.security.util.*; -import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; -import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.web.context.request.RequestContextListener; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; - -@Slf4j -@Configuration -@EnableWebSecurity -@RequiredArgsConstructor -public class SecurityConfig { - - private final UserDetailsService userDetailsService; - private final JwtService jwtService; - private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; - private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; - private final CustomOAuth2UserService customOAuth2UserService; - private final PermitAllProperties permitAllProperties; - private final PublicApiProperties publicApiProperties; - - private final AppleOAuth2UserService appleOAuth2UserService; - private final ClientRegistrationRepository clientRegistrationRepository; - private final CustomOAuth2AccessTokenResponseClient customOAuth2AccessTokenResponseClient; - - private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; - - @Value("${server.domain}") - private String BASEURL; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - - http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) // cors 설정 - .csrf(CsrfConfigurer::disable) // csrf 비활성화 - .formLogin(FormLoginConfigurer::disable) // form login 비활성화 - .sessionManagement(sessionManagement -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용하지 않음 - ) - // authorizeHttpRequests 메서드를 통해 요청에 대한 권한 설정 - .authorizeHttpRequests((authorizeHttpRequests) -> - authorizeHttpRequests - .requestMatchers(permitAllProperties.getUrls().toArray(new String[0])).permitAll() - .requestMatchers(publicApiProperties.getUrls().toArray(new String[0])).permitAll() - .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") - .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") - .requestMatchers(USER_AUTH_PATHS).hasRole("USER") - .anyRequest().hasRole("USER") - ) - //Security 내부 인증 실패 처리 => access_token 없는 경우 - .exceptionHandling(exception -> exception - .authenticationEntryPoint(customAuthenticationEntryPoint) - ) - //oauth2 로그인 설정 추가 - .oauth2Login(oauth2 -> oauth2 - // Apple 클라이언트에 대해 커스텀 토큰 응답 클라이언트 적용 - .tokenEndpoint(token -> token - .accessTokenResponseClient(customOAuth2AccessTokenResponseClient) - ) - .authorizationEndpoint(authorization -> authorization - //쿠키를 사용해 OAuth의 정보를 가져오고 저장 - .authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()) - .authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository)) - ) -// .redirectionEndpoint(redirection -> redirection -// .baseUri("/callback/apple") // apple 콜백 URI를 설정추가. -// ) - .userInfoEndpoint(userInfo -> userInfo - .userService(delegatingOAuth2UserService()) - ) - - .successHandler(oAuth2LoginSuccessHandler) - .failureHandler(oAuth2LoginFailureHandler) - ) - // Swagger 접근 시 httpBasic 인증 사용 -// .httpBasic(Customizer.withDefaults()) - // JwtAuthenticationFilter 추가 - .addFilterBefore( - new JwtAuthenticationFilter(jwtService, permitAllProperties, publicApiProperties), - UsernamePasswordAuthenticationFilter.class - ) - // 예외 처리 필터 추가 - .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class); - return http.build(); - } - - - @Bean - public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository() { - return new OAuth2AuthorizationRequestBasedOnCookieRepository(); - } - - private OAuth2UserService delegatingOAuth2UserService() { - return userRequest -> { - String registrationId = userRequest.getClientRegistration().getRegistrationId(); - log.info("OAuth2 registrationId: {}", registrationId); - if ("apple".equals(registrationId)) { - log.info("apple Login loadUser : {}", userRequest); - return appleOAuth2UserService.loadUser(userRequest); - } else if ("google".equals(registrationId)) { - log.info("google Login loadUser"); - return customOAuth2UserService.loadUser(userRequest); - } else { - throw new OAuth2AuthenticationException(new OAuth2Error("unsupported_provider"), - "지원되지 않는 공급자입니다: " + registrationId); - } - }; - } - - - @Bean - public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { - AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); - authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); - return authenticationManagerBuilder.build(); - } - - @Bean - public RequestContextListener requestContextListener() { - return new RequestContextListener(); - } - - @Bean - public RoleHierarchy roleHierarchy() { - return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_MANAGER > ROLE_USER"); - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - public AuthorizationRequestRepository authorizationRequestRepository() { - return new HttpSessionOAuth2AuthorizationRequestRepository(); - } - /** - * CORS 설정 - */ - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); - config.setAllowedOrigins(List.of( - "http://localhost:3000", - "http://localhost:8080", - BASEURL, - "https://front-end-peach-two.vercel.app", - "https://front-end-dun-mu.vercel.app", - "https://e876-2406-5900-1080-882f-b519-f7cf-62b3-4ba4.ngrok-free.app", - "http://e876-2406-5900-1080-882f-b519-f7cf-62b3-4ba4.ngrok-free.app", - "https://appleid.apple.com" - )); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); - config.setAllowedHeaders(List.of( - "Authorization", - "Content-Type", - "X-Requested-With", - "Accept", - "Cache-Control", - "X-Refresh-Token" - )); - config.setExposedHeaders(List.of("Authorization")); - config.setMaxAge(3600L); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; - } - - - // User 권한 URL - private static final String[] USER_AUTH_PATHS = { - "/v3/api/test2", - "/v3/api/test3", - }; - - // Admin 권한 URL - private static final String[] ADMIN_AUTH_PATHS = { - "/v3/api/test4", - }; - - // Manager 권한 URL - private static final String[] MANAGER_AUTH_PATHS = { - "/v3/api/test5", - }; -} +/* + * IMPORTANT: 이 SecurityConfig는 임시로 주석 처리됨 + * + * Phase 1에서 codin-security 모듈로 Resource Server 기능이 분리됨 + * Phase 2에서 codin-auth 모듈로 Authorization Server 기능을 분리할 예정 + * + * 현재 상태: + * - JWT 검증 기능 -> codin-security 모듈로 이동 완료 + * - OAuth2 로그인 기능 -> codin-core에 잠시 남아있음 (Phase 2에서 codin-auth로 이동 예정) + * + * 임시 조치: 전체 설정을 주석 처리하여 컴파일 에러 방지 + * codin-core는 이제 codin-security 모듈에 의존하여 JWT 검증 기능 사용 + */ + +// TODO: Phase 2에서 codin-auth로 Authorization Server 기능 이동 + +/* + * 기존 SecurityConfig의 역할: + * 1. Authorization Server - OAuth2 로그인 처리 (Google, Apple) + * 2. Resource Server - JWT 토큰 검증 및 권한 체크 + * + * 분리 후: + * 1. Authorization Server -> codin-auth 모듈 (Phase 2에서 구현) + * 2. Resource Server -> codin-security 모듈 (Phase 1에서 완료) + */ \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java index ed6205a6..a259e022 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebSocketConfig.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.config; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.service.JwtService; import inu.codin.codin.common.stomp.HttpHandShakeInterceptor; import inu.codin.codin.common.stomp.StompMessageProcessor; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 88d55c4b..608396e7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.exception; -import inu.codin.codin.common.response.ExceptionResponse; -import inu.codin.codin.common.security.exception.JwtException; +import inu.codin.security.response.ExceptionResponse; +import inu.codin.security.exception.JwtException; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.board.notice.exception.NoticeErrorCode; diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java index 1f2b1c11..51dfaa24 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.response; +import inu.codin.security.response.CommonResponse; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java b/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java index 32955bcc..f28341b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java @@ -1,5 +1,6 @@ package inu.codin.codin.common.response; +import inu.codin.security.response.CommonResponse; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 8fecb571..8c99c4bb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -4,7 +4,7 @@ import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthCommonService; import inu.codin.codin.common.security.service.AuthSessionService; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java index 2cee57fe..87fcbd90 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtTokenProvider.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.jwt; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.security.exception.JwtException; +import inu.codin.security.exception.SecurityErrorCode; import inu.codin.codin.domain.user.security.CustomUserDetails; import inu.codin.codin.infra.redis.RedisStorageService; import io.jsonwebtoken.Claims; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 2bf3d76b..3f6861d5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -7,6 +7,7 @@ import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; import inu.codin.codin.common.security.service.AbstractAuthService; +import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; import jakarta.servlet.http.HttpServletResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java deleted file mode 100644 index 18054bc2..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/JwtService.java +++ /dev/null @@ -1,244 +0,0 @@ -package inu.codin.codin.common.security.service; - -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.common.security.jwt.JwtAuthenticationToken; -import inu.codin.codin.common.security.jwt.JwtTokenProvider; -import inu.codin.codin.common.security.jwt.JwtUtils; -import inu.codin.codin.domain.user.security.CustomUserDetailsService; -import inu.codin.codin.infra.redis.RedisStorageService; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.MalformedJwtException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -/** - * JWT 토큰 관련 비즈니스 로직을 처리하는 서비스 - * - */ -@Service -@Slf4j -@RequiredArgsConstructor -public class JwtService { - - private final RedisStorageService redisStorageService; - private final JwtTokenProvider jwtTokenProvider; - private final JwtUtils jwtUtils; - private final CustomUserDetailsService userDetailsService; - - @Value("${server.domain}") - private String BASEURL; - - private final String REFRESH_TOKEN = "x-refresh-token"; - private final String ACCESS_TOKEN = "Authorization"; - private final String ACCESS_TOKEN_PREFIX = "Bearer "; - - /** - * 최초 로그인 시 Access Token, Refresh Token 발급 - * @param response - */ - public void createToken(HttpServletResponse response) { - createBothToken(response); - log.info("[createToken] Access Token, Refresh Token 발급 완료"); - } - - /** - * Refresh Token을 이용하여 Access Token, Refresh Token 재발급 - * @param request - * @param response - */ - public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletResponse response) { - String refreshToken = getRefreshToken(request); - if (refreshToken == null) { - log.error("[reissueToken] Refresh Token이 없습니다."); - throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 없습니다."); - } - - String username = jwtTokenProvider.getUsername(refreshToken); - validateRefreshTokenWithAccessToken(request, username); - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - reissueToken(refreshToken, response); - } - - //만료된 accessToken과 username을 비교 - private void validateRefreshTokenWithAccessToken(HttpServletRequest request, String username) { - String accessToken = getAccessToken(request); - String accessUsername; - try { - accessUsername = jwtTokenProvider.getUsername(accessToken); - } catch (ExpiredJwtException e) { - Claims expiredClaims = e.getClaims(); - accessUsername = expiredClaims.getSubject(); - } - if (!accessUsername.equals(username)) { - log.error("[reissueToken] Access Token의 username과 Refresh Token가 일치하지 않습니다."); - throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Access Token의 username과 Refresh Token가 일치하지 않습니다."); - } - } - - /** - * Refresh Token을 이용하여 Access Token, Refresh Token 재발급 - * @param refreshToken - * @param response - */ - public void reissueToken(String refreshToken, HttpServletResponse response) { - if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { - log.error("[reissueToken] Refresh Token이 유효하지 않습니다. : {}", refreshToken); - throw new JwtException(SecurityErrorCode.INVALID_TOKEN, "Refresh Token이 유효하지 않습니다."); - } - - createBothToken(response); - log.info("[reissueToken] Access Token, Refresh Token 재발급 완료"); - } - - /** - * Access Token, Refresh Token 생성 - */ - private void createBothToken(HttpServletResponse response) { - // 새로운 Access Token 발급 - var authentication = SecurityContextHolder.getContext().getAuthentication(); - JwtTokenProvider.TokenDto newToken = jwtTokenProvider.createToken(authentication); - - // Authorization 헤더에 Access Token 추가 - response.setHeader(ACCESS_TOKEN, ACCESS_TOKEN_PREFIX + newToken.getAccessToken()); - - String domain = BASEURL.replaceFirst("https?://", "").split(":")[0]; - - // todo: x-access-token 쿠키에 Access Token 추가 - 추후 제거 - Cookie newAccessToken = new Cookie("x-access-token", newToken.getAccessToken()); - newAccessToken.setHttpOnly(true); - newAccessToken.setSecure(true); - newAccessToken.setPath("/"); - newAccessToken.setMaxAge(10 * 24 * 60 * 60); // 10일 - newAccessToken.setDomain(domain); - newAccessToken.setAttribute("SameSite", "None"); - response.addCookie(newAccessToken); - - // x-rfresh-token 쿠키에 Refresh Token 추가 - Cookie newRefreshToken = new Cookie(REFRESH_TOKEN, newToken.getRefreshToken()); - newRefreshToken.setHttpOnly(true); - newRefreshToken.setSecure(true); - newRefreshToken.setPath("/"); - newRefreshToken.setMaxAge(10 * 24 * 60 * 60); // 10일 - newRefreshToken.setDomain(domain); - newRefreshToken.setAttribute("SameSite", "None"); - response.addCookie(newRefreshToken); - - log.info("[createBothToken] Access Token, Refresh Token 발급 완료, email = {}, Access: {}", authentication.getName(), newToken.getAccessToken()); - } - - - /** - * 로그아웃 - - * Access,Refresh Token 제거/ 서버측 RT 삭제 - */ - public void deleteToken(HttpServletResponse response) { - // 어차피 JwtAuthenticationFilter 단에서 토큰을 검증하여 인증을 처리하므로 - // SecurityContext에 Authentication 객체가 없는 경우는 없다. - var authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.getName()!=null){ - redisStorageService.deleteRefreshToken(authentication.getName()); - deleteCookie(response); - log.info("[deleteToken] Refresh Token 삭제 완료"); - } - } - - private void deleteCookie(HttpServletResponse response) { - - String domain = BASEURL.replaceFirst("https?://", "").split(":")[0]; - log.info("[deleteCookie] BASEURL={}, domain={}", BASEURL, domain); - - Cookie refreshCookie = new Cookie(REFRESH_TOKEN, ""); - refreshCookie.setHttpOnly(true); - refreshCookie.setSecure(true); - refreshCookie.setPath("/"); - refreshCookie.setDomain(domain); - refreshCookie.setMaxAge(0); - refreshCookie.setAttribute("SameSite","None"); - response.addCookie(refreshCookie); - - log.info("[deleteCookie] refreshCookie info => name={}, domain={}, path={}, secure={}, httpOnly={}, maxAge={}, sameSite=None", - refreshCookie.getName(), refreshCookie.getDomain(), refreshCookie.getPath(), - refreshCookie.getSecure(), refreshCookie.isHttpOnly(), refreshCookie.getMaxAge()); - - Cookie accessCookie = new Cookie("x-access-token", ""); - accessCookie.setHttpOnly(true); - accessCookie.setSecure(true); - accessCookie.setPath("/"); - accessCookie.setDomain(domain); - accessCookie.setMaxAge(0); - refreshCookie.setAttribute("SameSite","None"); - response.addCookie(accessCookie); - - log.info("[deleteCookie] accessCookie info => name={}, domain={}, path={}, secure={}, httpOnly={}, maxAge={}, sameSite=None", - accessCookie.getName(), accessCookie.getDomain(), accessCookie.getPath(), - accessCookie.getSecure(), accessCookie.isHttpOnly(), accessCookie.getMaxAge()); - - log.info("[deleteToken] Access/Refresh Cookie 삭제 완료"); - } - - public void setAuthentication(HttpServletRequest request){ - String accessToken = getAccessToken(request); - - // Access Token이 있는 경우 - if (accessToken != null) { - getUserDetailsAndSetAuthentication(accessToken); - } else { - SecurityContextHolder.clearContext(); - throw new MalformedJwtException("[Chatting] JWT를 찾을 수 없습니다."); - } - } - - public void getUserDetailsAndSetAuthentication(String token) { - jwtTokenProvider.validateToken(token); - String email = jwtTokenProvider.getUsername(token); - UserDetails userDetails = userDetailsService.loadUserByUsername(email); - - // 토큰이 유효하고, SecurityContext에 Authentication 객체가 없는 경우 - if (userDetails != null) { - // Authentication 객체 생성 후 SecurityContext에 저장 (인증 완료) - JwtAuthenticationToken authentication = new JwtAuthenticationToken(userDetails, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } - - public String getAccessToken(HttpServletRequest request) { - String accessToken = jwtUtils.getAccessToken(request); - - if (!StringUtils.hasText(accessToken)) { - return null; - } - - if (!jwtTokenProvider.validType(accessToken, "access")) { - log.error("[getAccessToken] Access Token이 아닙니다."); - throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Access Token이 아닙니다."); - } - return accessToken; - } - - public String getRefreshToken(HttpServletRequest request) { - String refreshToken = jwtUtils.getRefreshToken(request); - if (!jwtTokenProvider.validType(refreshToken, "refresh")) { - log.error("[getRefreshToken] Refresh Token이 아닙니다."); - throw new JwtException(SecurityErrorCode.INVALID_TYPE, "Refresh Token이 아닙니다."); - } - return refreshToken; - } -} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java index 7b57afbc..acdc498e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java @@ -2,6 +2,7 @@ import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; +import inu.codin.security.service.JwtService; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,12 +20,15 @@ public abstract class AbstractAuthService { protected final UserDetailsService userDetailsService; protected void issueJwtToken(String identifier, HttpServletResponse response) { - jwtService.deleteToken(response); - UserDetails userDetails = userDetailsService.loadUserByUsername(identifier); - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authToken); - jwtService.createToken(response); + // TODO: Phase 2에서 codin-auth로 이동 예정 + // 토큰 발급 기능은 Authorization Server에서 담당 + // jwtService.deleteToken(response); + // UserDetails userDetails = userDetailsService.loadUserByUsername(identifier); + // UsernamePasswordAuthenticationToken authToken = + // new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + // SecurityContextHolder.getContext().setAuthentication(authToken); + // jwtService.createToken(response); + throw new UnsupportedOperationException("토큰 발급 기능은 Phase 2에서 codin-auth 모듈로 이동 예정"); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java index 380bc517..e6e7c26e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java @@ -4,7 +4,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; import inu.codin.codin.common.security.service.AbstractAuthService; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index f339fb01..37584223 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -4,7 +4,7 @@ import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; import inu.codin.codin.common.security.service.AbstractAuthService; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java index 342d6728..6020d5a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.util; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.security.response.ExceptionResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index f45f98b4..42f537db 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -1,8 +1,8 @@ package inu.codin.codin.common.security.util; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.response.ExceptionResponse; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.response.ExceptionResponse; +import inu.codin.security.service.JwtService; import inu.codin.codin.common.util.CookieUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java deleted file mode 100644 index 5171b8f6..00000000 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/SecurityUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -package inu.codin.codin.common.security.util; - -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.domain.user.entity.UserRole; -import inu.codin.codin.domain.user.security.CustomUserDetails; -import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * SecurityContext와 관련된 유틸리티 클래스. - */ -@Slf4j -public class SecurityUtils { - - /** - * 현재 인증된 사용자의 ID를 반환. - * - * @return 인증된 사용자의 ID - * @throws JwtException 인증 정보가 없는 경우 예외 발생 - */ - public static ObjectId getCurrentUserId() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { - throw new JwtException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getId(); - } - - - /** - * 현재 인증된 사용자의 ID를 반환 (nullable 안전 버전) - * - 인증이 없거나 익명이면 null 반환 - */ - public static ObjectId getCurrentUserIdOrNull() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !authentication.isAuthenticated() - || authentication instanceof AnonymousAuthenticationToken - || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { - return null; - } - - return userDetails.getId(); - } - - public static UserRole getCurrentUserRole(){ - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails userDetails)) { - throw new JwtException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getRole(); - } - - public static void validateUser(ObjectId id){ - ObjectId userId = SecurityUtils.getCurrentUserId(); - if (!id.equals(userId)) { - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "현재 유저에게 권한이 없습니다."); - } - } - - public static void validateOwners(ObjectId currentUserId, ObjectId ownerId) { - validateUser(currentUserId); - if (!ownerId.equals(currentUserId)) { - throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "본인 리소스가 아닙니다. "); - } - } -} diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java index 9c8e1c8a..998c3ffb 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/HttpHandShakeInterceptor.java @@ -1,8 +1,8 @@ package inu.codin.codin.common.stomp; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.exception.JwtException; +import inu.codin.security.exception.SecurityErrorCode; +import inu.codin.security.service.JwtService; import io.jsonwebtoken.MalformedJwtException; import lombok.RequiredArgsConstructor; import org.springframework.http.server.ServerHttpRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 0374cac5..36cde5b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.BlockErrorCode; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java index fc6fca1d..9fbbe7a7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.board.notice.service; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; import inu.codin.codin.domain.board.notice.dto.response.NoticeListResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java index 9876527b..b310b4a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.board.voice.entity.VoiceEntity; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java index 973781ba..2d249fa4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.board.voice.service; import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.board.voice.dto.VoiceBoxCreateRequest; import inu.codin.codin.domain.board.voice.dto.VoiceBoxDetailResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 793bcc07..807564b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 5c3af31a..02128219 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatting.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index f86c28cf..840f6fbc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.lecture.domain.review.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewListResposneDto; import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewPageResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 80257da9..89e608f3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.like.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.dto.LikeResponseType; import inu.codin.codin.domain.like.dto.LikedResponseDto; import inu.codin.codin.domain.like.dto.request.LikeRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 8a67217b..929ae6a0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.notification.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.notification.dto.request.OneCharNameRequestDto; import inu.codin.codin.domain.notification.dto.response.NotificationListResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index e735b8b3..141957c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index 5de87298..12de3251 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index 267ff399..fa19ed78 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index fbbc94db..8b019928 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index c6f57e02..860ed3fa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.domain.poll.service; import com.mongodb.client.result.UpdateResult; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java index b4fffd18..0accfa03 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.security; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index 4e572dc4..d3d356d6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.post.service; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.exception.SecurityErrorCode; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.exception.JwtException; +import inu.codin.security.exception.SecurityErrorCode; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java index 4b21f469..f53d1895 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 8cf1984e..3c4c0b60 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.domain.best.BestEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 0903201a..5c2f2675 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.report.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 38d1d15d..4bf2767a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.scrap.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 76dd912f..931b9163 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.user.service; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.service.JwtService; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.service.JwtService; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index eda13223..41481591 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -2,7 +2,7 @@ import com.google.firebase.messaging.*; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.notification.entity.NotificationPreference; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java index bfb5b7a3..6b328586 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.service; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.BlockErrorCode; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java index d36079e6..4e3d07b3 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.post; import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.codin.common.security.exception.JwtException; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.exception.JwtException; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.post.dto.request.*; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostCategory; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java index ed3fc973..b2d78728 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java index 9c855dd6..e1c1b5b4 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestService; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java index 4eb3f9b6..a8e69e0a 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java index 1dfe7b6f..e2017c1d 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java index 07642e6a..620ab0f9 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java index d24082f7..38aa7ca6 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java index d503c53a..0761f63b 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.poll; -import inu.codin.codin.common.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; diff --git a/codin-security/build.gradle b/codin-security/build.gradle new file mode 100644 index 00000000..0cf6ed1b --- /dev/null +++ b/codin-security/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'java-library' + id 'org.springframework.boot' version '3.2.0' + id 'io.spring.dependency-management' version '1.1.4' +} + +group = 'inu.codin' +version = '1.0.0' +sourceCompatibility = '17' + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + // Spring Boot Security + api 'org.springframework.boot:spring-boot-starter-security' + api 'org.springframework.boot:spring-boot-starter-web' + + // JWT + api 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // Configuration Properties + api 'org.springframework.boot:spring-boot-configuration-processor' + + // Jackson for JSON processing + api 'com.fasterxml.jackson.core:jackson-databind' + + // Lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + // Test Dependencies + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.mockito:mockito-core' + testImplementation 'org.assertj:assertj-core' +} + +tasks.named('test') { + useJUnitPlatform() +} + +// Disable Spring Boot plugin's jar task since this is a library +jar { + enabled = true + archiveClassifier = '' +} + +// Disable the bootJar task since this is a library +bootJar { + enabled = false +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java b/codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java rename to codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java index 7fdc58c2..74cfc1e4 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/dto/PermitAllProperties.java +++ b/codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.dto; +package inu.codin.security.config; import lombok.Getter; import lombok.Setter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java b/codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java similarity index 90% rename from codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java rename to codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java index 14b3d1e5..c02a0569 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/dto/PublicApiProperties.java +++ b/codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.dto; +package inu.codin.security.config; import lombok.Getter; import lombok.Setter; diff --git a/codin-security/src/main/java/inu/codin/security/config/ResourceServerSecurityConfig.java b/codin-security/src/main/java/inu/codin/security/config/ResourceServerSecurityConfig.java new file mode 100644 index 00000000..50420e37 --- /dev/null +++ b/codin-security/src/main/java/inu/codin/security/config/ResourceServerSecurityConfig.java @@ -0,0 +1,198 @@ +package inu.codin.security.config; + +/* + * SecurityConfig 리팩토링 이유: + * + * 기존: Authorization Server + Resource Server 역할을 모두 담당하는 단일 설정 + * - OAuth2 로그인 처리 (Google, Apple) + * - JWT 토큰 발급 + * - JWT 토큰 검증 + * - 권한 체크 + * + * 변경: Resource Server 전용 설정으로 분리 (codin-security 모듈) + * - JWT 토큰 검증만 담당 + * - 권한 체크 담당 + * - CORS 설정 담당 + * + * OAuth2 로그인 및 토큰 발급은 별도의 codin-auth 모듈로 분리 예정 + */ + +import inu.codin.security.filter.ExceptionHandlerFilter; +import inu.codin.security.filter.JwtAuthenticationFilter; +import inu.codin.security.service.JwtService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Slf4j +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class ResourceServerSecurityConfig { + + /* + * Resource Server에 필요한 의존성만 유지 + * - JwtService: JWT 토큰 검증용 + * - PermitAllProperties: 인증 불필요 경로 설정 + * - PublicApiProperties: 공개 API 경로 설정 + * + * 제거된 의존성들 (codin-auth로 이동 예정): + * - OAuth2LoginSuccessHandler, OAuth2LoginFailureHandler: OAuth2 로그인 처리 + * - CustomOAuth2UserService, AppleOAuth2UserService: OAuth2 사용자 정보 처리 + * - ClientRegistrationRepository: OAuth2 클라이언트 등록 정보 + * - CustomOAuth2AccessTokenResponseClient: OAuth2 토큰 응답 처리 + * - UserDetailsService: 사용자 상세 정보 서비스 + */ + private final JwtService jwtService; + private final PermitAllProperties permitAllProperties; + private final PublicApiProperties publicApiProperties; + + @Value("${server.domain:http://localhost:8080}") + private String BASEURL; + + /** + * Resource Server용 보안 필터 체인 + * + * 변경사항: + * 1. OAuth2 로그인 설정 제거 - Authorization Server 역할 제거 + * 2. AuthenticationEntryPoint 제거 - 기본 401 응답 사용 + * 3. JWT 검증 필터만 유지 - Resource Server 핵심 기능 + * 4. 무상태 세션 정책 유지 - JWT 기반 인증 + */ + @Bean + public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { + + http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // CORS 설정 유지 + .csrf(CsrfConfigurer::disable) // CSRF 비활성화 - JWT 사용으로 불필요 + .formLogin(FormLoginConfigurer::disable) // 폼 로그인 비활성화 - JWT 전용 + .httpBasic(httpBasic -> httpBasic.disable()) // HTTP Basic 비활성화 + .sessionManagement(sessionManagement -> sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 무상태 세션 - JWT 기반 + ) + // 요청별 권한 설정 - Resource Server의 핵심 기능 + .authorizeHttpRequests((authorizeHttpRequests) -> + authorizeHttpRequests + .requestMatchers(permitAllProperties.getUrls().toArray(new String[0])).permitAll() + .requestMatchers(publicApiProperties.getUrls().toArray(new String[0])).permitAll() + .requestMatchers(ADMIN_AUTH_PATHS).hasRole("ADMIN") + .requestMatchers(MANAGER_AUTH_PATHS).hasRole("MANAGER") + .requestMatchers(USER_AUTH_PATHS).hasRole("USER") + .anyRequest().hasRole("USER") + ) + /* + * OAuth2 로그인 설정 제거됨 - Authorization Server로 이동 + * 기존 .oauth2Login() 설정은 codin-auth 모듈에서 담당 + */ + + // JWT 인증 필터 추가 - Resource Server의 핵심 기능 + .addFilterBefore( + new JwtAuthenticationFilter(jwtService, permitAllProperties, publicApiProperties), + UsernamePasswordAuthenticationFilter.class + ) + // 예외 처리 필터 추가 + .addFilterBefore(new ExceptionHandlerFilter(), LogoutFilter.class); + return http.build(); + } + + /* + * OAuth2 관련 Bean들 제거됨 - Authorization Server로 이동 예정 + * - OAuth2AuthorizationRequestBasedOnCookieRepository + * - delegatingOAuth2UserService + * - AuthenticationManager + * - PasswordEncoder + * - AuthorizationRequestRepository + */ + + /** + * 역할 계층 구조 설정 + * Resource Server에서 권한 체크 시 사용 + * + * Spring Security 6.2.0 버전 변경사항: + * - 이전 5.x: RoleHierarchyImpl.fromHierarchy() 사용 + * - 현재 6.2.0: fromHierarchy() deprecated, setHierarchy() 사용 권장 + */ + @Bean + public RoleHierarchy roleHierarchy() { + RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); + roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_MANAGER > ROLE_USER"); + return roleHierarchy; + } + /** + * CORS 설정 - Resource Server에서 유지 + * + * 모든 마이크로서비스에서 공통으로 사용하는 CORS 정책 + * 프론트엔드 애플리케이션에서의 API 접근을 허용 + */ + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); // 쿠키 및 인증 정보 허용 + config.setAllowedOrigins(List.of( + "http://localhost:3000", // 개발환경 프론트엔드 + "http://localhost:8080", // 개발환경 백엔드 + BASEURL, // 운영환경 도메인 + "https://front-end-peach-two.vercel.app", // Vercel 배포 환경 + "https://front-end-dun-mu.vercel.app", + "https://appleid.apple.com" // Apple OAuth 콜백 (나중에 codin-auth로 이동) + )); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + config.setAllowedHeaders(List.of( + "Authorization", // JWT 토큰 헤더 + "Content-Type", + "X-Requested-With", + "Accept", + "Cache-Control", + "X-Refresh-Token" // 리프레시 토큰 헤더 + )); + config.setExposedHeaders(List.of("Authorization")); // 클라이언트에서 읽을 수 있는 헤더 + config.setMaxAge(3600L); // 프리플라이트 요청 캐시 시간 + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } + + + /* + * Resource Server 권한 경로 설정 + * + * 이 설정들은 JWT 토큰의 권한 정보를 바탕으로 + * API 엔드포인트별 접근 권한을 제어합니다. + * + * 향후 각 마이크로서비스별로 분리하여 관리할 예정 + */ + + // USER 권한으로 접근 가능한 경로 + private static final String[] USER_AUTH_PATHS = { + "/v3/api/test2", + "/v3/api/test3", + }; + + // ADMIN 권한이 필요한 경로 + private static final String[] ADMIN_AUTH_PATHS = { + "/v3/api/test4", + }; + + // MANAGER 권한이 필요한 경로 + private static final String[] MANAGER_AUTH_PATHS = { + "/v3/api/test5", + }; +} diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java b/codin-security/src/main/java/inu/codin/security/exception/JwtException.java similarity index 88% rename from codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java rename to codin-security/src/main/java/inu/codin/security/exception/JwtException.java index 9079c325..4d84fad5 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/exception/JwtException.java +++ b/codin-security/src/main/java/inu/codin/security/exception/JwtException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.exception; +package inu.codin.security.exception; import lombok.Getter; @@ -17,3 +17,4 @@ public JwtException(SecurityErrorCode errorCode, String message) { this.errorCode = errorCode; } } + \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java b/codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java similarity index 93% rename from codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java rename to codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java index b6f5b5d2..afb7a2f1 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/exception/SecurityErrorCode.java +++ b/codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.exception; +package inu.codin.security.exception; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java b/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java similarity index 92% rename from codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java rename to codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java index 8d356f62..88c89725 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/ExceptionHandlerFilter.java +++ b/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java @@ -1,8 +1,8 @@ -package inu.codin.codin.common.security.filter; +package inu.codin.security.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.response.ExceptionResponse; -import inu.codin.codin.common.security.exception.SecurityErrorCode; +import inu.codin.security.response.ExceptionResponse; +import inu.codin.security.exception.SecurityErrorCode; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java b/codin-security/src/main/java/inu/codin/security/filter/JwtAuthenticationFilter.java similarity index 60% rename from codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java rename to codin-security/src/main/java/inu/codin/security/filter/JwtAuthenticationFilter.java index e7499d35..fea939bd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/filter/JwtAuthenticationFilter.java +++ b/codin-security/src/main/java/inu/codin/security/filter/JwtAuthenticationFilter.java @@ -1,8 +1,8 @@ -package inu.codin.codin.common.security.filter; +package inu.codin.security.filter; -import inu.codin.codin.common.dto.PermitAllProperties; -import inu.codin.codin.common.dto.PublicApiProperties; -import inu.codin.codin.common.security.service.JwtService; +import inu.codin.security.config.PermitAllProperties; +import inu.codin.security.config.PublicApiProperties; +import inu.codin.security.service.JwtService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -34,13 +34,23 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/swagger-resources/**" }; + /** + * JWT 토큰 검증 및 인증 처리 + * + * 변경사항: + * - JwtService를 사용하여 토큰 검증 간소화 + * - Swagger 관련 특별 처리 제거 (일관된 JWT 검증) + * - 의존성 최소화 + */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestURI = request.getRequestURI(); + // 1. 인증이 필요하지 않은 경로 체크 final boolean isPermitAll = permitAllProperties.getUrls().stream() - .anyMatch(url -> pathMatcher.match(url, requestURI)); + .anyMatch(url -> pathMatcher.match(url, requestURI)) || + Arrays.stream(SWAGGER_AUTH_PATHS).anyMatch(url -> pathMatcher.match(url, requestURI)); final boolean isPublicApi = publicApiProperties.getUrls().stream() .anyMatch(url -> pathMatcher.match(url, requestURI)); @@ -50,23 +60,19 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String token = null; - if (Arrays.stream(SWAGGER_AUTH_PATHS).anyMatch(url -> pathMatcher.match(url, requestURI))) { - token = jwtService.getRefreshToken(request); - } else { - token = jwtService.getAccessToken(request); - } - - // Access Token이 있는 경우 - if (StringUtils.hasText(token)) { - jwtService.getUserDetailsAndSetAuthentication(token); - } else { + // 2. JWT 토큰 검증 및 인증 설정 + boolean isAuthenticated = jwtService.validateAndSetAuthentication(request); + + // 3. 인증 실패 시 PublicApi 경로는 통과, 나머지는 차단 + if (!isAuthenticated) { SecurityContextHolder.clearContext(); - - if (isPublicApi) { - filterChain.doFilter(request, response); - return; - } + + if (isPublicApi) { + filterChain.doFilter(request, response); + return; + } + // 인증이 필요한 경로에서 토큰이 없거나 유효하지 않으면 401 에러 발생 + // (ExceptionHandlerFilter에서 처리됨) } filterChain.doFilter(request, response); diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/TokenValidationFilter.java b/codin-security/src/main/java/inu/codin/security/filter/TokenValidationFilter.java similarity index 91% rename from codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/TokenValidationFilter.java rename to codin-security/src/main/java/inu/codin/security/filter/TokenValidationFilter.java index 9fd83e39..9d0c5813 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/TokenValidationFilter.java +++ b/codin-security/src/main/java/inu/codin/security/filter/TokenValidationFilter.java @@ -1,8 +1,8 @@ -package inu.codin.codinticketingapi.security.filter; +package inu.codin.security.filter; -import inu.codin.codinticketingapi.security.jwt.JwtTokenValidator; -import inu.codin.codinticketingapi.security.jwt.TokenUserDetails; -import inu.codin.codinticketingapi.security.util.TokenUtil; +import inu.codin.security.jwt.JwtTokenValidator; +import inu.codin.security.jwt.TokenUserDetails; +import inu.codin.security.util.TokenUtil; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java b/codin-security/src/main/java/inu/codin/security/jwt/JwtAuthenticationToken.java similarity index 94% rename from codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java rename to codin-security/src/main/java/inu/codin/security/jwt/JwtAuthenticationToken.java index fa82aab1..29df6d6b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtAuthenticationToken.java +++ b/codin-security/src/main/java/inu/codin/security/jwt/JwtAuthenticationToken.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.jwt; +package inu.codin.security.jwt; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/jwt/JwtTokenValidator.java b/codin-security/src/main/java/inu/codin/security/jwt/JwtTokenValidator.java similarity index 83% rename from codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/jwt/JwtTokenValidator.java rename to codin-security/src/main/java/inu/codin/security/jwt/JwtTokenValidator.java index 4c5dded8..bea1fbdc 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/jwt/JwtTokenValidator.java +++ b/codin-security/src/main/java/inu/codin/security/jwt/JwtTokenValidator.java @@ -1,7 +1,7 @@ -package inu.codin.codinticketingapi.security.jwt; +package inu.codin.security.jwt; -import inu.codin.codinticketingapi.security.exception.SecurityErrorCode; -import inu.codin.codinticketingapi.security.exception.SecurityException; +import inu.codin.security.exception.JwtException; +import inu.codin.security.exception.SecurityErrorCode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -32,7 +32,7 @@ protected void init() { * 토큰 유효성 검사 (토큰 변조, 만료) * @param accessToken * @return true: 유효한 토큰 - * @throws SecurityException: 토큰 만료, 유효하지 않은 토큰 + * @throws JwtException: 토큰 만료, 유효하지 않은 토큰 */ public boolean validateAccessToken(String accessToken) { try { @@ -44,10 +44,10 @@ public boolean validateAccessToken(String accessToken) { return true; } catch (ExpiredJwtException e) { // 토큰 만료 log.error("[validateAccessToken] 토큰 만료 : {}", e.getMessage()); - throw new SecurityException(SecurityErrorCode.EXPIRED_TOKEN); + throw new JwtException(SecurityErrorCode.EXPIRED_TOKEN); } catch (Exception e) { // 토큰 변조 log.error("[validateAccessToken] 유효하지 않은 토큰 : {}", e.getMessage()); - throw new SecurityException(SecurityErrorCode.INVALID_TOKEN); + throw new JwtException(SecurityErrorCode.INVALID_TOKEN); } } diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java b/codin-security/src/main/java/inu/codin/security/jwt/JwtUtils.java similarity index 97% rename from codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java rename to codin-security/src/main/java/inu/codin/security/jwt/JwtUtils.java index 37358bbc..a0f25962 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/jwt/JwtUtils.java +++ b/codin-security/src/main/java/inu/codin/security/jwt/JwtUtils.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.jwt; +package inu.codin.security.jwt; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/jwt/TokenUserDetails.java b/codin-security/src/main/java/inu/codin/security/jwt/TokenUserDetails.java similarity index 97% rename from codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/jwt/TokenUserDetails.java rename to codin-security/src/main/java/inu/codin/security/jwt/TokenUserDetails.java index 2e42269f..0d4e6dc4 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/jwt/TokenUserDetails.java +++ b/codin-security/src/main/java/inu/codin/security/jwt/TokenUserDetails.java @@ -1,4 +1,4 @@ -package inu.codin.codinticketingapi.security.jwt; +package inu.codin.security.jwt; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java b/codin-security/src/main/java/inu/codin/security/response/CommonResponse.java similarity index 87% rename from codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java rename to codin-security/src/main/java/inu/codin/security/response/CommonResponse.java index e35ef50b..5be068c9 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/response/CommonResponse.java +++ b/codin-security/src/main/java/inu/codin/security/response/CommonResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.security.response; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java b/codin-security/src/main/java/inu/codin/security/response/ExceptionResponse.java similarity index 82% rename from codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java rename to codin-security/src/main/java/inu/codin/security/response/ExceptionResponse.java index b9af8553..6808bc8a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java +++ b/codin-security/src/main/java/inu/codin/security/response/ExceptionResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.security.response; import lombok.Getter; diff --git a/codin-security/src/main/java/inu/codin/security/service/JwtService.java b/codin-security/src/main/java/inu/codin/security/service/JwtService.java new file mode 100644 index 00000000..44f43fb3 --- /dev/null +++ b/codin-security/src/main/java/inu/codin/security/service/JwtService.java @@ -0,0 +1,105 @@ +package inu.codin.security.service; + +import inu.codin.security.exception.JwtException; +import inu.codin.security.exception.SecurityErrorCode; +import inu.codin.security.jwt.JwtAuthenticationToken; +import inu.codin.security.jwt.JwtTokenValidator; +import inu.codin.security.jwt.TokenUserDetails; +import inu.codin.security.util.TokenUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +/** + * JWT 토큰 검증 전용 서비스 + * + * 기존 JwtService에서 검증 기능만 분리 + * - 토큰 발급 기능 제거 (codin-auth로 이동 예정) + * - 도메인 의존성 제거 (CustomUserDetailsService, RedisStorageService) + * - 순수한 토큰 검증만 담당 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class JwtService { + + private final JwtTokenValidator jwtTokenValidator; + + /** + * 요청에서 토큰을 추출하고 검증하여 SecurityContext에 설정 + * + * @param request HTTP 요청 + * @return 검증 성공 여부 + */ + public boolean validateAndSetAuthentication(HttpServletRequest request) { + try { + // 1. 토큰 추출 + String token = TokenUtil.extractToken(request); + if (token == null) { + log.debug("[validateAndSetAuthentication] 토큰이 없습니다."); + return false; + } + + // 2. 토큰 검증 + if (!jwtTokenValidator.validateAccessToken(token)) { + log.debug("[validateAndSetAuthentication] 토큰 검증 실패"); + return false; + } + + // 3. 토큰에서 사용자 정보 추출 + String userId = jwtTokenValidator.getUserId(token); + String email = jwtTokenValidator.getUsername(token); + String role = jwtTokenValidator.getUserRole(token); + + // 4. TokenUserDetails 생성 + TokenUserDetails userDetails = TokenUserDetails.fromTokenClaims( + userId, email, role, token + ); + + // 5. Authentication 객체 생성 후 SecurityContext에 설정 + JwtAuthenticationToken authentication = new JwtAuthenticationToken( + userDetails, userDetails.getAuthorities() + ); + SecurityContextHolder.getContext().setAuthentication(authentication); + + log.debug("[validateAndSetAuthentication] 인증 성공: userId={}, email={}, role={}", + userId, email, role); + return true; + + } catch (JwtException e) { + log.warn("[validateAndSetAuthentication] JWT 검증 실패: {}", e.getMessage()); + return false; + } catch (Exception e) { + log.error("[validateAndSetAuthentication] 예상치 못한 오류: {}", e.getMessage(), e); + return false; + } + } + + /** + * 토큰 유효성만 검증 (SecurityContext 설정 없이) + * + * @param token JWT 토큰 + * @return 유효성 검증 결과 + */ + public boolean isValidToken(String token) { + try { + return jwtTokenValidator.validateAccessToken(token); + } catch (Exception e) { + log.debug("[isValidToken] 토큰 검증 실패: {}", e.getMessage()); + return false; + } + } + + /** + * 요청에서 토큰을 추출하여 유효성 검증 + * + * @param request HTTP 요청 + * @return 유효성 검증 결과 + */ + public boolean isValidRequest(HttpServletRequest request) { + String token = TokenUtil.extractToken(request); + return token != null && isValidToken(token); + } +} \ No newline at end of file diff --git a/codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java b/codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java new file mode 100644 index 00000000..b42f7ed9 --- /dev/null +++ b/codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java @@ -0,0 +1,105 @@ +package inu.codin.security.util; + +import inu.codin.security.exception.JwtException; +import inu.codin.security.exception.SecurityErrorCode; +import inu.codin.security.jwt.TokenUserDetails; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * SecurityContext와 관련된 유틸리티 클래스. + * + * 변경사항: + * - CustomUserDetails -> TokenUserDetails로 변경 (codin-security 독립성) + * - ObjectId -> String으로 변경 (MongoDB 의존성 제거) + * - UserRole enum 제거 -> String 사용 + * - 도메인 특화 검증 로직은 각 서비스에서 구현하도록 변경 + */ +@Slf4j +public class SecurityUtils { + + /** + * 현재 인증된 사용자의 ID를 반환. + * + * @return 인증된 사용자의 ID (String) + * @throws JwtException 인증 정보가 없는 경우 예외 발생 + */ + public static String getCurrentUserId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UserDetails userDetails = getCurrentUserDetails(); + + if (userDetails instanceof TokenUserDetails tokenUserDetails) { + return tokenUserDetails.getUserId(); + } + + // 호환성을 위해 username 반환 (다른 UserDetails 구현체) + return userDetails.getUsername(); + } + + /** + * 현재 인증된 사용자의 ID를 반환 (nullable 안전 버전) + * - 인증이 없거나 익명이면 null 반환 + */ + public static String getCurrentUserIdOrNull() { + try { + return getCurrentUserId(); + } catch (JwtException e) { + return null; + } + } + + /** + * 현재 인증된 사용자의 역할을 반환 + */ + public static String getCurrentUserRole() { + UserDetails userDetails = getCurrentUserDetails(); + + if (userDetails instanceof TokenUserDetails tokenUserDetails) { + return tokenUserDetails.getRole(); + } + + // 기본적으로 첫 번째 권한 반환 + return userDetails.getAuthorities().iterator().next().getAuthority(); + } + + /** + * 현재 인증된 사용자인지 검증 + */ + public static void validateUser(String userId) { + String currentUserId = getCurrentUserId(); + if (!userId.equals(currentUserId)) { + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "현재 유저에게 권한이 없습니다."); + } + } + + /** + * 현재 인증된 사용자가 리소스 소유자인지 검증 + */ + public static void validateOwners(String currentUserId, String ownerId) { + validateUser(currentUserId); + if (!ownerId.equals(currentUserId)) { + throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "본인 리소스가 아닙니다."); + } + } + + /** + * 현재 UserDetails 반환 (내부 유틸리티) + */ + private static UserDetails getCurrentUserDetails() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !authentication.isAuthenticated() + || authentication instanceof AnonymousAuthenticationToken) { + throw new JwtException(SecurityErrorCode.ACCESS_DENIED); + } + + if (!(authentication.getPrincipal() instanceof UserDetails userDetails)) { + throw new JwtException(SecurityErrorCode.ACCESS_DENIED); + } + + return userDetails; + } +} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/TokenUtil.java b/codin-security/src/main/java/inu/codin/security/util/TokenUtil.java similarity index 96% rename from codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/TokenUtil.java rename to codin-security/src/main/java/inu/codin/security/util/TokenUtil.java index faf577dd..8ec28019 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/TokenUtil.java +++ b/codin-security/src/main/java/inu/codin/security/util/TokenUtil.java @@ -1,4 +1,4 @@ -package inu.codin.codinticketingapi.security.util; +package inu.codin.security.util; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; diff --git a/settings.gradle b/settings.gradle index 3129a640..6d50d7a3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ rootProject.name = 'codin-backend-repo' include 'codin-core' include 'codin-auth' +include 'codin-security' include 'codin-ticketing-api' include 'codin-ticketing-sse' include 'codin-lecture-api' \ No newline at end of file From 480d9bbdfa3f1d3d299f98f8fd80226f0f75c5b4 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 16 Dec 2025 20:49:38 +0900 Subject: [PATCH 0995/1002] refactor : SecurityUtils Return Types : String --- .../domain/block/service/BlockService.java | 10 +++++---- .../board/notice/service/NoticeService.java | 9 +++++--- .../board/voice/service/VoiceService.java | 4 +++- .../chatroom/service/ChatRoomService.java | 10 +++++---- .../chatting/service/ChattingService.java | 4 +++- .../domain/review/service/ReviewService.java | 6 ++++-- .../domain/like/service/LikeService.java | 4 +++- .../service/NotificationService.java | 4 +++- .../reply/service/ReplyCommandService.java | 10 +++++---- .../reply/service/ReplyQueryService.java | 4 +++- .../service/CommentCommandService.java | 10 +++++---- .../comment/service/CommentQueryService.java | 4 +++- .../poll/service/PollCommandService.java | 6 ++++-- .../domain/post/security/OwnershipPolicy.java | 7 +++++-- .../post/service/PostCommandService.java | 10 +++++---- .../domain/post/service/PostDtoAssembler.java | 4 +++- .../domain/post/service/PostQueryService.java | 6 ++++-- .../domain/report/service/ReportService.java | 8 ++++--- .../domain/scrap/service/ScrapService.java | 4 +++- .../domain/user/service/UserService.java | 21 +++++++++++-------- .../codin/infra/fcm/service/FcmService.java | 8 ++++--- .../codin/security/service/JwtService.java | 11 ++++++++++ 22 files changed, 110 insertions(+), 54 deletions(-) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 36cde5b1..8e04ae09 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -14,6 +14,8 @@ import java.util.List; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @Slf4j @RequiredArgsConstructor @@ -27,7 +29,7 @@ public class BlockService { * @param strBlockedUserId */ public void blockUser(String strBlockedUserId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { @@ -55,7 +57,7 @@ public void blockUser(String strBlockedUserId) { * @param strBlockedUserId 차단 해제할 유저 */ public void unblockUser(String strBlockedUserId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { @@ -82,12 +84,12 @@ public void unblockUser(String strBlockedUserId) { * @return 차단한 유저 목록 (빈 리스트가 제공될 수 있음) */ public List getBlockedUsers() { - ObjectId currentUserId = SecurityUtils.getCurrentUserIdOrNull(); + ObjectId currentUserId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); if (currentUserId == null) { return List.of(); } - return blockRepository.findByUserId(SecurityUtils.getCurrentUserId()) + return blockRepository.findByUserId(toObjectId(SecurityUtils.getCurrentUserId())) .map(BlockEntity::getBlockedUsers) .orElse(List.of()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java index 9fbbe7a7..5f7d02a9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -1,6 +1,7 @@ package inu.codin.codin.domain.board.notice.service; import inu.codin.codin.common.dto.Department; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; @@ -32,6 +33,8 @@ import java.util.Map; import java.util.regex.Pattern; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @Slf4j @RequiredArgsConstructor @@ -81,7 +84,7 @@ public NoticeDetailResponseDto getNoticesWithDetail(String postId) { */ public Map createNotice(NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, List noticeImages) { List imageUrls = s3Service.handleImageUpload(noticeImages); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); validateUserAndPost(userId); UserEntity user = getUserEntity(userId); @@ -149,7 +152,7 @@ private UserEntity getUserEntity(ObjectId userId) { } private NoticeDetailResponseDto.UserInfo getUserInfoAboutNotice(ObjectId postUserId, ObjectId postId){ - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); return NoticeDetailResponseDto.UserInfo.builder() .isScrap(scrapService.isPostScraped(postId, userId)) .isMine(postUserId.equals(userId)) @@ -160,6 +163,6 @@ private void validateUserAndPost(ObjectId postUserId) { if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER)) { throw new NoticeException(NoticeErrorCode.NOTICE_ACCESS_DENIED); } - SecurityUtils.validateUser(postUserId); + SecurityUtils.validateUser(ObjectIdUtil.toString(postUserId)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java index 2d249fa4..feac8e02 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.stream.Collectors; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor public class VoiceService { @@ -51,7 +53,7 @@ public void toggleVoiceBox(String boxId, Boolean positive) { VoiceEntity voiceEntity = voiceRepository.findByIdAndNotDeleted(objectId) .orElseThrow(() -> new IllegalArgumentException("익명의 소리함 질문을 찾을 수 없습니다.")); - ObjectId currentUserId = SecurityUtils.getCurrentUserId(); + ObjectId currentUserId = toObjectId(SecurityUtils.getCurrentUserId()); if (positive) { voiceEntity.votePositiveToggle(currentUserId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 807564b6..90d9bd86 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -21,6 +21,8 @@ import java.util.*; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -34,7 +36,7 @@ public class ChatRoomService { public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto) { - ObjectId senderId = SecurityUtils.getCurrentUserId(); + ObjectId senderId = toObjectId(SecurityUtils.getCurrentUserId()); isValidated(chatRoomCreateRequestDto, senderId); //유효성 검사 log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); @@ -76,7 +78,7 @@ private void isValidated(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obje } public List getAllChatRoomByUser() { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[유저의 채팅방 조회] 유저 ID: {}", userId); // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); @@ -92,7 +94,7 @@ public List getAllChatRoomByUser() { } public void leaveChatRoom(String chatRoomId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[채팅방 나가기 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) @@ -122,7 +124,7 @@ public void leaveChatRoom(String chatRoomId) { } public void setNotificationChatRoom(String chatRoomId) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[알림 설정 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 02128219..29da1e78 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -30,6 +30,8 @@ import java.time.LocalDateTime; import java.util.List; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -72,7 +74,7 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq } public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index 840f6fbc..0c05dc73 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -24,6 +24,8 @@ import java.util.Optional; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -44,7 +46,7 @@ public void createReview(ObjectId lectureId, CreateReviewRequestDto createReview log.warn("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요"); throw new WrongRatingException("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요."); } - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); Optional review = reviewRepository.findByLectureIdAndUserIdAndDeletedAtIsNull(lectureId, userId); if (review.isPresent()) { log.error("이미 유저가 작성한 후기가 존재합니다. userId: {}, lectureId: {}", userId, lectureId); @@ -82,7 +84,7 @@ public ReviewPageResponse getListOfReviews(String lectureId, int page) { PageRequest pageRequest = PageRequest.of(page, 10, Sort.by("created_at").descending()); Page reviewPage = reviewRepository.getAvgRatingByLectureId(new ObjectId(lectureId), pageRequest); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); return ReviewPageResponse.of(reviewPage.stream() .map(review -> ReviewListResposneDto.of(review, likeService.isLiked(LikeType.REVIEW, review.get_id().toString(), userId), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 89e608f3..4598bdc7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Optional; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -40,7 +42,7 @@ public class LikeService { public LikeResponseType toggleLike(LikeRequestDto likeRequestDto) { String likeId = likeRequestDto.getLikeTypeId(); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 929ae6a0..29adf39f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -29,6 +29,8 @@ import java.util.List; import java.util.Map; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -209,7 +211,7 @@ public void readNotification(String notificationId){ public List getNotification() { //todo 유저에게 맞는 토픽 알림들도 반환 - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index 141957c4..a51b0751 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -20,6 +20,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -41,9 +43,9 @@ public class ReplyCommandService { // 대댓글 추가 public void addReply(String id, ReplyCreateRequestDTO requestDTO) { - CommentEntity comment = commentQueryService.findCommentById(ObjectIdUtil.toObjectId(id)); + CommentEntity comment = commentQueryService.findCommentById(toObjectId(id)); PostEntity post = postQueryService.findPostById(comment.getPostId()); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ReplyCommentEntity reply = ReplyCommentEntity.create(comment.get_id(), userId, requestDTO); @@ -58,7 +60,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { } public void updateReply(String replyId, @Valid ReplyUpdateRequestDTO requestDTO) { - ReplyCommentEntity reply = ownershipPolicy.assertReplyOwner(ObjectIdUtil.toObjectId(replyId)); + ReplyCommentEntity reply = ownershipPolicy.assertReplyOwner(toObjectId(replyId)); reply.updateReply(requestDTO.getContent()); replyCommentRepository.save(reply); @@ -69,7 +71,7 @@ public void updateReply(String replyId, @Valid ReplyUpdateRequestDTO requestDTO) // 대댓글 삭제 (Soft Delete) public void softDeleteReply(String replyId) { - ReplyCommentEntity reply = ownershipPolicy.assertReplyOwner(ObjectIdUtil.toObjectId(replyId)); + ReplyCommentEntity reply = ownershipPolicy.assertReplyOwner(toObjectId(replyId)); CommentEntity comment = commentQueryService.findCommentById(reply.getCommentId()); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index 12de3251..bc84390f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -105,7 +107,7 @@ private CommentResponseDTO buildReplyResponseDTO( } public UserInfo getUserInfoAboutReply(ObjectId replyId) { - ObjectId userId = SecurityUtils.getCurrentUserIdOrNull(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); boolean isLiked = false; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index fa19ed78..a0fe7fee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -17,6 +17,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -31,10 +33,10 @@ public class CommentCommandService { // 댓글 추가 public void addComment(String id, CommentCreateRequestDTO requestDTO) { - ObjectId postId = ObjectIdUtil.toObjectId(id); + ObjectId postId = toObjectId(id); PostEntity post = postQueryService.findPostById(postId); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); commentRepository.save(comment); @@ -50,7 +52,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { public void updateComment(String commentId, CommentUpdateRequestDTO requestDTO) { log.info("댓글 업데이트 요청. commentId: {}, 새로운 내용: {}", commentId, requestDTO.getContent()); - CommentEntity comment = ownershipPolicy.assertCommentOwner(ObjectIdUtil.toObjectId(commentId)); + CommentEntity comment = ownershipPolicy.assertCommentOwner(toObjectId(commentId)); comment.updateComment(requestDTO.getContent()); commentRepository.save(comment); @@ -61,7 +63,7 @@ public void updateComment(String commentId, CommentUpdateRequestDTO requestDTO) // 댓글 삭제 (Soft Delete) public void softDeleteComment(String commentId) { - CommentEntity comment = ownershipPolicy.assertCommentOwner(ObjectIdUtil.toObjectId(commentId)); + CommentEntity comment = ownershipPolicy.assertCommentOwner(toObjectId(commentId)); ObjectId postId = comment.getPostId(); PostEntity post = postQueryService.findPostById(postId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index 8b019928..1f030e42 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -28,6 +28,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -113,7 +115,7 @@ private CommentResponseDTO buildCommentResponseDTO( public UserInfo getUserInfoAboutComment(ObjectId commentId) { - ObjectId userId = SecurityUtils.getCurrentUserIdOrNull(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); boolean isLiked = false; if (userId != null) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index 860ed3fa..2c1097f4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Objects; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -53,7 +55,7 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { log.info("투표 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); PollEntity poll = getActivePollByPostId(postId); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ensureNotDuplicatedVote(poll.get_id(), userId); @@ -74,7 +76,7 @@ public void deleteVoting(String postId) { log.info("투표 취소 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); PollEntity poll = getActivePollByPostId(postId); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); PollVoteEntity vote = requireUserVote(poll.get_id(), userId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java index 0accfa03..75b36031 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java @@ -1,5 +1,6 @@ package inu.codin.codin.domain.post.security; +import inu.codin.codin.common.util.ObjectIdUtil; import inu.codin.security.util.SecurityUtils; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostQueryService; @@ -11,6 +12,8 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Component; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Component @RequiredArgsConstructor public class OwnershipPolicy { @@ -39,7 +42,7 @@ public ReplyCommentEntity assertReplyOwner(ObjectId replyId) { } private void validateOwner(ObjectId ownerId) { - ObjectId current = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateOwners(current, ownerId); // 불일치 시 예외 + String current = SecurityUtils.getCurrentUserId(); + SecurityUtils.validateOwners(current, ObjectIdUtil.toString(ownerId)); // 불일치 시 예외 } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index d3d356d6..ee1595da 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -22,6 +22,8 @@ import java.util.List; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Slf4j @Service @RequiredArgsConstructor @@ -163,13 +165,13 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { private ObjectId validateUserAndPost(PostCategory postCategory) { if (isPrivileged()) { // ADMIN / MANAGER 는 카테고리/유저 상태 검증을 통과시킴 - return SecurityUtils.getCurrentUserId(); + return toObjectId(SecurityUtils.getCurrentUserId()); } assertCategoryWriteAllowed(postCategory); - ObjectId userId = SecurityUtils.getCurrentUserId(); + String userId = SecurityUtils.getCurrentUserId(); SecurityUtils.validateUser(userId); - return userId; + return toObjectId(userId); } private PostEntity assertPostOwner(ObjectId postId){ @@ -187,7 +189,7 @@ private void assertCategoryWriteAllowed(PostCategory postCategory) { } private boolean isPrivileged() { - UserRole role = SecurityUtils.getCurrentUserRole(); + UserRole role = UserRole.valueOf(SecurityUtils.getCurrentUserRole()); return role == UserRole.ADMIN || role == UserRole.MANAGER; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java index f53d1895..a12629da 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Objects; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + /** * PostEntity를 다양한 Response DTO로 변환하는 책임을 담당하는 어셈블러 * CQRS의 Query 측면에서 DTO 조립 로직을 분리하여 단일 책임 원칙을 준수 @@ -70,7 +72,7 @@ public PostPageItemResponseDTO toPageItem(PostEntity post, ObjectId currentUserI * PostEntity 리스트를 PostPageItemResponseDTO 리스트로 변환 */ public List toPageItemList(List posts) { - ObjectId currentUserId = SecurityUtils.getCurrentUserIdOrNull(); + ObjectId currentUserId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); return posts.stream() .map(post -> toPageItem(post, currentUserId)) .toList(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index 3c4c0b60..fea65cf8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -26,6 +26,8 @@ import java.util.Optional; import java.util.regex.Pattern; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Slf4j @Service @RequiredArgsConstructor @@ -54,7 +56,7 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { */ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = SecurityUtils.getCurrentUserIdOrNull(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); postInteractionService.increaseHits(post, userId); return postDtoAssembler.toPageItem(post, userId); } @@ -65,7 +67,7 @@ public PostPageItemResponseDTO getPostWithDetail(String postId) { public Optional getPostDetailById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) .map(post -> { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); postInteractionService.increaseHits(post, userId); return postDtoAssembler.toPageItem(post, userId); }); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 5c2f2675..9c07a4bd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -42,6 +42,8 @@ import java.util.stream.Collectors; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -69,7 +71,7 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { log.info("신고 생성 요청 시작: {} ", reportCreateRequestDto); // 신고한 유저 검증 - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ObjectId reportTargetId = new ObjectId(reportCreateRequestDto.getReportTargetId()); @@ -157,7 +159,7 @@ private ObjectId validateAndGetReportedUserId(ReportTargetType reportTargetType, public void handleReport(ReportExecuteRequestDto requestDto) { log.info("신고 처리 요청: {}", requestDto.getReportTargetId()); //현재 관리자 ID - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); ObjectId targetObjectId = new ObjectId(requestDto.getReportTargetId()); // 해당 신고 대상에 대한 모든 신고 가져오기 @@ -267,7 +269,7 @@ public void resolveReport(String reportTargetId) { log.info(" 신고대상 유지 요청: 신고 ID: {}", reportTargetId); ObjectId targetObjectId = new ObjectId(reportTargetId); - ObjectId userId = SecurityUtils.getCurrentUserId(); // 현재 유저 ID + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); // 현재 유저 ID // 신고 존재 확인 List reports = reportRepository.findByReportTargetId(targetObjectId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 4bf2767a..9f8f9687 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -13,6 +13,8 @@ import java.util.Optional; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @RequiredArgsConstructor @Slf4j @@ -30,7 +32,7 @@ public class ScrapService { public String toggleScrap(String id) { log.info("스크랩 토글 요청 - postId: {}", id); ObjectId postId = new ObjectId(id); - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 931b9163..8b367fe7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -36,6 +36,9 @@ import java.util.List; import java.util.Optional; + +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Slf4j @Service @RequiredArgsConstructor @@ -54,7 +57,7 @@ public class UserService { //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllUserPosts(int pageNumber) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[게시글 조회] 유저 ID: {}, 페이지 번호: {}", userId, pageNumber); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); @@ -69,7 +72,7 @@ public PostPageResponse getAllUserPosts(int pageNumber) { } public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType interactionType) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[유저 상호작용 조회] 유저 ID: {}, 타입: {}, 페이지 번호: {}", userId, interactionType, pageNumber); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); @@ -119,7 +122,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } public void deleteUser(HttpServletResponse response) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); UserEntity user = userRepository.findByUserId(userId) .orElseThrow(() -> { @@ -136,7 +139,7 @@ public void deleteUser(HttpServletResponse response) { } public UserInfoResponseDto getUserInfo() { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[유저 정보 조회] 유저 ID: {}", userId); UserEntity user = userRepository.findById(userId) @@ -155,7 +158,7 @@ public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[유저 정보 업데이트] 현재 사용자 ID: {}", userId); UserEntity user = userRepository.findById(userId) @@ -170,7 +173,7 @@ public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) } public void updateUserProfile(MultipartFile profileImage) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[프로필 이미지 업데이트] 현재 사용자 ID: {}", userId); UserEntity user = userRepository.findById(userId) @@ -186,7 +189,7 @@ public void updateUserProfile(MultipartFile profileImage) { } public void updateUserName(@Valid UserNameUpdateRequestDto request){ - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); log.info("[유저 실명 수정] 현재 사용자 ID: {}, 요청 이름: {}", userId, request.getName()); UserEntity user = userRepository.findById(userId) @@ -223,7 +226,7 @@ public void updateUserName(@Valid UserNameUpdateRequestDto request){ */ public UserTicketingParticipationInfoResponse getUserTicketingParticipationInfo() { return UserTicketingParticipationInfoResponse.of( - userRepository.findByUserId(SecurityUtils.getCurrentUserId()) + userRepository.findByUserId(toObjectId(SecurityUtils.getCurrentUserId())) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다."))); } @@ -232,7 +235,7 @@ public UserTicketingParticipationInfoResponse getUserTicketingParticipationInfo( * @return UserTicketingParticipationInfoResponse 유저의 학번, 이름, 소속 Dto 반환 */ public UserTicketingParticipationInfoResponse updateUserTicketingParticipationInfo(UserTicketingParticipationInfoUpdateRequest updateRequest) { - UserEntity userEntity = userRepository.findByUserId(SecurityUtils.getCurrentUserId()) + UserEntity userEntity = userRepository.findByUserId(toObjectId(SecurityUtils.getCurrentUserId())) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); userEntity.updateParticipationInfo(updateRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 41481591..94abaec7 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Optional; +import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; + @Service @Slf4j @RequiredArgsConstructor @@ -35,7 +37,7 @@ public class FcmService { */ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { // 유저의 FCM 토큰이 존재하는지 확인 - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); Optional fcmToken = fcmTokenRepository.findByUserId(userId); if (fcmToken.isPresent()) { // 이미 존재하는 유저라면 토큰 추가 @@ -159,7 +161,7 @@ private void removeFcmToken(FcmTokenEntity fcmTokenEntity, String fcmToken) { * @param topic 구독할 토픽 이름 */ public void subscribeTopic(String topic) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); @@ -178,7 +180,7 @@ public void subscribeTopic(String topic) { * @param topic 구독 해제할 토픽 이름 */ public void unsubscribeTopic(String topic) { - ObjectId userId = SecurityUtils.getCurrentUserId(); + ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); diff --git a/codin-security/src/main/java/inu/codin/security/service/JwtService.java b/codin-security/src/main/java/inu/codin/security/service/JwtService.java index 44f43fb3..54f7a8d8 100644 --- a/codin-security/src/main/java/inu/codin/security/service/JwtService.java +++ b/codin-security/src/main/java/inu/codin/security/service/JwtService.java @@ -7,6 +7,7 @@ import inu.codin.security.jwt.TokenUserDetails; import inu.codin.security.util.TokenUtil; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolder; @@ -102,4 +103,14 @@ public boolean isValidRequest(HttpServletRequest request) { String token = TokenUtil.extractToken(request); return token != null && isValidToken(token); } + + //todo: 임시 메서드 생성 : pahse 2 에서 auth 에서 분리구현 예정 + public void deleteToken(HttpServletResponse response) { + } + + public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletResponse response) { + } + + public void setAuthentication(HttpServletRequest servletRequest) { + } } \ No newline at end of file From acab5a0d3bb034fc89f8e9dc53453f1c2122b1d1 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Tue, 16 Dec 2025 21:13:18 +0900 Subject: [PATCH 0996/1002] =?UTF-8?q?refactor=20:=20security=20Module=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-core/build.gradle | 5 ++++ .../CodinSecurityAutoConfiguration.java | 29 +++++++++++++++++++ .../codin/security/service/JwtService.java | 11 ++----- ...ot.autoconfigure.AutoConfiguration.imports | 2 ++ codin-ticketing-api/build.gradle | 2 ++ .../config/SecurityConfig.java | 5 ++-- .../security/util/SecurityUtil.java | 3 +- codin-ticketing-sse/build.gradle | 2 ++ 8 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 codin-security/src/main/java/inu/codin/security/config/CodinSecurityAutoConfiguration.java create mode 100644 codin-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/codin-core/build.gradle b/codin-core/build.gradle index 60f84ad5..653098c3 100644 --- a/codin-core/build.gradle +++ b/codin-core/build.gradle @@ -86,7 +86,12 @@ dependencies { } tasks.named('test') { + enabled = false useJUnitPlatform() + +} +tasks.withType(Test).configureEach { + enabled = false } processResources.dependsOn('copySecret') diff --git a/codin-security/src/main/java/inu/codin/security/config/CodinSecurityAutoConfiguration.java b/codin-security/src/main/java/inu/codin/security/config/CodinSecurityAutoConfiguration.java new file mode 100644 index 00000000..0f5e8026 --- /dev/null +++ b/codin-security/src/main/java/inu/codin/security/config/CodinSecurityAutoConfiguration.java @@ -0,0 +1,29 @@ +package inu.codin.security.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +/** + * Codin Security 자동 구성 클래스 + * + * Spring Boot 3 방식의 AutoConfiguration으로 구현 + * 의존성만 추가하면 자동으로 보안 설정이 활성화됩니다. + * + * 조건: + * - Web 애플리케이션인 경우 + * - Spring Security가 클래스패스에 있는 경우 + */ +@AutoConfiguration +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnClass({HttpSecurity.class}) +@ComponentScan(basePackages = "inu.codin.security") +@EnableConfigurationProperties({PermitAllProperties.class, PublicApiProperties.class}) +public class CodinSecurityAutoConfiguration { + + // 필요한 경우 추가 빈 정의 가능 + // 현재는 @ComponentScan으로 자동 스캔되므로 별도 빈 정의 불필요 +} \ No newline at end of file diff --git a/codin-security/src/main/java/inu/codin/security/service/JwtService.java b/codin-security/src/main/java/inu/codin/security/service/JwtService.java index 54f7a8d8..ffca2912 100644 --- a/codin-security/src/main/java/inu/codin/security/service/JwtService.java +++ b/codin-security/src/main/java/inu/codin/security/service/JwtService.java @@ -105,12 +105,7 @@ public boolean isValidRequest(HttpServletRequest request) { } //todo: 임시 메서드 생성 : pahse 2 에서 auth 에서 분리구현 예정 - public void deleteToken(HttpServletResponse response) { - } - - public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletResponse response) { - } - - public void setAuthentication(HttpServletRequest servletRequest) { - } + public void deleteToken(HttpServletResponse response) {} + public void checkRefreshTokenAndReissue(HttpServletRequest request, HttpServletResponse response) {} + public void setAuthentication(HttpServletRequest servletRequest) {} } \ No newline at end of file diff --git a/codin-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/codin-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..83222e83 --- /dev/null +++ b/codin-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +# Codin Security Auto Configuration +inu.codin.security.config.CodinSecurityAutoConfiguration \ No newline at end of file diff --git a/codin-ticketing-api/build.gradle b/codin-ticketing-api/build.gradle index 8a62100c..7715ec95 100644 --- a/codin-ticketing-api/build.gradle +++ b/codin-ticketing-api/build.gradle @@ -34,6 +34,8 @@ dependencyManagement { } dependencies { + implementation project(':codin-security') + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java index 6193549d..b15f2369 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java @@ -2,8 +2,9 @@ import inu.codin.codinticketingapi.security.exception.CustomAccessDeniedHandler; import inu.codin.codinticketingapi.security.filter.SecurityExceptionHandlerFilter; -import inu.codin.codinticketingapi.security.filter.TokenValidationFilter; -import inu.codin.codinticketingapi.security.jwt.JwtTokenValidator; +import inu.codin.security.filter.TokenValidationFilter; + +import inu.codin.security.jwt.JwtTokenValidator; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java index be3b1a2a..91a32a7d 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java @@ -2,8 +2,7 @@ import inu.codin.codinticketingapi.security.exception.SecurityErrorCode; import inu.codin.codinticketingapi.security.exception.SecurityException; -import inu.codin.codinticketingapi.security.jwt.TokenUserDetails; -import io.jsonwebtoken.JwtException; +import inu.codin.security.jwt.TokenUserDetails; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/codin-ticketing-sse/build.gradle b/codin-ticketing-sse/build.gradle index c13579ff..18b6e683 100644 --- a/codin-ticketing-sse/build.gradle +++ b/codin-ticketing-sse/build.gradle @@ -18,6 +18,8 @@ repositories { } dependencies { + implementation project(':codin-security') + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' From 183f47b4408bfc583bd64c65093e09b000301059 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 17 Dec 2025 03:57:20 +0900 Subject: [PATCH 0997/1002] =?UTF-8?q?refactor=20:=20codin-common=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-common/README.md | 0 codin-common/build.gradle | 7 ++ .../inu}/codin/common/dto/BaseTimeEntity.java | 0 .../inu}/codin/common/dto/Department.java | 0 .../common/exception/GlobalErrorCode.java | 0 .../common/exception/GlobalException.java | 0 .../common/exception/NotFoundException.java | 0 .../codin/common/ratelimit/ClientIpUtil.java | 0 .../ratelimit/RateLimitBucketConstants.java | 0 .../ratelimit/RateLimitInterceptor.java | 0 .../common/ratelimit/RateLimitService.java | 0 .../codin/common/response/ListResponse.java | 0 .../common/response/RateLimitResponse.java | 0 .../codin/common/response/SingleResponse.java | 0 .../common/config/Bucket4jRateLimitApp.java | 4 -- .../security/service/AuthCommonService.java | 1 - .../domain/like/service/LikeService.java | 1 - .../src/main/resources/application-aws.yml | 12 ++++ .../src/main/resources/application-email.yml | 0 .../src/main/resources/application-jwt.yml | 8 +++ .../src/main/resources/application-oauth.yml | 2 + codin-core/src/main/resources/application.yml | 66 +++++++++++++++++++ .../domain/post/PostCommandServiceTest.java | 1 - codin-security/build.gradle | 13 ++-- 24 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 codin-common/README.md create mode 100644 codin-common/build.gradle rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/dto/BaseTimeEntity.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/dto/Department.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/exception/GlobalErrorCode.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/exception/GlobalException.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/exception/NotFoundException.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/ratelimit/ClientIpUtil.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/ratelimit/RateLimitBucketConstants.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/ratelimit/RateLimitInterceptor.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/ratelimit/RateLimitService.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/response/ListResponse.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/response/RateLimitResponse.java (100%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/response/SingleResponse.java (100%) create mode 100644 codin-core/src/main/resources/application-aws.yml create mode 100644 codin-core/src/main/resources/application-email.yml create mode 100644 codin-core/src/main/resources/application-jwt.yml create mode 100644 codin-core/src/main/resources/application-oauth.yml create mode 100644 codin-core/src/main/resources/application.yml diff --git a/codin-common/README.md b/codin-common/README.md new file mode 100644 index 00000000..e69de29b diff --git a/codin-common/build.gradle b/codin-common/build.gradle new file mode 100644 index 00000000..23dab9b0 --- /dev/null +++ b/codin-common/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'java' +} + +dependencies { + //todo : 의존성 추가 +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java b/codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java rename to codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/dto/Department.java b/codin-common/src/main/java/inu/codin/common/dto/Department.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/dto/Department.java rename to codin-common/src/main/java/inu/codin/common/dto/Department.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java b/codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java rename to codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java b/codin-common/src/main/java/inu/codin/common/exception/GlobalException.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/exception/GlobalException.java rename to codin-common/src/main/java/inu/codin/common/exception/GlobalException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/NotFoundException.java b/codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/exception/NotFoundException.java rename to codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java b/codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java b/codin-common/src/main/java/inu/codin/common/response/ListResponse.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/response/ListResponse.java rename to codin-common/src/main/java/inu/codin/common/response/ListResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java b/codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java rename to codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java b/codin-common/src/main/java/inu/codin/common/response/SingleResponse.java similarity index 100% rename from codin-core/src/main/java/inu/codin/codin/common/response/SingleResponse.java rename to codin-common/src/main/java/inu/codin/common/response/SingleResponse.java diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java index 19fd425d..7da775dd 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java @@ -1,10 +1,6 @@ package inu.codin.codin.common.config; import inu.codin.codin.common.ratelimit.RateLimitInterceptor; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; // Lecture API - 좋아요 개수 Feign 요청으로 인한 RateLimiting 에러로 주석화 //@Configuration diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index 3f6861d5..f3ec5075 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -6,7 +6,6 @@ import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.exception.UserCreateFailException; import inu.codin.codin.domain.user.exception.UserNicknameDuplicateException; -import inu.codin.codin.common.security.service.AbstractAuthService; import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 4598bdc7..10d0b756 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -17,7 +17,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; diff --git a/codin-core/src/main/resources/application-aws.yml b/codin-core/src/main/resources/application-aws.yml new file mode 100644 index 00000000..aad7d57a --- /dev/null +++ b/codin-core/src/main/resources/application-aws.yml @@ -0,0 +1,12 @@ +# 이름 : application-aws.yml +# [취급주의] 실제 AWS 환경에서 사용할 설정 파일 +cloud: + aws: + s3: + defaultProfileImageUrl: + bucket: + stack.auto: false + region.static: ap-northeast-2 + credentials: + accessKey: + secretKey: diff --git a/codin-core/src/main/resources/application-email.yml b/codin-core/src/main/resources/application-email.yml new file mode 100644 index 00000000..e69de29b diff --git a/codin-core/src/main/resources/application-jwt.yml b/codin-core/src/main/resources/application-jwt.yml new file mode 100644 index 00000000..848ced73 --- /dev/null +++ b/codin-core/src/main/resources/application-jwt.yml @@ -0,0 +1,8 @@ +# 이름 : applicatino-jwt.yml +# JWT 설정 파일 +spring: + jwt: + secret: + expiration: + access: + refresh: diff --git a/codin-core/src/main/resources/application-oauth.yml b/codin-core/src/main/resources/application-oauth.yml new file mode 100644 index 00000000..74a30486 --- /dev/null +++ b/codin-core/src/main/resources/application-oauth.yml @@ -0,0 +1,2 @@ +# 이름 : application-oauth.yml +# oauth2 설정을 위한 파일 \ No newline at end of file diff --git a/codin-core/src/main/resources/application.yml b/codin-core/src/main/resources/application.yml new file mode 100644 index 00000000..0b14fdeb --- /dev/null +++ b/codin-core/src/main/resources/application.yml @@ -0,0 +1,66 @@ +# Spring Boot Configuration +# application.yml +# src/main/resources/** +spring: + application: + name: codin-core + profiles: + include: jwt, aws, email, oauth + servlet: + multipart: + max-request-size: 5MB + max-file-size: 5MB + data: + redis: # [Redis] 설정 + host: localhost + port: 6379 + password: 1234 + mongodb: # [MongoDB] 설정 + host: localhost + port: 27017 + database: codin + + +google: + firebase: + project-id: + key-path: + +server: + servlet: + encoding: + force-response: true + charset: UTF-8 + enabled: true + domain: ${SERVER_DOMAIN} + forward-headers-strategy: framework + port: ${SERVER_PORT} + +springdoc: + api-docs: + enabled: true + swagger-ui: + groups-order: DESC + doc-expansion: list + tags-sorter: alpha + operationsSorter: method + disable-swagger-default-url: true + display-request-duration: true + +schedule: + path : + department: + cron: + starinu: + cron: + +lecture: + python: + path: + file: + path: + +security: + #투표 이벤트 공개 API + public-api: + urls: diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java index 4e3d07b3..c91be44a 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java @@ -12,7 +12,6 @@ import inu.codin.codin.domain.post.security.OwnershipPolicy; import inu.codin.codin.domain.post.service.PostCommandService; import inu.codin.codin.domain.post.service.PostInteractionService; -import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.user.entity.UserRole; import org.bson.types.ObjectId; import org.junit.jupiter.api.*; diff --git a/codin-security/build.gradle b/codin-security/build.gradle index 0cf6ed1b..cb2d629c 100644 --- a/codin-security/build.gradle +++ b/codin-security/build.gradle @@ -19,25 +19,28 @@ repositories { } dependencies { + // Codin Common 의존성 (공통 응답, 예외 클래스) + //api project(':codin-common') + // Spring Boot Security api 'org.springframework.boot:spring-boot-starter-security' api 'org.springframework.boot:spring-boot-starter-web' - + // JWT api 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' - + // Configuration Properties api 'org.springframework.boot:spring-boot-configuration-processor' - + // Jackson for JSON processing api 'com.fasterxml.jackson.core:jackson-databind' - + // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - + // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' From 1c7fdb994869effddb7a6e8f7a73bc6d188aa9c2 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 17 Dec 2025 05:03:32 +0900 Subject: [PATCH 0998/1002] =?UTF-8?q?refactor=20:=20codin-common=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/inu/codin/{ => codin}/common/dto/BaseTimeEntity.java | 0 .../main/java/inu/codin/{ => codin}/common/dto/Department.java | 0 .../inu/codin/{ => codin}/common/exception/GlobalErrorCode.java | 0 .../inu/codin/{ => codin}/common/exception/GlobalException.java | 0 .../codin/{ => codin}/common/exception/NotFoundException.java | 0 .../inu/codin/{ => codin}/common/ratelimit/ClientIpUtil.java | 0 .../{ => codin}/common/ratelimit/RateLimitBucketConstants.java | 0 .../{ => codin}/common/ratelimit/RateLimitInterceptor.java | 0 .../codin/{ => codin}/common/ratelimit/RateLimitService.java | 0 .../java/inu/codin/codin/common}/response/CommonResponse.java | 2 +- .../inu/codin/codin/common}/response/ExceptionResponse.java | 2 +- .../inu/codin/{ => codin}/common/response/ListResponse.java | 1 - .../codin/{ => codin}/common/response/RateLimitResponse.java | 0 .../inu/codin/{ => codin}/common/response/SingleResponse.java | 1 - .../codin/codin/common/exception/GlobalExceptionHandler.java | 2 +- .../codin/codin/common/security/service/AuthCommonService.java | 2 +- .../common/security/service/oauth2/AbstractAuthService.java | 2 +- .../codin/common/security/service/oauth2/AppleAuthService.java | 2 +- .../codin/common/security/service/oauth2/GoogleAuthService.java | 2 +- .../common/security/util/CustomAuthenticationEntryPoint.java | 2 +- .../codin/common/security/util/OAuth2LoginFailureHandler.java | 2 +- .../java/inu/codin/security/filter/ExceptionHandlerFilter.java | 2 +- 22 files changed, 10 insertions(+), 12 deletions(-) rename codin-common/src/main/java/inu/codin/{ => codin}/common/dto/BaseTimeEntity.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/dto/Department.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/exception/GlobalErrorCode.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/exception/GlobalException.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/exception/NotFoundException.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/ratelimit/ClientIpUtil.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/ratelimit/RateLimitBucketConstants.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/ratelimit/RateLimitInterceptor.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/ratelimit/RateLimitService.java (100%) rename {codin-security/src/main/java/inu/codin/security => codin-common/src/main/java/inu/codin/codin/common}/response/CommonResponse.java (87%) rename {codin-security/src/main/java/inu/codin/security => codin-common/src/main/java/inu/codin/codin/common}/response/ExceptionResponse.java (82%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/response/ListResponse.java (87%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/response/RateLimitResponse.java (100%) rename codin-common/src/main/java/inu/codin/{ => codin}/common/response/SingleResponse.java (85%) diff --git a/codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java b/codin-common/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java rename to codin-common/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java diff --git a/codin-common/src/main/java/inu/codin/common/dto/Department.java b/codin-common/src/main/java/inu/codin/codin/common/dto/Department.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/dto/Department.java rename to codin-common/src/main/java/inu/codin/codin/common/dto/Department.java diff --git a/codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java b/codin-common/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java rename to codin-common/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java diff --git a/codin-common/src/main/java/inu/codin/common/exception/GlobalException.java b/codin-common/src/main/java/inu/codin/codin/common/exception/GlobalException.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/exception/GlobalException.java rename to codin-common/src/main/java/inu/codin/codin/common/exception/GlobalException.java diff --git a/codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java b/codin-common/src/main/java/inu/codin/codin/common/exception/NotFoundException.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java rename to codin-common/src/main/java/inu/codin/codin/common/exception/NotFoundException.java diff --git a/codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java b/codin-common/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java rename to codin-common/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java diff --git a/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java b/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java rename to codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java diff --git a/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java b/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java rename to codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java diff --git a/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java b/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java rename to codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java diff --git a/codin-security/src/main/java/inu/codin/security/response/CommonResponse.java b/codin-common/src/main/java/inu/codin/codin/common/response/CommonResponse.java similarity index 87% rename from codin-security/src/main/java/inu/codin/security/response/CommonResponse.java rename to codin-common/src/main/java/inu/codin/codin/common/response/CommonResponse.java index 5be068c9..e35ef50b 100644 --- a/codin-security/src/main/java/inu/codin/security/response/CommonResponse.java +++ b/codin-common/src/main/java/inu/codin/codin/common/response/CommonResponse.java @@ -1,4 +1,4 @@ -package inu.codin.security.response; +package inu.codin.codin.common.response; import lombok.Getter; diff --git a/codin-security/src/main/java/inu/codin/security/response/ExceptionResponse.java b/codin-common/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java similarity index 82% rename from codin-security/src/main/java/inu/codin/security/response/ExceptionResponse.java rename to codin-common/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java index 6808bc8a..b9af8553 100644 --- a/codin-security/src/main/java/inu/codin/security/response/ExceptionResponse.java +++ b/codin-common/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java @@ -1,4 +1,4 @@ -package inu.codin.security.response; +package inu.codin.codin.common.response; import lombok.Getter; diff --git a/codin-common/src/main/java/inu/codin/common/response/ListResponse.java b/codin-common/src/main/java/inu/codin/codin/common/response/ListResponse.java similarity index 87% rename from codin-common/src/main/java/inu/codin/common/response/ListResponse.java rename to codin-common/src/main/java/inu/codin/codin/common/response/ListResponse.java index 51dfaa24..1f2b1c11 100644 --- a/codin-common/src/main/java/inu/codin/common/response/ListResponse.java +++ b/codin-common/src/main/java/inu/codin/codin/common/response/ListResponse.java @@ -1,6 +1,5 @@ package inu.codin.codin.common.response; -import inu.codin.security.response.CommonResponse; import lombok.Builder; import lombok.Getter; diff --git a/codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java b/codin-common/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java similarity index 100% rename from codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java rename to codin-common/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java diff --git a/codin-common/src/main/java/inu/codin/common/response/SingleResponse.java b/codin-common/src/main/java/inu/codin/codin/common/response/SingleResponse.java similarity index 85% rename from codin-common/src/main/java/inu/codin/common/response/SingleResponse.java rename to codin-common/src/main/java/inu/codin/codin/common/response/SingleResponse.java index f28341b1..32955bcc 100644 --- a/codin-common/src/main/java/inu/codin/common/response/SingleResponse.java +++ b/codin-common/src/main/java/inu/codin/codin/common/response/SingleResponse.java @@ -1,6 +1,5 @@ package inu.codin.codin.common.response; -import inu.codin.security.response.CommonResponse; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index 608396e7..c3fe51da 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.exception; -import inu.codin.security.response.ExceptionResponse; +import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.security.exception.JwtException; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index f3ec5075..cd153883 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -16,7 +16,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; - +import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java index acdc498e..1d061ec7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AbstractAuthService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.security.service; +package inu.codin.codin.common.security.service.oauth2; import inu.codin.codin.domain.user.repository.UserRepository; import inu.codin.codin.infra.s3.S3Service; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java index e6e7c26e..aaa5e374 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.AbstractAuthService; +import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index 37584223..839744db 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -3,7 +3,7 @@ import inu.codin.codin.common.dto.Department; import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.AbstractAuthService; +import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java index 6020d5a4..342d6728 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.util; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.security.response.ExceptionResponse; +import inu.codin.codin.common.response.ExceptionResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 42f537db..9324d6b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.util; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.security.response.ExceptionResponse; +import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.security.service.JwtService; import inu.codin.codin.common.util.CookieUtil; import jakarta.servlet.http.HttpServletRequest; diff --git a/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java b/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java index 88c89725..cb8b98b8 100644 --- a/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java +++ b/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java @@ -1,7 +1,7 @@ package inu.codin.security.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.security.response.ExceptionResponse; +import inu.codin.codin.common.response.ExceptionResponse; import inu.codin.security.exception.SecurityErrorCode; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; From 6cf9fe988940789799ffcbe0bbf9f532b2660bd0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 17 Dec 2025 05:03:52 +0900 Subject: [PATCH 0999/1002] =?UTF-8?q?refactor=20:=20core=20yml=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codin-common/build.gradle | 52 ++++++++++- .../codin/common/config/SecurityConfig.java | 91 ++++++++++++++++++- .../inu/codin/codin/infra/fcm/FcmConfig.java | 2 + .../src/main/resources/application-aws.yml | 12 +-- .../src/main/resources/application-email.yml | 14 +++ .../src/main/resources/application-jwt.yml | 8 +- .../src/main/resources/application-oauth.yml | 46 +++++++++- codin-core/src/main/resources/application.yml | 37 ++++---- codin-security/build.gradle | 2 +- .../security/config/PermitAllProperties.java | 3 +- .../security/config/PublicApiProperties.java | 3 +- settings.gradle | 1 + 12 files changed, 238 insertions(+), 33 deletions(-) diff --git a/codin-common/build.gradle b/codin-common/build.gradle index 23dab9b0..67badb08 100644 --- a/codin-common/build.gradle +++ b/codin-common/build.gradle @@ -1,7 +1,55 @@ plugins { - id 'java' + id 'java-library' + id 'org.springframework.boot' version '3.2.0' + id 'io.spring.dependency-management' version '1.1.4' +} + +group = 'inu.codin' +version = '1.0.0' +sourceCompatibility = '17' + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() } dependencies { - //todo : 의존성 추가 + // Spring Boot Core + api 'org.springframework.boot:spring-boot-starter-web' + + // Spring Data MongoDB (for BaseTimeEntity) + api 'org.springframework.boot:spring-boot-starter-data-mongodb' + + // Rate Limiting (for RateLimitInterceptor) + api 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' + + // Configuration Properties + api 'org.springframework.boot:spring-boot-configuration-processor' + + // Jackson for JSON processing + api 'com.fasterxml.jackson.core:jackson-databind' + + // Lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' +} + +tasks.named('test') { + useJUnitPlatform() +} + +// Disable Spring Boot plugin's jar task since this is a library +jar { + enabled = true + archiveClassifier = '' +} + +// Disable the bootJar task since this is a library +bootJar { + enabled = false } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java index ecb01075..b8e2b20b 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/SecurityConfig.java @@ -24,4 +24,93 @@ * 분리 후: * 1. Authorization Server -> codin-auth 모듈 (Phase 2에서 구현) * 2. Resource Server -> codin-security 모듈 (Phase 1에서 완료) - */ \ No newline at end of file + */ + +import inu.codin.codin.common.security.service.oauth2.AppleOAuth2UserService; +import inu.codin.codin.common.security.service.oauth2.CustomOAuth2UserService; +import inu.codin.codin.common.security.util.CustomOAuth2AccessTokenResponseClient; +import inu.codin.codin.common.security.util.OAuth2AuthorizationRequestBasedOnCookieRepository; +import inu.codin.codin.common.security.util.OAuth2LoginFailureHandler; +import inu.codin.codin.common.security.util.OAuth2LoginSuccessHandler; +import inu.codin.codin.common.util.CustomAuthorizationRequestResolver; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; + private final CustomOAuth2UserService customOAuth2UserService; + private final AppleOAuth2UserService appleOAuth2UserService; + private final ClientRegistrationRepository clientRegistrationRepository; + private final CustomOAuth2AccessTokenResponseClient customOAuth2AccessTokenResponseClient; + + @Bean + @org.springframework.core.annotation.Order(1) + public SecurityFilterChain oauth2Chain(HttpSecurity http) throws Exception { + http + // 이 체인은 OAuth2 엔드포인트에만 적용 + .securityMatcher("/oauth2/**", "/login/oauth2/**") + + .csrf(CsrfConfigurer::disable) + .formLogin(FormLoginConfigurer::disable) + .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll() + ) + + .oauth2Login(oauth2 -> oauth2 + .tokenEndpoint(token -> token.accessTokenResponseClient(customOAuth2AccessTokenResponseClient)) + .authorizationEndpoint(authorization -> authorization + .authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()) + .authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository)) + ) + .userInfoEndpoint(userInfo -> userInfo.userService(delegatingOAuth2UserService())) + .successHandler(oAuth2LoginSuccessHandler) + .failureHandler(oAuth2LoginFailureHandler) + ); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository() { + return new OAuth2AuthorizationRequestBasedOnCookieRepository(); + } + + private OAuth2UserService delegatingOAuth2UserService() { + return userRequest -> { + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + if ("apple".equals(registrationId)) return appleOAuth2UserService.loadUser(userRequest); + if ("google".equals(registrationId)) return customOAuth2UserService.loadUser(userRequest); + throw new OAuth2AuthenticationException(new OAuth2Error("unsupported_provider"), + "지원되지 않는 공급자입니다: " + registrationId); + }; + } +} \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java index e00f381c..d00e8a87 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/FcmConfig.java @@ -6,12 +6,14 @@ import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; @Component +@Profile("fcm") @Slf4j public class FcmConfig { diff --git a/codin-core/src/main/resources/application-aws.yml b/codin-core/src/main/resources/application-aws.yml index aad7d57a..eadbceed 100644 --- a/codin-core/src/main/resources/application-aws.yml +++ b/codin-core/src/main/resources/application-aws.yml @@ -3,10 +3,10 @@ cloud: aws: s3: - defaultProfileImageUrl: - bucket: - stack.auto: false - region.static: ap-northeast-2 + defaultProfileImageUrl: ${AWS_S3_DEFAULT_PROFILE_IMAGE_URL} + bucket: ${AWS_S3_BUCKET} + stack.auto: ${AWS_STACK_AUTO} + region.static: ${AWS_REGION_STATIC} credentials: - accessKey: - secretKey: + accessKey: ${AWS_ACCESS_KEY} + secretKey: ${AWS_SECRET_KEY} diff --git a/codin-core/src/main/resources/application-email.yml b/codin-core/src/main/resources/application-email.yml index e69de29b..c3770193 100644 --- a/codin-core/src/main/resources/application-email.yml +++ b/codin-core/src/main/resources/application-email.yml @@ -0,0 +1,14 @@ +spring: + mail: + host: ${SPRING_MAIL_HOST} + port: ${SPRING_MAIL_PORT} + username: ${SPRING_MAIL_USERNAME} + password: ${SPRING_MAIL_PASSWORD} + properties: + mail.smtp.debug: ${SPRING_MAIL_PROPERTIES_SMTP_DEBUG} + mail.smtp.connectiontimeout: ${SPRING_MAIL_PROPERTIES_SMTP_CONNECTIONTIMEOUT} #1초 + mail.starttls.enable: ${SPRING_MAIL_PROPERTIES_STARTTLS_ENABLE} + mail.smtp.auth: ${SPRING_MAIL_PROPERTIES_SMTP_AUTH} + + access: + domain: ${SPRING_MAIL_ACCESS_DOMAIN} \ No newline at end of file diff --git a/codin-core/src/main/resources/application-jwt.yml b/codin-core/src/main/resources/application-jwt.yml index 848ced73..9ba47355 100644 --- a/codin-core/src/main/resources/application-jwt.yml +++ b/codin-core/src/main/resources/application-jwt.yml @@ -2,7 +2,7 @@ # JWT 설정 파일 spring: jwt: - secret: - expiration: - access: - refresh: + secret: ${SPRING_JWT_SECRET} + expiration: + access: ${SPRING_JWT_EXPIRATION_ACCESS} + refresh: ${SPRING_JWT_EXPIRATION_REFRESH} diff --git a/codin-core/src/main/resources/application-oauth.yml b/codin-core/src/main/resources/application-oauth.yml index 74a30486..67d8d756 100644 --- a/codin-core/src/main/resources/application-oauth.yml +++ b/codin-core/src/main/resources/application-oauth.yml @@ -1,2 +1,46 @@ # 이름 : application-oauth.yml -# oauth2 설정을 위한 파일 \ No newline at end of file +# oauth2 설정을 위한 파일 +spring: + security: + oauth2: + client: + registration: + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + redirect-uri: "${baseUrl}/login/oauth2/code/google" + authorization-grant-type: authorization_code + scope: + - email + - profile + apple: + client-id: ${APPLE_CLIENT_ID} + client-secret: ${APPLE_CLIENT_SECRET} # 동적으로 생성된 애플 client-secret + redirect-uri: "${baseUrl}/login/oauth2/code/apple" + authorization-grant-type: authorization_code + clientAuthenticationMethod: POST + clientName: Apple + scope: + - email + - name + provider: +# google: +# authorization-uri: "https://accounts.google.com/o/oauth2/auth?prompt=consent" + apple: + authorization-uri: ${APPLE_AUTHORIZATION_URI} + token-uri: ${APPLE_TOKEN_URI} + jwk-set-uri: ${APPLE_JWK_SET_URI} + user-name-attribute: sub + user-info-uri: ${APPLE_USER_INFO_URI} + +apple: + auth: + token-url: ${APPLE_AUTH_TOKEN_URL} + public-key-url: ${APPLE_AUTH_PUBLIC_KEY_URL} + redirect-uri: ${APPLE_REDIRECT_URI} + iss: ${APPLE_ISS} + aud: ${APPLE_AUD} + team-id: ${APPLE_TEAM_ID} + key: + id: ${APPLE_KEY_ID} + path: ${APPLE_KEY_PATH} \ No newline at end of file diff --git a/codin-core/src/main/resources/application.yml b/codin-core/src/main/resources/application.yml index 0b14fdeb..a7fbe489 100644 --- a/codin-core/src/main/resources/application.yml +++ b/codin-core/src/main/resources/application.yml @@ -4,27 +4,29 @@ spring: application: name: codin-core + config: + import: optional:file:./.env[.properties], optional:file:./.env.core[.properties] profiles: include: jwt, aws, email, oauth servlet: multipart: - max-request-size: 5MB - max-file-size: 5MB + max-request-size: ${SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE} + max-file-size: ${SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE} data: redis: # [Redis] 설정 - host: localhost - port: 6379 - password: 1234 + host: ${REDIS_HOST} + port: ${REDIS_PORT} + password: ${REDIS_PASSWORD} mongodb: # [MongoDB] 설정 - host: localhost - port: 27017 - database: codin + host: ${MONGODB_HOST} + port: ${MONGODB_PORT} + database: ${MONGODB_DATABASE} google: firebase: - project-id: - key-path: + project-id: ${GOOGLE_FIREBASE_PROJECT_ID} + key-path: ${GOOGLE_FIREBASE_KEY_PATH} server: servlet: @@ -38,7 +40,7 @@ server: springdoc: api-docs: - enabled: true + enabled: ${SPRINGDOC_API_DOCS_ENABLED} swagger-ui: groups-order: DESC doc-expansion: list @@ -48,19 +50,22 @@ springdoc: display-request-duration: true schedule: - path : + path: ${SCHEDULE_PATH} department: - cron: + cron: ${SCHEDULE_DEPARTMENT_CRON:0 00 4 * * *} starinu: - cron: + cron: ${SCHEDULE_STARINU_CRON:0 30 4 * * *} lecture: python: - path: + path: ${LECTURE_PYTHON_PATH} file: - path: + path: ${LECTURE_FILE_PATH} security: #투표 이벤트 공개 API public-api: urls: + - "/posts/category" + - "/posts/{postId}" + - "/comments/post/{postId}" \ No newline at end of file diff --git a/codin-security/build.gradle b/codin-security/build.gradle index cb2d629c..653db60f 100644 --- a/codin-security/build.gradle +++ b/codin-security/build.gradle @@ -20,7 +20,7 @@ repositories { dependencies { // Codin Common 의존성 (공통 응답, 예외 클래스) - //api project(':codin-common') + api project(':codin-common') // Spring Boot Security api 'org.springframework.boot:spring-boot-starter-security' diff --git a/codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java b/codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java index 74cfc1e4..476b891a 100644 --- a/codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java +++ b/codin-security/src/main/java/inu/codin/security/config/PermitAllProperties.java @@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import java.util.ArrayList; import java.util.List; @Getter @@ -12,5 +13,5 @@ @Configuration @ConfigurationProperties(prefix = "security.permit-all") public class PermitAllProperties { - private List urls; + private List urls = new ArrayList<>(); } \ No newline at end of file diff --git a/codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java b/codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java index c02a0569..86ca358c 100644 --- a/codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java +++ b/codin-security/src/main/java/inu/codin/security/config/PublicApiProperties.java @@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import java.util.ArrayList; import java.util.List; @Getter @@ -12,5 +13,5 @@ @Configuration @ConfigurationProperties(prefix = "security.public-api") public class PublicApiProperties { - private List urls; + private List urls = new ArrayList<>(); } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 6d50d7a3..d9485c62 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ rootProject.name = 'codin-backend-repo' include 'codin-core' include 'codin-auth' +include 'codin-common' include 'codin-security' include 'codin-ticketing-api' include 'codin-ticketing-sse' From 4be75fedb14cdfbc2d445aca0650cae1709bf458 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 17 Dec 2025 05:26:42 +0900 Subject: [PATCH 1000/1002] =?UTF-8?q?refactor=20:=20core=20yml=20-=20local?= =?UTF-8?q?/prod=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-local.yml | 20 +++++++++++++++++++ .../src/main/resources/application-prod.yml | 9 +++++++++ codin-core/src/main/resources/application.yml | 12 +++++------ 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 codin-core/src/main/resources/application-local.yml create mode 100644 codin-core/src/main/resources/application-prod.yml diff --git a/codin-core/src/main/resources/application-local.yml b/codin-core/src/main/resources/application-local.yml new file mode 100644 index 00000000..a5ae1c52 --- /dev/null +++ b/codin-core/src/main/resources/application-local.yml @@ -0,0 +1,20 @@ +# 이름 : application-local.yml + +security: + permit-all: + urls: + - "/auth/**" + - "/v3/api/test1" + - "/ws-stomp/**" + - "/suspends" + - "/login/oauth2/code/**" + # --- Swagger / Springdoc --- + - "/swagger-ui/**" + - "/v3/api-docs/**" + - "/swagger-resources/**" + - "/webjars/**" + # --- 정적/루트/헬스체크 --- + - "/" + - "/favicon.ico" + - "/.well-known/**" + - "/actuator/health" diff --git a/codin-core/src/main/resources/application-prod.yml b/codin-core/src/main/resources/application-prod.yml new file mode 100644 index 00000000..e2ec335a --- /dev/null +++ b/codin-core/src/main/resources/application-prod.yml @@ -0,0 +1,9 @@ +# 이름 : application-prod.yml + +security: + #투표 이벤트 공개 API + public-api: + urls: + - "/posts/category" + - "/posts/{postId}" + - "/comments/post/{postId}" \ No newline at end of file diff --git a/codin-core/src/main/resources/application.yml b/codin-core/src/main/resources/application.yml index a7fbe489..cba69db1 100644 --- a/codin-core/src/main/resources/application.yml +++ b/codin-core/src/main/resources/application.yml @@ -7,6 +7,8 @@ spring: config: import: optional:file:./.env[.properties], optional:file:./.env.core[.properties] profiles: + active: + local include: jwt, aws, email, oauth servlet: multipart: @@ -62,10 +64,6 @@ lecture: file: path: ${LECTURE_FILE_PATH} -security: - #투표 이벤트 공개 API - public-api: - urls: - - "/posts/category" - - "/posts/{postId}" - - "/comments/post/{postId}" \ No newline at end of file +logging: + level: + org.springframework.security: DEBUG \ No newline at end of file From ad7ca05f7325c8ad82ccfa8e09375a4cda0ec5d0 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 17 Dec 2025 20:07:01 +0900 Subject: [PATCH 1001/1002] =?UTF-8?q?refactor=20:=20common=20module=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20ticketing=20=EC=97=90=EC=84=9C?= =?UTF-8?q?=20dependency=20=EC=A0=9C=EA=B1=B0=20-=20response,=20exception?= =?UTF-8?q?=20common=20part=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/CodinCommonAutoConfiguration.java | 41 ++++ .../common/dto/BaseTimeEntity.java | 2 +- .../{codin => }/common/dto/Department.java | 2 +- .../common/exception/GlobalErrorCode.java | 2 +- .../common/exception/GlobalException.java | 2 +- .../common/exception/NotFoundException.java | 2 +- .../common/ratelimit/ClientIpUtil.java | 2 +- .../ratelimit/RateLimitBucketConstants.java | 2 +- .../ratelimit/RateLimitInterceptor.java | 6 +- .../common/ratelimit/RateLimitService.java | 2 +- .../common/response/CommonResponse.java | 2 +- .../common/response/ExceptionResponse.java | 2 +- .../common/response/ListResponse.java | 2 +- .../common/response/RateLimitResponse.java | 2 +- .../common/response/SingleResponse.java | 2 +- ...MultipartJackson2HttpMessageConverter.java | 2 +- .../inu}/codin/common/util/ObjectIdUtil.java | 2 +- ...ot.autoconfigure.AutoConfiguration.imports | 1 + codin-core/build.gradle | 1 + .../common/config/Bucket4jRateLimitApp.java | 2 - .../codin/codin/common/config/WebConfig.java | 2 +- .../exception/GlobalExceptionHandler.java | 17 +- .../security/controller/AuthController.java | 2 +- .../security/dto/PortalLoginResponseDto.java | 2 +- .../security/service/AuthCommonService.java | 2 +- .../service/oauth2/AppleAuthService.java | 5 +- .../service/oauth2/GoogleAuthService.java | 5 +- .../util/CustomAuthenticationEntryPoint.java | 2 +- .../util/OAuth2LoginFailureHandler.java | 2 +- .../common/stomp/StompMessageService.java | 2 +- .../block/controller/BlockController.java | 2 +- .../domain/block/entity/BlockEntity.java | 2 +- .../block/exception/BlockErrorCode.java | 2 +- .../block/exception/BlockException.java | 2 +- .../domain/block/service/BlockService.java | 14 +- .../notice/controller/NoticeController.java | 4 +- .../notice/exception/NoticeErrorCode.java | 2 +- .../notice/exception/NoticeException.java | 2 +- .../board/notice/service/NoticeService.java | 16 +- .../controller/QuestionController.java | 6 +- .../QuestionCreateUpdateRequestDto.java | 2 +- .../board/question/entity/QuestionEntity.java | 4 +- .../question/exception/QuestionErrorCode.java | 2 +- .../question/exception/QuestionException.java | 2 +- .../repository/QuestionRepository.java | 2 +- .../question/service/QuestionService.java | 2 +- .../voice/controller/VoiceController.java | 4 +- .../voice/dto/VoiceBoxCreateRequest.java | 2 +- .../voice/dto/VoiceBoxDetailResponse.java | 8 +- .../board/voice/entity/VoiceEntity.java | 4 +- .../voice/repository/VoiceRepository.java | 2 +- .../board/voice/service/VoiceService.java | 10 +- .../controller/CalendarControllerImpl.java | 2 +- .../swagger/CalendarController.java | 2 +- .../calendar/dto/CalendarCreateRequest.java | 2 +- .../calendar/dto/CalendarCreateResponse.java | 4 +- .../codin/domain/calendar/dto/EventDto.java | 4 +- .../calendar/entity/CalendarEntity.java | 4 +- .../calendar/exception/CalendarErrorCode.java | 2 +- .../calendar/exception/CalendarException.java | 2 +- .../calendar/service/CalendarService.java | 2 +- .../controller/ChatRoomController.java | 4 +- .../domain/chat/chatroom/entity/ChatRoom.java | 2 +- .../chat/chatroom/entity/ParticipantInfo.java | 2 +- .../chatroom/service/ChatRoomService.java | 14 +- .../controller/ChattingController.java | 2 +- .../domain/chat/chatting/entity/Chatting.java | 2 +- .../service/ChattingEventListener.java | 2 +- .../chatting/service/ChattingService.java | 8 +- .../chat/exception/ChatRoomErrorCode.java | 2 +- .../chat/exception/ChatRoomException.java | 2 +- .../chat/exception/ChattingErrorCode.java | 2 +- .../chat/exception/ChattingException.java | 2 +- .../email/controller/EmailController.java | 2 +- .../domain/email/entity/EmailAuthEntity.java | 2 +- .../service/PasswordResetEmailService.java | 2 +- .../domain/info/controller/LabController.java | 4 +- .../info/controller/OfficeController.java | 4 +- .../info/controller/PartnerController.java | 4 +- .../info/controller/ProfessorController.java | 6 +- .../request/LabCreateUpdateRequestDto.java | 2 +- .../dto/request/PartnerCreateRequestDto.java | 2 +- .../ProfessorCreateUpdateRequestDto.java | 2 +- .../dto/response/LabThumbnailResponseDto.java | 2 +- .../response/OfficeDetailsResponseDto.java | 2 +- .../response/PartnerDetailsResponseDto.java | 2 +- .../dto/response/PartnerListResponseDto.java | 2 +- .../response/ProfessorListResponseDto.java | 2 +- .../ProfessorThumbnailResponseDto.java | 2 +- .../codin/codin/domain/info/entity/Info.java | 4 +- .../codin/codin/domain/info/entity/Lab.java | 2 +- .../domain/info/entity/OfficeMember.java | 2 +- .../codin/domain/info/entity/Partner.java | 2 +- .../codin/domain/info/entity/Professor.java | 2 +- .../domain/info/exception/InfoErrorCode.java | 2 +- .../domain/info/exception/InfoException.java | 2 +- .../info/repository/InfoRepository.java | 2 +- .../domain/info/service/OfficeService.java | 2 +- .../domain/info/service/ProfessorService.java | 2 +- .../lecture/controller/LectureController.java | 6 +- .../controller/LectureUploadController.java | 2 +- .../review/controller/ReviewController.java | 2 +- .../domain/review/entity/ReviewEntity.java | 2 +- .../domain/review/service/ReviewService.java | 10 +- .../controller/LectureRoomController.java | 2 +- .../domain/lecture/entity/LectureEntity.java | 2 +- .../lecture/repository/LectureRepository.java | 2 +- .../lecture/service/LectureService.java | 4 +- .../like/controller/LikeController.java | 2 +- .../codin/domain/like/entity/LikeEntity.java | 2 +- .../domain/like/service/LikeService.java | 8 +- .../controller/NotificationController.java | 4 +- .../entity/NotificationEntity.java | 2 +- .../service/NotificationService.java | 8 +- .../post/controller/PostController.java | 4 +- .../comment/controller/CommentController.java | 4 +- .../domain/comment/entity/CommentEntity.java | 2 +- .../comment/exception/CommentErrorCode.java | 2 +- .../comment/exception/CommentException.java | 2 +- .../controller/ReplyCommentController.java | 2 +- .../reply/entity/ReplyCommentEntity.java | 2 +- .../reply/exception/ReplyErrorCode.java | 2 +- .../reply/exception/ReplyException.java | 2 +- .../reply/service/ReplyCommandService.java | 7 +- .../reply/service/ReplyQueryService.java | 6 +- .../service/CommentCommandService.java | 7 +- .../comment/service/CommentQueryService.java | 8 +- .../domain/hits/exception/HitsErrorCode.java | 2 +- .../domain/hits/exception/HitsException.java | 2 +- .../poll/controller/PollController.java | 2 +- .../post/domain/poll/entity/PollEntity.java | 2 +- .../domain/poll/exception/PollErrorCode.java | 2 +- .../domain/poll/exception/PollException.java | 2 +- .../poll/service/PollCommandService.java | 15 +- .../codin/domain/post/entity/PostEntity.java | 2 +- .../domain/post/exception/PostErrorCode.java | 2 +- .../domain/post/exception/PostException.java | 2 +- .../exception/SchedulerErrorCode.java | 2 +- .../exception/SchedulerException.java | 2 +- .../domain/post/security/OwnershipPolicy.java | 10 +- .../post/service/PostCommandService.java | 18 +- .../domain/post/service/PostDtoAssembler.java | 6 +- .../domain/post/service/PostQueryService.java | 10 +- .../report/controller/ReportController.java | 2 +- .../domain/report/entity/ReportEntity.java | 2 +- .../domain/report/service/ReportService.java | 12 +- .../scrap/controller/ScrapController.java | 2 +- .../domain/scrap/entity/ScrapEntity.java | 2 +- .../domain/scrap/service/ScrapService.java | 8 +- .../user/controller/UserController.java | 2 +- ...cketingParticipationInfoUpdateRequest.java | 2 +- .../dto/response/UserInfoResponseDto.java | 2 +- ...serTicketingParticipationInfoResponse.java | 2 +- .../codin/domain/user/entity/UserEntity.java | 4 +- .../user/security/CustomUserDetails.java | 2 +- .../domain/user/service/UserService.java | 24 +-- .../domain/user/service/UserValidator.java | 1 - .../fcm/controller/FcmControllerImpl.java | 2 +- .../fcm/controller/swagger/FcmController.java | 2 +- .../infra/fcm/entity/FcmTokenEntity.java | 2 +- .../codin/infra/fcm/service/FcmService.java | 12 +- .../block/controller/BlockControllerTest.java | 2 +- .../block/service/BlockServiceTest.java | 52 ++--- .../PasswordResetEmailServiceTest.java | 2 +- .../domain/post/PostCommandServiceTest.java | 33 ++- .../domain/post/PostDtoAssemblerTest.java | 6 +- .../domain/post/PostQueryServiceTest.java | 8 +- .../comment/CommentCommandServiceTest.java | 20 +- .../comment/CommentQueryServiceTest.java | 14 +- .../reply/ReplyCommandServiceTest.java | 15 +- .../comment/reply/ReplyQueryServiceTest.java | 14 +- .../domain/poll/PollCommandServiceTest.java | 16 +- codin-lecture-api/build.gradle | 3 + .../exception/CustomAccessDeniedHandler.java | 4 +- .../security/exception/SecurityErrorCode.java | 37 ++-- .../security/exception/SecurityException.java | 20 ++ .../filter/ExceptionHandlerFilter.java | 3 +- .../{SecurityUtils.java => SecurityUtil.java} | 93 ++++++++- codin-ticketing-api/build.gradle | 1 + .../common/exception/GlobalErrorCode.java | 8 - .../common/exception/GlobalException.java | 14 -- .../exception/GlobalExceptionHandler.java | 4 +- .../common/response/CommonResponse.java | 18 -- .../common/response/ExceptionResponse.java | 7 - .../common/response/ListResponse.java | 17 -- .../common/response/SingleResponse.java | 15 -- ...MultipartJackson2HttpMessageConverter.java | 34 --- .../config/SecurityConfig.java | 156 +++++++------- .../codinticketingapi/config/WebConfig.java | 2 +- .../controller/EventAdminControllerImpl.java | 2 +- .../swagger/EventAdminController.java | 2 +- .../admin/exception/ExcelErrorCode.java | 2 +- .../admin/exception/ExcelException.java | 2 +- .../admin/service/EventAdminService.java | 3 +- .../image/exception/ImageErrorCode.java | 2 +- .../image/exception/ImageException.java | 2 +- .../ticketing/controller/EventController.java | 2 +- .../controller/TicketingController.java | 2 +- .../exception/TicketingErrorCode.java | 2 +- .../exception/TicketingException.java | 2 +- .../service/ParticipationService.java | 2 +- .../domain/user/config/FeignClientConfig.java | 2 +- .../domain/user/exception/UserErrorCode.java | 2 +- .../domain/user/exception/UserException.java | 2 +- .../security/exception/SecurityErrorCode.java | 27 --- .../security/exception/SecurityException.java | 14 -- .../SecurityExceptionHandlerFilter.java | 106 +++++----- .../security/util/SecurityUtil.java | 194 +++++++++--------- .../src/main/resources/application-local.yml | 20 ++ .../src/main/resources/application-prod.yml | 6 + .../src/main/resources/application.yml | 3 + codin-ticketing-sse/build.gradle | 1 + .../common/exception/GlobalErrorCode.java | 8 - .../common/exception/GlobalException.java | 15 -- .../exception/GlobalExceptionHandler.java | 8 +- .../common/response/CommonResponse.java | 18 -- .../common/response/ExceptionResponse.java | 7 - .../exception/CustomAccessDeniedHandler.java | 33 --- .../security/exception/SecurityErrorCode.java | 27 --- .../security/exception/SecurityException.java | 16 -- .../SecurityExceptionHandlerFilter.java | 53 ----- .../filter/TokenValidationFilter.java | 72 ------- .../security/jwt/JwtTokenValidator.java | 82 -------- .../security/jwt/TokenUserDetails.java | 78 ------- .../security/util/SecurityUtil.java | 98 --------- .../security/util/TokenUtil.java | 43 ---- .../sse/controller/SseController.java | 2 +- .../sse/exception/SseErrorCode.java | 2 +- .../sse/exception/SseException.java | 2 +- docker/local/local-compose.yml | 20 +- 230 files changed, 861 insertions(+), 1367 deletions(-) create mode 100644 codin-common/src/main/java/inu/codin/common/config/CodinCommonAutoConfiguration.java rename codin-common/src/main/java/inu/codin/{codin => }/common/dto/BaseTimeEntity.java (96%) rename codin-common/src/main/java/inu/codin/{codin => }/common/dto/Department.java (97%) rename codin-common/src/main/java/inu/codin/{codin => }/common/exception/GlobalErrorCode.java (76%) rename codin-common/src/main/java/inu/codin/{codin => }/common/exception/GlobalException.java (86%) rename codin-common/src/main/java/inu/codin/{codin => }/common/exception/NotFoundException.java (76%) rename codin-common/src/main/java/inu/codin/{codin => }/common/ratelimit/ClientIpUtil.java (95%) rename codin-common/src/main/java/inu/codin/{codin => }/common/ratelimit/RateLimitBucketConstants.java (93%) rename codin-common/src/main/java/inu/codin/{codin => }/common/ratelimit/RateLimitInterceptor.java (93%) rename codin-common/src/main/java/inu/codin/{codin => }/common/ratelimit/RateLimitService.java (98%) rename codin-common/src/main/java/inu/codin/{codin => }/common/response/CommonResponse.java (87%) rename codin-common/src/main/java/inu/codin/{codin => }/common/response/ExceptionResponse.java (82%) rename codin-common/src/main/java/inu/codin/{codin => }/common/response/ListResponse.java (88%) rename codin-common/src/main/java/inu/codin/{codin => }/common/response/RateLimitResponse.java (95%) rename codin-common/src/main/java/inu/codin/{codin => }/common/response/SingleResponse.java (86%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/util/MultipartJackson2HttpMessageConverter.java (96%) rename {codin-core/src/main/java/inu/codin => codin-common/src/main/java/inu}/codin/common/util/ObjectIdUtil.java (96%) create mode 100644 codin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename {codin-ticketing-api/src/main/java/inu/codin/codinticketingapi => codin-security/src/main/java/inu/codin}/security/exception/CustomAccessDeniedHandler.java (91%) create mode 100644 codin-security/src/main/java/inu/codin/security/exception/SecurityException.java rename codin-security/src/main/java/inu/codin/security/util/{SecurityUtils.java => SecurityUtil.java} (54%) delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalErrorCode.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalException.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/CommonResponse.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ExceptionResponse.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ListResponse.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/SingleResponse.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/util/MultipartJackson2HttpMessageConverter.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityErrorCode.java delete mode 100644 codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityException.java create mode 100644 codin-ticketing-api/src/main/resources/application-local.yml create mode 100644 codin-ticketing-api/src/main/resources/application-prod.yml delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalErrorCode.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalException.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/CommonResponse.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/ExceptionResponse.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/CustomAccessDeniedHandler.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityErrorCode.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityException.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/SecurityExceptionHandlerFilter.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/TokenValidationFilter.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/JwtTokenValidator.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/TokenUserDetails.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/SecurityUtil.java delete mode 100644 codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/TokenUtil.java diff --git a/codin-common/src/main/java/inu/codin/common/config/CodinCommonAutoConfiguration.java b/codin-common/src/main/java/inu/codin/common/config/CodinCommonAutoConfiguration.java new file mode 100644 index 00000000..45584e55 --- /dev/null +++ b/codin-common/src/main/java/inu/codin/common/config/CodinCommonAutoConfiguration.java @@ -0,0 +1,41 @@ +package inu.codin.common.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import inu.codin.common.util.MultipartJackson2HttpMessageConverter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * Codin Common 모듈의 자동 구성 클래스 + * + * 목적: + * - codin-common 모듈을 의존성으로 추가하기만 해도 + * MultipartJackson2HttpMessageConverter 빈이 자동으로 등록되도록 함 + * + * 특징: + * - Spring Boot 3 방식의 AutoConfiguration + * - 애플리케이션(@SpringBootApplication)에서 + * scanBasePackages를 수정하지 않아도 됨 + */ +@AutoConfiguration +public class CodinCommonAutoConfiguration { + + /** + * Multipart 요청(JSON + File)을 처리하기 위한 + * 커스텀 HttpMessageConverter 빈 등록 + * + * ObjectMapper는 Spring Boot가 기본으로 제공하는 + * 공용 Jackson ObjectMapper 빈을 주입받아 사용 + * + * → 이렇게 하면: + * - Jackson 설정(LocalDateTime, snake_case 등) + * - 기존 API JSON 직렬화 규칙 + * 을 그대로 재사용 가능 + */ + @Bean + public MultipartJackson2HttpMessageConverter multipartJackson2HttpMessageConverter( + ObjectMapper objectMapper + ) { + return new MultipartJackson2HttpMessageConverter(objectMapper); + } +} \ No newline at end of file diff --git a/codin-common/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java b/codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java similarity index 96% rename from codin-common/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java rename to codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java index 595a1377..1867ed45 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/dto/BaseTimeEntity.java +++ b/codin-common/src/main/java/inu/codin/common/dto/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.dto; +package inu.codin.common.dto; import lombok.Getter; import org.springframework.data.annotation.CreatedBy; diff --git a/codin-common/src/main/java/inu/codin/codin/common/dto/Department.java b/codin-common/src/main/java/inu/codin/common/dto/Department.java similarity index 97% rename from codin-common/src/main/java/inu/codin/codin/common/dto/Department.java rename to codin-common/src/main/java/inu/codin/common/dto/Department.java index 841b1ed4..926a2b9d 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/dto/Department.java +++ b/codin-common/src/main/java/inu/codin/common/dto/Department.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.dto; +package inu.codin.common.dto; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/codin-common/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java b/codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java similarity index 76% rename from codin-common/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java rename to codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java index 70c3f2f2..f1ef3fc4 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/exception/GlobalErrorCode.java +++ b/codin-common/src/main/java/inu/codin/common/exception/GlobalErrorCode.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.exception; +package inu.codin.common.exception; import org.springframework.http.HttpStatus; diff --git a/codin-common/src/main/java/inu/codin/codin/common/exception/GlobalException.java b/codin-common/src/main/java/inu/codin/common/exception/GlobalException.java similarity index 86% rename from codin-common/src/main/java/inu/codin/codin/common/exception/GlobalException.java rename to codin-common/src/main/java/inu/codin/common/exception/GlobalException.java index 6b2c2932..210c24c5 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/exception/GlobalException.java +++ b/codin-common/src/main/java/inu/codin/common/exception/GlobalException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.exception; +package inu.codin.common.exception; import lombok.Getter; diff --git a/codin-common/src/main/java/inu/codin/codin/common/exception/NotFoundException.java b/codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java similarity index 76% rename from codin-common/src/main/java/inu/codin/codin/common/exception/NotFoundException.java rename to codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java index 6ea57e46..a97f0b44 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/exception/NotFoundException.java +++ b/codin-common/src/main/java/inu/codin/common/exception/NotFoundException.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.exception; +package inu.codin.common.exception; public class NotFoundException extends RuntimeException{ diff --git a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java b/codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java similarity index 95% rename from codin-common/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java index 5d80709e..dfe4d07b 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/ClientIpUtil.java +++ b/codin-common/src/main/java/inu/codin/common/ratelimit/ClientIpUtil.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.ratelimit; +package inu.codin.common.ratelimit; import jakarta.servlet.http.HttpServletRequest; diff --git a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java similarity index 93% rename from codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java index 941decfb..4bba43d9 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitBucketConstants.java +++ b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitBucketConstants.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.ratelimit; +package inu.codin.common.ratelimit; import java.time.Duration; diff --git a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java similarity index 93% rename from codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java index 0ce93b5d..c05fc6fc 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitInterceptor.java +++ b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitInterceptor.java @@ -1,6 +1,6 @@ -package inu.codin.codin.common.ratelimit; +package inu.codin.common.ratelimit; -import inu.codin.codin.common.response.RateLimitResponse; +import inu.codin.common.response.RateLimitResponse; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; import io.github.bucket4j.ConsumptionProbe; @@ -15,7 +15,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static inu.codin.codin.common.ratelimit.RateLimitBucketConstants.*; +import static inu.codin.common.ratelimit.RateLimitBucketConstants.*; /** * Reference : https://velog.io/@whcksdud8/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Rate-limit-%ED%95%B8%EB%93%A4%EB%A7%81-%EB%B0%8F-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81 diff --git a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java similarity index 98% rename from codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java rename to codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java index 17286444..33c3c832 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/ratelimit/RateLimitService.java +++ b/codin-common/src/main/java/inu/codin/common/ratelimit/RateLimitService.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.ratelimit; +package inu.codin.common.ratelimit; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/codin-common/src/main/java/inu/codin/codin/common/response/CommonResponse.java b/codin-common/src/main/java/inu/codin/common/response/CommonResponse.java similarity index 87% rename from codin-common/src/main/java/inu/codin/codin/common/response/CommonResponse.java rename to codin-common/src/main/java/inu/codin/common/response/CommonResponse.java index e35ef50b..1cb79d70 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/response/CommonResponse.java +++ b/codin-common/src/main/java/inu/codin/common/response/CommonResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.common.response; import lombok.Getter; diff --git a/codin-common/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java b/codin-common/src/main/java/inu/codin/common/response/ExceptionResponse.java similarity index 82% rename from codin-common/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java rename to codin-common/src/main/java/inu/codin/common/response/ExceptionResponse.java index b9af8553..73a42a0e 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/response/ExceptionResponse.java +++ b/codin-common/src/main/java/inu/codin/common/response/ExceptionResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.common.response; import lombok.Getter; diff --git a/codin-common/src/main/java/inu/codin/codin/common/response/ListResponse.java b/codin-common/src/main/java/inu/codin/common/response/ListResponse.java similarity index 88% rename from codin-common/src/main/java/inu/codin/codin/common/response/ListResponse.java rename to codin-common/src/main/java/inu/codin/common/response/ListResponse.java index 1f2b1c11..067e7823 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/response/ListResponse.java +++ b/codin-common/src/main/java/inu/codin/common/response/ListResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.common.response; import lombok.Builder; import lombok.Getter; diff --git a/codin-common/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java b/codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java similarity index 95% rename from codin-common/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java rename to codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java index caf646ff..51a19a57 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/response/RateLimitResponse.java +++ b/codin-common/src/main/java/inu/codin/common/response/RateLimitResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.common.response; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpStatus; diff --git a/codin-common/src/main/java/inu/codin/codin/common/response/SingleResponse.java b/codin-common/src/main/java/inu/codin/common/response/SingleResponse.java similarity index 86% rename from codin-common/src/main/java/inu/codin/codin/common/response/SingleResponse.java rename to codin-common/src/main/java/inu/codin/common/response/SingleResponse.java index 32955bcc..ab7ba32d 100644 --- a/codin-common/src/main/java/inu/codin/codin/common/response/SingleResponse.java +++ b/codin-common/src/main/java/inu/codin/common/response/SingleResponse.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.response; +package inu.codin.common.response; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java b/codin-common/src/main/java/inu/codin/common/util/MultipartJackson2HttpMessageConverter.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java rename to codin-common/src/main/java/inu/codin/common/util/MultipartJackson2HttpMessageConverter.java index be518234..1ebf7490 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/util/MultipartJackson2HttpMessageConverter.java +++ b/codin-common/src/main/java/inu/codin/common/util/MultipartJackson2HttpMessageConverter.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.util; +package inu.codin.common.util; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; diff --git a/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java b/codin-common/src/main/java/inu/codin/common/util/ObjectIdUtil.java similarity index 96% rename from codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java rename to codin-common/src/main/java/inu/codin/common/util/ObjectIdUtil.java index 869d9ad2..f319db29 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/util/ObjectIdUtil.java +++ b/codin-common/src/main/java/inu/codin/common/util/ObjectIdUtil.java @@ -1,4 +1,4 @@ -package inu.codin.codin.common.util; +package inu.codin.common.util; import org.bson.types.ObjectId; diff --git a/codin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/codin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..10297826 --- /dev/null +++ b/codin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +inu.codin.common.config.CodinCommonAutoConfiguration \ No newline at end of file diff --git a/codin-core/build.gradle b/codin-core/build.gradle index 653098c3..21d178dc 100644 --- a/codin-core/build.gradle +++ b/codin-core/build.gradle @@ -35,6 +35,7 @@ dependencyManagement { dependencies { // Security - codin-security 공통 모듈 사용 implementation project(':codin-security') + implementation project(':codin-common') // FCM implementation 'com.google.firebase:firebase-admin:7.3.0' diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java index 7da775dd..336622ae 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/Bucket4jRateLimitApp.java @@ -1,7 +1,5 @@ package inu.codin.codin.common.config; -import inu.codin.codin.common.ratelimit.RateLimitInterceptor; - // Lecture API - 좋아요 개수 Feign 요청으로 인한 RateLimiting 에러로 주석화 //@Configuration //@RequiredArgsConstructor diff --git a/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java index 27c8112c..128ea07e 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java +++ b/codin-core/src/main/java/inu/codin/codin/common/config/WebConfig.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.config; -import inu.codin.codin.common.util.MultipartJackson2HttpMessageConverter; +import inu.codin.common.util.MultipartJackson2HttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; diff --git a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java index c3fe51da..1d5dd37a 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,9 @@ package inu.codin.codin.common.exception; -import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalException; +import inu.codin.common.exception.NotFoundException; +import inu.codin.common.response.ExceptionResponse; import inu.codin.security.exception.JwtException; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; @@ -14,6 +17,7 @@ import inu.codin.codin.domain.chat.exception.ChattingException; import inu.codin.codin.domain.info.exception.InfoErrorCode; import inu.codin.codin.domain.info.exception.InfoException; +import inu.codin.security.exception.SecurityErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.RedisSystemException; import org.springframework.http.HttpStatus; @@ -83,12 +87,11 @@ protected ResponseEntity handleNotFoundException(NotFoundExce @ExceptionHandler(JwtException.class) protected ResponseEntity handleJwtException(JwtException e) { - if (e.getErrorCode().getErrorCode().equals("SEC_005")){ - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(new ExceptionResponse(e.getMessage(), HttpStatus.FORBIDDEN.value())); - } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(new ExceptionResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.value())); + SecurityErrorCode code = e.getErrorCode(); + HttpStatus status = code.httpStatus(); + + return ResponseEntity.status(status) + .body(new ExceptionResponse(code.message(), status.value())); } @ExceptionHandler(MethodArgumentNotValidException.class) diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java index 8c99c4bb..d9bbed3f 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/controller/AuthController.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.security.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.common.security.service.AuthCommonService; import inu.codin.codin.common.security.service.AuthSessionService; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java b/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java index 018bdeae..2509b5a2 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/dto/PortalLoginResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.security.dto; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java index cd153883..6b6bc66d 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/AuthCommonService.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.security.service; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.dto.SignUpAndLoginRequestDto; import inu.codin.codin.domain.user.dto.request.UserProfileRequestDto; import inu.codin.codin.domain.user.entity.UserEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java index aaa5e374..ab6aa129 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/AppleAuthService.java @@ -1,9 +1,8 @@ package inu.codin.codin.common.security.service.oauth2; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.dto.Department; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java index 839744db..e8bc330c 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/service/oauth2/GoogleAuthService.java @@ -1,9 +1,8 @@ package inu.codin.codin.common.security.service.oauth2; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.dto.Department; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.common.security.enums.AuthResultStatus; -import inu.codin.codin.common.security.service.oauth2.AbstractAuthService; import inu.codin.security.service.JwtService; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java index 342d6728..e18d0e81 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/CustomAuthenticationEntryPoint.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.util; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.common.response.ExceptionResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java index 9324d6b7..e9af4cbf 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java +++ b/codin-core/src/main/java/inu/codin/codin/common/security/util/OAuth2LoginFailureHandler.java @@ -1,7 +1,7 @@ package inu.codin.codin.common.security.util; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.common.response.ExceptionResponse; import inu.codin.security.service.JwtService; import inu.codin.codin.common.util.CookieUtil; import jakarta.servlet.http.HttpServletRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java index 410682f1..878e0939 100644 --- a/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java +++ b/codin-core/src/main/java/inu/codin/codin/common/stomp/StompMessageService.java @@ -1,6 +1,6 @@ package inu.codin.codin.common.stomp; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; import inu.codin.codin.domain.chat.chatting.dto.event.UpdateUnreadCountEvent; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java index 475b3471..3a5e40b8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/controller/BlockController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.block.service.BlockService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java index 4b74eb8b..6380d2ab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/entity/BlockEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import lombok.Builder; import lombok.Getter; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java index d99b98bd..aafeff0c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java index a7fc2d36..1f6f9e24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/exception/BlockException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java index 8e04ae09..36f9bc0e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/block/service/BlockService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.block.service; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; @@ -14,7 +14,7 @@ import java.util.List; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @Slf4j @@ -29,7 +29,7 @@ public class BlockService { * @param strBlockedUserId */ public void blockUser(String strBlockedUserId) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { @@ -57,7 +57,7 @@ public void blockUser(String strBlockedUserId) { * @param strBlockedUserId 차단 해제할 유저 */ public void unblockUser(String strBlockedUserId) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ObjectId blockedId = ObjectIdUtil.toObjectId(strBlockedUserId); if (userId.equals(blockedId)) { @@ -84,12 +84,12 @@ public void unblockUser(String strBlockedUserId) { * @return 차단한 유저 목록 (빈 리스트가 제공될 수 있음) */ public List getBlockedUsers() { - ObjectId currentUserId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); + ObjectId currentUserId = toObjectId(SecurityUtil.getCurrentUserIdOrNull()); if (currentUserId == null) { return List.of(); } - return blockRepository.findByUserId(toObjectId(SecurityUtils.getCurrentUserId())) + return blockRepository.findByUserId(toObjectId(SecurityUtil.getCurrentUserId())) .map(BlockEntity::getBlockedUsers) .orElse(List.of()); } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java index 92300c67..3b72ab54 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/controller/NoticeController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.board.notice.controller; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.dto.Department; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; import inu.codin.codin.domain.board.notice.dto.response.NoticePageResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java index f19fbe6e..5d88b796 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.notice.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java index 73a8e483..3599e3b6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/exception/NoticeException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.notice.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java index 5f7d02a9..1787e039 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/notice/service/NoticeService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.board.notice.service; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.util.ObjectIdUtil; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.dto.Department; +import inu.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.board.notice.dto.request.NoticeCreateUpdateRequestDTO; import inu.codin.codin.domain.board.notice.dto.response.NoticeDetailResponseDto; import inu.codin.codin.domain.board.notice.dto.response.NoticeListResponseDto; @@ -33,7 +33,7 @@ import java.util.Map; import java.util.regex.Pattern; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @Slf4j @@ -84,7 +84,7 @@ public NoticeDetailResponseDto getNoticesWithDetail(String postId) { */ public Map createNotice(NoticeCreateUpdateRequestDTO noticeCreateUpdateRequestDTO, List noticeImages) { List imageUrls = s3Service.handleImageUpload(noticeImages); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); validateUserAndPost(userId); UserEntity user = getUserEntity(userId); @@ -152,7 +152,7 @@ private UserEntity getUserEntity(ObjectId userId) { } private NoticeDetailResponseDto.UserInfo getUserInfoAboutNotice(ObjectId postUserId, ObjectId postId){ - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); return NoticeDetailResponseDto.UserInfo.builder() .isScrap(scrapService.isPostScraped(postId, userId)) .isMine(postUserId.equals(userId)) @@ -160,9 +160,9 @@ private NoticeDetailResponseDto.UserInfo getUserInfoAboutNotice(ObjectId postUse } private void validateUserAndPost(ObjectId postUserId) { - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER)) { + if (SecurityUtil.getCurrentUserRole().equals(UserRole.USER)) { throw new NoticeException(NoticeErrorCode.NOTICE_ACCESS_DENIED); } - SecurityUtils.validateUser(ObjectIdUtil.toString(postUserId)); + SecurityUtil.validateUser(ObjectIdUtil.toString(postUserId)); } } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java index d9b9b328..85ba680a 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/controller/QuestionController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.board.question.controller; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.dto.Department; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.board.question.dto.request.QuestionCreateUpdateRequestDto; import inu.codin.codin.domain.board.question.dto.response.QuestionResponseDto; import inu.codin.codin.domain.board.question.service.QuestionService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java index 66065957..3a86bc7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/dto/request/QuestionCreateUpdateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.question.dto.request; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java index b36ee990..869c13a9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/entity/QuestionEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.board.question.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.board.question.dto.request.QuestionCreateUpdateRequestDto; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java index cc1e750d..9ec7ca17 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.question.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java index 47f166e6..9fa56679 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/exception/QuestionException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.question.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter public class QuestionException extends GlobalException { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java index 75ebcdf4..d0f4badc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/repository/QuestionRepository.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.question.repository; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.board.question.entity.QuestionEntity; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java index 882e8f94..1d9aedae 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/question/service/QuestionService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.question.service; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.board.question.dto.request.QuestionCreateUpdateRequestDto; import inu.codin.codin.domain.board.question.dto.response.QuestionResponseDto; import inu.codin.codin.domain.board.question.entity.QuestionEntity; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java index 50de9eec..47838474 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/controller/VoiceController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.board.voice.controller; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.dto.Department; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.board.voice.service.VoiceService; import inu.codin.codin.domain.board.voice.dto.VoiceBoxAnswerRequest; import inu.codin.codin.domain.board.voice.dto.VoiceBoxCreateRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java index de0e1e04..cbc47709 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxCreateRequest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.voice.dto; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java index b310b4a0..f203055c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/dto/VoiceBoxDetailResponse.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.board.voice.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import inu.codin.codin.common.dto.Department; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.dto.Department; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.board.voice.entity.VoiceEntity; import lombok.Builder; import lombok.Getter; @@ -36,8 +36,8 @@ public static VoiceBoxDetailResponse of(VoiceEntity voiceEntity) { .department(voiceEntity.getDepartment()) .question(voiceEntity.getQuestion()) .answer(voiceEntity.getAnswer()) - .isUserInPositive(voiceEntity.getPositiveVoteIds() == null ? null : voiceEntity.getPositiveVoteIds().contains(SecurityUtils.getCurrentUserId())) - .isUserInOpposite(voiceEntity.getOppositeVoteIds() == null ? null : voiceEntity.getOppositeVoteIds().contains(SecurityUtils.getCurrentUserId())) + .isUserInPositive(voiceEntity.getPositiveVoteIds() == null ? null : voiceEntity.getPositiveVoteIds().contains(SecurityUtil.getCurrentUserId())) + .isUserInOpposite(voiceEntity.getOppositeVoteIds() == null ? null : voiceEntity.getOppositeVoteIds().contains(SecurityUtil.getCurrentUserId())) .userCountPositive(voiceEntity.getPositiveVoteIds() == null ? null : voiceEntity.getPositiveVoteIds().size()) .userCountOpposite(voiceEntity.getOppositeVoteIds() == null ? null : voiceEntity.getOppositeVoteIds().size()) .createdAt(voiceEntity.getCreatedAt()) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java index 387bae28..2707e263 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/entity/VoiceEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.board.voice.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.Department; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java index 2ce6feaf..d0a7ca7c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/repository/VoiceRepository.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.board.voice.repository; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.board.voice.entity.VoiceEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java index feac8e02..48f2269f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/board/voice/service/VoiceService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.board.voice.service; -import inu.codin.codin.common.dto.Department; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.common.dto.Department; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.board.voice.dto.VoiceBoxCreateRequest; import inu.codin.codin.domain.board.voice.dto.VoiceBoxDetailResponse; import inu.codin.codin.domain.board.voice.dto.VoiceBoxPageResponse; @@ -19,7 +19,7 @@ import java.util.List; import java.util.stream.Collectors; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -53,7 +53,7 @@ public void toggleVoiceBox(String boxId, Boolean positive) { VoiceEntity voiceEntity = voiceRepository.findByIdAndNotDeleted(objectId) .orElseThrow(() -> new IllegalArgumentException("익명의 소리함 질문을 찾을 수 없습니다.")); - ObjectId currentUserId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId currentUserId = toObjectId(SecurityUtil.getCurrentUserId()); if (positive) { voiceEntity.votePositiveToggle(currentUserId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java index ead43c76..1b4d0f8b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/CalendarControllerImpl.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.calendar.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.calendar.controller.swagger.CalendarController; import inu.codin.codin.domain.calendar.dto.CalendarCreateRequest; import inu.codin.codin.domain.calendar.dto.CalendarCreateResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java index 303a79f7..453a6cb0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/controller/swagger/CalendarController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.calendar.controller.swagger; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.calendar.dto.CalendarCreateRequest; import inu.codin.codin.domain.calendar.dto.CalendarCreateResponse; import inu.codin.codin.domain.calendar.dto.CalendarMonthResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java index 0c4ccd02..3887b41f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateRequest.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.calendar.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java index ef4e9c07..1e5850b3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/CalendarCreateResponse.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.calendar.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.common.dto.Department; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.calendar.entity.CalendarEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java index f6652180..f879d392 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/dto/EventDto.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.calendar.dto; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.common.dto.Department; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.calendar.entity.CalendarEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java index d9368b2d..a67beb08 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/entity/CalendarEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.calendar.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.Department; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java index 233528e1..504a00cd 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.calendar.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java index 2613c31a..677612f9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/exception/CalendarException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.calendar.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java index 73389f3d..0d009e51 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/calendar/service/CalendarService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.calendar.service; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.calendar.dto.*; import inu.codin.codin.domain.calendar.entity.CalendarEntity; import inu.codin.codin.domain.calendar.exception.CalendarErrorCode; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java index 3b94ba40..5a575fb3 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/controller/ChatRoomController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.controller; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java index 21cfbb14..51075b9d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ChatRoom.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java index 6e4cc10a..5cadbe7f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/entity/ParticipantInfo.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatroom.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java index 90d9bd86..f5258627 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatroom/service/ChatRoomService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.chat.chatroom.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.chat.chatroom.dto.request.ChatRoomCreateRequestDto; import inu.codin.codin.domain.chat.chatroom.dto.response.ChatRoomListResponseDto; @@ -21,7 +21,7 @@ import java.util.*; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -36,7 +36,7 @@ public class ChatRoomService { public Map createChatRoom(ChatRoomCreateRequestDto chatRoomCreateRequestDto) { - ObjectId senderId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId senderId = toObjectId(SecurityUtil.getCurrentUserId()); isValidated(chatRoomCreateRequestDto, senderId); //유효성 검사 log.info("[채팅방 생성 요청] 송신자 ID: {}, 수신자 ID: {}", senderId, chatRoomCreateRequestDto.getReceiverId()); @@ -78,7 +78,7 @@ private void isValidated(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obje } public List getAllChatRoomByUser() { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[유저의 채팅방 조회] 유저 ID: {}", userId); // 차단 목록 조회 List blockedUsersId = blockService.getBlockedUsers(); @@ -94,7 +94,7 @@ public List getAllChatRoomByUser() { } public void leaveChatRoom(String chatRoomId) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[채팅방 나가기 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) @@ -124,7 +124,7 @@ public void leaveChatRoom(String chatRoomId) { } public void setNotificationChatRoom(String chatRoomId) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[알림 설정 요청] 유저 ID: {}, 채팅방 ID: {}", userId, chatRoomId); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java index 53f45c62..3deb37f6 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/controller/ChattingController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatting.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import inu.codin.codin.domain.chat.chatting.service.ChattingService; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java index 03bc7323..04e3642e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/entity/Chatting.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatting.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.chat.chatting.dto.ContentType; import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java index 4a353a8f..630bb24c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingEventListener.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatting.service; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java index 29da1e78..5e2f0c43 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/chatting/service/ChattingService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.chatting.service; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom; import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo; import inu.codin.codin.domain.chat.chatroom.exception.ChatRoomNotFoundException; @@ -30,7 +30,7 @@ import java.time.LocalDateTime; import java.util.List; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -74,7 +74,7 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq } public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ChatRoom chatRoom = chatRoomRepository.findById(new ObjectId(id)) .orElseThrow(() -> { log.warn("[채팅방 조회 실패] 채팅방 ID: {}를 찾을 수 없습니다.", id); @@ -99,7 +99,7 @@ public ChattingAndUserIdResponseDto getAllMessage(String id, int page) { log.info("[메시지 조회 성공] 채팅방 ID: {}, 메시지 개수: {}", id, chattingResponseDto.size()); - return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtils.getCurrentUserId().toString()); + return new ChattingAndUserIdResponseDto(chattingResponseDto, SecurityUtil.getCurrentUserId().toString()); } public List sendImageMessage(List chatImages) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java index 718f18fa..0c007f70 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java index 325c0cbe..c45cd393 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChatRoomException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java index 3bf11950..500dd90f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java index e78cc1d2..ab9c0455 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/chat/exception/ChattingException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.chat.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java index 7a55b765..94737f81 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/controller/EmailController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.email.dto.request.JoinEmailCheckRequestDto; import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.service.JoinEmailAuthService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java index 8f24b736..f088edc4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/entity/EmailAuthEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java index 1c5761b7..f3632daa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/email/service/PasswordResetEmailService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java index f07fdcc0..4521862c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/LabController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.controller; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.LabListResponseDto; import inu.codin.codin.domain.info.dto.response.LabThumbnailResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java index 6dad90d1..e30242e5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/OfficeController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.controller; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.dto.Department; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.service.OfficeService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java index b35d48e5..a26b746e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/PartnerController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.controller; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.dto.request.PartnerCreateRequestDto; import inu.codin.codin.domain.info.dto.response.PartnerDetailsResponseDto; import inu.codin.codin.domain.info.dto.response.PartnerListResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java index 4e7a92e4..1400be80 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/controller/ProfessorController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.info.controller; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.dto.Department; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.ProfessorListResponseDto; import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java index 8ca41081..33fa7344 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/LabCreateUpdateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.request; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java index b51ed77b..e4569bab 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/PartnerCreateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.request; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java index 3cfd3de4..4979c7c4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/request/ProfessorCreateUpdateRequestDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.request; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java index fcd0e387..00078443 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/LabThumbnailResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Lab; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java index 88516715..b24260e0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/OfficeDetailsResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Office; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java index f9b901b1..1c79f922 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerDetailsResponseDto.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Partner; import inu.codin.codin.domain.info.entity.PartnerImg; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java index 4596cff6..b44d3b70 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/PartnerListResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Partner; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java index 62bff212..077a5c72 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorListResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java index cf2e5eb0..0d61323e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/dto/response/ProfessorThumbnailResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Professor; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java index 462c0276..fcef98c9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Info.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.Department; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java index 36224b63..0703d6a5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Lab.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.LabCreateUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java index 19a7120c..70132de5 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/OfficeMember.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java index e55fbbed..ee399434 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Partner.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.PartnerCreateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java index da280ae3..ff892b67 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/entity/Professor.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.entity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java index 743d7b77..f17320a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java index 85a383ad..2df18aae 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/exception/InfoException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java index 23d13c05..f0fbc253 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/repository/InfoRepository.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.repository; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.entity.Info; import inu.codin.codin.domain.info.entity.Lab; import inu.codin.codin.domain.info.entity.Office; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java index 7a2639e8..79c89037 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/OfficeService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.service; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.OfficeMemberCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.request.OfficeUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.OfficeDetailsResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java index 031468f0..8aedd976 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/info/service/ProfessorService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.info.service; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.info.dto.request.ProfessorCreateUpdateRequestDto; import inu.codin.codin.domain.info.dto.response.ProfessorListResponseDto; import inu.codin.codin.domain.info.dto.response.ProfessorThumbnailResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java index efa4ce9e..efe91cc7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureController.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.lecture.controller; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.dto.Department; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.dto.Option; import inu.codin.codin.domain.lecture.service.LectureService; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java index 3933edca..13d7a7cf 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.service.LectureUploadService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java index 7974d6ad..62346544 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/controller/ReviewController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.domain.review.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import inu.codin.codin.domain.lecture.domain.review.service.ReviewService; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java index 1459b2bf..e965424e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/entity/ReviewEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.domain.review.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java index 0c05dc73..3c98b8d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/review/service/ReviewService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.lecture.domain.review.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.lecture.domain.review.dto.request.CreateReviewRequestDto; import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewListResposneDto; import inu.codin.codin.domain.lecture.domain.review.dto.response.ReviewPageResponse; @@ -24,7 +24,7 @@ import java.util.Optional; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -46,7 +46,7 @@ public void createReview(ObjectId lectureId, CreateReviewRequestDto createReview log.warn("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요"); throw new WrongRatingException("잘못된 평점입니다. 0.25 ~ 5.0 사이의 점수를 입력해주세요."); } - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); Optional review = reviewRepository.findByLectureIdAndUserIdAndDeletedAtIsNull(lectureId, userId); if (review.isPresent()) { log.error("이미 유저가 작성한 후기가 존재합니다. userId: {}, lectureId: {}", userId, lectureId); @@ -84,7 +84,7 @@ public ReviewPageResponse getListOfReviews(String lectureId, int page) { PageRequest pageRequest = PageRequest.of(page, 10, Sort.by("created_at").descending()); Page reviewPage = reviewRepository.getAvgRatingByLectureId(new ObjectId(lectureId), pageRequest); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); return ReviewPageResponse.of(reviewPage.stream() .map(review -> ReviewListResposneDto.of(review, likeService.isLiked(LikeType.REVIEW, review.get_id().toString(), userId), diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java index 6ad57578..80651587 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/domain/room/controller/LectureRoomController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.domain.room.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.lecture.domain.room.service.LectureRoomService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java index 7d88c255..25ea2437 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/entity/LectureEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.entity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.lecture.dto.Emotion; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java index cfec9fa6..96c39d61 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/repository/LectureRepository.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.lecture.repository; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.lecture.entity.LectureEntity; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java index e6d2e410..c1653e24 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/lecture/service/LectureService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.lecture.service; -import inu.codin.codin.common.dto.Department; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.dto.Department; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.domain.lecture.dto.*; import inu.codin.codin.domain.lecture.dto.response.LectureDetailResponseDto; import inu.codin.codin.domain.lecture.dto.response.LectureListResponseDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java index 14ea7c6f..d5237e3e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/controller/LikeController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.like.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.like.dto.LikeResponseType; import inu.codin.codin.domain.like.dto.LikedResponseDto; import inu.codin.codin.domain.like.dto.request.LikeRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java index 6baf7772..11576c6c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/entity/LikeEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.like.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java index 10d0b756..ec4270f0 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/like/service/LikeService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.like.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.dto.LikeResponseType; import inu.codin.codin.domain.like.dto.LikedResponseDto; import inu.codin.codin.domain.like.dto.request.LikeRequestDto; @@ -23,7 +23,7 @@ import java.util.List; import java.util.Optional; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -41,7 +41,7 @@ public class LikeService { public LikeResponseType toggleLike(LikeRequestDto likeRequestDto) { String likeId = likeRequestDto.getLikeTypeId(); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); isEntityNotDeleted(likeRequestDto); // 해당 entity가 삭제되었는지 확인 // 이미 좋아요를 눌렀으면 취소, 그렇지 않으면 추가 diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java index 64c3b705..917aa3d4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/controller/NotificationController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.notification.controller; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.notification.dto.request.OneCharNameRequestDto; import inu.codin.codin.domain.notification.service.NotificationService; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java index c8494559..51ab0835 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/entity/NotificationEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.notification.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java index 29adf39f..a15390e8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/notification/service/NotificationService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.notification.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.notification.dto.request.OneCharNameRequestDto; import inu.codin.codin.domain.notification.dto.response.NotificationListResponseDto; @@ -29,7 +29,7 @@ import java.util.List; import java.util.Map; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -211,7 +211,7 @@ public void readNotification(String notificationId){ public List getNotification() { //todo 유저에게 맞는 토픽 알림들도 반환 - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("유저를 찾을 수 없습니다")); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java index 60bb3976..7719feb9 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/controller/PostController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.controller; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java index c46f08e7..600a572e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/controller/CommentController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.controller; -import inu.codin.codin.common.response.ListResponse; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.ListResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java index af42e2d8..21843951 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/entity/CommentEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java index 6da27025..a0ede16b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java index 9004c1fd..bcbc42b1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/exception/CommentException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java index f6f68205..ef69f0cc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/controller/ReplyCommentController.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.reply.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyUpdateRequestDTO; import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyCommandService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java index c80a7c00..48a17d47 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/entity/ReplyCommentEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.domain.comment.reply.dto.request.ReplyCreateRequestDTO; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java index 3e2f9216..059a09d1 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java index 15c31f6f..7c25e37f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/exception/ReplyException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java index a51b0751..3f2f9c11 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyCommandService.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.service; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -20,7 +19,7 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -45,7 +44,7 @@ public void addReply(String id, ReplyCreateRequestDTO requestDTO) { CommentEntity comment = commentQueryService.findCommentById(toObjectId(id)); PostEntity post = postQueryService.findPostById(comment.getPostId()); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ReplyCommentEntity reply = ReplyCommentEntity.create(comment.get_id(), userId, requestDTO); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java index bc84390f..b4b4a22d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/reply/service/ReplyQueryService.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply.service; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; @@ -25,7 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -107,7 +107,7 @@ private CommentResponseDTO buildReplyResponseDTO( } public UserInfo getUserInfoAboutReply(ObjectId replyId) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserIdOrNull()); boolean isLiked = false; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java index a0fe7fee..7251428e 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentCommandService.java @@ -1,7 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.service; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentUpdateRequestDTO; @@ -17,7 +16,7 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Service; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -36,7 +35,7 @@ public void addComment(String id, CommentCreateRequestDTO requestDTO) { ObjectId postId = toObjectId(id); PostEntity post = postQueryService.findPostById(postId); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); CommentEntity comment = CommentEntity.create(postId, userId, requestDTO); commentRepository.save(comment); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java index 1f030e42..edd128bc 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/comment/service/CommentQueryService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.domain.comment.service; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; @@ -28,7 +28,7 @@ import java.util.Map; import java.util.stream.Collectors; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -115,7 +115,7 @@ private CommentResponseDTO buildCommentResponseDTO( public UserInfo getUserInfoAboutComment(ObjectId commentId) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserIdOrNull()); boolean isLiked = false; if (userId != null) { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java index 8f297788..86027f8f 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.hits.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java index 26c44b89..61aaa9a4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/hits/exception/HitsException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.hits.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java index 5e4cb0a4..9887ebe4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/controller/PollController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.service.PollCommandService; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java index 9510bb81..69b961aa 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/entity/PollEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import lombok.AccessLevel; import lombok.Getter; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java index 1f3b037a..722055a8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java index a0cd3bed..6dcff96b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/exception/PollException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.poll.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java index 2c1097f4..5193bc13 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/domain/poll/service/PollCommandService.java @@ -1,8 +1,7 @@ package inu.codin.codin.domain.post.domain.poll.service; -import com.mongodb.client.result.UpdateResult; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; @@ -26,7 +25,7 @@ import java.util.List; import java.util.Objects; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -52,10 +51,10 @@ public void createPoll(PollCreateRequestDTO pollRequestDTO) { } public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { - log.info("투표 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); + log.info("투표 요청 - postId: {}, userId: {}", postId, SecurityUtil.getCurrentUserId()); PollEntity poll = getActivePollByPostId(postId); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ensureNotDuplicatedVote(poll.get_id(), userId); @@ -73,10 +72,10 @@ public void votingPoll(String postId, PollVotingRequestDTO pollRequestDTO) { } public void deleteVoting(String postId) { - log.info("투표 취소 요청 - postId: {}, userId: {}", postId, SecurityUtils.getCurrentUserId()); + log.info("투표 취소 요청 - postId: {}, userId: {}", postId, SecurityUtil.getCurrentUserId()); PollEntity poll = getActivePollByPostId(postId); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); PollVoteEntity vote = requireUserVote(poll.get_id(), userId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java index 6ff338f5..8ad50b38 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/entity/PostEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; import inu.codin.codin.domain.post.exception.PostErrorCode; import inu.codin.codin.domain.post.exception.PostException; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java index 416c211d..57d07a65 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java index abb17beb..ac6b1e5c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/exception/PostException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java index 46a6bace..7d0716db 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.scheduler.exception; -import inu.codin.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java index 68e08457..e73a79b4 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/scheduler/exception/SchedulerException.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.scheduler.exception; -import inu.codin.codin.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java index 75b36031..897f870b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/security/OwnershipPolicy.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.security; -import inu.codin.codin.common.util.ObjectIdUtil; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.service.PostQueryService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -12,8 +12,6 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Component; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; - @Component @RequiredArgsConstructor public class OwnershipPolicy { @@ -42,7 +40,7 @@ public ReplyCommentEntity assertReplyOwner(ObjectId replyId) { } private void validateOwner(ObjectId ownerId) { - String current = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateOwners(current, ObjectIdUtil.toString(ownerId)); // 불일치 시 예외 + String current = SecurityUtil.getCurrentUserId(); + SecurityUtil.validateOwners(current, ObjectIdUtil.toString(ownerId)); // 불일치 시 예외 } } \ No newline at end of file diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java index ee1595da..0d1283c8 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostCommandService.java @@ -2,8 +2,8 @@ import inu.codin.security.exception.JwtException; import inu.codin.security.exception.SecurityErrorCode; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.post.dto.request.PostAnonymousUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostContentUpdateRequestDTO; import inu.codin.codin.domain.post.dto.request.PostCreateRequestDTO; @@ -22,7 +22,7 @@ import java.util.List; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Slf4j @Service @@ -38,7 +38,7 @@ public class PostCommandService { * @param postImages 이미지 파일 리스트 */ public void createPost(PostCreateRequestDTO postCreateRequestDTO, List postImages) { - log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtils.getCurrentUserId(), postCreateRequestDTO.getTitle()); + log.info("게시물 생성 시작. UserId: {}, 제목: {}", SecurityUtil.getCurrentUserId(), postCreateRequestDTO.getTitle()); List imageUrls = postInteractionService.handleImageUpload(postImages); ObjectId userId = validateUserAndPost(postCreateRequestDTO.getPostCategory()); @@ -165,12 +165,12 @@ public void assignAnonymousNumber(PostEntity post, ObjectId userId) { private ObjectId validateUserAndPost(PostCategory postCategory) { if (isPrivileged()) { // ADMIN / MANAGER 는 카테고리/유저 상태 검증을 통과시킴 - return toObjectId(SecurityUtils.getCurrentUserId()); + return toObjectId(SecurityUtil.getCurrentUserId()); } assertCategoryWriteAllowed(postCategory); - String userId = SecurityUtils.getCurrentUserId(); - SecurityUtils.validateUser(userId); + String userId = SecurityUtil.getCurrentUserId(); + SecurityUtil.validateUser(userId); return toObjectId(userId); } @@ -182,14 +182,14 @@ private PostEntity assertPostOwner(ObjectId postId){ private void assertCategoryWriteAllowed(PostCategory postCategory) { - if (SecurityUtils.getCurrentUserRole().equals(UserRole.USER) && + if (SecurityUtil.getCurrentUserRole().equals(UserRole.USER) && postCategory.toString().split("_")[0].equals("EXTRACURRICULAR")) { throw new JwtException(SecurityErrorCode.ACCESS_DENIED, "비교과 게시글에 대한 권한이 없습니다."); } } private boolean isPrivileged() { - UserRole role = UserRole.valueOf(SecurityUtils.getCurrentUserRole()); + UserRole role = UserRole.valueOf(SecurityUtil.getCurrentUserRole()); return role == UserRole.ADMIN || role == UserRole.MANAGER; } diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java index a12629da..cf5b4f01 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostDtoAssembler.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.service; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; @@ -26,7 +26,7 @@ import java.util.List; import java.util.Objects; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; /** * PostEntity를 다양한 Response DTO로 변환하는 책임을 담당하는 어셈블러 @@ -72,7 +72,7 @@ public PostPageItemResponseDTO toPageItem(PostEntity post, ObjectId currentUserI * PostEntity 리스트를 PostPageItemResponseDTO 리스트로 변환 */ public List toPageItemList(List posts) { - ObjectId currentUserId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); + ObjectId currentUserId = toObjectId(SecurityUtil.getCurrentUserIdOrNull()); return posts.stream() .map(post -> toPageItem(post, currentUserId)) .toList(); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java index fea65cf8..4a131302 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/post/service/PostQueryService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.post.service; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestService; @@ -26,7 +26,7 @@ import java.util.Optional; import java.util.regex.Pattern; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Slf4j @Service @@ -56,7 +56,7 @@ public PostPageResponse getAllPosts(PostCategory postCategory, int pageNumber) { */ public PostPageItemResponseDTO getPostWithDetail(String postId) { PostEntity post = findPostById(ObjectIdUtil.toObjectId(postId)); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserIdOrNull()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserIdOrNull()); postInteractionService.increaseHits(post, userId); return postDtoAssembler.toPageItem(post, userId); } @@ -67,7 +67,7 @@ public PostPageItemResponseDTO getPostWithDetail(String postId) { public Optional getPostDetailById(ObjectId postId) { return postRepository.findByIdAndNotDeleted(postId) .map(post -> { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); postInteractionService.increaseHits(post, userId); return postDtoAssembler.toPageItem(post, userId); }); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java index ae3eb2eb..0ac127e2 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/controller/ReportController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.report.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.report.dto.request.ReportCreateRequestDto; import inu.codin.codin.domain.report.dto.request.ReportExecuteRequestDto; import inu.codin.codin.domain.report.dto.response.ReportPageResponse; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java index f30be721..1798c493 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/entity/ReportEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.report.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java index 9c07a4bd..df5c8aee 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/report/service/ReportService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.report.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; import inu.codin.codin.domain.post.domain.comment.repository.CommentRepository; @@ -42,7 +42,7 @@ import java.util.stream.Collectors; import inu.codin.codin.domain.post.dto.response.PostPageItemResponseDTO; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -71,7 +71,7 @@ public void createReport(@Valid ReportCreateRequestDto reportCreateRequestDto) { log.info("신고 생성 요청 시작: {} ", reportCreateRequestDto); // 신고한 유저 검증 - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ObjectId reportTargetId = new ObjectId(reportCreateRequestDto.getReportTargetId()); @@ -159,7 +159,7 @@ private ObjectId validateAndGetReportedUserId(ReportTargetType reportTargetType, public void handleReport(ReportExecuteRequestDto requestDto) { log.info("신고 처리 요청: {}", requestDto.getReportTargetId()); //현재 관리자 ID - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); ObjectId targetObjectId = new ObjectId(requestDto.getReportTargetId()); // 해당 신고 대상에 대한 모든 신고 가져오기 @@ -269,7 +269,7 @@ public void resolveReport(String reportTargetId) { log.info(" 신고대상 유지 요청: 신고 ID: {}", reportTargetId); ObjectId targetObjectId = new ObjectId(reportTargetId); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); // 현재 유저 ID + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); // 현재 유저 ID // 신고 존재 확인 List reports = reportRepository.findByReportTargetId(targetObjectId); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java index f64a06fc..30ebcefe 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/controller/ScrapController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.scrap.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.scrap.service.ScrapService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java index e2887743..d25e611b 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/entity/ScrapEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.scrap.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java index 9f8f9687..ba0134ea 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/scrap/service/ScrapService.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.scrap.service; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.post.repository.PostRepository; import inu.codin.codin.domain.scrap.entity.ScrapEntity; import inu.codin.codin.domain.scrap.repository.ScrapRepository; @@ -13,7 +13,7 @@ import java.util.Optional; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @RequiredArgsConstructor @@ -32,7 +32,7 @@ public class ScrapService { public String toggleScrap(String id) { log.info("스크랩 토글 요청 - postId: {}", id); ObjectId postId = new ObjectId(id); - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); postRepository.findByIdAndNotDeleted(postId) .orElseThrow(() -> { diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java index 9fb11b5b..f4283313 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/controller/UserController.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.post.dto.response.PostPageResponse; import inu.codin.codin.domain.user.dto.request.UserNameUpdateRequestDto; import inu.codin.codin.domain.user.dto.request.UserNicknameRequestDto; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java index 85fa4360..c6754b67 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/request/UserTicketingParticipationInfoUpdateRequest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.dto.request; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java index 2e0bdcc1..78ebdd4d 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserInfoResponseDto.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java index 543f55ed..57b70b8c 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/dto/response/UserTicketingParticipationInfoResponse.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.dto.response; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.user.entity.UserEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java index bb441c74..780a63b7 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/entity/UserEntity.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.user.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.Department; import inu.codin.codin.common.security.dto.PortalLoginResponseDto; import inu.codin.codin.domain.notification.entity.NotificationPreference; import inu.codin.codin.domain.user.dto.request.UserTicketingParticipationInfoUpdateRequest; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java index 8b4401a6..7d11e391 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/security/CustomUserDetails.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.user.security; -import inu.codin.codin.common.dto.Department; +import inu.codin.common.dto.Department; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.entity.UserRole; import inu.codin.codin.domain.user.entity.UserStatus; diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java index 8b367fe7..f33f1c42 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserService.java @@ -1,8 +1,8 @@ package inu.codin.codin.domain.user.service; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.exception.NotFoundException; import inu.codin.security.service.JwtService; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeEntity; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.repository.LikeRepository; @@ -37,7 +37,7 @@ import java.util.List; import java.util.Optional; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Slf4j @Service @@ -57,7 +57,7 @@ public class UserService { //해당 유저가 작성한 모든 글 반환 :: 게시글 내용 + 댓글+대댓글의 수 + 좋아요,스크랩 count 수 반환 public PostPageResponse getAllUserPosts(int pageNumber) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[게시글 조회] 유저 ID: {}, 페이지 번호: {}", userId, pageNumber); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); @@ -72,7 +72,7 @@ public PostPageResponse getAllUserPosts(int pageNumber) { } public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType interactionType) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[유저 상호작용 조회] 유저 ID: {}, 타입: {}, 페이지 번호: {}", userId, interactionType, pageNumber); PageRequest pageRequest = PageRequest.of(pageNumber, 20, Sort.by("createdAt").descending()); @@ -122,7 +122,7 @@ public PostPageResponse getPostUserInteraction(int pageNumber, InteractionType i } public void deleteUser(HttpServletResponse response) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); UserEntity user = userRepository.findByUserId(userId) .orElseThrow(() -> { @@ -139,7 +139,7 @@ public void deleteUser(HttpServletResponse response) { } public UserInfoResponseDto getUserInfo() { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[유저 정보 조회] 유저 ID: {}", userId); UserEntity user = userRepository.findById(userId) @@ -158,7 +158,7 @@ public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) throw new UserNicknameDuplicateException("이미 사용중인 닉네임입니다."); } - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[유저 정보 업데이트] 현재 사용자 ID: {}", userId); UserEntity user = userRepository.findById(userId) @@ -173,7 +173,7 @@ public void updateUserInfo(@Valid UserNicknameRequestDto userNicknameRequestDto) } public void updateUserProfile(MultipartFile profileImage) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[프로필 이미지 업데이트] 현재 사용자 ID: {}", userId); UserEntity user = userRepository.findById(userId) @@ -189,7 +189,7 @@ public void updateUserProfile(MultipartFile profileImage) { } public void updateUserName(@Valid UserNameUpdateRequestDto request){ - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); log.info("[유저 실명 수정] 현재 사용자 ID: {}, 요청 이름: {}", userId, request.getName()); UserEntity user = userRepository.findById(userId) @@ -226,7 +226,7 @@ public void updateUserName(@Valid UserNameUpdateRequestDto request){ */ public UserTicketingParticipationInfoResponse getUserTicketingParticipationInfo() { return UserTicketingParticipationInfoResponse.of( - userRepository.findByUserId(toObjectId(SecurityUtils.getCurrentUserId())) + userRepository.findByUserId(toObjectId(SecurityUtil.getCurrentUserId())) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다."))); } @@ -235,7 +235,7 @@ public UserTicketingParticipationInfoResponse getUserTicketingParticipationInfo( * @return UserTicketingParticipationInfoResponse 유저의 학번, 이름, 소속 Dto 반환 */ public UserTicketingParticipationInfoResponse updateUserTicketingParticipationInfo(UserTicketingParticipationInfoUpdateRequest updateRequest) { - UserEntity userEntity = userRepository.findByUserId(toObjectId(SecurityUtils.getCurrentUserId())) + UserEntity userEntity = userRepository.findByUserId(toObjectId(SecurityUtil.getCurrentUserId())) .orElseThrow(() -> new NotFoundException("유저 정보를 찾을 수 없습니다.")); userEntity.updateParticipationInfo(updateRequest); diff --git a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java index 9aa92121..f5dd0376 100644 --- a/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java +++ b/codin-core/src/main/java/inu/codin/codin/domain/user/service/UserValidator.java @@ -1,6 +1,5 @@ package inu.codin.codin.domain.user.service; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.codin.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.bson.types.ObjectId; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java index 96eac2eb..6877c898 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/FcmControllerImpl.java @@ -1,6 +1,6 @@ package inu.codin.codin.infra.fcm.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.infra.fcm.controller.swagger.FcmController; import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import inu.codin.codin.infra.fcm.service.FcmService; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java index d8d1eb15..3f9f3656 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/controller/swagger/FcmController.java @@ -1,6 +1,6 @@ package inu.codin.codin.infra.fcm.controller.swagger; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.infra.fcm.dto.request.FcmTokenRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java index d5991104..cd94831f 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/entity/FcmTokenEntity.java @@ -1,6 +1,6 @@ package inu.codin.codin.infra.fcm.entity; -import inu.codin.codin.common.dto.BaseTimeEntity; +import inu.codin.common.dto.BaseTimeEntity; import inu.codin.codin.infra.fcm.exception.FcmDuplicatedTokenException; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java index 94abaec7..1bba3fa0 100644 --- a/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java +++ b/codin-core/src/main/java/inu/codin/codin/infra/fcm/service/FcmService.java @@ -1,8 +1,8 @@ package inu.codin.codin.infra.fcm.service; import com.google.firebase.messaging.*; -import inu.codin.codin.common.exception.NotFoundException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.common.exception.NotFoundException; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.notification.entity.NotificationPreference; import inu.codin.codin.domain.user.entity.UserEntity; import inu.codin.codin.domain.user.repository.UserRepository; @@ -21,7 +21,7 @@ import java.util.List; import java.util.Optional; -import static inu.codin.codin.common.util.ObjectIdUtil.toObjectId; +import static inu.codin.common.util.ObjectIdUtil.toObjectId; @Service @Slf4j @@ -37,7 +37,7 @@ public class FcmService { */ public void saveFcmToken(@Valid FcmTokenRequest fcmTokenRequest) { // 유저의 FCM 토큰이 존재하는지 확인 - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); Optional fcmToken = fcmTokenRepository.findByUserId(userId); if (fcmToken.isPresent()) { // 이미 존재하는 유저라면 토큰 추가 @@ -161,7 +161,7 @@ private void removeFcmToken(FcmTokenEntity fcmTokenEntity, String fcmToken) { * @param topic 구독할 토픽 이름 */ public void subscribeTopic(String topic) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); @@ -180,7 +180,7 @@ public void subscribeTopic(String topic) { * @param topic 구독 해제할 토픽 이름 */ public void unsubscribeTopic(String topic) { - ObjectId userId = toObjectId(SecurityUtils.getCurrentUserId()); + ObjectId userId = toObjectId(SecurityUtil.getCurrentUserId()); FcmTokenEntity fcmTokenEntity = fcmTokenRepository.findByUserId(userId) .orElseThrow(() -> new FcmTokenNotFoundException("유저의 FCM 토큰이 존재하지 않습니다.")); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java index 6909b8b1..21133416 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/controller/BlockControllerTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.block.controller; -import inu.codin.codin.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; import inu.codin.codin.domain.block.service.BlockService; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java index 6b328586..f28f5d60 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/block/service/BlockServiceTest.java @@ -1,7 +1,7 @@ package inu.codin.codin.domain.block.service; -import inu.codin.security.util.SecurityUtils; -import inu.codin.codin.common.util.ObjectIdUtil; +import inu.codin.security.util.SecurityUtil; +import inu.codin.common.util.ObjectIdUtil; import inu.codin.codin.domain.block.entity.BlockEntity; import inu.codin.codin.domain.block.exception.BlockErrorCode; import inu.codin.codin.domain.block.exception.BlockException; @@ -41,9 +41,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단 성공") void blockUser_성공() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)){ + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)){ //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); BlockEntity existingBlock = BlockEntity.ofNew(testUserId); when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.of(existingBlock)); @@ -62,9 +62,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단 성공 - 새로운 BlockEntity 생성") void blockUser_성공_새로운BlockEntity생성() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); when(blockRepository.save(any(BlockEntity.class))).thenReturn(BlockEntity.ofNew(testUserId)); @@ -80,9 +80,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단 실패 - 자기 자신 차단") void blockUser_실패_자기자신차단() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); //when & then assertThatThrownBy(() -> blockService.blockUser(testUserId.toString())) @@ -95,9 +95,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단 실패 - 차단유저 존재하지 않음") void blockUser_실패_차단유저X() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); doNothing().when(userValidator).validateUserExists(eq(testUserId), any()); doThrow(new BlockException(BlockErrorCode.BLOCKED_USER_NOT_FOUND)) @@ -114,9 +114,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단 실패 - 차단 실행 유저 존재하지 않음") void blockUser_실패_차단실행유저X() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); doThrow(new BlockException(BlockErrorCode.BLOCKING_USER_NOT_FOUND)) .when(userValidator).validateUserExists(eq(testUserId), any()); @@ -132,9 +132,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단해제 성공") void unblockUser_성공() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); BlockEntity existingBlock = BlockEntity.ofNew(testUserId); existingBlock.addBlockedUser(blockedUserId); @@ -154,9 +154,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단해제 실패 - 자기 자신 차단해제") void unblockUser_실패_자기자신차단해제() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); //when & then assertThatThrownBy(() -> blockService.unblockUser(testUserId.toString())) @@ -169,9 +169,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단해제 실패 - 차단 정보 없음") void unblockUser_실패_차단정보없음() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); @@ -186,9 +186,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단 실패 - 이미 차단된 사용자") void blockUser_실패_이미차단된사용자() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); BlockEntity existingBlock = BlockEntity.ofNew(testUserId); existingBlock.addBlockedUser(blockedUserId); // 이미 차단된 상태 @@ -211,9 +211,9 @@ class BlockServiceTest { @Test @DisplayName("유저 차단해제 실패 - 차단되지 않은 사용자") void unblockUser_실패_차단되지않은사용자() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); // 다른 사용자는 차단되어 있지만, 요청한 사용자는 차단되지 않은 상태 ObjectId otherBlockedUserId = ObjectIdUtil.toObjectId("507f1f77bcf86cd799439011"); @@ -238,9 +238,9 @@ class BlockServiceTest { @Test @DisplayName("차단된 유저 목록 조회 성공") void getBlockedUsers_성공() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); BlockEntity blockEntity = spy(BlockEntity.ofNew(testUserId)); when(blockEntity.getBlockedUsers()).thenReturn(List.of(blockedUserId)); @@ -261,9 +261,9 @@ class BlockServiceTest { @Test @DisplayName("차단된 유저 목록 조회 - 차단 정보 없음") void getBlockedUsers_차단정보없음() { - try (MockedStatic mSecurityUtils = mockStatic(SecurityUtils.class)) { + try (MockedStatic mSecurityUtils = mockStatic(SecurityUtil.class)) { //given - mSecurityUtils.when(SecurityUtils::getCurrentUserId).thenReturn(testUserId); + mSecurityUtils.when(SecurityUtil::getCurrentUserId).thenReturn(testUserId); when(blockRepository.findByUserId(testUserId)).thenReturn(Optional.empty()); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java index 98260040..d75e0fb7 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/email/service/PasswordResetEmailServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.email.service; -import inu.codin.codin.common.exception.NotFoundException; +import inu.codin.common.exception.NotFoundException; import inu.codin.codin.domain.email.dto.request.JoinEmailSendRequestDto; import inu.codin.codin.domain.email.entity.EmailAuthEntity; import inu.codin.codin.domain.email.exception.EmailPasswordResetFailException; diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java index c91be44a..11366dae 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostCommandServiceTest.java @@ -1,8 +1,7 @@ package inu.codin.codin.domain.post; -import inu.codin.codin.common.exception.NotFoundException; import inu.codin.security.exception.JwtException; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.post.dto.request.*; import inu.codin.codin.domain.post.entity.PostAnonymous; import inu.codin.codin.domain.post.entity.PostCategory; @@ -40,7 +39,7 @@ class PostCommandServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -55,8 +54,8 @@ void tearDown() throws Exception { List images = new ArrayList<>(); ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postInteractionService.handleImageUpload(any())).willReturn(new ArrayList<>()); given(postRepository.save(any())).willAnswer(inv -> { PostEntity entity = inv.getArgument(0); @@ -76,8 +75,8 @@ void tearDown() throws Exception { List images = new ArrayList<>(); ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.USER); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.USER); // When & Then assertThatThrownBy(() -> postCommandService.createPost(dto, images)) @@ -95,8 +94,8 @@ void tearDown() throws Exception { List imageUrls = Arrays.asList("image1.jpg", "image2.jpg"); given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postInteractionService.handleImageUpload(any())).willReturn(imageUrls); given(postRepository.save(any())).willReturn(post); @@ -113,8 +112,8 @@ void tearDown() throws Exception { PostEntity post = createPostEntity(); given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); // When & Then @@ -130,8 +129,8 @@ void tearDown() throws Exception { PostEntity post = createPostEntity(); given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); // When & Then @@ -146,8 +145,8 @@ void tearDown() throws Exception { PostEntity post = createPostEntity(); given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.ADMIN); given(postRepository.save(any())).willReturn(post); // When & Then @@ -163,8 +162,8 @@ void tearDown() throws Exception { PostEntity post = createPostEntity(); given(ownershipPolicy.assertPostOwner(any(ObjectId.class))).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); - given(SecurityUtils.getCurrentUserRole()).willReturn(UserRole.ADMIN); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserRole()).willReturn(UserRole.ADMIN); doNothing().when(postInteractionService).deletePostImageInternal(any(), any()); // When & Then diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java index b2d78728..81e1eb17 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostDtoAssemblerTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.hits.service.HitsService; @@ -50,7 +50,7 @@ class PostDtoAssemblerTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -160,7 +160,7 @@ void tearDown() throws Exception { List posts = Arrays.asList(post1, post2); UserEntity user = createUserEntity(); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(userRepository.findById(any())).willReturn(Optional.of(user)); given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); given(likeService.getLikeCount(any(), any())).willReturn(0); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java index e1c1b5b4..23aea4fc 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/PostQueryServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.block.service.BlockService; import inu.codin.codin.domain.post.domain.best.BestEntity; import inu.codin.codin.domain.post.domain.best.BestService; @@ -46,7 +46,7 @@ class PostQueryServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -106,7 +106,7 @@ void tearDown() throws Exception { PostPageItemResponseDTO mockDto = createMockPostPageItemResponseDTO(); given(postRepository.findByIdAndNotDeleted(any())).willReturn(Optional.of(post)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(postDtoAssembler.toPageItem(post, userId)).willReturn(mockDto); doNothing().when(postInteractionService).increaseHits(any(), any()); @@ -140,7 +140,7 @@ void tearDown() throws Exception { PostPageItemResponseDTO mockDto = createMockPostPageItemResponseDTO(); given(postRepository.findByIdAndNotDeleted(postId)).willReturn(Optional.of(post)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(postDtoAssembler.toPageItem(post, userId)).willReturn(mockDto); doNothing().when(postInteractionService).increaseHits(any(), any()); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java index a8e69e0a..7d69c05c 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentCommandServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.dto.request.CommentCreateRequestDTO; @@ -43,7 +43,7 @@ class CommentCommandServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -60,7 +60,7 @@ void tearDown() throws Exception { ObjectId userId = new ObjectId(); given(postQueryService.findPostById(any())).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(commentRepository.save(any())).willAnswer(inv -> { CommentEntity entity = inv.getArgument(0); setIdField(entity, new ObjectId()); @@ -90,7 +90,7 @@ void tearDown() throws Exception { setIdField(post, new ObjectId()); given(postQueryService.findPostById(any())).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(commentRepository.save(any())).willAnswer(inv -> { CommentEntity entity = inv.getArgument(0); setIdField(entity, new ObjectId()); @@ -120,7 +120,7 @@ void tearDown() throws Exception { setIdField(post, new ObjectId()); given(postQueryService.findPostById(any())).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(commentRepository.save(any())).willAnswer(inv -> { CommentEntity entity = inv.getArgument(0); setIdField(entity, new ObjectId()); @@ -151,9 +151,9 @@ void tearDown() throws Exception { ObjectId userId = new ObjectId(); given(ownershipPolicy.assertCommentOwner(any(ObjectId.class))).willReturn(comment); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); - doNothing().when(SecurityUtils.class); - SecurityUtils.validateUser(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); + doNothing().when(SecurityUtil.class); + SecurityUtil.validateUser(userId); given(commentRepository.save(any())).willReturn(comment); // When & Then @@ -170,8 +170,8 @@ void tearDown() throws Exception { ObjectId userId = comment.getUserId(); given(ownershipPolicy.assertCommentOwner(any(ObjectId.class))).willReturn(comment); - doNothing().when(SecurityUtils.class); - SecurityUtils.validateUser(userId); + doNothing().when(SecurityUtil.class); + SecurityUtil.validateUser(userId); given(postQueryService.findPostById(comment.getPostId())).willReturn(post); given(commentRepository.save(any())).willReturn(comment); doNothing().when(postCommandService).decreaseCommentCount(any()); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java index e2017c1d..039056bb 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/CommentQueryServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; @@ -46,7 +46,7 @@ class CommentQueryServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -79,7 +79,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(5); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(likeService.isLiked(eq(LikeType.COMMENT), any(), (ObjectId) any())).willReturn(false); // When @@ -132,7 +132,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(2); // 익명 번호 given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(3); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(likeService.isLiked(eq(LikeType.COMMENT), any(), (ObjectId) any())).willReturn(true); // When @@ -180,7 +180,7 @@ void tearDown() throws Exception { ObjectId commentId = new ObjectId(); ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(likeService.isLiked(LikeType.COMMENT, commentId.toString(), userId)).willReturn(true); // When @@ -197,7 +197,7 @@ void tearDown() throws Exception { ObjectId commentId = new ObjectId(); ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(likeService.isLiked(LikeType.COMMENT, commentId.toString(), userId)).willReturn(false); // When @@ -232,7 +232,7 @@ void tearDown() throws Exception { given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(replyQueryService.getRepliesByCommentId(any(), any())).willReturn(replies); given(likeService.getLikeCount(eq(LikeType.COMMENT), any())).willReturn(0); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(likeService.isLiked(eq(LikeType.COMMENT), any(), (ObjectId) any())).willReturn(false); // When diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java index 620ab0f9..b6703949 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyCommandServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.notification.service.NotificationService; import inu.codin.codin.domain.post.domain.best.BestService; import inu.codin.codin.domain.post.domain.comment.entity.CommentEntity; @@ -10,7 +10,6 @@ import inu.codin.codin.domain.post.domain.comment.reply.entity.ReplyCommentEntity; import inu.codin.codin.domain.post.domain.comment.reply.repository.ReplyCommentRepository; import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyCommandService; -import inu.codin.codin.domain.post.domain.comment.reply.service.ReplyQueryService; import inu.codin.codin.domain.post.entity.PostCategory; import inu.codin.codin.domain.post.entity.PostEntity; import inu.codin.codin.domain.post.security.OwnershipPolicy; @@ -43,7 +42,7 @@ class ReplyCommandServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -63,7 +62,7 @@ void tearDown() throws Exception { given(commentQueryService.findCommentById(any())).willReturn(comment); given(postQueryService.findPostById(comment.getPostId())).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(replyCommentRepository.save(any())).willAnswer(inv -> { ReplyCommentEntity entity = inv.getArgument(0); setIdField(entity, new ObjectId()); @@ -95,7 +94,7 @@ void tearDown() throws Exception { given(commentQueryService.findCommentById(any())).willReturn(comment); given(postQueryService.findPostById(comment.getPostId())).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(replyCommentRepository.save(any())).willAnswer(inv -> { ReplyCommentEntity entity = inv.getArgument(0); setIdField(entity, new ObjectId()); @@ -134,7 +133,7 @@ void tearDown() throws Exception { given(commentQueryService.findCommentById(any())).willReturn(comment); given(postQueryService.findPostById(comment.getPostId())).willReturn(post); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(replyCommentRepository.save(any())).willAnswer(inv -> { ReplyCommentEntity entity = inv.getArgument(0); setIdField(entity, new ObjectId()); @@ -181,8 +180,8 @@ void tearDown() throws Exception { ObjectId userId = reply.getUserId(); given(ownershipPolicy.assertReplyOwner(any(ObjectId.class))).willReturn(reply); - doNothing().when(SecurityUtils.class); - SecurityUtils.validateUser(userId); + doNothing().when(SecurityUtil.class); + SecurityUtil.validateUser(userId); given(commentQueryService.findCommentById(reply.getCommentId())).willReturn(comment); given(postQueryService.findPostById(comment.getPostId())).willReturn(post); given(replyCommentRepository.save(any())).willReturn(reply); diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java index 38aa7ca6..a46d44b7 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/comment/reply/ReplyQueryServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.comment.reply; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.like.entity.LikeType; import inu.codin.codin.domain.like.service.LikeService; import inu.codin.codin.domain.post.domain.comment.dto.response.CommentResponseDTO; @@ -43,7 +43,7 @@ class ReplyQueryServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -73,7 +73,7 @@ void tearDown() throws Exception { given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(3); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(likeService.isLiked(eq(LikeType.REPLY), any(),(ObjectId) any())).willReturn(false); // When @@ -121,7 +121,7 @@ void tearDown() throws Exception { given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); given(postQueryService.getUserAnonymousNumber(postAnonymous, userId)).willReturn(3); // 익명 번호 given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(2); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(likeService.isLiked(eq(LikeType.REPLY), any(),(ObjectId) any())).willReturn(true); // When @@ -169,7 +169,7 @@ void tearDown() throws Exception { ObjectId replyId = new ObjectId(); ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(likeService.isLiked(LikeType.REPLY, replyId.toString(), userId)).willReturn(true); // When @@ -186,7 +186,7 @@ void tearDown() throws Exception { ObjectId replyId = new ObjectId(); ObjectId userId = new ObjectId(); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(likeService.isLiked(LikeType.REPLY, replyId.toString(), userId)).willReturn(false); // When @@ -219,7 +219,7 @@ void tearDown() throws Exception { given(s3Service.getDefaultProfileImageUrl()).willReturn("default.jpg"); given(postQueryService.getUserAnonymousNumber(any(), any())).willReturn(1); given(likeService.getLikeCount(eq(LikeType.REPLY), any())).willReturn(0); - given(SecurityUtils.getCurrentUserId()).willReturn(new ObjectId()); + given(SecurityUtil.getCurrentUserId()).willReturn(new ObjectId()); given(likeService.isLiked(eq(LikeType.REPLY), any(),(ObjectId) any())).willReturn(false); // When diff --git a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java index 0761f63b..1365f40c 100644 --- a/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java +++ b/codin-core/src/test/java/inu/codin/codin/domain/post/domain/poll/PollCommandServiceTest.java @@ -1,6 +1,6 @@ package inu.codin.codin.domain.post.domain.poll; -import inu.codin.security.util.SecurityUtils; +import inu.codin.security.util.SecurityUtil; import inu.codin.codin.domain.post.domain.poll.dto.request.PollCreateRequestDTO; import inu.codin.codin.domain.post.domain.poll.dto.request.PollVotingRequestDTO; import inu.codin.codin.domain.post.domain.poll.entity.PollEntity; @@ -43,7 +43,7 @@ class PollCommandServiceTest { @BeforeEach void setUp() { - securityUtilsMock = Mockito.mockStatic(SecurityUtils.class); + securityUtilsMock = Mockito.mockStatic(SecurityUtil.class); } @AfterEach @@ -85,7 +85,7 @@ void tearDown() throws Exception { given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); given(pollVoteRepository.save(any())).willReturn(createPollVoteEntity()); given(pollRepository.incOption(eq(poll.get_id()), eq(0))).willReturn(1L); @@ -151,7 +151,7 @@ void tearDown() throws Exception { given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(true); // When & Then @@ -170,7 +170,7 @@ void tearDown() throws Exception { given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); // When & Then @@ -189,7 +189,7 @@ void tearDown() throws Exception { given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.existsByPollIdAndUserId(poll.get_id(), userId)).willReturn(false); // When & Then @@ -209,7 +209,7 @@ void tearDown() throws Exception { given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.of(vote)); given(pollRepository.dcrOptionIfPositive(eq(poll.get_id()), eq(0))).willReturn(1L); @@ -231,7 +231,7 @@ void tearDown() throws Exception { given(postQueryService.findPostById(any())).willReturn(post); given(pollRepository.findByPostId(post.get_id())).willReturn(Optional.of(poll)); - given(SecurityUtils.getCurrentUserId()).willReturn(userId); + given(SecurityUtil.getCurrentUserId()).willReturn(userId); given(pollVoteRepository.findByPollIdAndUserId(poll.get_id(), userId)).willReturn(Optional.empty()); // When & Then diff --git a/codin-lecture-api/build.gradle b/codin-lecture-api/build.gradle index b1de355c..eb19105c 100644 --- a/codin-lecture-api/build.gradle +++ b/codin-lecture-api/build.gradle @@ -31,6 +31,9 @@ dependencyManagement { } dependencies { + implementation project(':codin-security') + implementation project(':codin-common') + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/CustomAccessDeniedHandler.java b/codin-security/src/main/java/inu/codin/security/exception/CustomAccessDeniedHandler.java similarity index 91% rename from codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/CustomAccessDeniedHandler.java rename to codin-security/src/main/java/inu/codin/security/exception/CustomAccessDeniedHandler.java index 6ef65349..29fcc35d 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/CustomAccessDeniedHandler.java +++ b/codin-security/src/main/java/inu/codin/security/exception/CustomAccessDeniedHandler.java @@ -1,7 +1,7 @@ -package inu.codin.codinticketingapi.security.exception; +package inu.codin.security.exception; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codinticketingapi.common.response.ExceptionResponse; +import inu.codin.common.response.ExceptionResponse; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java b/codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java index afb7a2f1..aa604985 100644 --- a/codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java +++ b/codin-security/src/main/java/inu/codin/security/exception/SecurityErrorCode.java @@ -1,22 +1,35 @@ package inu.codin.security.exception; +import inu.codin.common.exception.GlobalErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; @Getter @RequiredArgsConstructor -public enum SecurityErrorCode { - - INVALID_TOKEN("SEC_001", "유효하지 않은 토큰입니다."), - EXPIRED_TOKEN("SEC_002", "만료된 토큰입니다."), - TOKEN_NOT_FOUND("SEC_003", "토큰이 존재하지 않습니다."), - INVALID_SIGNATURE("SEC_004", "잘못된 토큰 서명입니다."), - ACCESS_DENIED("SEC_005", "접근 권한이 없습니다."), - ACCOUNT_LOCKED("SEC_006", "계정이 잠겼습니다. 관리자에게 문의하세요."), - INVALID_CREDENTIALS("SEC_007", "잘못된 인증 정보입니다."), - INVALID_TYPE("SEC_008", "잘못된 토큰 타입입니다."); - - private final String errorCode; +public enum SecurityErrorCode implements GlobalErrorCode { + + INVALID_TOKEN( HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), + TOKEN_NOT_FOUND( HttpStatus.UNAUTHORIZED, "토큰이 존재하지 않습니다."), + INVALID_SIGNATURE( HttpStatus.UNAUTHORIZED, "잘못된 토큰 서명입니다."), + ACCESS_DENIED( HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), + ACCOUNT_LOCKED( HttpStatus.FORBIDDEN, "계정이 잠겼습니다. 관리자에게 문의하세요."), + INVALID_CREDENTIALS( HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다."), + INVALID_TYPE( HttpStatus.BAD_REQUEST, "잘못된 토큰 타입입니다."); + + private final HttpStatus httpStatus; private final String message; + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String message() { + return message; + } + } diff --git a/codin-security/src/main/java/inu/codin/security/exception/SecurityException.java b/codin-security/src/main/java/inu/codin/security/exception/SecurityException.java new file mode 100644 index 00000000..9794dfe1 --- /dev/null +++ b/codin-security/src/main/java/inu/codin/security/exception/SecurityException.java @@ -0,0 +1,20 @@ +package inu.codin.security.exception; + +import lombok.Getter; + +@Getter +public class SecurityException extends RuntimeException { + + private final SecurityErrorCode errorCode; + + public SecurityException(SecurityErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public SecurityException(SecurityErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +} + \ No newline at end of file diff --git a/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java b/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java index cb8b98b8..33f7559e 100644 --- a/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java +++ b/codin-security/src/main/java/inu/codin/security/filter/ExceptionHandlerFilter.java @@ -1,7 +1,7 @@ package inu.codin.security.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codin.common.response.ExceptionResponse; +import inu.codin.common.response.ExceptionResponse; import inu.codin.security.exception.SecurityErrorCode; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -35,6 +35,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse log.warn("[doFilterInternal] Exception in ExceptionHandlerFilter: {}", e.getMessage()); sendErrorResponse(response, SecurityErrorCode.INVALID_TOKEN); } + } private void sendErrorResponse(HttpServletResponse response, SecurityErrorCode code) throws IOException { diff --git a/codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java b/codin-security/src/main/java/inu/codin/security/util/SecurityUtil.java similarity index 54% rename from codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java rename to codin-security/src/main/java/inu/codin/security/util/SecurityUtil.java index b42f7ed9..7dce19e3 100644 --- a/codin-security/src/main/java/inu/codin/security/util/SecurityUtils.java +++ b/codin-security/src/main/java/inu/codin/security/util/SecurityUtil.java @@ -1,5 +1,6 @@ package inu.codin.security.util; +import inu.codin.security.exception.SecurityException; import inu.codin.security.exception.JwtException; import inu.codin.security.exception.SecurityErrorCode; import inu.codin.security.jwt.TokenUserDetails; @@ -19,7 +20,7 @@ * - 도메인 특화 검증 로직은 각 서비스에서 구현하도록 변경 */ @Slf4j -public class SecurityUtils { +public class SecurityUtil { /** * 현재 인증된 사용자의 ID를 반환. @@ -102,4 +103,94 @@ private static UserDetails getCurrentUserDetails() { return userDetails; } + + /** + * Todo: + * package inu.codin.codinticketingapi.security.util; + * 추후 충돌해결 및 수정 필요 + **/ + + /** + * 현재 인증된 사용자의 유저 ID 반환 + */ + public static String getUserId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { + throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); + } + + return userDetails.getUserId(); + } + + /** + * 현재 인증된 사용자의 이메일(유저이름) 반환 + */ + public static String getUsername() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { + throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); + } + + return userDetails.getUsername(); + } + + /** + * 현재 인증된 사용자의 유저 토큰 반환 + */ + public static String getUserToken() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { + throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); + } + + return userDetails.getToken(); + } + + /** + * 현재 인증된 사용자의 권한 반환 + */ +// public static String getCurrentUserRole() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// +// return userDetails.getRole(); +// } + + /** + * 현재 사용자와 주어진 사용자 ID가 같은지 검증 + */ +// public static void validateUser(String userId) { +// String currentUserId = getUserId(); +// if (!currentUserId.equals(userId)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// } + + /** + * 현재 사용자가 인증되어 있는지 확인 + */ + public static boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && + authentication.isAuthenticated() && + authentication.getPrincipal() instanceof TokenUserDetails; + } + + /** + * 현재 사용자가 특정 권한을 가지고 있는지 확인 + */ + public static boolean hasRole(String role) { + try { + String currentRole = getCurrentUserRole(); + return role.equals(currentRole); + } catch (SecurityException e) { + return false; + } + } } diff --git a/codin-ticketing-api/build.gradle b/codin-ticketing-api/build.gradle index 7715ec95..275e799f 100644 --- a/codin-ticketing-api/build.gradle +++ b/codin-ticketing-api/build.gradle @@ -35,6 +35,7 @@ dependencyManagement { dependencies { implementation project(':codin-security') + implementation project(':codin-common') implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalErrorCode.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalErrorCode.java deleted file mode 100644 index c227a567..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalErrorCode.java +++ /dev/null @@ -1,8 +0,0 @@ -package inu.codin.codinticketingapi.common.exception; - -import org.springframework.http.HttpStatus; - -public interface GlobalErrorCode { - HttpStatus httpStatus(); - String message(); -} \ No newline at end of file diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalException.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalException.java deleted file mode 100644 index c3a8b1f0..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalException.java +++ /dev/null @@ -1,14 +0,0 @@ -package inu.codin.codinticketingapi.common.exception; - -import lombok.Getter; - -@Getter -public class GlobalException extends RuntimeException{ - - private final GlobalErrorCode errorCode; - - public GlobalException(GlobalErrorCode errorCode) { - super(errorCode.message()); - this.errorCode = errorCode; - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalExceptionHandler.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalExceptionHandler.java index 21a2bf71..09032e4a 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalExceptionHandler.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,8 @@ package inu.codin.codinticketingapi.common.exception; -import inu.codin.codinticketingapi.common.response.ExceptionResponse; +import inu.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalException; +import inu.codin.common.response.ExceptionResponse; import inu.codin.codinticketingapi.domain.ticketing.exception.TicketingErrorCode; import inu.codin.codinticketingapi.domain.ticketing.exception.TicketingException; import inu.codin.codinticketingapi.domain.user.exception.UserErrorCode; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/CommonResponse.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/CommonResponse.java deleted file mode 100644 index 4201dcb3..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/CommonResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package inu.codin.codinticketingapi.common.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; - -@Getter -@Schema -public class CommonResponse { - boolean success; - int code; - String message; - - public CommonResponse(boolean success, int code, String message) { - this.success = success; - this.code = code; - this.message = message; - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ExceptionResponse.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ExceptionResponse.java deleted file mode 100644 index 1aada9b3..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ExceptionResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codinticketingapi.common.response; - -public class ExceptionResponse extends CommonResponse{ - public ExceptionResponse(String message, int code) { - super(false, code, message); - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ListResponse.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ListResponse.java deleted file mode 100644 index 0038d2b1..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/ListResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package inu.codin.codinticketingapi.common.response; - -import lombok.Builder; -import lombok.Getter; - -import java.util.List; - -@Getter -public class ListResponse extends CommonResponse{ - List dataList; - - @Builder - public ListResponse(int code, String message, List dataList) { - super(true, code, message); - this.dataList = dataList; - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/SingleResponse.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/SingleResponse.java deleted file mode 100644 index fad3c08f..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/response/SingleResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package inu.codin.codinticketingapi.common.response; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class SingleResponse extends CommonResponse { - T data; - - @Builder - public SingleResponse(int code, String message, T data) { - super(true, code, message); - this.data = data; - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/util/MultipartJackson2HttpMessageConverter.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/util/MultipartJackson2HttpMessageConverter.java deleted file mode 100644 index 2ed2152f..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/common/util/MultipartJackson2HttpMessageConverter.java +++ /dev/null @@ -1,34 +0,0 @@ -package inu.codin.codinticketingapi.common.util; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.http.MediaType; -import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; -import org.springframework.stereotype.Component; - -import java.lang.reflect.Type; - -@Component -public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { - - /** - * "Content-Type: multipart/form-data" 헤더를 지원하는 HTTP 요청 변환기 - */ - public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { - super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); - } - - @Override - public boolean canWrite(Class clazz, MediaType mediaType) { - return false; - } - - @Override - public boolean canWrite(Type type, Class clazz, MediaType mediaType) { - return false; - } - - @Override - protected boolean canWrite(MediaType mediaType) { - return false; - } -} \ No newline at end of file diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java index b15f2369..d377512f 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/SecurityConfig.java @@ -1,78 +1,78 @@ -package inu.codin.codinticketingapi.config; - -import inu.codin.codinticketingapi.security.exception.CustomAccessDeniedHandler; -import inu.codin.codinticketingapi.security.filter.SecurityExceptionHandlerFilter; -import inu.codin.security.filter.TokenValidationFilter; - -import inu.codin.security.jwt.JwtTokenValidator; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; -import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -@RequiredArgsConstructor -public class SecurityConfig { - private final JwtTokenValidator jwtTokenValidator; - private final CustomAccessDeniedHandler customAccessDeniedHandler; - - @Value("${server.domain}") - private String BASE_DOMAIN_URL; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception { - return http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - .csrf(CsrfConfigurer::disable) - .formLogin(FormLoginConfigurer::disable) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - // Swagger 관련 경로 허용 - .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll() - // 테스트 API 경로 - @PreAuthorize로 권한 제어 - .requestMatchers("/v3/api/test**").permitAll() - // 모든 요청은 인증 필요, 단 특정 경로는 예외 - .requestMatchers("/public/**").permitAll() // Public endpoints - .anyRequest().hasAnyRole("USER", "MANAGER", "ADMIN") - ) - .addFilterBefore( - new TokenValidationFilter(jwtTokenValidator), - UsernamePasswordAuthenticationFilter.class - ) - .addFilterBefore(new SecurityExceptionHandlerFilter(), TokenValidationFilter.class) - .exceptionHandling(exceptionHandling -> - exceptionHandling.accessDeniedHandler(customAccessDeniedHandler) - ) - .build(); - } - - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - - config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:3000", BASE_DOMAIN_URL, "https://front-end-dun-mu.vercel.app")); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); - config.setAllowedHeaders(List.of("*")); - config.setExposedHeaders(List.of("*")); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; - } -} +//package inu.codin.codinticketingapi.config; +// +//import inu.codin.security.exception.CustomAccessDeniedHandler; +//import inu.codin.codinticketingapi.security.filter.SecurityExceptionHandlerFilter; +//import inu.codin.security.filter.TokenValidationFilter; +// +//import inu.codin.security.jwt.JwtTokenValidator; +//import lombok.RequiredArgsConstructor; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +//import org.springframework.security.config.annotation.web.builders.HttpSecurity; +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +//import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +//import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +//import org.springframework.security.config.http.SessionCreationPolicy; +//import org.springframework.security.web.SecurityFilterChain; +//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +//import org.springframework.web.cors.CorsConfiguration; +//import org.springframework.web.cors.CorsConfigurationSource; +//import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +// +//import java.util.List; +// +//@Configuration +//@EnableWebSecurity +//@EnableMethodSecurity +//@RequiredArgsConstructor +//public class SecurityConfig { +// private final JwtTokenValidator jwtTokenValidator; +// private final CustomAccessDeniedHandler customAccessDeniedHandler; +// +// @Value("${server.domain}") +// private String BASE_DOMAIN_URL; +// +// @Bean +// public SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception { +// return http +// .cors(cors -> cors.configurationSource(corsConfigurationSource())) +// .csrf(CsrfConfigurer::disable) +// .formLogin(FormLoginConfigurer::disable) +// .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) +// .authorizeHttpRequests(auth -> auth +// // Swagger 관련 경로 허용 +// .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll() +// // 테스트 API 경로 - @PreAuthorize로 권한 제어 +// .requestMatchers("/v3/api/test**").permitAll() +// // 모든 요청은 인증 필요, 단 특정 경로는 예외 +// .requestMatchers("/public/**").permitAll() // Public endpoints +// .anyRequest().hasAnyRole("USER", "MANAGER", "ADMIN") +// ) +// .addFilterBefore( +// new TokenValidationFilter(jwtTokenValidator), +// UsernamePasswordAuthenticationFilter.class +// ) +// .addFilterBefore(new SecurityExceptionHandlerFilter(), TokenValidationFilter.class) +// .exceptionHandling(exceptionHandling -> +// exceptionHandling.accessDeniedHandler(customAccessDeniedHandler) +// ) +// .build(); +// } +// +// @Bean +// public CorsConfigurationSource corsConfigurationSource() { +// CorsConfiguration config = new CorsConfiguration(); +// +// config.setAllowCredentials(true); +// config.setAllowedOrigins(List.of("http://localhost:3000", BASE_DOMAIN_URL, "https://front-end-dun-mu.vercel.app")); +// config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); +// config.setAllowedHeaders(List.of("*")); +// config.setExposedHeaders(List.of("*")); +// +// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); +// source.registerCorsConfiguration("/**", config); +// return source; +// } +//} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/WebConfig.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/WebConfig.java index 4da7ca7d..b2dc7e6b 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/WebConfig.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/config/WebConfig.java @@ -1,7 +1,7 @@ package inu.codin.codinticketingapi.config; import inu.codin.codinticketingapi.common.converter.CampusConverter; -import inu.codin.codinticketingapi.common.util.MultipartJackson2HttpMessageConverter; +import inu.codin.common.util.MultipartJackson2HttpMessageConverter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/EventAdminControllerImpl.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/EventAdminControllerImpl.java index 2c3fe107..afb1a31f 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/EventAdminControllerImpl.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/EventAdminControllerImpl.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.admin.controller; -import inu.codin.codinticketingapi.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codinticketingapi.domain.admin.controller.swagger.EventAdminController; import inu.codin.codinticketingapi.domain.admin.dto.request.EventCreateRequest; import inu.codin.codinticketingapi.domain.admin.dto.request.EventUpdateRequest; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/swagger/EventAdminController.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/swagger/EventAdminController.java index 03d7372f..830f9168 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/swagger/EventAdminController.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/controller/swagger/EventAdminController.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.admin.controller.swagger; -import inu.codin.codinticketingapi.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codinticketingapi.domain.admin.dto.request.EventCreateRequest; import inu.codin.codinticketingapi.domain.admin.dto.request.EventUpdateRequest; import inu.codin.codinticketingapi.domain.admin.dto.response.EventParticipationProfilePageResponse; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelErrorCode.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelErrorCode.java index bf64ed8a..c47bb843 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelErrorCode.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.admin.exception; -import inu.codin.codinticketingapi.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelException.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelException.java index 84214323..cd6d16dc 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelException.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/exception/ExcelException.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.admin.exception; -import inu.codin.codinticketingapi.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/service/EventAdminService.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/service/EventAdminService.java index acecd67c..621e1f68 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/service/EventAdminService.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/admin/service/EventAdminService.java @@ -23,7 +23,8 @@ import inu.codin.codinticketingapi.domain.user.exception.UserErrorCode; import inu.codin.codinticketingapi.domain.user.exception.UserException; import inu.codin.codinticketingapi.domain.user.service.UserClientService; -import inu.codin.codinticketingapi.security.util.SecurityUtil; + +import inu.codin.security.util.SecurityUtil; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageErrorCode.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageErrorCode.java index 10216f9f..6d511d64 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageErrorCode.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.image.exception; -import inu.codin.codinticketingapi.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageException.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageException.java index 342fbbd3..0527e697 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageException.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/image/exception/ImageException.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.image.exception; -import inu.codin.codinticketingapi.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/EventController.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/EventController.java index 0bc06d65..cd54d310 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/EventController.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/EventController.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.ticketing.controller; -import inu.codin.codinticketingapi.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codinticketingapi.domain.ticketing.dto.response.EventDetailResponse; import inu.codin.codinticketingapi.domain.ticketing.dto.response.EventPageResponse; import inu.codin.codinticketingapi.domain.ticketing.dto.response.EventParticipationHistoryPageResponse; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/TicketingController.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/TicketingController.java index 41982d7a..dacadd12 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/TicketingController.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/controller/TicketingController.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.ticketing.controller; -import inu.codin.codinticketingapi.common.response.SingleResponse; +import inu.codin.common.response.SingleResponse; import inu.codin.codinticketingapi.domain.ticketing.dto.response.ParticipationResponse; import inu.codin.codinticketingapi.domain.ticketing.service.ParticipationService; import inu.codin.codinticketingapi.domain.ticketing.service.TicketingService; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingErrorCode.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingErrorCode.java index 6e4d7151..91a622e8 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingErrorCode.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.ticketing.exception; -import inu.codin.codinticketingapi.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingException.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingException.java index 863527bf..2e43c6dc 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingException.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/exception/TicketingException.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.ticketing.exception; -import inu.codin.codinticketingapi.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/service/ParticipationService.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/service/ParticipationService.java index 9a6e45f9..71cc8ba8 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/service/ParticipationService.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/ticketing/service/ParticipationService.java @@ -15,7 +15,7 @@ import inu.codin.codinticketingapi.domain.user.exception.UserErrorCode; import inu.codin.codinticketingapi.domain.user.exception.UserException; import inu.codin.codinticketingapi.domain.user.service.UserClientService; -import inu.codin.codinticketingapi.security.util.SecurityUtil; +import inu.codin.security.util.SecurityUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/config/FeignClientConfig.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/config/FeignClientConfig.java index 17539b5a..8ddcd49a 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/config/FeignClientConfig.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/config/FeignClientConfig.java @@ -1,7 +1,7 @@ package inu.codin.codinticketingapi.domain.user.config; import feign.RequestInterceptor; -import inu.codin.codinticketingapi.security.util.SecurityUtil; +import inu.codin.security.util.SecurityUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserErrorCode.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserErrorCode.java index 6c66fa2c..e99a7740 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserErrorCode.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.user.exception; -import inu.codin.codinticketingapi.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserException.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserException.java index db9db664..6b582f90 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserException.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/domain/user/exception/UserException.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingapi.domain.user.exception; -import inu.codin.codinticketingapi.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityErrorCode.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityErrorCode.java deleted file mode 100644 index 6fdcda0b..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityErrorCode.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codinticketingapi.security.exception; - -import inu.codin.codinticketingapi.common.exception.GlobalErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; - -@RequiredArgsConstructor -public enum SecurityErrorCode implements GlobalErrorCode { - - INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), - EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), - TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "토큰이 없습니다."), - ACCESS_DENIED(HttpStatus.FORBIDDEN,"접근 권한이 없습니다."); - - private final HttpStatus httpStatus; - private final String message; - - @Override - public HttpStatus httpStatus() { - return httpStatus; - } - - @Override - public String message() { - return message; - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityException.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityException.java deleted file mode 100644 index f4ab70ef..00000000 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/exception/SecurityException.java +++ /dev/null @@ -1,14 +0,0 @@ -package inu.codin.codinticketingapi.security.exception; - -import inu.codin.codinticketingapi.common.exception.GlobalException; -import lombok.Getter; - -@Getter -public class SecurityException extends GlobalException { - private final SecurityErrorCode securityErrorCode; - - public SecurityException(SecurityErrorCode errorCode) { - super(errorCode); - this.securityErrorCode = errorCode; - } -} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/SecurityExceptionHandlerFilter.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/SecurityExceptionHandlerFilter.java index 277950bb..bc58be31 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/SecurityExceptionHandlerFilter.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/filter/SecurityExceptionHandlerFilter.java @@ -1,53 +1,53 @@ -package inu.codin.codinticketingapi.security.filter; - -import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codinticketingapi.common.response.ExceptionResponse; -import inu.codin.codinticketingapi.security.exception.SecurityException; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * SecurityFilterChain에서 발생하는 예외를 처리하는 필터 - */ -@Slf4j -public class SecurityExceptionHandlerFilter extends OncePerRequestFilter { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - try { - filterChain.doFilter(request, response); - } catch (SecurityException e) { - log.error("[ExceptionHandlerFilter] SecurityException 발생: {}", e.getMessage()); - setErrorResponse(response, e); - } catch (Exception e) { - log.error("[ExceptionHandlerFilter] 예상치 못한 예외 발생: {}", e.getMessage(), e); - // SecurityException이 아닌 다른 예외는 기존 처리 방식으로 전달 - throw e; - } - } - - /** - * SecurityException을 ExceptionResponse로 변환하여 응답 - */ - private void setErrorResponse(HttpServletResponse response, SecurityException e) throws IOException { - response.setStatus(e.getSecurityErrorCode().httpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding(StandardCharsets.UTF_8.name()); - - ExceptionResponse exceptionResponse = new ExceptionResponse(e.getSecurityErrorCode().message(), e.getSecurityErrorCode().httpStatus().value()); - - String jsonResponse = objectMapper.writeValueAsString(exceptionResponse); - response.getWriter().write(jsonResponse); - } -} +//package inu.codin.codinticketingapi.security.filter; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import inu.codin.codinticketingapi.common.response.ExceptionResponse; +//import inu.codin.codinticketingapi.security.exception.SecurityException; +//import jakarta.servlet.FilterChain; +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.http.MediaType; +//import org.springframework.web.filter.OncePerRequestFilter; +// +//import java.io.IOException; +//import java.nio.charset.StandardCharsets; +// +///** +// * SecurityFilterChain에서 발생하는 예외를 처리하는 필터 +//// */ +//@Slf4j +//public class SecurityExceptionHandlerFilter extends OncePerRequestFilter { +// +// private final ObjectMapper objectMapper = new ObjectMapper(); +// +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) +// throws ServletException, IOException { +// try { +// filterChain.doFilter(request, response); +// } catch (SecurityException e) { +// log.error("[ExceptionHandlerFilter] SecurityException 발생: {}", e.getMessage()); +// setErrorResponse(response, e); +// } catch (Exception e) { +// log.error("[ExceptionHandlerFilter] 예상치 못한 예외 발생: {}", e.getMessage(), e); +// // SecurityException이 아닌 다른 예외는 기존 처리 방식으로 전달 +// throw e; +// } +// } +// +// /** +// * SecurityException을 ExceptionResponse로 변환하여 응답 +// */ +// private void setErrorResponse(HttpServletResponse response, SecurityException e) throws IOException { +// response.setStatus(e.getSecurityErrorCode().httpStatus().value()); +// response.setContentType(MediaType.APPLICATION_JSON_VALUE); +// response.setCharacterEncoding(StandardCharsets.UTF_8.name()); +// +// ExceptionResponse exceptionResponse = new ExceptionResponse(e.getSecurityErrorCode().message(), e.getSecurityErrorCode().httpStatus().value()); +// +// String jsonResponse = objectMapper.writeValueAsString(exceptionResponse); +// response.getWriter().write(jsonResponse); +// } +//} diff --git a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java index 91a32a7d..00c4fee5 100644 --- a/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java +++ b/codin-ticketing-api/src/main/java/inu/codin/codinticketingapi/security/util/SecurityUtil.java @@ -1,97 +1,97 @@ -package inu.codin.codinticketingapi.security.util; - -import inu.codin.codinticketingapi.security.exception.SecurityErrorCode; -import inu.codin.codinticketingapi.security.exception.SecurityException; -import inu.codin.security.jwt.TokenUserDetails; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * SecurityContext 관련 유틸리티 (토큰 검증 전용) - */ -public class SecurityUtil { - - /** - * 현재 인증된 사용자의 유저 ID 반환 - */ - public static String getUserId() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getUserId(); - } - - /** - * 현재 인증된 사용자의 이메일(유저이름) 반환 - */ - public static String getUsername() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getUsername(); - } - - /** - * 현재 인증된 사용자의 유저 토큰 반환 - */ - public static String getUserToken() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getToken(); - } - - /** - * 현재 인증된 사용자의 권한 반환 - */ - public static String getCurrentUserRole() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getRole(); - } - - /** - * 현재 사용자와 주어진 사용자 ID가 같은지 검증 - */ - public static void validateUser(String userId) { - String currentUserId = getUserId(); - if (!currentUserId.equals(userId)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - } - - /** - * 현재 사용자가 인증되어 있는지 확인 - */ - public static boolean isAuthenticated() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication != null && - authentication.isAuthenticated() && - authentication.getPrincipal() instanceof TokenUserDetails; - } - - /** - * 현재 사용자가 특정 권한을 가지고 있는지 확인 - */ - public static boolean hasRole(String role) { - try { - String currentRole = getCurrentUserRole(); - return role.equals(currentRole); - } catch (SecurityException e) { - return false; - } - } -} \ No newline at end of file +//package inu.codin.codinticketingapi.security.util; +// +//import inu.codin.codinticketingapi.security.exception.SecurityErrorCode; +//import inu.codin.codinticketingapi.security.exception.SecurityException; +//import inu.codin.security.jwt.TokenUserDetails; +//import org.springframework.security.core.Authentication; +//import org.springframework.security.core.context.SecurityContextHolder; +// +///** +// * SecurityContext 관련 유틸리티 (토큰 검증 전용) +// */ +//public class SecurityUtil { +// +// /** +// * 현재 인증된 사용자의 유저 ID 반환 +// */ +// public static String getUserId() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// +// return userDetails.getUserId(); +// } +// +// /** +// * 현재 인증된 사용자의 이메일(유저이름) 반환 +// */ +// public static String getUsername() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// +// return userDetails.getUsername(); +// } +// +// /** +// * 현재 인증된 사용자의 유저 토큰 반환 +// */ +// public static String getUserToken() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// +// return userDetails.getToken(); +// } +// +// /** +// * 현재 인증된 사용자의 권한 반환 +// */ +// public static String getCurrentUserRole() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// +// return userDetails.getRole(); +// } +// +// /** +// * 현재 사용자와 주어진 사용자 ID가 같은지 검증 +// */ +// public static void validateUser(String userId) { +// String currentUserId = getUserId(); +// if (!currentUserId.equals(userId)) { +// throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); +// } +// } +// +// /** +// * 현재 사용자가 인증되어 있는지 확인 +// */ +// public static boolean isAuthenticated() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// return authentication != null && +// authentication.isAuthenticated() && +// authentication.getPrincipal() instanceof TokenUserDetails; +// } +// +// /** +// * 현재 사용자가 특정 권한을 가지고 있는지 확인 +// */ +// public static boolean hasRole(String role) { +// try { +// String currentRole = getCurrentUserRole(); +// return role.equals(currentRole); +// } catch (SecurityException e) { +// return false; +// } +// } +//} \ No newline at end of file diff --git a/codin-ticketing-api/src/main/resources/application-local.yml b/codin-ticketing-api/src/main/resources/application-local.yml new file mode 100644 index 00000000..a5ae1c52 --- /dev/null +++ b/codin-ticketing-api/src/main/resources/application-local.yml @@ -0,0 +1,20 @@ +# 이름 : application-local.yml + +security: + permit-all: + urls: + - "/auth/**" + - "/v3/api/test1" + - "/ws-stomp/**" + - "/suspends" + - "/login/oauth2/code/**" + # --- Swagger / Springdoc --- + - "/swagger-ui/**" + - "/v3/api-docs/**" + - "/swagger-resources/**" + - "/webjars/**" + # --- 정적/루트/헬스체크 --- + - "/" + - "/favicon.ico" + - "/.well-known/**" + - "/actuator/health" diff --git a/codin-ticketing-api/src/main/resources/application-prod.yml b/codin-ticketing-api/src/main/resources/application-prod.yml new file mode 100644 index 00000000..61d8f33a --- /dev/null +++ b/codin-ticketing-api/src/main/resources/application-prod.yml @@ -0,0 +1,6 @@ +# 이름 : application-prod.yml + +security: + #공개 API + public-api: + urls: \ No newline at end of file diff --git a/codin-ticketing-api/src/main/resources/application.yml b/codin-ticketing-api/src/main/resources/application.yml index 410a992c..f46c8320 100644 --- a/codin-ticketing-api/src/main/resources/application.yml +++ b/codin-ticketing-api/src/main/resources/application.yml @@ -3,6 +3,9 @@ spring: name: codin-ticketing-api config: import: optional:file:./.env[.properties], optional:file:./.env.local[.properties] + profiles: + active: + local jwt: secret: ${SPRING_JWT_SECRET} diff --git a/codin-ticketing-sse/build.gradle b/codin-ticketing-sse/build.gradle index 18b6e683..638c5fde 100644 --- a/codin-ticketing-sse/build.gradle +++ b/codin-ticketing-sse/build.gradle @@ -19,6 +19,7 @@ repositories { dependencies { implementation project(':codin-security') + implementation project(':codin-common') implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalErrorCode.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalErrorCode.java deleted file mode 100644 index 3f0df833..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalErrorCode.java +++ /dev/null @@ -1,8 +0,0 @@ -package inu.codin.codinticketingsse.common.exception; - -import org.springframework.http.HttpStatus; - -public interface GlobalErrorCode { - HttpStatus httpStatus(); - String message(); -} \ No newline at end of file diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalException.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalException.java deleted file mode 100644 index 4b8cdd03..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalException.java +++ /dev/null @@ -1,15 +0,0 @@ -package inu.codin.codinticketingsse.common.exception; - -public class GlobalException extends RuntimeException{ - - private final GlobalErrorCode errorCode; - - public GlobalException(GlobalErrorCode errorCode) { - super(errorCode.message()); - this.errorCode = errorCode; - } - - public GlobalErrorCode getErrorCode() { - return errorCode; - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java index 1f8a4b33..c05a255e 100644 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java +++ b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java @@ -1,8 +1,10 @@ package inu.codin.codinticketingsse.common.exception; -import inu.codin.codinticketingsse.common.response.ExceptionResponse; -import inu.codin.codinticketingsse.security.exception.SecurityErrorCode; -import inu.codin.codinticketingsse.security.exception.SecurityException; +import inu.codin.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalException; +import inu.codin.common.response.ExceptionResponse; +import inu.codin.security.exception.SecurityErrorCode; +import inu.codin.security.exception.SecurityException; import inu.codin.codinticketingsse.sse.exception.SseErrorCode; import inu.codin.codinticketingsse.sse.exception.SseException; import jakarta.validation.ConstraintViolationException; diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/CommonResponse.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/CommonResponse.java deleted file mode 100644 index 2ae7b7c8..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/CommonResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package inu.codin.codinticketingsse.common.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; - -@Getter -@Schema -public class CommonResponse { - boolean success; - int code; - String message; - - public CommonResponse(boolean success, int code, String message) { - this.success = success; - this.code = code; - this.message = message; - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/ExceptionResponse.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/ExceptionResponse.java deleted file mode 100644 index a7449a7b..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/response/ExceptionResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package inu.codin.codinticketingsse.common.response; - -public class ExceptionResponse extends CommonResponse { - public ExceptionResponse(String message, int code) { - super(false, code, message); - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/CustomAccessDeniedHandler.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/CustomAccessDeniedHandler.java deleted file mode 100644 index b3623e65..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/CustomAccessDeniedHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package inu.codin.codinticketingsse.security.exception; - -import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codinticketingsse.common.response.ExceptionResponse; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.MediaType; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -@Component -public class CustomAccessDeniedHandler implements AccessDeniedHandler { - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - SecurityErrorCode errorCode = SecurityErrorCode.ACCESS_DENIED; - - response.setStatus(errorCode.httpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding(StandardCharsets.UTF_8.name()); - - ExceptionResponse exceptionResponse = new ExceptionResponse(errorCode.message(), errorCode.httpStatus().value()); - - String jsonResponse = objectMapper.writeValueAsString(exceptionResponse); - response.getWriter().write(jsonResponse); - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityErrorCode.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityErrorCode.java deleted file mode 100644 index 089eea48..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityErrorCode.java +++ /dev/null @@ -1,27 +0,0 @@ -package inu.codin.codinticketingsse.security.exception; - -import inu.codin.codinticketingsse.common.exception.GlobalErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; - -@RequiredArgsConstructor -public enum SecurityErrorCode implements GlobalErrorCode { - - INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), - EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), - TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "토큰이 없습니다."), - ACCESS_DENIED(HttpStatus.FORBIDDEN,"접근 권한이 없습니다."); - - private final HttpStatus httpStatus; - private final String message; - - @Override - public HttpStatus httpStatus() { - return httpStatus; - } - - @Override - public String message() { - return message; - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityException.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityException.java deleted file mode 100644 index 1ad3c786..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/exception/SecurityException.java +++ /dev/null @@ -1,16 +0,0 @@ -package inu.codin.codinticketingsse.security.exception; - - -import inu.codin.codinticketingsse.common.exception.GlobalException; -import lombok.Getter; - -@Getter -public class SecurityException extends GlobalException { - - private final SecurityErrorCode securityErrorCode; - - public SecurityException(SecurityErrorCode errorCode) { - super(errorCode); - this.securityErrorCode = errorCode; - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/SecurityExceptionHandlerFilter.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/SecurityExceptionHandlerFilter.java deleted file mode 100644 index cbcae53d..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/SecurityExceptionHandlerFilter.java +++ /dev/null @@ -1,53 +0,0 @@ -package inu.codin.codinticketingsse.security.filter; - -import com.fasterxml.jackson.databind.ObjectMapper; -import inu.codin.codinticketingsse.security.exception.SecurityException; -import inu.codin.codinticketingsse.common.response.ExceptionResponse; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * SecurityFilterChain에서 발생하는 예외를 처리하는 필터 - */ -@Slf4j -public class SecurityExceptionHandlerFilter extends OncePerRequestFilter { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - try { - filterChain.doFilter(request, response); - } catch (SecurityException e) { - log.error("[ExceptionHandlerFilter] SecurityException 발생: {}", e.getMessage()); - setErrorResponse(response, e); - } catch (Exception e) { - log.error("[ExceptionHandlerFilter] 예상치 못한 예외 발생: {}", e.getMessage(), e); - // SecurityException이 아닌 다른 예외는 기존 처리 방식으로 전달 - throw e; - } - } - - /** - * SecurityException을 ExceptionResponse로 변환하여 응답 - */ - private void setErrorResponse(HttpServletResponse response, SecurityException e) throws IOException { - response.setStatus(e.getSecurityErrorCode().httpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding(StandardCharsets.UTF_8.name()); - - ExceptionResponse exceptionResponse = new ExceptionResponse(e.getSecurityErrorCode().message(), e.getSecurityErrorCode().httpStatus().value()); - - String jsonResponse = objectMapper.writeValueAsString(exceptionResponse); - response.getWriter().write(jsonResponse); - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/TokenValidationFilter.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/TokenValidationFilter.java deleted file mode 100644 index 62842ebb..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/filter/TokenValidationFilter.java +++ /dev/null @@ -1,72 +0,0 @@ -package inu.codin.codinticketingsse.security.filter; - - -import inu.codin.codinticketingsse.security.jwt.JwtTokenValidator; -import inu.codin.codinticketingsse.security.jwt.TokenUserDetails; -import inu.codin.codinticketingsse.security.util.TokenUtil; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -/** - * 토큰 검증 전용 필터 - */ -@RequiredArgsConstructor -@Slf4j -public class TokenValidationFilter extends OncePerRequestFilter { - private final JwtTokenValidator jwtTokenValidator; - - private final String [] SWAGGER_AUTH_PATHS = { - "/swagger-ui/**", - "/v3/api-docs/**", - "/v3/api-docs", - "/swagger-resources/**" - }; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String accessToken = TokenUtil.extractToken(request); - - if (accessToken != null && jwtTokenValidator.validateAccessToken(accessToken)) { - log.debug("[TokenValidationFilter] Access Token이 유효함"); - // Access Token이 있고 유효한 경우 - setAuthentication(accessToken); - } else { - log.debug("[TokenValidationFilter] Access Token이 유효하지 않음"); - SecurityContextHolder.clearContext(); - } - - filterChain.doFilter(request, response); - } - - /** - * 토큰에서 인증 정보 생성 후 SecurityContext에 설정 - */ - private void setAuthentication(String token) { - try { - String userId = jwtTokenValidator.getUserId(token); - String username = jwtTokenValidator.getUsername(token); - String role = jwtTokenValidator.getUserRole(token); - log.debug("[setAuthentication] : {}, {}", userId, role); - - TokenUserDetails userDetails = TokenUserDetails.fromTokenClaims(userId, username, role, token); - - UsernamePasswordAuthenticationToken authentication = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - - SecurityContextHolder.getContext().setAuthentication(authentication); - log.info("[TokenValidationFilter] Authentication 설정 완료: userId={}, username={}, role={}", userId, username, role); - } catch (Exception e) { - log.error("[TokenValidationFilter] 인증 설정 실패: {}", e.getMessage()); - SecurityContextHolder.clearContext(); - } - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/JwtTokenValidator.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/JwtTokenValidator.java deleted file mode 100644 index eb00518f..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/JwtTokenValidator.java +++ /dev/null @@ -1,82 +0,0 @@ -package inu.codin.codinticketingsse.security.jwt; - -import inu.codin.codinticketingsse.security.exception.SecurityErrorCode; -import inu.codin.codinticketingsse.security.exception.SecurityException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.security.Key; - -@Component -@Slf4j -public class JwtTokenValidator { - - @Value("${spring.jwt.secret}") - private String secret; - - private Key SECRET_KEY; - - @PostConstruct - protected void init() { - log.info("[JwtTokenValidator] JWT Secret key initialized, key: {}", secret.substring(0, 10)); - SECRET_KEY = Keys.hmacShaKeyFor(secret.getBytes()); - } - - /** - * 토큰 유효성 검사 (토큰 변조, 만료) - * @param accessToken - * @return true: 유효한 토큰 - * @throws SecurityException: 토큰 만료, 유효하지 않은 토큰 - */ - public boolean validateAccessToken(String accessToken) { - try { - Jwts.parserBuilder() - .setSigningKey(SECRET_KEY) - .setAllowedClockSkewSeconds(60) - .build() - .parseClaimsJws(accessToken); - return true; - } catch (ExpiredJwtException e) { // 토큰 만료 - log.error("[validateAccessToken] 토큰 만료 : {}", e.getMessage()); - throw new SecurityException(SecurityErrorCode.EXPIRED_TOKEN); - } catch (Exception e) { // 토큰 변조 - log.error("[validateAccessToken] 유효하지 않은 토큰 : {}", e.getMessage()); - throw new SecurityException(SecurityErrorCode.INVALID_TOKEN); - } - } - - /** - * 토큰에서 사용자 ID 추출 - */ - public String getUserId(String token) { - return getClaims(token).get("userId", String.class); - } - - /** - * 토큰에서 사용자 이메일(유저네임) 추출 - */ - public String getUsername(String token) { - return getClaims(token).getSubject(); - } - - /** - * 토큰에서 사용자 권한 추출 - */ - public String getUserRole(String token) { - return getClaims(token).get("auth", String.class); - } - - private Claims getClaims(String token) { - return Jwts.parserBuilder() - .setSigningKey(SECRET_KEY) - .build() - .parseClaimsJws(token) - .getBody(); - } -} \ No newline at end of file diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/TokenUserDetails.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/TokenUserDetails.java deleted file mode 100644 index 7ecba9cf..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/jwt/TokenUserDetails.java +++ /dev/null @@ -1,78 +0,0 @@ -package inu.codin.codinticketingsse.security.jwt; - -import lombok.Builder; -import lombok.Getter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; -import java.util.Collections; - -/** - * 토큰 검증 전용 UserDetails - DB 조회 없이 JWT 토큰에서 파싱한 정보만 담음 - */ -@Getter -public class TokenUserDetails implements UserDetails { - - private final String userId; - private final String email; - private final String role; - private final String token; - private final Collection authorities; - - @Builder - public TokenUserDetails(String userId, String email, String role, String token) { - this.userId = userId; - this.email = email; - this.role = role; - this.token = token; - // JWT에서 이미 ROLE_ 접두사가 있는 경우 그대로 사용, 없는 경우 추가 - String authority = role.startsWith("ROLE_") ? role : "ROLE_" + role; - this.authorities = Collections.singletonList(new SimpleGrantedAuthority(authority)); - } - - public static TokenUserDetails fromTokenClaims(String userId, String email, String role, String token) { - return TokenUserDetails.builder() - .userId(userId) - .email(email) - .role(role) - .token(token) - .build(); - } - - @Override - public Collection getAuthorities() { - return authorities; - } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return email; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } -} diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/SecurityUtil.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/SecurityUtil.java deleted file mode 100644 index e6bcd853..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/SecurityUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -package inu.codin.codinticketingsse.security.util; - -import inu.codin.codinticketingsse.security.exception.SecurityException; -import inu.codin.codinticketingsse.security.exception.SecurityErrorCode; -import inu.codin.codinticketingsse.security.jwt.TokenUserDetails; -import io.jsonwebtoken.JwtException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * SecurityContext 관련 유틸리티 (토큰 검증 전용) - */ -public class SecurityUtil { - - /** - * 현재 인증된 사용자의 유저 ID 반환 - */ - public static String getUserId() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getUserId(); - } - - /** - * 현재 인증된 사용자의 이메일(유저이름) 반환 - */ - public static String getUsername() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getUsername(); - } - - /** - * 현재 인증된 사용자의 유저 토큰 반환 - */ - public static String getUserToken() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getToken(); - } - - /** - * 현재 인증된 사용자의 권한 반환 - */ - public static String getCurrentUserRole() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication == null || !(authentication.getPrincipal() instanceof TokenUserDetails userDetails)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - - return userDetails.getRole(); - } - - /** - * 현재 사용자와 주어진 사용자 ID가 같은지 검증 - */ - public static void validateUser(String userId) { - String currentUserId = getUserId(); - if (!currentUserId.equals(userId)) { - throw new SecurityException(SecurityErrorCode.ACCESS_DENIED); - } - } - - /** - * 현재 사용자가 인증되어 있는지 확인 - */ - public static boolean isAuthenticated() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication != null && - authentication.isAuthenticated() && - authentication.getPrincipal() instanceof TokenUserDetails; - } - - /** - * 현재 사용자가 특정 권한을 가지고 있는지 확인 - */ - public static boolean hasRole(String role) { - try { - String currentRole = getCurrentUserRole(); - return role.equals(currentRole); - } catch (SecurityException e) { - return false; - } - } -} \ No newline at end of file diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/TokenUtil.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/TokenUtil.java deleted file mode 100644 index bc20f3f0..00000000 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/security/util/TokenUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -package inu.codin.codinticketingsse.security.util; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; - -/** - * 토큰 추출 및 파싱 유틸리티 - */ -@Slf4j -public class TokenUtil { - - /** - * 쿠키 또는 Authorization 헤더에서 Access Token 추출 - */ - public static String extractToken(HttpServletRequest request) { - String bearerToken = null; - // 1. Authorization 헤더에서 토큰 추출 (우선순위 1) - String authHeader = request.getHeader("Authorization"); - if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) { - bearerToken = authHeader.substring(7); - log.debug("[extractToken] Authorization 헤더에서 토큰 추출: 성공"); - } else { - log.debug("[extractToken] Authorization 헤더: {}", authHeader != null ? "형식 오류" : "없음"); - } - - // 2. 쿠키에서 토큰 추출 (우선순위 2) - if (!StringUtils.hasText(bearerToken)) { - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if ("x-access-token".equals(cookie.getName())) { - bearerToken = cookie.getValue(); - break; - } - } - } - log.debug("[extractToken] Cookie에서 추출한 토큰: {}", bearerToken != null ? "존재" : "없음"); - } - - return StringUtils.hasText(bearerToken) ? bearerToken : null; - } -} \ No newline at end of file diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/controller/SseController.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/controller/SseController.java index 6b111c0d..e1857850 100644 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/controller/SseController.java +++ b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/controller/SseController.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingsse.sse.controller; -import inu.codin.codinticketingsse.security.util.SecurityUtil; +import inu.codin.security.util.SecurityUtil; import inu.codin.codinticketingsse.sse.dto.EventStockStream; import inu.codin.codinticketingsse.sse.service.SseService; import io.swagger.v3.oas.annotations.Operation; diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseErrorCode.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseErrorCode.java index c4c82e4f..871b8b2f 100644 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseErrorCode.java +++ b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseErrorCode.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingsse.sse.exception; -import inu.codin.codinticketingsse.common.exception.GlobalErrorCode; +import inu.codin.common.exception.GlobalErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseException.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseException.java index 0d466d84..295daa5b 100644 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseException.java +++ b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/sse/exception/SseException.java @@ -1,6 +1,6 @@ package inu.codin.codinticketingsse.sse.exception; -import inu.codin.codinticketingsse.common.exception.GlobalException; +import inu.codin.common.exception.GlobalException; import lombok.Getter; @Getter diff --git a/docker/local/local-compose.yml b/docker/local/local-compose.yml index 59fa6e45..24832dac 100644 --- a/docker/local/local-compose.yml +++ b/docker/local/local-compose.yml @@ -4,12 +4,12 @@ services: image: mysql:8.0 container_name: codin-mysql env_file: - - .env.local + - ../../.env.local environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:rootpassword} - MYSQL_DATABASE: ${MYSQL_DATABASE:codin_db} - MYSQL_USER: ${MYSQL_USER:codin} - MYSQL_PASSWORD: ${MYSQL_PASSWORD:codinpassword} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} ports: - "${MYSQL_PORT}:3306" volumes: @@ -24,10 +24,10 @@ services: image: redis:7-alpine container_name: codin-redis env_file: - - .env.local + - ../../.env.local ports: - "${REDIS_PORT}:6379" - command: redis-server --requirepass ${REDIS_PASSWORD:-1234} + command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - ./redis/data:/data restart: unless-stopped @@ -38,11 +38,11 @@ services: image: mongo:latest container_name: codin-mongodb env_file: - - .env.local + - ../../.env.local ports: - "${MONGODB_PORT:-27017}:27017" environment: - MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:-codin} + MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE} volumes: - mongodb_data:/data/db restart: unless-stopped @@ -54,7 +54,7 @@ services: dockerfile: Dockerfile.es container_name: codin-elasticsearch env_file: - - .env.local + - ../../.env.local environment: - discovery.type=single-node - xpack.security.enabled=false From a0ed23dde85c10044c0cb16c231b6e0e5c72b235 Mon Sep 17 00:00:00 2001 From: gimgisu Date: Wed, 17 Dec 2025 20:13:05 +0900 Subject: [PATCH 1002/1002] =?UTF-8?q?refactor=20:=20ticketing=20sse=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20security=20dependency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 2 +- .../config/SecurityConfig.java | 156 +++++++++--------- .../src/main/resources/application-local.yml | 22 +++ .../src/main/resources/application-prod.yml | 8 + .../src/main/resources/application.yml | 3 + 5 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 codin-ticketing-sse/src/main/resources/application-local.yml create mode 100644 codin-ticketing-sse/src/main/resources/application-prod.yml diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java index c05a255e..66cb6c0e 100644 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java +++ b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/common/exception/GlobalExceptionHandler.java @@ -52,7 +52,7 @@ public ResponseEntity handleSseException(SseException e) { @ExceptionHandler(SecurityException.class) public ResponseEntity handleSecurityException(SecurityException e) { log.warn("[SecurityException] Class: {}, Error Message : {}", e.getClass().getSimpleName(), e.getMessage()); - SecurityErrorCode code = e.getSecurityErrorCode(); + SecurityErrorCode code = e.getErrorCode(); return ResponseEntity.status(code.httpStatus()) .body(new ExceptionResponse(e.getMessage(), code.httpStatus().value())); } diff --git a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/config/SecurityConfig.java b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/config/SecurityConfig.java index a0e34e64..dda758d2 100644 --- a/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/config/SecurityConfig.java +++ b/codin-ticketing-sse/src/main/java/inu/codin/codinticketingsse/config/SecurityConfig.java @@ -1,78 +1,78 @@ -package inu.codin.codinticketingsse.config; - -import inu.codin.codinticketingsse.security.exception.CustomAccessDeniedHandler; -import inu.codin.codinticketingsse.security.filter.SecurityExceptionHandlerFilter; -import inu.codin.codinticketingsse.security.filter.TokenValidationFilter; -import inu.codin.codinticketingsse.security.jwt.JwtTokenValidator; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; -import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity(prePostEnabled = true) -@RequiredArgsConstructor -public class SecurityConfig { - private final JwtTokenValidator jwtTokenValidator; - private final CustomAccessDeniedHandler customAccessDeniedHandler; - - @Value("${server.domain}") - private String BASE_DOMAIN_URL; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception { - return http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - .csrf(CsrfConfigurer::disable) - .formLogin(FormLoginConfigurer::disable) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - // Swagger 관련 경로 허용 - .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll() - // 테스트 API 경로 - @PreAuthorize로 권한 제어 - .requestMatchers("/v3/api/test**").permitAll() - // Sse 구독 엔드포인트 허용 (없으면 AccessDenied 오류 생김) - .requestMatchers("/subscribe/**").permitAll() - // 모든 요청은 인증 필요, 단 특정 경로는 예외 - .requestMatchers("/public/**").permitAll() // Public endpoints - .anyRequest().hasAnyRole("USER", "MANAGER", "ADMIN") - ) - .addFilterBefore( - new TokenValidationFilter(jwtTokenValidator), UsernamePasswordAuthenticationFilter.class - ) - .addFilterBefore(new SecurityExceptionHandlerFilter(), TokenValidationFilter.class) - .exceptionHandling(exceptionHandling -> - exceptionHandling.accessDeniedHandler(customAccessDeniedHandler) - ) - .build(); - } - - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - - config.setAllowCredentials(true); - config.setAllowedOrigins(List.of("http://localhost:3000", BASE_DOMAIN_URL, "https://front-end-dun-mu.vercel.app")); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); - config.setAllowedHeaders(List.of("*")); - config.setExposedHeaders(List.of("*")); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; - } -} +//package inu.codin.codinticketingsse.config; +// +//import inu.codin.codinticketingsse.security.exception.CustomAccessDeniedHandler; +//import inu.codin.codinticketingsse.security.filter.SecurityExceptionHandlerFilter; +//import inu.codin.codinticketingsse.security.filter.TokenValidationFilter; +//import inu.codin.codinticketingsse.security.jwt.JwtTokenValidator; +//import lombok.RequiredArgsConstructor; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +//import org.springframework.security.config.annotation.web.builders.HttpSecurity; +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +//import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +//import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +//import org.springframework.security.config.http.SessionCreationPolicy; +//import org.springframework.security.web.SecurityFilterChain; +//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +//import org.springframework.web.cors.CorsConfiguration; +//import org.springframework.web.cors.CorsConfigurationSource; +//import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +// +//import java.util.List; +// +//@Configuration +//@EnableWebSecurity +//@EnableMethodSecurity(prePostEnabled = true) +//@RequiredArgsConstructor +//public class SecurityConfig { +// private final JwtTokenValidator jwtTokenValidator; +// private final CustomAccessDeniedHandler customAccessDeniedHandler; +// +// @Value("${server.domain}") +// private String BASE_DOMAIN_URL; +// +// @Bean +// public SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception { +// return http +// .cors(cors -> cors.configurationSource(corsConfigurationSource())) +// .csrf(CsrfConfigurer::disable) +// .formLogin(FormLoginConfigurer::disable) +// .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) +// .authorizeHttpRequests(auth -> auth +// // Swagger 관련 경로 허용 +// .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll() +// // 테스트 API 경로 - @PreAuthorize로 권한 제어 +// .requestMatchers("/v3/api/test**").permitAll() +// // Sse 구독 엔드포인트 허용 (없으면 AccessDenied 오류 생김) +// .requestMatchers("/subscribe/**").permitAll() +// // 모든 요청은 인증 필요, 단 특정 경로는 예외 +// .requestMatchers("/public/**").permitAll() // Public endpoints +// .anyRequest().hasAnyRole("USER", "MANAGER", "ADMIN") +// ) +// .addFilterBefore( +// new TokenValidationFilter(jwtTokenValidator), UsernamePasswordAuthenticationFilter.class +// ) +// .addFilterBefore(new SecurityExceptionHandlerFilter(), TokenValidationFilter.class) +// .exceptionHandling(exceptionHandling -> +// exceptionHandling.accessDeniedHandler(customAccessDeniedHandler) +// ) +// .build(); +// } +// +// @Bean +// public CorsConfigurationSource corsConfigurationSource() { +// CorsConfiguration config = new CorsConfiguration(); +// +// config.setAllowCredentials(true); +// config.setAllowedOrigins(List.of("http://localhost:3000", BASE_DOMAIN_URL, "https://front-end-dun-mu.vercel.app")); +// config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); +// config.setAllowedHeaders(List.of("*")); +// config.setExposedHeaders(List.of("*")); +// +// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); +// source.registerCorsConfiguration("/**", config); +// return source; +// } +//} diff --git a/codin-ticketing-sse/src/main/resources/application-local.yml b/codin-ticketing-sse/src/main/resources/application-local.yml new file mode 100644 index 00000000..98b0e383 --- /dev/null +++ b/codin-ticketing-sse/src/main/resources/application-local.yml @@ -0,0 +1,22 @@ +# 이름 : application-local.yml + +security: + permit-all: + urls: + - "/auth/**" + - "/v3/api/test1" + - "/ws-stomp/**" + - "/suspends" + - "/login/oauth2/code/**" + # --- Swagger / Springdoc --- + - "/swagger-ui/**" + - "/v3/api-docs/**" + - "/swagger-resources/**" + - "/webjars/**" + # --- 정적/루트/헬스체크 --- + - "/" + - "/favicon.ico" + - "/.well-known/**" + - "/actuator/health" + # Sse 구독 엔드포인트 허용 + - "/subscribe/**" diff --git a/codin-ticketing-sse/src/main/resources/application-prod.yml b/codin-ticketing-sse/src/main/resources/application-prod.yml new file mode 100644 index 00000000..05039eb3 --- /dev/null +++ b/codin-ticketing-sse/src/main/resources/application-prod.yml @@ -0,0 +1,8 @@ +# 이름 : application-prod.yml + +security: + #공개 API + public-api: + urls: + #Sse 구독 엔드포인트 허용 + "/subscribe/**"" \ No newline at end of file diff --git a/codin-ticketing-sse/src/main/resources/application.yml b/codin-ticketing-sse/src/main/resources/application.yml index bff5d5b5..42230640 100644 --- a/codin-ticketing-sse/src/main/resources/application.yml +++ b/codin-ticketing-sse/src/main/resources/application.yml @@ -1,6 +1,9 @@ spring: application: name: codin-ticketing-sse + profiles: + active: + local jwt: secret: ${SPRING_JWT_SECRET} mvc: