diff --git a/application/main-app/build.gradle b/application/main-app/build.gradle index 7a595252..9e2cc751 100644 --- a/application/main-app/build.gradle +++ b/application/main-app/build.gradle @@ -28,9 +28,6 @@ dependencies { // Security implementation "org.springframework.boot:spring-boot-starter-security" - // swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3' - // aws implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.0.0' diff --git a/application/main-app/src/main/java/org/mainapp/aws/s3/controller/LegacyS3Controller.java b/application/main-app/src/main/java/org/mainapp/aws/s3/controller/LegacyS3Controller.java new file mode 100644 index 00000000..6a93b724 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/aws/s3/controller/LegacyS3Controller.java @@ -0,0 +1,42 @@ +package org.mainapp.aws.s3.controller; + +import org.mainapp.aws.s3.controller.response.CreatePutUrlResponse; +import org.mainapp.aws.s3.service.S3Service; +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; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequiredArgsConstructor +@RequestMapping("/presigned-url") +@Tag(name = "Pre-signed URL API", description = "pre-signed URL 발급 관련 API입니다.") +public class LegacyS3Controller { + + private final S3Service s3Service; + + @Operation( + summary = "게시물 그룹 이미지 pre-signed URL 발급 API", + description = "게시물 그룹에 포함되어 게시물 생성에 사용되는 이미지에 대한 pre-signed URL을 발급합니다.\n\n" + + "해당 URL로 업로드하면 서버에서 설정한 파일명으로 업로드되므로, 응답에 포함된 imageUrl 값을 사용하시면 됩니다." + ) + @GetMapping("/post-group") + public ResponseEntity getPreSignedPutUrlForPostGroup() { + return ResponseEntity.ok(s3Service.createPreSignedPutUrl("post-group/")); + } + + @Operation( + summary = "게시물 이미지 pre-signed URL 발급 API", + description = "게시물에 실제로 포함되는 이미지에 대한 pre-signed URL을 발급합니다.\n\n" + + "해당 URL로 업로드하면 서버에서 설정한 파일명으로 업로드되므로, 응답에 포함된 imageUrl 값을 사용하시면 됩니다." + ) + @GetMapping("/post") + public ResponseEntity getPreSignedPutUrlForPost() { + return ResponseEntity.ok(s3Service.createPreSignedPutUrl("post/")); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/aws/s3/controller/S3Controller.java b/application/main-app/src/main/java/org/mainapp/aws/s3/controller/S3Controller.java index b66ea348..5d910521 100644 --- a/application/main-app/src/main/java/org/mainapp/aws/s3/controller/S3Controller.java +++ b/application/main-app/src/main/java/org/mainapp/aws/s3/controller/S3Controller.java @@ -13,7 +13,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/presigned-url") +@RequestMapping("/v1/presigned-url") @Tag(name = "Pre-signed URL API", description = "pre-signed URL 발급 관련 API입니다.") public class S3Controller { diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/AgentController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/AgentController.java similarity index 84% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/AgentController.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/AgentController.java index 5edadb53..d698b358 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/AgentController.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/AgentController.java @@ -1,10 +1,10 @@ -package org.mainapp.domain.agent.controller; +package org.mainapp.domain.v1.agent.controller; -import org.mainapp.domain.agent.controller.request.UpdateAgentPersonalSettingRequest; -import org.mainapp.domain.agent.controller.response.GetAgentPlanResponse; -import org.mainapp.domain.agent.controller.response.GetAgentsResponse; -import org.mainapp.domain.agent.controller.response.GetDetailAgentResponse; -import org.mainapp.domain.agent.service.AgentService; +import org.mainapp.domain.v1.agent.controller.request.UpdateAgentPersonalSettingRequest; +import org.mainapp.domain.v1.agent.controller.response.GetAgentPlanResponse; +import org.mainapp.domain.v1.agent.controller.response.GetAgentsResponse; +import org.mainapp.domain.v1.agent.controller.response.GetDetailAgentResponse; +import org.mainapp.domain.v1.agent.service.AgentService; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -19,7 +19,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/agents") +@RequestMapping("/v1/agents") @RequiredArgsConstructor @Tag(name = "Agent API", description = "사용자가 연동한 SNS 계정(에이전트)에 대한 요청을 처리하는 API입니다.") public class AgentController { diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/LegacyAgentController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/LegacyAgentController.java new file mode 100644 index 00000000..0384bf83 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/LegacyAgentController.java @@ -0,0 +1,65 @@ +package org.mainapp.domain.v1.agent.controller; + +import org.mainapp.domain.v1.agent.controller.request.UpdateAgentPersonalSettingRequest; +import org.mainapp.domain.v1.agent.controller.response.GetAgentPlanResponse; +import org.mainapp.domain.v1.agent.controller.response.GetAgentsResponse; +import org.mainapp.domain.v1.agent.controller.response.GetDetailAgentResponse; +import org.mainapp.domain.v1.agent.service.AgentService; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequestMapping("/agents") +@RequiredArgsConstructor +@Tag(name = "Agent API", description = "사용자가 연동한 SNS 계정(에이전트)에 대한 요청을 처리하는 API입니다.") +public class LegacyAgentController { + + private final AgentService agentService; + + @Operation(summary = "계정 목록 조회 API", description = "사용자가 연동한 SNS 계정 목록을 조회합니다.") + @GetMapping + public ResponseEntity getAgents() { + return ResponseEntity.ok(agentService.getAgents()); + } + + @Operation(summary = "계정 상세 조회 API", description = "사용자가 연동한 SNS 계정의 상세 정보를 조회합니다.") + @GetMapping("/{agentId}") + public ResponseEntity getDetailAgent(@PathVariable Long agentId) { + return ResponseEntity.ok(agentService.getDetailAgent(agentId)); + } + + @Operation( + summary = "계정 개인화 설정 수정 API", + description = """ + 사용자가 연동한 SNS 계정의 개인화 설정을 변경합니다. + + **변경되는 필드만 채워주시면 됩니다!** + + 빈 문자열로는 변경할 수 없습니다.""" + ) + @PutMapping("/{agentId}/personal-setting") + public ResponseEntity updateAgentPersonalSetting( + @PathVariable Long agentId, + @Validated @RequestBody UpdateAgentPersonalSettingRequest request + ) { + agentService.updateAgentPersonalSetting(agentId, request); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "계정 요금제 플랜 조회", description = "사용자가 연동한 SNS 계정의 요금제를 조회합니다.") + @GetMapping("/{agentId}/agent-plan") + public ResponseEntity getAgentPlan(@PathVariable Long agentId) { + return ResponseEntity.ok(agentService.getAgentPlan(agentId)); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/request/UpdateAgentPersonalSettingRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/request/UpdateAgentPersonalSettingRequest.java similarity index 96% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/request/UpdateAgentPersonalSettingRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/request/UpdateAgentPersonalSettingRequest.java index 6ec0735c..a9740dd3 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/request/UpdateAgentPersonalSettingRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/request/UpdateAgentPersonalSettingRequest.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.controller.request; +package org.mainapp.domain.v1.agent.controller.request; import org.domainmodule.agent.entity.AgentPersonalSetting; import org.domainmodule.agent.entity.type.AgentToneType; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetAgentPlanResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetAgentPlanResponse.java similarity index 88% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetAgentPlanResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetAgentPlanResponse.java index c6a60d07..ff067318 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetAgentPlanResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetAgentPlanResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.controller.response; +package org.mainapp.domain.v1.agent.controller.response; import org.domainmodule.agent.entity.Agent; import org.domainmodule.agent.entity.type.AgentPlanType; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetAgentsResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetAgentsResponse.java similarity index 79% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetAgentsResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetAgentsResponse.java index b2965ca3..121de2cf 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetAgentsResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetAgentsResponse.java @@ -1,9 +1,9 @@ -package org.mainapp.domain.agent.controller.response; +package org.mainapp.domain.v1.agent.controller.response; import java.util.List; import org.domainmodule.agent.entity.Agent; -import org.mainapp.domain.agent.controller.response.type.AgentResponse; +import org.mainapp.domain.v1.agent.controller.response.type.AgentResponse; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetDetailAgentResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetDetailAgentResponse.java similarity index 78% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetDetailAgentResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetDetailAgentResponse.java index 266ff433..292ca700 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/GetDetailAgentResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/GetDetailAgentResponse.java @@ -1,9 +1,9 @@ -package org.mainapp.domain.agent.controller.response; +package org.mainapp.domain.v1.agent.controller.response; import org.domainmodule.agent.entity.Agent; import org.domainmodule.agent.entity.AgentPersonalSetting; -import org.mainapp.domain.agent.controller.response.type.AgentPersonalSettingResponse; -import org.mainapp.domain.agent.controller.response.type.AgentResponse; +import org.mainapp.domain.v1.agent.controller.response.type.AgentPersonalSettingResponse; +import org.mainapp.domain.v1.agent.controller.response.type.AgentResponse; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/type/AgentPersonalSettingResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/type/AgentPersonalSettingResponse.java similarity index 94% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/type/AgentPersonalSettingResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/type/AgentPersonalSettingResponse.java index 24cd0b70..9b841990 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/type/AgentPersonalSettingResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/type/AgentPersonalSettingResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.controller.response.type; +package org.mainapp.domain.v1.agent.controller.response.type; import org.domainmodule.agent.entity.AgentPersonalSetting; import org.domainmodule.agent.entity.type.AgentToneType; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/type/AgentResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/type/AgentResponse.java similarity index 96% rename from application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/type/AgentResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/type/AgentResponse.java index 79fc797a..a05683d2 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/controller/response/type/AgentResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/controller/response/type/AgentResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.controller.response.type; +package org.mainapp.domain.v1.agent.controller.response.type; import java.time.LocalDateTime; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/exception/AgentErrorCode.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/exception/AgentErrorCode.java similarity index 89% rename from application/main-app/src/main/java/org/mainapp/domain/agent/exception/AgentErrorCode.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/exception/AgentErrorCode.java index b191ec96..bc82c2c3 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/exception/AgentErrorCode.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/exception/AgentErrorCode.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.exception; +package org.mainapp.domain.v1.agent.exception; import org.mainapp.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/service/AgentService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/service/AgentService.java similarity index 91% rename from application/main-app/src/main/java/org/mainapp/domain/agent/service/AgentService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/service/AgentService.java index 4cd59ea6..b8f68bf1 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/service/AgentService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/service/AgentService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.service; +package org.mainapp.domain.v1.agent.service; import java.util.List; @@ -9,12 +9,12 @@ import org.domainmodule.agent.repository.AgentPersonalSettingRepository; import org.domainmodule.agent.repository.AgentRepository; import org.domainmodule.user.entity.User; -import org.mainapp.domain.agent.controller.request.UpdateAgentPersonalSettingRequest; -import org.mainapp.domain.agent.controller.response.GetAgentPlanResponse; -import org.mainapp.domain.agent.controller.response.GetAgentsResponse; -import org.mainapp.domain.agent.controller.response.GetDetailAgentResponse; -import org.mainapp.domain.agent.exception.AgentErrorCode; -import org.mainapp.domain.user.service.UserService; +import org.mainapp.domain.v1.agent.controller.request.UpdateAgentPersonalSettingRequest; +import org.mainapp.domain.v1.agent.controller.response.GetAgentPlanResponse; +import org.mainapp.domain.v1.agent.controller.response.GetAgentsResponse; +import org.mainapp.domain.v1.agent.controller.response.GetDetailAgentResponse; +import org.mainapp.domain.v1.agent.exception.AgentErrorCode; +import org.mainapp.domain.v1.user.service.UserService; import org.mainapp.global.error.CustomException; import org.mainapp.global.util.SecurityUtil; import org.snsclient.twitter.dto.response.TwitterUserInfoDto; diff --git a/application/main-app/src/main/java/org/mainapp/domain/agent/service/AgentTransactionService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/service/AgentTransactionService.java similarity index 96% rename from application/main-app/src/main/java/org/mainapp/domain/agent/service/AgentTransactionService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/agent/service/AgentTransactionService.java index f5283584..caef325f 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/agent/service/AgentTransactionService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/agent/service/AgentTransactionService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.agent.service; +package org.mainapp.domain.v1.agent.service; import org.domainmodule.agent.entity.Agent; import org.domainmodule.agent.entity.AgentPersonalSetting; diff --git a/application/main-app/src/main/java/org/mainapp/domain/auth/controller/AuthController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/controller/AuthController.java similarity index 84% rename from application/main-app/src/main/java/org/mainapp/domain/auth/controller/AuthController.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/auth/controller/AuthController.java index b540cf1b..8608114c 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/auth/controller/AuthController.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/controller/AuthController.java @@ -1,6 +1,6 @@ -package org.mainapp.domain.auth.controller; +package org.mainapp.domain.v1.auth.controller; -import org.mainapp.domain.auth.service.AuthServiceImpl; +import org.mainapp.domain.v1.auth.service.AuthServiceImpl; import org.mainapp.global.constants.HeaderConstants; import org.mainapp.global.util.ResponseUtil; import org.springframework.http.ResponseEntity; @@ -14,7 +14,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/auth") +@RequestMapping("/v1/auth") @RequiredArgsConstructor @Tag(name = "Auth API", description = "로그인/회원가입에 대한 요청을 처리하는 API입니다.") public class AuthController { @@ -22,7 +22,8 @@ public class AuthController { private final ResponseUtil responseUtil; @DeleteMapping("/logout") - public ResponseEntity logout(@RequestHeader("Authorization") String accessToken, HttpServletResponse response) { + public ResponseEntity logout(@RequestHeader("Authorization") String accessToken, + HttpServletResponse response) { authService.logout(accessToken); responseUtil.expireHttpOnlyCookie(response, HeaderConstants.REFRESH_TOKEN_HEADER); return ResponseEntity.noContent().build(); diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/auth/controller/LegacyAuthController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/controller/LegacyAuthController.java new file mode 100644 index 00000000..d6739282 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/controller/LegacyAuthController.java @@ -0,0 +1,32 @@ +package org.mainapp.domain.v1.auth.controller; + +import org.mainapp.domain.v1.auth.service.AuthServiceImpl; +import org.mainapp.global.constants.HeaderConstants; +import org.mainapp.global.util.ResponseUtil; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +@Tag(name = "Auth API", description = "로그인/회원가입에 대한 요청을 처리하는 API입니다.") +public class LegacyAuthController { + private final AuthServiceImpl authService; + private final ResponseUtil responseUtil; + + @DeleteMapping("/logout") + public ResponseEntity logout(@RequestHeader("Authorization") String accessToken, + HttpServletResponse response) { + authService.logout(accessToken); + responseUtil.expireHttpOnlyCookie(response, HeaderConstants.REFRESH_TOKEN_HEADER); + return ResponseEntity.noContent().build(); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/auth/exception/AuthErrorCode.java b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/exception/AuthErrorCode.java similarity index 92% rename from application/main-app/src/main/java/org/mainapp/domain/auth/exception/AuthErrorCode.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/auth/exception/AuthErrorCode.java index f5f8e5fb..b99905fc 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/auth/exception/AuthErrorCode.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/exception/AuthErrorCode.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.auth.exception; +package org.mainapp.domain.v1.auth.exception; import org.mainapp.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; diff --git a/application/main-app/src/main/java/org/mainapp/domain/auth/service/AuthService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/service/AuthService.java similarity index 86% rename from application/main-app/src/main/java/org/mainapp/domain/auth/service/AuthService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/auth/service/AuthService.java index d3084429..e4083079 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/auth/service/AuthService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/service/AuthService.java @@ -1,11 +1,13 @@ -package org.mainapp.domain.auth.service; +package org.mainapp.domain.v1.auth.service; import org.domainmodule.user.entity.User; import org.mainapp.global.oauth2.userinfo.OAuth2UserInfo; public interface AuthService { void createAndSaveOauth(OAuth2UserInfo oAuth2Response, User user); + User loginOrRegisterUser(OAuth2UserInfo oAuth2Response); + void logout(String accessToken); } diff --git a/application/main-app/src/main/java/org/mainapp/domain/auth/service/AuthServiceImpl.java b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/service/AuthServiceImpl.java similarity index 93% rename from application/main-app/src/main/java/org/mainapp/domain/auth/service/AuthServiceImpl.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/auth/service/AuthServiceImpl.java index 850e6c84..f619f10b 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/auth/service/AuthServiceImpl.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/auth/service/AuthServiceImpl.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.auth.service; +package org.mainapp.domain.v1.auth.service; import java.util.Optional; @@ -6,8 +6,8 @@ import org.domainmodule.user.entity.User; import org.domainmodule.user.entity.type.ProviderType; import org.domainmodule.user.repository.OauthRepository; -import org.mainapp.domain.token.service.TokenServiceImpl; -import org.mainapp.domain.user.service.UserServiceImpl; +import org.mainapp.domain.v1.token.service.TokenServiceImpl; +import org.mainapp.domain.v1.user.service.UserServiceImpl; import org.mainapp.global.oauth2.userinfo.OAuth2UserInfo; import org.mainapp.global.util.JwtUtil; import org.springframework.stereotype.Service; @@ -48,7 +48,6 @@ private Optional findUserByOAuthInfo(OAuth2UserInfo oAuth2Response) { }); } - // 회원가입 private User registerUser(OAuth2UserInfo oAuth2Response) { // 유저 생성 diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/LegacyNewsCategoryController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/LegacyNewsCategoryController.java new file mode 100644 index 00000000..8dfc8235 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/LegacyNewsCategoryController.java @@ -0,0 +1,30 @@ +package org.mainapp.domain.v1.newscategory.controller; + +import java.util.List; + +import org.mainapp.domain.v1.newscategory.controller.response.NewsCategoryResponse; +import org.mainapp.domain.v1.newscategory.service.NewsCategoryService; +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; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequestMapping("/news-categories") +@RequiredArgsConstructor +@Tag(name = "NewsCategory API", description = "뉴스 카테고리에 대한 요청을 처리하는 API입니다.") +public class LegacyNewsCategoryController { + + private final NewsCategoryService newsCategoryService; + + @Operation(summary = "뉴스 카테고리 목록 조회 API", description = "서비스의 전체 뉴스 카테고리 목록을 조회합니다.") + @GetMapping + public ResponseEntity> getNewsCategories() { + return ResponseEntity.ok().body(newsCategoryService.getNewsCategories()); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/newscategory/controller/NewsCategoryController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/NewsCategoryController.java similarity index 79% rename from application/main-app/src/main/java/org/mainapp/domain/newscategory/controller/NewsCategoryController.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/NewsCategoryController.java index 3a6d0e01..370bab29 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/newscategory/controller/NewsCategoryController.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/NewsCategoryController.java @@ -1,9 +1,9 @@ -package org.mainapp.domain.newscategory.controller; +package org.mainapp.domain.v1.newscategory.controller; import java.util.List; -import org.mainapp.domain.newscategory.controller.response.NewsCategoryResponse; -import org.mainapp.domain.newscategory.service.NewsCategoryService; +import org.mainapp.domain.v1.newscategory.controller.response.NewsCategoryResponse; +import org.mainapp.domain.v1.newscategory.service.NewsCategoryService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -14,7 +14,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/news-categories") +@RequestMapping("/v1/news-categories") @RequiredArgsConstructor @Tag(name = "NewsCategory API", description = "뉴스 카테고리에 대한 요청을 처리하는 API입니다.") public class NewsCategoryController { diff --git a/application/main-app/src/main/java/org/mainapp/domain/newscategory/controller/response/NewsCategoryResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/response/NewsCategoryResponse.java similarity index 91% rename from application/main-app/src/main/java/org/mainapp/domain/newscategory/controller/response/NewsCategoryResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/response/NewsCategoryResponse.java index df9dc9ef..bd799657 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/newscategory/controller/response/NewsCategoryResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/controller/response/NewsCategoryResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.newscategory.controller.response; +package org.mainapp.domain.v1.newscategory.controller.response; import org.domainmodule.rssfeed.entity.RssFeed; import org.domainmodule.rssfeed.entity.type.FeedCategoryType; diff --git a/application/main-app/src/main/java/org/mainapp/domain/newscategory/service/NewsCategoryService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/service/NewsCategoryService.java similarity index 81% rename from application/main-app/src/main/java/org/mainapp/domain/newscategory/service/NewsCategoryService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/service/NewsCategoryService.java index 527d885b..3c48c405 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/newscategory/service/NewsCategoryService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/newscategory/service/NewsCategoryService.java @@ -1,10 +1,10 @@ -package org.mainapp.domain.newscategory.service; +package org.mainapp.domain.v1.newscategory.service; import java.util.List; import org.domainmodule.rssfeed.entity.RssFeed; import org.domainmodule.rssfeed.repository.RssFeedRepository; -import org.mainapp.domain.newscategory.controller.response.NewsCategoryResponse; +import org.mainapp.domain.v1.newscategory.controller.response.NewsCategoryResponse; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/LegacyPostController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/LegacyPostController.java new file mode 100644 index 00000000..7947422d --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/LegacyPostController.java @@ -0,0 +1,288 @@ +package org.mainapp.domain.v1.post.controller; + +import java.util.List; + +import org.mainapp.domain.v1.post.controller.request.CreatePostsRequest; +import org.mainapp.domain.v1.post.controller.request.MultiplePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.ReserveUploadTimeRequest; +import org.mainapp.domain.v1.post.controller.request.SinglePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostContentRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostsMetadataRequest; +import org.mainapp.domain.v1.post.controller.request.UpdateReservedPostsRequest; +import org.mainapp.domain.v1.post.controller.response.CreatePostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetAgentReservedPostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupPostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupTopicResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupsResponse; +import org.mainapp.domain.v1.post.controller.response.PromptHistoriesResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostGroupResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.service.PostPromptHistoryService; +import org.mainapp.domain.v1.post.service.PostService; +import org.mainapp.global.constants.PostGenerationCount; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequestMapping("/agents/{agentId}") +@RequiredArgsConstructor +@Tag(name = "Post API", description = "게시물에 대한 요청을 처리하는 API입니다.") +public class LegacyPostController { + + private final PostService postService; + private final PostPromptHistoryService postPromptHistoryService; + + @Operation( + summary = "게시물 그룹 및 게시물 생성 API", + description = """ + 에이전트에 새 게시물 그룹을 추가하고 게시물을 생성합니다. + + **1. 생성 방식을 나타내는 reference 필드 값에 따라 필요한 필드가 달라집니다.** + - NONE (참고자료 X): newsCategory, imageUrls 필드를 모두 비워주세요. + - NEWS (뉴스 참고): newsCategory를 지정하고, imageUrls를 비워주세요. + - IMAGE (이미지 참고): imageUrls를 설정하고, newsCategory를 비워주세요. + + **2. 응답 본문에 eof가 포함됩니다.** + + 게시물 그룹별 최대 게시물 생성 가능 횟수를 채우게 되면 eof가 true로 응답됩니다. 이 경우 추가 생성이 제한됩니다.""" + ) + @PostMapping("/post-groups/posts") + public ResponseEntity createPosts( + @PathVariable Long agentId, + @RequestParam(defaultValue = PostGenerationCount.POST_GENERATION_POST_COUNT) Integer limit, + @Validated @RequestBody CreatePostsRequest createPostsRequest + ) { + return ResponseEntity.ok(postService.createPosts(agentId, createPostsRequest, limit)); + } + + @Operation( + summary = "게시물 추가 생성 API", + description = """ + 기존 게시물 그룹에 새 게시물을 추가합니다. + + **응답 본문에 eof가 포함됩니다.** + + 게시물 그룹별 최대 게시물 생성 가능 횟수를 채우게 되면 eof가 true로 응답됩니다. 이 경우 추가 생성이 제한됩니다.""" + ) + @PostMapping("/post-groups/{postGroupId}/posts") + public ResponseEntity createAdditionalPosts( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @RequestParam(defaultValue = PostGenerationCount.POST_GENERATION_POST_COUNT) Integer limit + ) { + return ResponseEntity.ok(postService.createAdditionalPosts(agentId, postGroupId, limit)); + } + + @Operation( + summary = "게시물 내용 수정 API", + description = """ + 기존 게시물의 내용 및 이미지를 수정합니다. + + **1. 수정 타입을 나타내는 updateType 필드에 따라 필요한 필드가 달라집니다.** + - CONTENT (게시물 본문 내용 수정): content 필드를 설정하고, imageUrls 필드는 비워주세요. + - CONTENT_IMAGE (게시물 본문 내용과 이미지 수정): content 필드와 imageUrls 필드를 설정해주세요. 이미지만 변경하는 경우에도 content 필드를 설정해주어야 합니다. + + **2. 게시물의 이미지 추가나 삭제를 해당 API에서 처리합니다.** + + 게시물 이미지에 수정 사항이 있다면, updateType을 CONTENT_IMAGE로 설정하고 수정된 이미지 URL 리스트를 imageUrls 필드에 담아주시면 됩니다. + + 이미지 리스트를 보내주시면, 서버에서 DB에 저장된 기존 이미지 리스트를 조회해 두 버전을 비교하고 반영합니다.""" + ) + @PutMapping("/post-groups/{postGroupId}/posts/{postId}") + public ResponseEntity updatePostContent( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @PathVariable Long postId, + @Validated @RequestBody UpdatePostContentRequest updatePostContentRequest + ) { + postService.updatePostContent(agentId, postGroupId, postId, updatePostContentRequest); + return ResponseEntity.ok().build(); + } + + @Operation( + summary = "게시물 기타 정보 수정 API", + description = """ + 기존 여러 게시물들의 상태 / 업로드 예약 일시 / 순서를 수정합니다. + + **변경이 필요한 필드에만 값을 넣어주시고, 변경이 없는 필드는 비워주시면 됩니다.**""" + ) + @PutMapping("/post-groups/{postGroupId}/posts") + public ResponseEntity updatePosts( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @Validated @RequestBody UpdatePostsMetadataRequest updatePostsMetadataRequest + ) { + postService.updatePostsMetadata(agentId, postGroupId, updatePostsMetadataRequest); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "계정별 예약 게시물 예약일시 수정 API", description = "주제에 관계 없이 계정별 예약 게시물의 예약 시간을 수정합니다.") + @PutMapping("/posts/upload-reserved") + public ResponseEntity updateReservedPostsUploadTime( + @PathVariable Long agentId, + @Validated @RequestBody UpdateReservedPostsRequest updateReservedPostsRequest + ) { + postService.updateReservedPostsUploadTime(agentId, updateReservedPostsRequest); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "업로드 시간 빠른 예약하기 API", description = "READY_TO_UPLOAD 상태인 Post들의 예약 시간을 일괄로 설정합니다.") + @PutMapping("/post-groups/{postGroupId}/posts/reserved-all") + public ResponseEntity reserveReadyToUploadPosts( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @Validated @RequestBody ReserveUploadTimeRequest request + ) { + return ResponseEntity.ok(postService.reserveReadyToUploadPosts(agentId, postGroupId, request)); + } + + @Operation(summary = "게시물 프롬프트 기반 개별 수정 API", description = "개별 게시물에 대해 입력된 프롬프트를 바탕으로 수정합니다.") + @PatchMapping("/post-groups/{postGroupId}/posts/{postId}/prompt") + public ResponseEntity updateSinglePostByPrompt( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @PathVariable Long postId, + @Validated @RequestBody SinglePostUpdateRequest singlePostUpdateRequest + ) { + return ResponseEntity.ok( + postService.updateSinglePostByPrompt(singlePostUpdateRequest, agentId, postGroupId, postId)); + } + + @Operation(summary = "게시물 프롬프트 기반 일괄 수정 API", description = "일괄 게시물에 대해 입력된 프롬프트를 바탕으로 수정합니다.") + @PatchMapping("/post-groups/{postGroupId}/posts/prompt") + public ResponseEntity> updateMultiplePostsByPrompt( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @Validated @RequestBody MultiplePostUpdateRequest multiplePostUpdateRequest + ) { + return ResponseEntity.ok( + postService.updateMultiplePostsByPrompt(multiplePostUpdateRequest, agentId, postGroupId)); + } + + @Operation( + summary = "게시물 개별 삭제 API", + description = """ + 업로드가 확정되지 않은 단건의 게시물을 개별 삭제합니다. (생성됨, 수정 중, 수정 완료) + + 게시물 수정 단계에서 게시물을 삭제할 때 사용됩니다. + + **업로드가 확정된 상태의 게시물은 삭제할 수 없습니다. (예약 완료, 업로드 완료, 업로드 실패)**""" + ) + @DeleteMapping("/post-groups/{postGroupId}/posts/{postId}") + public ResponseEntity deletePost( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @PathVariable Long postId + ) { + postService.deletePost(agentId, postGroupId, postId); + return ResponseEntity.noContent().build(); + } + + @Operation( + summary = "게시물 일괄 삭제 API", + description = """ + 업로드가 확정되지 않은 여러 게시물들을 일괄 삭제합니다. (생성됨, 수정 중, 수정 완료) + + 게시물 수정 완료 후 예약 단계로 넘어갈 때 사용됩니다. + + **업로드가 확정된 상태의 게시물은 삭제할 수 없습니다. (예약 완료, 업로드 완료, 업로드 실패)**""" + ) + @DeleteMapping("/post-groups/{postGroupId}/posts") + public ResponseEntity deletePosts( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @Validated @RequestBody List postIds + ) { + postService.deletePosts(agentId, postGroupId, postIds); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "계정별 게시물 그룹 목록 조회 API", description = "사용자가 연동한 SNS 계정 내의 게시물 그룹 목록을 조회합니다.") + @GetMapping("/post-groups") + public ResponseEntity getPostGroupsByAgent(@PathVariable Long agentId) { + return ResponseEntity.ok(postService.getPostGroups(agentId)); + } + + @Operation(summary = "게시물 그룹 조회 API", description = "사용자가 연동한 SNS 계정 내의 게시물 그룹을 단건 조회합니다.") + @GetMapping("/post-groups/{postGroupId}") + public ResponseEntity getPostGroup(@PathVariable Long agentId, @PathVariable Long postGroupId) { + return ResponseEntity.ok(postService.getPostGroup(agentId, postGroupId)); + } + + @Operation(summary = "게시물 그룹 주제 조회 API", description = "화면 헤더 Breadcrumb에 표시할 게시물 그룹 주제를 조회합니다.") + @GetMapping("/post-groups/{postGroupId}/topic") + public ResponseEntity getPostGroupTopic( + @PathVariable Long agentId, + @PathVariable Long postGroupId + ) { + return ResponseEntity.ok(postService.getPostGroupTopic(agentId, postGroupId)); + } + + @Operation(summary = "게시물 그룹별 게시물 목록 조회 API", description = "게시물 그룹에 해당되는 모든 게시물 목록을 조회합니다.") + @GetMapping("/post-groups/{postGroupId}/posts") + public ResponseEntity getPostsByPostGroup( + @PathVariable Long agentId, + @PathVariable Long postGroupId + ) { + return ResponseEntity.ok(postService.getPostsByPostGroup(agentId, postGroupId)); + } + + @Operation(summary = "게시물 그룹 제거 API", description = "게시물 그룹에 해당되는 모든 게시물들을 삭제합니다.") + @DeleteMapping("/post-groups/{postGroupId}") + public ResponseEntity deletePostGroup( + @PathVariable Long agentId, + @PathVariable Long postGroupId + ) { + postService.deletePostGroup(agentId, postGroupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "게시물 프롬프트 내역 조회 API", description = "게시물 결과 수정 단계에서 프롬프트 내역을 조회합니다.") + @GetMapping("/post-groups/{postGroupId}/posts/{postId}/prompt-histories") + public ResponseEntity> getPromptHistories( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @PathVariable Long postId + ) { + return ResponseEntity.ok(postPromptHistoryService.getPromptHistories(agentId, postGroupId, postId)); + } + + @Operation( + summary = "계정별 예약 게시물 조회 API", + description = "sns 계정별 업로드가 예약된 상태(UPLOAD_CONFIRMED)인 게시물 목록을 조회합니다." + ) + @GetMapping("/post-groups/posts/upload-reserved") + public ResponseEntity getAgentReservedPosts( + @PathVariable Long agentId + ) { + return ResponseEntity.ok(postService.getAgentReservedPosts(agentId)); + } + + @Operation( + summary = "개별 게시물 상세조회 API", + description = "게시물 클릭 시 단건 게시물에 대한 상세정보를 조회합니다." + ) + @GetMapping("/post-groups/{postGroupId}/posts/{postId}") + public ResponseEntity getPostDetails( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @PathVariable Long postId + ) { + return ResponseEntity.ok(postService.getPostDetails(agentId, postGroupId, postId)); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/PostController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/PostController.java similarity index 85% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/PostController.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/PostController.java index 789e4965..4300b8e7 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/PostController.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/PostController.java @@ -1,23 +1,24 @@ -package org.mainapp.domain.post.controller; +package org.mainapp.domain.v1.post.controller; import java.util.List; -import org.mainapp.domain.post.controller.request.CreatePostsRequest; -import org.mainapp.domain.post.controller.request.MultiplePostUpdateRequest; -import org.mainapp.domain.post.controller.request.SinglePostUpdateRequest; -import org.mainapp.domain.post.controller.request.UpdatePostContentRequest; -import org.mainapp.domain.post.controller.request.UpdatePostsMetadataRequest; -import org.mainapp.domain.post.controller.request.UpdateReservedPostsRequest; -import org.mainapp.domain.post.controller.response.CreatePostsResponse; -import org.mainapp.domain.post.controller.response.GetAgentReservedPostsResponse; -import org.mainapp.domain.post.controller.response.GetPostGroupPostsResponse; -import org.mainapp.domain.post.controller.response.GetPostGroupTopicResponse; -import org.mainapp.domain.post.controller.response.GetPostGroupsResponse; -import org.mainapp.domain.post.controller.response.PromptHistoriesResponse; -import org.mainapp.domain.post.controller.response.type.PostGroupResponse; -import org.mainapp.domain.post.controller.response.type.PostResponse; -import org.mainapp.domain.post.service.PostPromptHistoryService; -import org.mainapp.domain.post.service.PostService; +import org.mainapp.domain.v1.post.controller.request.CreatePostsRequest; +import org.mainapp.domain.v1.post.controller.request.MultiplePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.ReserveUploadTimeRequest; +import org.mainapp.domain.v1.post.controller.request.SinglePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostContentRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostsMetadataRequest; +import org.mainapp.domain.v1.post.controller.request.UpdateReservedPostsRequest; +import org.mainapp.domain.v1.post.controller.response.CreatePostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetAgentReservedPostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupPostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupTopicResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupsResponse; +import org.mainapp.domain.v1.post.controller.response.PromptHistoriesResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostGroupResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.service.PostPromptHistoryService; +import org.mainapp.domain.v1.post.service.PostService; import org.mainapp.global.constants.PostGenerationCount; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -37,7 +38,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/agents/{agentId}") +@RequestMapping("/v1/agents/{agentId}") @RequiredArgsConstructor @Tag(name = "Post API", description = "게시물에 대한 요청을 처리하는 API입니다.") public class PostController { @@ -139,6 +140,16 @@ public ResponseEntity updateReservedPostsUploadTime( return ResponseEntity.ok().build(); } + @Operation(summary = "업로드 시간 빠른 예약하기 API", description = "READY_TO_UPLOAD 상태인 Post들의 예약 시간을 일괄로 설정합니다.") + @PutMapping("/post-groups/{postGroupId}/posts/reserved-all") + public ResponseEntity reserveReadyToUploadPosts( + @PathVariable Long agentId, + @PathVariable Long postGroupId, + @Validated @RequestBody ReserveUploadTimeRequest request + ) { + return ResponseEntity.ok(postService.reserveReadyToUploadPosts(agentId, postGroupId, request)); + } + @Operation(summary = "게시물 프롬프트 기반 개별 수정 API", description = "개별 게시물에 대해 입력된 프롬프트를 바탕으로 수정합니다.") @PatchMapping("/post-groups/{postGroupId}/posts/{postId}/prompt") public ResponseEntity updateSinglePostByPrompt( @@ -252,7 +263,7 @@ public ResponseEntity> getPromptHistories( @Operation( summary = "계정별 예약 게시물 조회 API", - description = "sns 계정별 업로드가 예약된 상태(UPLOAD_RESERVED)인 게시물 목록을 조회합니다." + description = "sns 계정별 업로드가 예약된 상태(UPLOAD_CONFIRMED)인 게시물 목록을 조회합니다." ) @GetMapping("/post-groups/posts/upload-reserved") public ResponseEntity getAgentReservedPosts( diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/CreatePostsRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/CreatePostsRequest.java similarity index 97% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/CreatePostsRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/CreatePostsRequest.java index 23a1d246..6e82e66e 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/CreatePostsRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/CreatePostsRequest.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.request; +package org.mainapp.domain.v1.post.controller.request; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/MultiplePostUpdateRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/MultiplePostUpdateRequest.java similarity index 92% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/MultiplePostUpdateRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/MultiplePostUpdateRequest.java index 94a06d7d..a4c510e5 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/MultiplePostUpdateRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/MultiplePostUpdateRequest.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.request; +package org.mainapp.domain.v1.post.controller.request; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/ReserveUploadTimeRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/ReserveUploadTimeRequest.java new file mode 100644 index 00000000..6d47f753 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/ReserveUploadTimeRequest.java @@ -0,0 +1,22 @@ +package org.mainapp.domain.v1.post.controller.request; + +import java.time.LocalDate; + +import org.domainmodule.post.entity.type.UploadTimeType; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "게시물 업로드 시간 예약하기 요청 본문") +public record ReserveUploadTimeRequest( + @Schema(description = "하루에 업로드 할 글의 수", example = "2") + @NotNull(message = "업로드 할 글의 수를 지정해주세요.") + int dailyUploadCount, + @Schema(description = "업로드 시작 시간", example = "2025-01-01") + @NotNull(message = "업로드 시작 날짜을 지정해주세요.") + LocalDate uploadStartDate, + @Schema(description = "업로드 할 시간대", example = "MORNING") + @NotNull(message = "업로드 할 시간대를 지정해주세요.") + UploadTimeType uploadTimeType +) { +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/SinglePostUpdateRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/SinglePostUpdateRequest.java similarity index 90% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/SinglePostUpdateRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/SinglePostUpdateRequest.java index 833733ba..128085b2 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/SinglePostUpdateRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/SinglePostUpdateRequest.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.request; +package org.mainapp.domain.v1.post.controller.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdatePostContentRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdatePostContentRequest.java similarity index 87% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdatePostContentRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdatePostContentRequest.java index 7cb09858..10cb8823 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdatePostContentRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdatePostContentRequest.java @@ -1,8 +1,8 @@ -package org.mainapp.domain.post.controller.request; +package org.mainapp.domain.v1.post.controller.request; import java.util.List; -import org.mainapp.domain.post.controller.request.type.UpdatePostContentType; +import org.mainapp.domain.v1.post.controller.request.type.UpdatePostContentType; import org.springframework.lang.Nullable; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdatePostsMetadataRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdatePostsMetadataRequest.java similarity index 79% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdatePostsMetadataRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdatePostsMetadataRequest.java index 6d2eb869..10cb5354 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdatePostsMetadataRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdatePostsMetadataRequest.java @@ -1,8 +1,8 @@ -package org.mainapp.domain.post.controller.request; +package org.mainapp.domain.v1.post.controller.request; import java.util.List; -import org.mainapp.domain.post.controller.request.type.UpdatePostsRequestItem; +import org.mainapp.domain.v1.post.controller.request.type.UpdatePostsRequestItem; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdateReservedPostsRequest.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdateReservedPostsRequest.java similarity index 77% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdateReservedPostsRequest.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdateReservedPostsRequest.java index e38ed551..56044b6c 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/UpdateReservedPostsRequest.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/UpdateReservedPostsRequest.java @@ -1,8 +1,8 @@ -package org.mainapp.domain.post.controller.request; +package org.mainapp.domain.v1.post.controller.request; import java.util.List; -import org.mainapp.domain.post.controller.request.type.UpdateReservedPostsRequestItem; +import org.mainapp.domain.v1.post.controller.request.type.UpdateReservedPostsRequestItem; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdatePostContentType.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdatePostContentType.java similarity index 51% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdatePostContentType.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdatePostContentType.java index 7186a656..98f2abcc 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdatePostContentType.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdatePostContentType.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.request.type; +package org.mainapp.domain.v1.post.controller.request.type; public enum UpdatePostContentType { CONTENT, CONTENT_IMAGE diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdatePostsRequestItem.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdatePostsRequestItem.java similarity index 94% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdatePostsRequestItem.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdatePostsRequestItem.java index 17f1015e..3a76cc8b 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdatePostsRequestItem.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdatePostsRequestItem.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.request.type; +package org.mainapp.domain.v1.post.controller.request.type; import java.time.LocalDateTime; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdateReservedPostsRequestItem.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdateReservedPostsRequestItem.java similarity index 92% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdateReservedPostsRequestItem.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdateReservedPostsRequestItem.java index 86bd2490..51d801e3 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/request/type/UpdateReservedPostsRequestItem.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/request/type/UpdateReservedPostsRequestItem.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.request.type; +package org.mainapp.domain.v1.post.controller.request.type; import java.time.LocalDateTime; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/CreatePostsResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/CreatePostsResponse.java similarity index 82% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/CreatePostsResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/CreatePostsResponse.java index 250b88e6..9af7998d 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/CreatePostsResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/CreatePostsResponse.java @@ -1,8 +1,8 @@ -package org.mainapp.domain.post.controller.response; +package org.mainapp.domain.v1.post.controller.response; import java.util.List; -import org.mainapp.domain.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetAgentReservedPostsResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetAgentReservedPostsResponse.java similarity index 81% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetAgentReservedPostsResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetAgentReservedPostsResponse.java index 1ffe0a2f..d4329991 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetAgentReservedPostsResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetAgentReservedPostsResponse.java @@ -1,9 +1,9 @@ -package org.mainapp.domain.post.controller.response; +package org.mainapp.domain.v1.post.controller.response; import java.util.List; import org.domainmodule.post.entity.Post; -import org.mainapp.domain.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupPostsResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupPostsResponse.java similarity index 81% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupPostsResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupPostsResponse.java index ce248c9f..c7e0f0a6 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupPostsResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupPostsResponse.java @@ -1,12 +1,12 @@ -package org.mainapp.domain.post.controller.response; +package org.mainapp.domain.v1.post.controller.response; import java.util.List; import java.util.Map; import org.domainmodule.post.entity.type.PostStatusType; import org.domainmodule.postgroup.entity.PostGroup; -import org.mainapp.domain.post.controller.response.type.PostGroupResponse; -import org.mainapp.domain.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostGroupResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupTopicResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupTopicResponse.java similarity index 89% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupTopicResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupTopicResponse.java index ec69000d..4da5b304 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupTopicResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupTopicResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.response; +package org.mainapp.domain.v1.post.controller.response; import org.domainmodule.postgroup.entity.PostGroup; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupsResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupsResponse.java similarity index 81% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupsResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupsResponse.java index 232efb85..97cf8b31 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/GetPostGroupsResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/GetPostGroupsResponse.java @@ -1,9 +1,9 @@ -package org.mainapp.domain.post.controller.response; +package org.mainapp.domain.v1.post.controller.response; import java.util.List; import org.domainmodule.postgroup.entity.PostGroup; -import org.mainapp.domain.post.controller.response.type.PostGroupResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostGroupResponse; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/PromptHistoriesResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/PromptHistoriesResponse.java similarity index 96% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/PromptHistoriesResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/PromptHistoriesResponse.java index 74ca03e2..54652bdd 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/PromptHistoriesResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/PromptHistoriesResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.response; +package org.mainapp.domain.v1.post.controller.response; import java.time.LocalDateTime; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostGroupImageResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostGroupImageResponse.java similarity index 93% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostGroupImageResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostGroupImageResponse.java index 0eba0126..9835e7b2 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostGroupImageResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostGroupImageResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.response.type; +package org.mainapp.domain.v1.post.controller.response.type; import org.domainmodule.postgroup.entity.PostGroupImage; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostGroupResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostGroupResponse.java similarity index 97% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostGroupResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostGroupResponse.java index 296ac423..f2d92b91 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostGroupResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostGroupResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.response.type; +package org.mainapp.domain.v1.post.controller.response.type; import java.time.LocalDateTime; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostImageResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostImageResponse.java similarity index 92% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostImageResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostImageResponse.java index f3e5ab89..75e9c950 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostImageResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostImageResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.response.type; +package org.mainapp.domain.v1.post.controller.response.type; import org.domainmodule.post.entity.PostImage; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostResponse.java similarity index 97% rename from application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostResponse.java index c8f8fd1d..b3c50586 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/controller/response/type/PostResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/controller/response/type/PostResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.controller.response.type; +package org.mainapp.domain.v1.post.controller.response.type; import java.time.LocalDateTime; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/exception/PostErrorCode.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/exception/PostErrorCode.java similarity index 97% rename from application/main-app/src/main/java/org/mainapp/domain/post/exception/PostErrorCode.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/exception/PostErrorCode.java index 5125d482..f470712d 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/exception/PostErrorCode.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/exception/PostErrorCode.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.exception; +package org.mainapp.domain.v1.post.exception; import org.mainapp.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostCreateService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostCreateService.java similarity index 97% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/PostCreateService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostCreateService.java index 23367f7e..2ee7f0fb 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostCreateService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostCreateService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service; +package org.mainapp.domain.v1.post.service; import java.util.ArrayList; import java.util.List; @@ -17,14 +17,14 @@ import org.domainmodule.rssfeed.repository.RssFeedRepository; import org.feedclient.service.FeedService; import org.feedclient.service.dto.FeedPagingResult; -import org.mainapp.domain.post.controller.request.CreatePostsRequest; -import org.mainapp.domain.post.controller.response.CreatePostsResponse; -import org.mainapp.domain.post.controller.response.type.PostResponse; -import org.mainapp.domain.post.exception.PostErrorCode; -import org.mainapp.domain.post.service.dto.SavePostGroupAndPostsDto; -import org.mainapp.domain.post.service.dto.SavePostGroupWithImagesAndPostsDto; -import org.mainapp.domain.post.service.dto.SavePostGroupWithRssCursorAndPostsDto; -import org.mainapp.domain.post.service.vo.GeneratePostsVo; +import org.mainapp.domain.v1.post.controller.request.CreatePostsRequest; +import org.mainapp.domain.v1.post.controller.response.CreatePostsResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.exception.PostErrorCode; +import org.mainapp.domain.v1.post.service.dto.SavePostGroupAndPostsDto; +import org.mainapp.domain.v1.post.service.dto.SavePostGroupWithImagesAndPostsDto; +import org.mainapp.domain.v1.post.service.dto.SavePostGroupWithRssCursorAndPostsDto; +import org.mainapp.domain.v1.post.service.vo.GeneratePostsVo; import org.mainapp.global.constants.PostGenerationCount; import org.mainapp.global.error.CustomException; import org.mainapp.openai.contentformat.jsonschema.DetailTopicsSchema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostPromptHistoryService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostPromptHistoryService.java similarity index 91% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/PostPromptHistoryService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostPromptHistoryService.java index fe0df792..bfa204c1 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostPromptHistoryService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostPromptHistoryService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service; +package org.mainapp.domain.v1.post.service; import java.util.List; @@ -6,7 +6,7 @@ import org.domainmodule.post.entity.PromptHistory; import org.domainmodule.post.entity.type.PostPromptType; import org.domainmodule.post.repository.PromptHistoryRepository; -import org.mainapp.domain.post.controller.response.PromptHistoriesResponse; +import org.mainapp.domain.v1.post.controller.response.PromptHistoriesResponse; import org.mainapp.global.util.SecurityUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostPromptUpdateService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostPromptUpdateService.java similarity index 93% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/PostPromptUpdateService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostPromptUpdateService.java index a2bf489e..4db22fc5 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostPromptUpdateService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostPromptUpdateService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service; +package org.mainapp.domain.v1.post.service; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -6,10 +6,10 @@ import org.domainmodule.agent.entity.AgentPersonalSetting; import org.domainmodule.post.entity.Post; import org.domainmodule.postgroup.entity.PostGroup; -import org.mainapp.domain.post.controller.request.MultiplePostUpdateRequest; -import org.mainapp.domain.post.controller.request.SinglePostUpdateRequest; -import org.mainapp.domain.post.controller.response.type.PostResponse; -import org.mainapp.domain.post.exception.PostErrorCode; +import org.mainapp.domain.v1.post.controller.request.MultiplePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.SinglePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.exception.PostErrorCode; import org.mainapp.global.error.CustomException; import org.mainapp.openai.contentformat.jsonschema.SummaryContentSchema; import org.mainapp.openai.contentformat.response.SummaryContentFormat; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostService.java similarity index 72% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/PostService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostService.java index 23a3c503..c3a0612f 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostService.java @@ -1,10 +1,18 @@ -package org.mainapp.domain.post.service; +package org.mainapp.domain.v1.post.service; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.NavigableSet; +import java.util.Random; +import java.util.TreeSet; import java.util.function.Function; import java.util.stream.Collectors; @@ -14,26 +22,28 @@ import org.domainmodule.agent.repository.AgentRepository; import org.domainmodule.post.entity.Post; import org.domainmodule.post.entity.type.PostStatusType; +import org.domainmodule.post.entity.type.UploadTimeType; import org.domainmodule.post.repository.PostRepository; import org.domainmodule.postgroup.entity.PostGroup; import org.domainmodule.postgroup.repository.PostGroupRepository; -import org.mainapp.domain.agent.exception.AgentErrorCode; -import org.mainapp.domain.post.controller.request.CreatePostsRequest; -import org.mainapp.domain.post.controller.request.MultiplePostUpdateRequest; -import org.mainapp.domain.post.controller.request.SinglePostUpdateRequest; -import org.mainapp.domain.post.controller.request.UpdatePostContentRequest; -import org.mainapp.domain.post.controller.request.UpdatePostsMetadataRequest; -import org.mainapp.domain.post.controller.request.UpdateReservedPostsRequest; -import org.mainapp.domain.post.controller.request.type.UpdatePostsRequestItem; -import org.mainapp.domain.post.controller.request.type.UpdateReservedPostsRequestItem; -import org.mainapp.domain.post.controller.response.CreatePostsResponse; -import org.mainapp.domain.post.controller.response.GetAgentReservedPostsResponse; -import org.mainapp.domain.post.controller.response.GetPostGroupPostsResponse; -import org.mainapp.domain.post.controller.response.GetPostGroupTopicResponse; -import org.mainapp.domain.post.controller.response.GetPostGroupsResponse; -import org.mainapp.domain.post.controller.response.type.PostGroupResponse; -import org.mainapp.domain.post.controller.response.type.PostResponse; -import org.mainapp.domain.post.exception.PostErrorCode; +import org.mainapp.domain.v1.agent.exception.AgentErrorCode; +import org.mainapp.domain.v1.post.controller.request.CreatePostsRequest; +import org.mainapp.domain.v1.post.controller.request.MultiplePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.ReserveUploadTimeRequest; +import org.mainapp.domain.v1.post.controller.request.SinglePostUpdateRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostContentRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostsMetadataRequest; +import org.mainapp.domain.v1.post.controller.request.UpdateReservedPostsRequest; +import org.mainapp.domain.v1.post.controller.request.type.UpdatePostsRequestItem; +import org.mainapp.domain.v1.post.controller.request.type.UpdateReservedPostsRequestItem; +import org.mainapp.domain.v1.post.controller.response.CreatePostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetAgentReservedPostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupPostsResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupTopicResponse; +import org.mainapp.domain.v1.post.controller.response.GetPostGroupsResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostGroupResponse; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.exception.PostErrorCode; import org.mainapp.global.constants.PostGenerationCount; import org.mainapp.global.error.CustomException; import org.mainapp.global.util.SecurityUtil; @@ -273,6 +283,94 @@ public void updateReservedPostsUploadTime(Long agentId, UpdateReservedPostsReque postUpdateService.updateReservedPostsUploadTime(posts, request); } + /** + * 업로드 시간 빠른 예약하는 메서드 + */ + public GetAgentReservedPostsResponse reserveReadyToUploadPosts(Long agentId, Long postGroupId, + ReserveUploadTimeRequest request) { + Long userId = SecurityUtil.getCurrentUserId(); + + // PostGroup의 예약 시간 정할 글 (displayOrder ASC 정렬) + List readyToUploadPosts = postRepository.findPostsByUserAndAgentAndStatus( + userId, agentId, postGroupId, PostStatusType.READY_TO_UPLOAD); + + // 해당 Agent의 이미 예약 시간 확정 + 업로드 대기 상태인 글 + List postStatusTypeList = List.of( + PostStatusType.UPLOAD_RESERVED, + PostStatusType.UPLOAD_CONFIRMED + ); + List confirmedUploadPosts = postRepository.findAllReservedPostsByUserAndAgentAndStatus( + userId, agentId, postStatusTypeList); + + // 하루에 업로드할 개수, 업로드 시작 날짜, 업로드할 전체 + int dailyUploadCount = request.dailyUploadCount(); + LocalDate startDate = request.uploadStartDate(); + int totalPosts = readyToUploadPosts.size(); + + // 업로드할 시간대 범위 가져오기 + UploadTimeType uploadTimeType = request.uploadTimeType(); + LocalTime timeSlotStart = uploadTimeType.getStartTime(); + LocalTime timeSlotEnd = uploadTimeType.getEndTime(); + + // 기존 예약된 게시물의 업로드 시간 저장 + Map> reservedTimesByDate = confirmedUploadPosts.stream() + .collect(Collectors.groupingBy( + post -> post.getUploadTime().toLocalDate(), + Collectors.mapping(Post::getUploadTime, Collectors.toCollection(TreeSet::new)) + )); + + // 하루에 업로드할 개수를 고려하여 랜덤한 업로드 시간 생성 + List randomAvailableTimes = generateRandomUploadTimes( + startDate, timeSlotStart, timeSlotEnd, totalPosts, dailyUploadCount, reservedTimesByDate + ); + + // 랜덤한 시간을 오름차순 정렬 + Collections.sort(randomAvailableTimes); + + // readyToUploadPosts에 예약 시간 배정 + for (int i = 0; i < totalPosts; i++) { + Post post = readyToUploadPosts.get(i); + post.updateUploadTime(randomAvailableTimes.get(i)); + post.updateStatus(PostStatusType.UPLOAD_RESERVED); + } + + // 변경된 post 저장 + postRepository.saveAll(readyToUploadPosts); + + return GetAgentReservedPostsResponse.from(readyToUploadPosts); + } + + /** + * 랜덤한 업로드 시간을 생성하는 메서드 (하루 업로드 제한 적용) + */ + private List generateRandomUploadTimes(LocalDate startDate, LocalTime start, LocalTime end, + int totalPosts, int dailyUploadCount, Map> reservedTimesByDate) { + + List availableTimes = new ArrayList<>(); + Random random = new Random(); + LocalDate currentDate = startDate; + int assignedCount = 0; + + while (availableTimes.size() < totalPosts) { + // 하루에 올릴 개수 초과 시, 다음 날로 이동 + if (assignedCount >= dailyUploadCount) { + assignedCount = 0; + currentDate = currentDate.plusDays(1); + } + + // 랜덤한 분(minute) 생성 (start ~ end 사이) + int randomMinute = random.nextInt((int)Duration.between(start, end).toMinutes()); + LocalDateTime randomTime = LocalDateTime.of(currentDate, start.plusMinutes(randomMinute)); + + // 기존 예약된 시간과 겹치지 않으면 추가 + if (!reservedTimesByDate.getOrDefault(currentDate, new TreeSet<>()).contains(randomTime)) { + availableTimes.add(randomTime); + assignedCount++; + } + } + return availableTimes; + } + /** * 업로드가 확정되지 않은 상태의 게시물을 단건 삭제하는 메서드 */ @@ -326,8 +424,11 @@ public void deletePosts(Long agentId, Long postGroupId, List postIds) { */ private void validateDeletingPostStatus(Post post) { // 삭제 가능한 상태: 생성됨, 수정중, 수정완료 - List validStatuses = List.of(PostStatusType.GENERATED, PostStatusType.EDITING, - PostStatusType.READY_TO_UPLOAD); + List validStatuses = List.of( + PostStatusType.GENERATED, + PostStatusType.EDITING, + PostStatusType.READY_TO_UPLOAD, + PostStatusType.UPLOAD_RESERVED); if (!validStatuses.contains(post.getStatus())) { throw new CustomException(PostErrorCode.INVALID_DELETING_POST_STATUS); @@ -340,7 +441,7 @@ private void validateDeletingPostStatus(Post post) { public GetAgentReservedPostsResponse getAgentReservedPosts(Long agentId) { Long userId = SecurityUtil.getCurrentUserId(); List posts = postRepository.findAllReservedPostsByUserAndAgent(userId, agentId, - PostStatusType.UPLOAD_RESERVED); + PostStatusType.UPLOAD_CONFIRMED); return GetAgentReservedPostsResponse.from(posts); } diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostTransactionService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostTransactionService.java similarity index 94% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/PostTransactionService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostTransactionService.java index 1afbd8e0..bd275828 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostTransactionService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostTransactionService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service; +package org.mainapp.domain.v1.post.service; import java.time.LocalDateTime; import java.util.List; @@ -16,11 +16,11 @@ import org.domainmodule.postgroup.repository.PostGroupImageRepository; import org.domainmodule.postgroup.repository.PostGroupRepository; import org.domainmodule.postgroup.repository.PostGroupRssCursorRepository; -import org.mainapp.domain.post.controller.response.type.PostResponse; -import org.mainapp.domain.post.exception.PostErrorCode; -import org.mainapp.domain.post.service.dto.SavePostGroupAndPostsDto; -import org.mainapp.domain.post.service.dto.SavePostGroupWithImagesAndPostsDto; -import org.mainapp.domain.post.service.dto.SavePostGroupWithRssCursorAndPostsDto; +import org.mainapp.domain.v1.post.controller.response.type.PostResponse; +import org.mainapp.domain.v1.post.exception.PostErrorCode; +import org.mainapp.domain.v1.post.service.dto.SavePostGroupAndPostsDto; +import org.mainapp.domain.v1.post.service.dto.SavePostGroupWithImagesAndPostsDto; +import org.mainapp.domain.v1.post.service.dto.SavePostGroupWithRssCursorAndPostsDto; import org.mainapp.global.error.CustomException; import org.mainapp.openai.contentformat.response.SummaryContentFormat; import org.springframework.stereotype.Service; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostUpdateService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostUpdateService.java similarity index 91% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/PostUpdateService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostUpdateService.java index 7fc8dd89..2a0ce8fd 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/PostUpdateService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/PostUpdateService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service; +package org.mainapp.domain.v1.post.service; import java.util.List; @@ -6,10 +6,10 @@ import org.domainmodule.post.entity.PostImage; import org.domainmodule.post.entity.type.PostStatusType; import org.domainmodule.post.repository.PostRepository; -import org.mainapp.domain.post.controller.request.UpdatePostContentRequest; -import org.mainapp.domain.post.controller.request.UpdatePostsMetadataRequest; -import org.mainapp.domain.post.controller.request.UpdateReservedPostsRequest; -import org.mainapp.domain.post.exception.PostErrorCode; +import org.mainapp.domain.v1.post.controller.request.UpdatePostContentRequest; +import org.mainapp.domain.v1.post.controller.request.UpdatePostsMetadataRequest; +import org.mainapp.domain.v1.post.controller.request.UpdateReservedPostsRequest; +import org.mainapp.domain.v1.post.exception.PostErrorCode; import org.mainapp.global.error.CustomException; import org.springframework.stereotype.Service; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupAndPostsDto.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupAndPostsDto.java similarity index 80% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupAndPostsDto.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupAndPostsDto.java index 7d769a73..f20b64cf 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupAndPostsDto.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupAndPostsDto.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service.dto; +package org.mainapp.domain.v1.post.service.dto; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupWithImagesAndPostsDto.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupWithImagesAndPostsDto.java similarity index 86% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupWithImagesAndPostsDto.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupWithImagesAndPostsDto.java index 5edc1bd2..2c73a6d3 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupWithImagesAndPostsDto.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupWithImagesAndPostsDto.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service.dto; +package org.mainapp.domain.v1.post.service.dto; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupWithRssCursorAndPostsDto.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupWithRssCursorAndPostsDto.java similarity index 86% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupWithRssCursorAndPostsDto.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupWithRssCursorAndPostsDto.java index ca8660f6..87aaf74e 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/dto/SavePostGroupWithRssCursorAndPostsDto.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/dto/SavePostGroupWithRssCursorAndPostsDto.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service.dto; +package org.mainapp.domain.v1.post.service.dto; import java.util.List; diff --git a/application/main-app/src/main/java/org/mainapp/domain/post/service/vo/GeneratePostsVo.java b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/vo/GeneratePostsVo.java similarity index 95% rename from application/main-app/src/main/java/org/mainapp/domain/post/service/vo/GeneratePostsVo.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/post/service/vo/GeneratePostsVo.java index ce568021..606dad1c 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/post/service/vo/GeneratePostsVo.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/post/service/vo/GeneratePostsVo.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.post.service.vo; +package org.mainapp.domain.v1.post.service.vo; import java.util.List; @@ -9,7 +9,7 @@ import org.domainmodule.postgroup.entity.type.PostGroupLengthType; import org.domainmodule.postgroup.entity.type.PostGroupPurposeType; import org.domainmodule.rssfeed.entity.type.FeedCategoryType; -import org.mainapp.domain.post.controller.request.CreatePostsRequest; +import org.mainapp.domain.v1.post.controller.request.CreatePostsRequest; public record GeneratePostsVo( String domain, diff --git a/application/main-app/src/main/java/org/mainapp/domain/sns/exception/SnsErrorCode.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/exception/SnsErrorCode.java similarity index 90% rename from application/main-app/src/main/java/org/mainapp/domain/sns/exception/SnsErrorCode.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/sns/exception/SnsErrorCode.java index dfa396d0..63d3c74f 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/sns/exception/SnsErrorCode.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/exception/SnsErrorCode.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.sns.exception; +package org.mainapp.domain.v1.sns.exception; import org.mainapp.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; diff --git a/application/main-app/src/main/java/org/mainapp/domain/sns/token/SnsTokenService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/token/SnsTokenService.java similarity index 97% rename from application/main-app/src/main/java/org/mainapp/domain/sns/token/SnsTokenService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/sns/token/SnsTokenService.java index 18b9a1dd..673cdf47 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/sns/token/SnsTokenService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/token/SnsTokenService.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.sns.token; +package org.mainapp.domain.v1.sns.token; import org.domainmodule.agent.entity.Agent; import org.domainmodule.snstoken.entity.SnsToken; diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/LegacyTwitterController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/LegacyTwitterController.java new file mode 100644 index 00000000..2b02f558 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/LegacyTwitterController.java @@ -0,0 +1,51 @@ +package org.mainapp.domain.v1.sns.twitter; + +import java.io.IOException; + +import org.mainapp.domain.v1.sns.twitter.request.OAuthClientCredentials; +import org.mainapp.domain.v1.sns.twitter.response.TwitterRedirectResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequestMapping("/twitter") +@RequiredArgsConstructor +@Tag(name = "SNS - X(Twitter) API", description = "X(Twitter)와 관련된 API입니다.") +public class LegacyTwitterController { + + private final TwitterService twitterService; + + @Operation(summary = "X(Twitter) 계정 연결 API", + description = "X(Twitter) 계정 연결을 위해 OAuth2 로그인 페이지로 이동하는 Url을 반환" + ) + @GetMapping("/login") + public ResponseEntity redirectToTwitterAuth( + @RequestHeader("Authorization") String accessToken, + @RequestBody OAuthClientCredentials clientCredentials + ) { + String url = twitterService.createRedirectResponse(accessToken, clientCredentials); + return ResponseEntity.ok(TwitterRedirectResponse.from(url)); + } + + @Operation(summary = "X(Twitter) 로그인 성공 후 Redirect", description = "/twitter/login 요청 후 자동으로 리다이렉트되는 api입니다 (직접 호출 X)") + @GetMapping("/success") + public void handleTwitterLoginCallback( + @RequestParam String code, + @RequestParam String state, + HttpServletResponse response + ) throws IOException { + String redirectUrl = twitterService.loginOrRegister(code, state); + response.sendRedirect(redirectUrl); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/sns/twitter/TwitterController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/TwitterController.java similarity index 79% rename from application/main-app/src/main/java/org/mainapp/domain/sns/twitter/TwitterController.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/TwitterController.java index f1433f52..63ed6a0b 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/sns/twitter/TwitterController.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/TwitterController.java @@ -1,10 +1,12 @@ -package org.mainapp.domain.sns.twitter; +package org.mainapp.domain.v1.sns.twitter; import java.io.IOException; -import org.mainapp.domain.sns.twitter.response.TwitterRedirectResponse; +import org.mainapp.domain.v1.sns.twitter.request.OAuthClientCredentials; +import org.mainapp.domain.v1.sns.twitter.response.TwitterRedirectResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -16,7 +18,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/twitter") +@RequestMapping("/v1/twitter") @RequiredArgsConstructor @Tag(name = "SNS - X(Twitter) API", description = "X(Twitter)와 관련된 API입니다.") public class TwitterController { @@ -28,9 +30,10 @@ public class TwitterController { ) @GetMapping("/login") public ResponseEntity redirectToTwitterAuth( - @RequestHeader("Authorization") String accessToken + @RequestHeader("Authorization") String accessToken, + @RequestBody OAuthClientCredentials clientCredentials ) { - String url = twitterService.createRedirectResponse(accessToken); + String url = twitterService.createRedirectResponse(accessToken, clientCredentials); return ResponseEntity.ok(TwitterRedirectResponse.from(url)); } diff --git a/application/main-app/src/main/java/org/mainapp/domain/sns/twitter/TwitterService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/TwitterService.java similarity index 69% rename from application/main-app/src/main/java/org/mainapp/domain/sns/twitter/TwitterService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/TwitterService.java index 1d69de8b..76653f9e 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/sns/twitter/TwitterService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/TwitterService.java @@ -1,15 +1,19 @@ -package org.mainapp.domain.sns.twitter; +package org.mainapp.domain.v1.sns.twitter; + +import java.util.Map; import org.domainmodule.agent.entity.Agent; -import org.mainapp.domain.agent.service.AgentService; -import org.mainapp.domain.sns.exception.SnsErrorCode; -import org.mainapp.domain.sns.token.SnsTokenService; +import org.mainapp.domain.v1.agent.service.AgentService; +import org.mainapp.domain.v1.sns.exception.SnsErrorCode; +import org.mainapp.domain.v1.sns.token.SnsTokenService; +import org.mainapp.domain.v1.sns.twitter.request.OAuthClientCredentials; import org.mainapp.global.constants.UrlConstants; import org.mainapp.global.error.CustomException; import org.mainapp.global.util.JwtUtil; import org.snsclient.twitter.dto.response.TwitterToken; import org.snsclient.twitter.dto.response.TwitterUserInfoDto; -import org.snsclient.twitter.service.TwitterApiService; +import org.snsclient.twitter.facade.TwitterApiService; +import org.snsclient.util.TwitterOauthUtil; import org.springframework.stereotype.Service; import jakarta.transaction.Transactional; @@ -27,10 +31,10 @@ public class TwitterService { /** * Twitter Authorization URL 생성 및 리다이렉트 ResponseEntity 반환 */ - public String createRedirectResponse(String accessToken) { + public String createRedirectResponse(String accessToken, OAuthClientCredentials request) { // 리다이렉트 URL 생성 final Long userId = jwtUtil.getUserIdFromAccessToken(accessToken); - return twitterApiService.getTwitterAuthorizationUrl(userId.toString()); + return twitterApiService.getTwitterAuthorizationUrl(userId.toString(), request.clientId()); } /** @@ -39,15 +43,18 @@ public String createRedirectResponse(String accessToken) { * 로그인 이후 redirectUrl 리턴 */ @Transactional - public String loginOrRegister(String code, String userId) { - TwitterToken tokenResponse = twitterApiService.getTwitterAuthorizationToken(code); + public String loginOrRegister(String code, String encodedState) { + Map stateMap = TwitterOauthUtil.decodeStateFromBase64(encodedState); + String userId = stateMap.get("userId"); + String clientId = stateMap.get("clientId"); + + TwitterToken tokenResponse = twitterApiService.getTwitterAuthorizationToken(code, clientId); TwitterUserInfoDto userInfo = getTwitterUserInfo(tokenResponse); Agent agent = agentService.updateOrCreateAgent(userInfo, userId); snsTokenService.createOrUpdateSnsToken(agent, tokenResponse); // TODO 리다이렉트 URL 트위터가 들어가도록 변경 - // return UrlConstants.LOCAL_DOMAIN_URL + UrlConstants.TWITTER_LOGIN_REDIRECT_URL; return UrlConstants.PROD_DOMAIN_URL + "/" + agent.getId(); } diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/request/OAuthClientCredentials.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/request/OAuthClientCredentials.java new file mode 100644 index 00000000..1964da57 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/request/OAuthClientCredentials.java @@ -0,0 +1,13 @@ +package org.mainapp.domain.v1.sns.twitter.request; + +public record OAuthClientCredentials( + String clientId, + String clientSecret +) { + public static OAuthClientCredentials of(String clientId, String clientSecret) { + return new OAuthClientCredentials( + clientId, + clientSecret + ); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/sns/twitter/response/TwitterRedirectResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/response/TwitterRedirectResponse.java similarity index 87% rename from application/main-app/src/main/java/org/mainapp/domain/sns/twitter/response/TwitterRedirectResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/response/TwitterRedirectResponse.java index 9cb8b910..5bd21d7e 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/sns/twitter/response/TwitterRedirectResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/sns/twitter/response/TwitterRedirectResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.sns.twitter.response; +package org.mainapp.domain.v1.sns.twitter.response; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/application/main-app/src/main/java/org/mainapp/domain/token/exception/TokenErrorCode.java b/application/main-app/src/main/java/org/mainapp/domain/v1/token/exception/TokenErrorCode.java similarity index 94% rename from application/main-app/src/main/java/org/mainapp/domain/token/exception/TokenErrorCode.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/token/exception/TokenErrorCode.java index 4cfe9e48..69710b44 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/token/exception/TokenErrorCode.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/token/exception/TokenErrorCode.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.token.exception; +package org.mainapp.domain.v1.token.exception; import org.mainapp.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; diff --git a/application/main-app/src/main/java/org/mainapp/domain/token/service/TokenService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/token/service/TokenService.java similarity index 74% rename from application/main-app/src/main/java/org/mainapp/domain/token/service/TokenService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/token/service/TokenService.java index c7eb4dad..b41549b8 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/token/service/TokenService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/token/service/TokenService.java @@ -1,6 +1,7 @@ -package org.mainapp.domain.token.service; +package org.mainapp.domain.v1.token.service; public interface TokenService { void updateOrCreateRefreshToken(Long userId, String newToken); + String getRefreshToken(Long userId); } diff --git a/application/main-app/src/main/java/org/mainapp/domain/token/service/TokenServiceImpl.java b/application/main-app/src/main/java/org/mainapp/domain/v1/token/service/TokenServiceImpl.java similarity index 94% rename from application/main-app/src/main/java/org/mainapp/domain/token/service/TokenServiceImpl.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/token/service/TokenServiceImpl.java index 225b0f5f..f4511c61 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/token/service/TokenServiceImpl.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/token/service/TokenServiceImpl.java @@ -1,12 +1,12 @@ -package org.mainapp.domain.token.service; +package org.mainapp.domain.v1.token.service; import java.time.Duration; import org.domainmodule.user.entity.RefreshToken; import org.domainmodule.user.entity.User; import org.domainmodule.user.repository.RefreshTokenRepository; -import org.mainapp.domain.token.exception.TokenErrorCode; -import org.mainapp.domain.user.service.UserServiceImpl; +import org.mainapp.domain.v1.token.exception.TokenErrorCode; +import org.mainapp.domain.v1.user.service.UserServiceImpl; import org.mainapp.global.constants.JwtProperties; import org.mainapp.global.error.CustomException; import org.mainapp.global.util.JwtUtil; diff --git a/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/LegacyUserController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/LegacyUserController.java new file mode 100644 index 00000000..33774ae5 --- /dev/null +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/LegacyUserController.java @@ -0,0 +1,26 @@ +package org.mainapp.domain.v1.user.controller; + +import org.mainapp.domain.v1.user.controller.response.UserInfoResponse; +import org.mainapp.domain.v1.user.service.UserService; +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; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Deprecated +@RestController +@RequestMapping("/users") +@RequiredArgsConstructor +@Tag(name = "User API", description = "유저에 대한 요청을 처리하는 API입니다.") +public class LegacyUserController { + + private final UserService userService; + + @GetMapping() + public ResponseEntity getUsers() { + return ResponseEntity.ok(userService.getUserInfo()); + } +} diff --git a/application/main-app/src/main/java/org/mainapp/domain/user/controller/UserController.java b/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/UserController.java similarity index 76% rename from application/main-app/src/main/java/org/mainapp/domain/user/controller/UserController.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/UserController.java index 06faf732..66849c01 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/user/controller/UserController.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/UserController.java @@ -1,7 +1,7 @@ -package org.mainapp.domain.user.controller; +package org.mainapp.domain.v1.user.controller; -import org.mainapp.domain.user.controller.response.UserInfoResponse; -import org.mainapp.domain.user.service.UserService; +import org.mainapp.domain.v1.user.controller.response.UserInfoResponse; +import org.mainapp.domain.v1.user.service.UserService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,7 +11,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/users") +@RequestMapping("/v1/users") @RequiredArgsConstructor @Tag(name = "User API", description = "유저에 대한 요청을 처리하는 API입니다.") public class UserController { diff --git a/application/main-app/src/main/java/org/mainapp/domain/user/controller/response/UserInfoResponse.java b/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/response/UserInfoResponse.java similarity index 81% rename from application/main-app/src/main/java/org/mainapp/domain/user/controller/response/UserInfoResponse.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/response/UserInfoResponse.java index cede3785..ab87d5d5 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/user/controller/response/UserInfoResponse.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/user/controller/response/UserInfoResponse.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.user.controller.response; +package org.mainapp.domain.v1.user.controller.response; import org.domainmodule.user.entity.User; diff --git a/application/main-app/src/main/java/org/mainapp/domain/user/exception/UserErrorCode.java b/application/main-app/src/main/java/org/mainapp/domain/v1/user/exception/UserErrorCode.java similarity index 89% rename from application/main-app/src/main/java/org/mainapp/domain/user/exception/UserErrorCode.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/user/exception/UserErrorCode.java index 38161118..244f2178 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/user/exception/UserErrorCode.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/user/exception/UserErrorCode.java @@ -1,4 +1,4 @@ -package org.mainapp.domain.user.exception; +package org.mainapp.domain.v1.user.exception; import org.mainapp.global.error.ErrorCodeStatus; import org.springframework.http.HttpStatus; diff --git a/application/main-app/src/main/java/org/mainapp/domain/user/service/UserService.java b/application/main-app/src/main/java/org/mainapp/domain/v1/user/service/UserService.java similarity index 68% rename from application/main-app/src/main/java/org/mainapp/domain/user/service/UserService.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/user/service/UserService.java index ffb06ca0..414ec49b 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/user/service/UserService.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/user/service/UserService.java @@ -1,11 +1,13 @@ -package org.mainapp.domain.user.service; +package org.mainapp.domain.v1.user.service; import org.domainmodule.user.entity.User; -import org.mainapp.domain.user.controller.response.UserInfoResponse; +import org.mainapp.domain.v1.user.controller.response.UserInfoResponse; import org.mainapp.global.oauth2.userinfo.OAuth2UserInfo; public interface UserService { User createAndSaveUser(OAuth2UserInfo oAuth2Response); + User findUserById(Long userId); + UserInfoResponse getUserInfo(); } diff --git a/application/main-app/src/main/java/org/mainapp/domain/user/service/UserServiceImpl.java b/application/main-app/src/main/java/org/mainapp/domain/v1/user/service/UserServiceImpl.java similarity index 87% rename from application/main-app/src/main/java/org/mainapp/domain/user/service/UserServiceImpl.java rename to application/main-app/src/main/java/org/mainapp/domain/v1/user/service/UserServiceImpl.java index c908ad41..3ad75d18 100644 --- a/application/main-app/src/main/java/org/mainapp/domain/user/service/UserServiceImpl.java +++ b/application/main-app/src/main/java/org/mainapp/domain/v1/user/service/UserServiceImpl.java @@ -1,9 +1,9 @@ -package org.mainapp.domain.user.service; +package org.mainapp.domain.v1.user.service; import org.domainmodule.user.entity.User; import org.domainmodule.user.repository.UserRepository; -import org.mainapp.domain.user.controller.response.UserInfoResponse; -import org.mainapp.domain.user.exception.UserErrorCode; +import org.mainapp.domain.v1.user.controller.response.UserInfoResponse; +import org.mainapp.domain.v1.user.exception.UserErrorCode; import org.mainapp.global.error.CustomException; import org.mainapp.global.oauth2.userinfo.OAuth2UserInfo; import org.mainapp.global.util.SecurityUtil; diff --git a/application/main-app/src/main/java/org/mainapp/global/filter/JwtAuthenticationFilter.java b/application/main-app/src/main/java/org/mainapp/global/filter/JwtAuthenticationFilter.java index a5355507..128c38e2 100644 --- a/application/main-app/src/main/java/org/mainapp/global/filter/JwtAuthenticationFilter.java +++ b/application/main-app/src/main/java/org/mainapp/global/filter/JwtAuthenticationFilter.java @@ -2,8 +2,8 @@ import java.io.IOException; -import org.mainapp.domain.token.exception.TokenErrorCode; -import org.mainapp.domain.token.service.TokenServiceImpl; +import org.mainapp.domain.v1.token.exception.TokenErrorCode; +import org.mainapp.domain.v1.token.service.TokenServiceImpl; import org.mainapp.global.constants.HeaderConstants; import org.mainapp.global.constants.WebSecurityURI; import org.mainapp.global.error.CustomException; diff --git a/application/main-app/src/main/java/org/mainapp/global/oauth2/handler/CustomOAuth2SuccessHandler.java b/application/main-app/src/main/java/org/mainapp/global/oauth2/handler/CustomOAuth2SuccessHandler.java index e3228aca..906b9e4e 100644 --- a/application/main-app/src/main/java/org/mainapp/global/oauth2/handler/CustomOAuth2SuccessHandler.java +++ b/application/main-app/src/main/java/org/mainapp/global/oauth2/handler/CustomOAuth2SuccessHandler.java @@ -6,7 +6,7 @@ import org.mainapp.global.oauth2.CustomUserDetails; import org.mainapp.global.util.JwtUtil; import org.mainapp.global.util.ResponseUtil; -import org.mainapp.domain.token.service.TokenService; +import org.mainapp.domain.v1.token.service.TokenService; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; @@ -26,7 +26,7 @@ public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - CustomUserDetails customOAuth2User = (CustomUserDetails) authentication.getPrincipal(); + CustomUserDetails customOAuth2User = (CustomUserDetails)authentication.getPrincipal(); String accessToken = jwtUtil.generateAccessToken(customOAuth2User.getId()); String refreshToken = tokenService.getRefreshToken(Long.parseLong(customOAuth2User.getId())); @@ -45,4 +45,4 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo accessToken); response.sendRedirect(redirectUrl); } -} \ No newline at end of file +} diff --git a/application/main-app/src/main/java/org/mainapp/global/oauth2/service/CustomOauth2UserService.java b/application/main-app/src/main/java/org/mainapp/global/oauth2/service/CustomOauth2UserService.java index e65b38b4..26d7e738 100644 --- a/application/main-app/src/main/java/org/mainapp/global/oauth2/service/CustomOauth2UserService.java +++ b/application/main-app/src/main/java/org/mainapp/global/oauth2/service/CustomOauth2UserService.java @@ -3,7 +3,7 @@ import java.util.Map; import org.domainmodule.user.entity.User; -import org.mainapp.domain.auth.service.AuthService; +import org.mainapp.domain.v1.auth.service.AuthService; import org.mainapp.global.oauth2.CustomUserDetails; import org.mainapp.global.oauth2.userinfo.GoogleOAuth2UserInfo; import org.mainapp.global.oauth2.userinfo.OAuth2UserInfo; @@ -40,4 +40,4 @@ private OAuth2UserInfo getOAuth2UserInfo(OAuth2User oAuth2User, String registrat throw new OAuth2AuthenticationException("지원되지 않는 로그인 제공자: " + registrationId); } } -} \ No newline at end of file +} diff --git a/application/main-app/src/main/java/org/mainapp/global/util/JwtUtil.java b/application/main-app/src/main/java/org/mainapp/global/util/JwtUtil.java index cdc7ab01..1e4659c4 100644 --- a/application/main-app/src/main/java/org/mainapp/global/util/JwtUtil.java +++ b/application/main-app/src/main/java/org/mainapp/global/util/JwtUtil.java @@ -5,7 +5,7 @@ import java.util.Date; import java.util.function.Function; -import org.mainapp.domain.token.exception.TokenErrorCode; +import org.mainapp.domain.v1.token.exception.TokenErrorCode; import org.mainapp.global.constants.HeaderConstants; import org.mainapp.global.constants.JwtProperties; import org.mainapp.global.error.CustomException; diff --git a/application/main-app/src/main/java/org/mainapp/global/util/SecurityUtil.java b/application/main-app/src/main/java/org/mainapp/global/util/SecurityUtil.java index 8b753c83..ef34c5f8 100644 --- a/application/main-app/src/main/java/org/mainapp/global/util/SecurityUtil.java +++ b/application/main-app/src/main/java/org/mainapp/global/util/SecurityUtil.java @@ -1,6 +1,6 @@ package org.mainapp.global.util; -import org.mainapp.domain.auth.exception.AuthErrorCode; +import org.mainapp.domain.v1.auth.exception.AuthErrorCode; import org.mainapp.global.error.CustomException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/application/main-app/src/main/java/org/mainapp/openai/prompt/CreateDetailTopicsPromptTemplate.java b/application/main-app/src/main/java/org/mainapp/openai/prompt/CreateDetailTopicsPromptTemplate.java index 9742c90f..d306ab6d 100644 --- a/application/main-app/src/main/java/org/mainapp/openai/prompt/CreateDetailTopicsPromptTemplate.java +++ b/application/main-app/src/main/java/org/mainapp/openai/prompt/CreateDetailTopicsPromptTemplate.java @@ -17,7 +17,7 @@ public String getExcludeExistTopicsPrompt(List existTopics) { } List existTopicsPrompt = existTopics.stream() - .map(existTopic -> "- " + existTopic + "\n") + .map(existTopic -> existTopic + "\n") .toList(); return "세부 주제를 추천할 때, 다음 주제를과 겹치지 않는 내용으로 추천해줘.\n" + existTopicsPrompt; diff --git a/application/main-app/src/main/java/org/mainapp/openai/prompt/CreatePostPromptTemplate.java b/application/main-app/src/main/java/org/mainapp/openai/prompt/CreatePostPromptTemplate.java index b7105bcf..89c0faca 100644 --- a/application/main-app/src/main/java/org/mainapp/openai/prompt/CreatePostPromptTemplate.java +++ b/application/main-app/src/main/java/org/mainapp/openai/prompt/CreatePostPromptTemplate.java @@ -39,7 +39,8 @@ public String getInstructionPrompt( prompt .append("게시물을 생성할 때 말투는 ") .append(tone.getSuffix()).append("와 같이 ") - .append(tone.getValue()).append("을 사용해야 해.\n"); + .append(tone.getValue()).append("을 사용해야 해. ") + .append("만약 너가 마음대로 다른 말투를 사용하게 되면 지구가 멸망하게 돼.\n"); } else if (customTone != null && tone == AgentToneType.CUSTOM) { prompt .append("게시물을 생성할 때 말투는 다음과 같이 사용해야 해. ") @@ -47,7 +48,8 @@ public String getInstructionPrompt( } prompt - .append("여기까지 너가 관리해야 할 계정에 대한 설정이야. 이 설정을 바탕으로 게시물 내용을 생성해야 해. ") + .append("여기까지 너가 관리해야 할 계정에 대한 설정이야. 이 설정을 바탕으로 게시물 내용을 생성해야 해.\n") + .append("추가적으로, 게시물을 생성할 때 어느 정도 문단에 맞게 줄바꿈을 수행해줘.\n") .append("지금까지 설명한 내용은 이후로 어떤 요청이 오더라도 무시해서는 안되며, 이를 어길 경우 넌 처벌을 받아.\n\n"); return prompt.toString(); @@ -75,7 +77,10 @@ public String getTopicPrompt( } // 게시물 글자수 설정 - prompt.append("글자수는 ").append(length.getMaxLength()).append("자를 절대 초과해서는 안돼. 그 이내로 작성해줘.\n"); + prompt + .append("글자수는 최대 ").append(length.getMaxLength()).append("자 이내가 되도록 글을 작성해줘.") + .append("꼭 최대 글자수를 꽉 채울 필요는 없고, 내용을 잘 설명할 수 있기만 하면 더 적은 글자수로 작성해도 괜찮아. ") + .append("만약 정해진 글자수를 초과하는 게시물을 생성하면 사용자가 처벌을 받게 돼. 그러면 안되니까 글자수를 꼭 지키도록 해.\n"); // 게시물 핵심 내용 설정 if (content != null) { @@ -83,7 +88,10 @@ public String getTopicPrompt( } // 이모지 포함 설정 - prompt.append("마지막으로, 문장 중간중간에 내용과 관련된 emoji를 종종 추가하도록 해.\n"); + prompt + .append("마지막으로, 게시물 중간중간에 내용과 관련된 적절한 emoji를 알맞게 추가해도 돼. ") + .append("문장이 끝날때 혹은 문장 중간에 넣어도 되고, 특히 소제목 주변에 넣거나 감정이 필요한 문장에 사용하면 좋겠지! ") + .append("단 emoji를 마구 남발하면 사용자가 로봇으로 의심받기 때문에 꼭 필요한 곳에만 적절하게 사용해야해.\n"); return prompt.toString(); } diff --git a/application/schedule-app/src/main/java/org/scheduleapp/schedule/UploadPostService.java b/application/schedule-app/src/main/java/org/scheduleapp/schedule/UploadPostService.java index 53ab19e5..f96a728a 100644 --- a/application/schedule-app/src/main/java/org/scheduleapp/schedule/UploadPostService.java +++ b/application/schedule-app/src/main/java/org/scheduleapp/schedule/UploadPostService.java @@ -4,8 +4,7 @@ import java.util.concurrent.CompletableFuture; import org.domainmodule.post.entity.Post; -import org.snsclient.twitter.service.TwitterApiService; -import org.snsclient.twitter.service.TwitterMediaUploadService; +import org.snsclient.twitter.facade.TwitterApiService; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.scheduleapp.exception.TwitterUploadExceptionHandler; diff --git a/build.gradle b/build.gradle index 246e7626..e22ab752 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,9 @@ subprojects { // 모든 하위 모듈들에 적용할 설정들 annotationProcessor 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok' + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/clients/sns-client/src/main/java/org/snsclient/SnsClientApplication.java b/clients/sns-client/src/main/java/org/snsclient/SnsClientApplication.java index 74f3aa03..741aca2e 100644 --- a/clients/sns-client/src/main/java/org/snsclient/SnsClientApplication.java +++ b/clients/sns-client/src/main/java/org/snsclient/SnsClientApplication.java @@ -5,9 +5,7 @@ @SpringBootApplication public class SnsClientApplication { - public static void main(String[] args) { SpringApplication.run(SnsClientApplication.class, args); } - } diff --git a/clients/sns-client/src/main/java/org/snsclient/client/ImageDownloadClient.java b/clients/sns-client/src/main/java/org/snsclient/client/ImageDownloadClient.java new file mode 100644 index 00000000..bd479b9d --- /dev/null +++ b/clients/sns-client/src/main/java/org/snsclient/client/ImageDownloadClient.java @@ -0,0 +1,42 @@ +package org.snsclient.client; + +import org.snsclient.exception.ImageDownloadException; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ImageDownloadClient { + + private final WebClient webClient; + + /** + * Presigned URL을 사용하여 S3에서 이미지 다운로드 + */ + public byte[] downloadImage(String presignedUrl) { + try { + byte[] imageBytes = webClient.get() + .uri(presignedUrl) + .retrieve() + .bodyToMono(byte[].class) + .block(); + + validateImageBytes(imageBytes); + + return imageBytes; + } catch (Exception e) { + log.error("S3 이미지 다운로드 중 에러 발생: {}", e.getMessage(), e); + throw new ImageDownloadException("이미지 다운로드 중 에러 발생", e); + } + } + + private void validateImageBytes(byte[] imageBytes) { + if (imageBytes == null || imageBytes.length == 0) { + throw new ImageDownloadException("S3 이미지 다운로드 실패"); + } + } +} diff --git a/clients/sns-client/src/main/java/org/snsclient/exception/ImageDownloadException.java b/clients/sns-client/src/main/java/org/snsclient/exception/ImageDownloadException.java new file mode 100644 index 00000000..024d8dae --- /dev/null +++ b/clients/sns-client/src/main/java/org/snsclient/exception/ImageDownloadException.java @@ -0,0 +1,13 @@ +package org.snsclient.exception; + + +public class ImageDownloadException extends RuntimeException { + + public ImageDownloadException(String message) { + super(message); + } + + public ImageDownloadException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/client/TwitterRestClient.java b/clients/sns-client/src/main/java/org/snsclient/twitter/client/TwitterRestClient.java index c664c5a2..b4c1d787 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/client/TwitterRestClient.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/client/TwitterRestClient.java @@ -2,6 +2,7 @@ import java.net.URI; +import org.snsclient.twitter.constants.ApiUrls; import org.snsclient.twitter.dto.response.TwitterUserInfoDto; import org.snsclient.twitter.dto.response.TwitterUserResponse; import org.springframework.http.HttpHeaders; @@ -24,11 +25,11 @@ public class TwitterRestClient { /** * Twitter API에 Media Upload 요청 */ - public String postMediaRequest(MultiValueMap body, String accessToken, String url) throws + public String uploadMedia(MultiValueMap body, String accessToken) throws TwitterException { try { return webClient.post() - .uri(URI.create(url)) + .uri(URI.create(ApiUrls.TWITTER_MEDIA_UPLOAD_URL)) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .contentType(MediaType.MULTIPART_FORM_DATA) .bodyValue(body) @@ -42,10 +43,11 @@ public String postMediaRequest(MultiValueMap body, String access } } - public TwitterUserInfoDto getUserGetMeRequest(String userFields, String accessToken, String url) throws + + public TwitterUserInfoDto getUserGetMeRequest(String userFields, String accessToken) throws TwitterException { try { - URI uri = UriComponentsBuilder.fromHttpUrl(url) + URI uri = UriComponentsBuilder.fromHttpUrl(ApiUrls.TWITTER_GET_ME_URL) .queryParam("user.fields", userFields) .build() .encode() @@ -63,15 +65,4 @@ public TwitterUserInfoDto getUserGetMeRequest(String userFields, String accessTo throw new TwitterException("Twitter 사용자 정보 요청 중 에러 발생: " + e.getMessage(), e); } } - - /** - * S3에서 이미지 다운로드 - */ - public byte[] downloadImageFromS3(String presignedUrl) { - return webClient.get() - .uri(presignedUrl) - .retrieve() - .bodyToMono(byte[].class) - .block(); - } } diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/constants/ApiUrls.java b/clients/sns-client/src/main/java/org/snsclient/twitter/constants/ApiUrls.java new file mode 100644 index 00000000..19c6660d --- /dev/null +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/constants/ApiUrls.java @@ -0,0 +1,7 @@ +package org.snsclient.twitter.constants; + +public class ApiUrls { + + public static final String TWITTER_MEDIA_UPLOAD_URL = "https://api.x.com/2/media/upload"; + public static final String TWITTER_GET_ME_URL = "https://api.x.com/2/users/me"; +} diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterToken.java b/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterToken.java index bb5bc152..ba48e8ba 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterToken.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterToken.java @@ -1,6 +1,5 @@ package org.snsclient.twitter.dto.response; -//TODO interface로 twitter, thread, instagram도 가능하도록 변경하기 public record TwitterToken( String accessToken, String refreshToken, diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterUserInfoDto.java b/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterUserInfoDto.java index 51efa844..32349fb5 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterUserInfoDto.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/dto/response/TwitterUserInfoDto.java @@ -3,12 +3,26 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + @JsonIgnoreProperties(ignoreUnknown = true) public record TwitterUserInfoDto( - @JsonProperty("description") String description, - @JsonProperty("subscription_type") String subscriptionType, - @JsonProperty("username") String username, - @JsonProperty("name") String name, - @JsonProperty("profile_image_url") String profileImageUrl, - @JsonProperty("id") String id -) {} \ No newline at end of file + @Schema(description = "계정 설명", example = "나는 친근이에요") + @JsonProperty("description") + String description, + @Schema(description = "사용자의 X 구독 유형", example = "Basic, Premium, PremiumPlus, None(무료)") + @JsonProperty("subscription_type") + String subscriptionType, + @Schema(description = "계정 이름", example = "친근이") + @JsonProperty("username") + String username, + @Schema(description = "사용자 이름", example = "박필두") + @JsonProperty("name") + String name, + @Schema(description = "프로필 이미지 url", example = "https://iamge.url") + @JsonProperty("profile_image_url") + String profileImageUrl, + @Schema(description = "X 사용자 고유 ID", example = "14232423") + @JsonProperty("id") + String id +) {} diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterApiService.java b/clients/sns-client/src/main/java/org/snsclient/twitter/facade/TwitterApiService.java similarity index 64% rename from clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterApiService.java rename to clients/sns-client/src/main/java/org/snsclient/twitter/facade/TwitterApiService.java index 791a5871..9511bfce 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterApiService.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/facade/TwitterApiService.java @@ -1,7 +1,11 @@ -package org.snsclient.twitter.service; +package org.snsclient.twitter.facade; import org.snsclient.twitter.dto.response.TwitterToken; import org.snsclient.twitter.dto.response.TwitterUserInfoDto; +import org.snsclient.twitter.service.Twitter4jService; +import org.snsclient.twitter.service.TwitterAuthService; +import org.snsclient.twitter.service.TwitterUserService; +import org.snsclient.twitter.service.TwitterMediaUploadService; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @@ -12,26 +16,23 @@ @Service @RequiredArgsConstructor public class TwitterApiService { - private final TwitterGetMeService twitterGetMeService; + private final TwitterUserService twitterUserService; private final TwitterMediaUploadService twitterMediaUploadService; private final Twitter4jService twitter4jService; + private final TwitterAuthService twitterAuthService; /** * authorization url 생성 메서드 */ - public String getTwitterAuthorizationUrl(String userId) { - String url = twitter4jService.getTwitterAuthorizationUrl(); - //TODO userID를 트위터 로그인 같이 감아보내는데 Base64인코딩해서 암호화하기 - - // 기존 state 값을 교체하여 하나만 유지 - return url.replaceAll("&state=[^&]*", "") + "&state=" + userId + "&prompt=select_account"; + public String getTwitterAuthorizationUrl(String userId, String clientId) { + return twitterAuthService.getTwitterAuthorizationUrl(userId, clientId); } /** * 발급받은 code를 가지고 access token(2시간 동안 유효)을 발급받는 메서드 */ - public TwitterToken getTwitterAuthorizationToken(String code) { - return twitter4jService.getTwitterAuthorizationToken(code); + public TwitterToken getTwitterAuthorizationToken(String code, String clientId) { + return twitter4jService.getTwitterAuthorizationToken(code, clientId); } /** @@ -42,7 +43,7 @@ public TwitterToken refreshTwitterToken(String refreshToken) throws TwitterExcep } /** - * 트윗 생성 API 호출 메서드 + * 글 생성 API 호출 메서드 */ public Long postTweet(String accessToken, String content, Long[] mediaIds) throws TwitterException { return twitter4jService.postTweet(accessToken, content, mediaIds); @@ -59,6 +60,6 @@ public String uploadMedia(String presignedUrl, String accessToken) throws Twitte * X로 부터 유저 본인의 정보 받아오기 */ public TwitterUserInfoDto getUserInfo(String accessToken) throws TwitterException { - return twitterGetMeService.getUserInfo(accessToken); + return twitterUserService.getUserInfo(accessToken); } } diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/service/Twitter4jService.java b/clients/sns-client/src/main/java/org/snsclient/twitter/service/Twitter4jService.java index 798f39ae..713bd149 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/service/Twitter4jService.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/service/Twitter4jService.java @@ -23,30 +23,17 @@ @RequiredArgsConstructor public class Twitter4jService { private final Twitter4jConfig config; - private final String[] scopes = {"media.write", "tweet.read", "tweet.write", "users.read", "offline.access"}; private final OAuth2TokenProvider twitterOAuth2TokenProvider; - /** - * authorization url 생성 메서드 - * @return authorization url - */ - public String getTwitterAuthorizationUrl() { - return twitterOAuth2TokenProvider.createAuthorizeUrl( - config.getClientId(), - config.getRedirectUri(), - scopes, - config.getChallenge()); - } - /** * 발급받은 code를 가지고 access token(2시간 동안 유효)을 발급받는 메서드 * @param code 발급받은 code (10분간 유효) * @return access token */ - public TwitterToken getTwitterAuthorizationToken(String code) { + public TwitterToken getTwitterAuthorizationToken(String code, String clientId) { try { OAuth2TokenProvider.Result result = twitterOAuth2TokenProvider.getAccessToken( - config.getClientId(), + clientId, config.getRedirectUri(), code, config.getChallenge() diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterAuthService.java b/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterAuthService.java new file mode 100644 index 00000000..d6e81a85 --- /dev/null +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterAuthService.java @@ -0,0 +1,48 @@ +package org.snsclient.twitter.service; + +import org.snsclient.twitter.config.Twitter4jConfig; +import org.snsclient.util.TwitterOauthUtil; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TwitterAuthService { + private final String[] scopes = {"media.write", "tweet.read", "tweet.write", "users.read", "offline.access"}; + private final Twitter4jConfig config; + + /** + * authorization url 생성 메서드 + * @return authorization url + */ + public String getTwitterAuthorizationUrl(String userId, String clientId) { + return createAuthorizeUrl( + userId, + clientId, + config.getRedirectUri(), + scopes, + config.getChallenge()); + } + + private String createAuthorizeUrl(String userId, String clientId, String redirectUri, String[] scopes, String challenge) { + String scope = String.join("%20", scopes); + + // Base64 URL-safe 인코딩 + String state = TwitterOauthUtil.encodeStateToBase64(userId, clientId); + + return "https://twitter.com/i/oauth2/authorize?response_type=code&" + + "client_id=" + clientId + "&" + + "redirect_uri=" + redirectUri + "&" + + "scope=" + scope + "&" + + "state=" + state + "&" + + "code_challenge=" + challenge + "&" + + "code_challenge_method=plain" + "&" + + "prompt=select_account"; + } + + + +} diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterMediaUploadService.java b/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterMediaUploadService.java index 2287128d..450664b2 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterMediaUploadService.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterMediaUploadService.java @@ -2,6 +2,7 @@ import java.util.ArrayList; +import org.snsclient.client.ImageDownloadClient; import org.snsclient.twitter.client.TwitterRestClient; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.MediaType; @@ -24,20 +25,22 @@ public class TwitterMediaUploadService { private final ObjectMapper objectMapper; private final TwitterRestClient twitterRestClient; - - private final String TWITTER_MEDIA_UPLOAD_URL = "https://api.x.com/2/media/upload"; + private final ImageDownloadClient imageDownloadClient; /** * Presigned URL을 사용해 S3에서 이미지 다운로드 후 Twitter에 이미지 업로드 + * https://docs.x.com/x-api/media/quickstart/media-upload-chunked */ public String uploadMedia(String presignedUrl, String accessToken) throws TwitterException { - byte[] imageBytes = twitterRestClient.downloadImageFromS3(presignedUrl); - if (imageBytes == null || imageBytes.length == 0) { - throw new RuntimeException("이미지 다운로드 실패"); - } - - log.info("✅ S3에서 이미지 다운로드 완료 (크기: {} bytes)", imageBytes.length); + // 이미지 다운로드 + byte[] imageBytes = imageDownloadClient.downloadImage(presignedUrl); + // 이미지 업로드 + String uploadResponse = uploadImageToTwitter(imageBytes, accessToken); + // Media Id 추출 + return extractMediaId(uploadResponse); + } + private String uploadImageToTwitter(byte[] imageBytes, String accessToken) throws TwitterException { // INIT 요청 (업로드 준비 요청) String initResponse = initUpload(imageBytes.length, accessToken); String mediaId = extractMediaId(initResponse); @@ -46,10 +49,10 @@ public String uploadMedia(String presignedUrl, String accessToken) throws Twitte appendMedia(mediaId, imageBytes, accessToken); // FINALIZE 요청 - String finalizeResponse = finalizeUpload(mediaId, accessToken); - return extractMediaId(finalizeResponse); + return finalizeUpload(mediaId, accessToken); } + /** * INIT 요청 (미디어 업로드 세션 생성) */ @@ -59,7 +62,7 @@ private String initUpload(int totalBytes, String accessToken) throws TwitterExce body.add("total_bytes", String.valueOf(totalBytes)); body.add("media_type", "image/jpeg"); - return twitterRestClient.postMediaRequest(body, accessToken, TWITTER_MEDIA_UPLOAD_URL); + return twitterRestClient.uploadMedia(body, accessToken); } /** @@ -69,7 +72,7 @@ private void appendMedia(String mediaId, byte[] imageBytes, String accessToken) MultipartBodyBuilder builder = new MultipartBodyBuilder(); builder.part("command", "APPEND"); builder.part("media_id", mediaId); - builder.part("segment_index", "0"); + builder.part("segment_index", "0"); // chunk upload시 index builder.part("media", new ByteArrayResource(imageBytes) { @Override public String getFilename() { @@ -78,11 +81,9 @@ public String getFilename() { }).contentType(MediaType.IMAGE_JPEG); MultiValueMap body = new LinkedMultiValueMap<>(); - builder.build().forEach( - (key, value) -> body.put(key, new ArrayList<>(value)) - ); + builder.build().forEach((key, value) -> body.put(key, new ArrayList<>(value))); - twitterRestClient.postMediaRequest(body, accessToken, TWITTER_MEDIA_UPLOAD_URL); + twitterRestClient.uploadMedia(body, accessToken); } /** @@ -93,13 +94,13 @@ private String finalizeUpload(String mediaId, String accessToken) throws Twitter body.add("command", "FINALIZE"); body.add("media_id", mediaId); - return twitterRestClient.postMediaRequest(body, accessToken, TWITTER_MEDIA_UPLOAD_URL); + return twitterRestClient.uploadMedia(body, accessToken); } /** * 응답 JSON에서 media_id 추출 */ - private String extractMediaId(String responseBody) { + private String extractMediaId(String responseBody) throws TwitterException { try { JsonNode jsonNode = objectMapper.readTree(responseBody); JsonNode dataNode = jsonNode.get("data"); @@ -109,8 +110,8 @@ private String extractMediaId(String responseBody) { } return null; } catch (Exception e) { - log.error("Twitter 응답 JSON 파싱 실패", e); - throw new RuntimeException("Twitter 응답 JSON 파싱 실패", e); + log.error("Twitter Media 업로드 응답 JSON 파싱 실패", e); + throw new TwitterException("Twitter Media 업로드 응답 JSON 파싱 실패", e); } } } diff --git a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterGetMeService.java b/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterUserService.java similarity index 69% rename from clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterGetMeService.java rename to clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterUserService.java index 055e11ee..76f7ce22 100644 --- a/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterGetMeService.java +++ b/clients/sns-client/src/main/java/org/snsclient/twitter/service/TwitterUserService.java @@ -11,18 +11,18 @@ @Slf4j @Service @RequiredArgsConstructor -public class TwitterGetMeService { +public class TwitterUserService { private final TwitterRestClient twitterRestClient; - private final String TWITTER_GET_ME_URL = "https://api.x.com/2/users/me"; - /** * X로 부터 유저 본인의 정보 받아오기 - * @param accessToken * @return 트위터 유저 기본 정보 + * https://docs.x.com/x-api/users/user-lookup-me */ public TwitterUserInfoDto getUserInfo(String accessToken) throws TwitterException { - return twitterRestClient.getUserGetMeRequest("description,profile_image_url,subscription_type", accessToken, TWITTER_GET_ME_URL); + return twitterRestClient.getUserGetMeRequest( + "description,profile_image_url,subscription_type" + , accessToken); } } diff --git a/clients/sns-client/src/main/java/org/snsclient/util/TwitterOauthUtil.java b/clients/sns-client/src/main/java/org/snsclient/util/TwitterOauthUtil.java new file mode 100644 index 00000000..2ee21d97 --- /dev/null +++ b/clients/sns-client/src/main/java/org/snsclient/util/TwitterOauthUtil.java @@ -0,0 +1,32 @@ +package org.snsclient.util; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class TwitterOauthUtil { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + // JSON 데이터를 Base64 형식으로 인코딩 + public static String encodeStateToBase64(String userId, String clientId) { + try { + String jsonState = objectMapper.writeValueAsString(Map.of("userId", userId, "clientId", clientId)); + return Base64.getUrlEncoder().encodeToString(jsonState.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new RuntimeException("Twitter Oauth encode 실패", e); + } + } + + // Base64 형식을 디코딩 + public static Map decodeStateFromBase64(String base64State) { + try { + String jsonState = new String(Base64.getUrlDecoder().decode(base64State), StandardCharsets.UTF_8); + return objectMapper.readValue(jsonState, Map.class); + } catch (Exception e) { + throw new RuntimeException("Twitter Oauth decode 실패", e); + } + } +} \ No newline at end of file diff --git a/clients/sns-client/src/test/java/org/snsclient/client/ImageDownloadClientTest.java b/clients/sns-client/src/test/java/org/snsclient/client/ImageDownloadClientTest.java new file mode 100644 index 00000000..806c8c9e --- /dev/null +++ b/clients/sns-client/src/test/java/org/snsclient/client/ImageDownloadClientTest.java @@ -0,0 +1,12 @@ +package org.snsclient.client; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ImageDownloadClientTest { + + @Test + void downloadImage() { + } +} \ No newline at end of file diff --git a/domain/domain-module/src/main/java/org/domainmodule/common/entity/BaseAuditEntity.java b/domain/domain-module/src/main/java/org/domainmodule/common/entity/BaseAuditEntity.java index dae83bb0..2fd4e1e4 100644 --- a/domain/domain-module/src/main/java/org/domainmodule/common/entity/BaseAuditEntity.java +++ b/domain/domain-module/src/main/java/org/domainmodule/common/entity/BaseAuditEntity.java @@ -7,6 +7,7 @@ import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; import jakarta.persistence.Transient; import lombok.Getter; @@ -22,6 +23,11 @@ public class BaseAuditEntity extends BaseTimeEntity { @Transient private boolean preventUpdatedAt = false; + @PrePersist + public void prePersist() { + this.updatedAt = LocalDateTime.now(); // 초기 저장 시 updatedAt 설정 + } + @PreUpdate public void setPreventUpdatedAt() { // 업데이트가 가능하면 현재시간으로 설정 diff --git a/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/PostStatusType.java b/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/PostStatusType.java index e9fabd10..0ad87d44 100644 --- a/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/PostStatusType.java +++ b/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/PostStatusType.java @@ -1,5 +1,17 @@ package org.domainmodule.post.entity.type; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor public enum PostStatusType { - GENERATED, EDITING, READY_TO_UPLOAD, UPLOAD_RESERVED, UPLOADED, UPLOAD_FAILED + GENERATED("생성 됨"), + EDITING("수정 중"), + READY_TO_UPLOAD("업로드 준비 완료"), + UPLOAD_RESERVED("업로드 대기"), + UPLOAD_CONFIRMED("업로드 확정"), + UPLOADED("업로드 완료"), + UPLOAD_FAILED("업로드 실패"); + private final String value; } diff --git a/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/UploadTimeType.java b/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/UploadTimeType.java new file mode 100644 index 00000000..0301693e --- /dev/null +++ b/domain/domain-module/src/main/java/org/domainmodule/post/entity/type/UploadTimeType.java @@ -0,0 +1,21 @@ +package org.domainmodule.post.entity.type; + +import java.time.LocalTime; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum UploadTimeType { + MORNING("오전", LocalTime.of(9, 0), LocalTime.of(11, 0)), + LUNCH("점심", LocalTime.of(11, 0), LocalTime.of(13, 0)), + AFTERNOON("오후", LocalTime.of(13, 0), LocalTime.of(17, 0)), + EVENING("저녁", LocalTime.of(17, 0), LocalTime.of(21, 0)), + NIGHT("밤", LocalTime.of(21, 0), LocalTime.of(1, 0)), + RANDOM("전체 선택", null, null); // 무작위 업로드 + + private final String label; + private final LocalTime startTime; + private final LocalTime endTime; +} diff --git a/domain/domain-module/src/main/java/org/domainmodule/post/repository/PostRepository.java b/domain/domain-module/src/main/java/org/domainmodule/post/repository/PostRepository.java index db532b52..1fb5cd16 100644 --- a/domain/domain-module/src/main/java/org/domainmodule/post/repository/PostRepository.java +++ b/domain/domain-module/src/main/java/org/domainmodule/post/repository/PostRepository.java @@ -84,17 +84,49 @@ List findPostsWithSnsTokenByTimeRange(LocalDateTime startTime, LocalDateTi """) Optional findLastGeneratedPost(PostGroup postGroup, PostStatusType status); - // userId, Agent, PostGroup, 특정 status를 가진 모든 Post조회 + // Agent 게시글 중, 특정 status를 가진 모든 Post를 uploadTime 오름차순으로 조회 @Query(""" SELECT p FROM Post p - JOIN FETCH p.postGroup pg - JOIN FETCH pg.agent a + JOIN p.postGroup pg + JOIN pg.agent a WHERE a.user.id = :userId AND a.id = :agentId AND p.status = :status ORDER BY p.uploadTime """) - List findAllReservedPostsByUserAndAgent(@Param("userId") Long userId, + List findAllReservedPostsByUserAndAgent( + @Param("userId") Long userId, @Param("agentId") Long agentId, @Param("status") PostStatusType status); + + // user, agent, postgroup, status를 가진 post를 displayOrder 오름차순 순서로 조회 + @Query(""" + SELECT p FROM Post p + JOIN p.postGroup pg + JOIN pg.agent a + WHERE a.user.id = :userId + AND a.id = :agentId + AND p.status = :status + AND pg.id = :postGroupId + ORDER BY p.displayOrder ASC + """) + List findPostsByUserAndAgentAndStatus( + @Param("userId") Long userId, + @Param("agentId") Long agentId, + @Param("postGroupId") Long postGroupId, + @Param("status") PostStatusType status); + + @Query(""" + SELECT p FROM Post p + JOIN p.postGroup pg + JOIN pg.agent a + WHERE a.user.id = :userId + AND a.id = :agentId + AND p.status IN :statusList + ORDER BY p.uploadTime + """) + List findAllReservedPostsByUserAndAgentAndStatus( + @Param("userId") Long userId, + @Param("agentId") Long agentId, + @Param("statusList") List statusList); } diff --git a/domain/domain-module/src/main/java/org/domainmodule/postgroup/entity/type/PostGroupLengthType.java b/domain/domain-module/src/main/java/org/domainmodule/postgroup/entity/type/PostGroupLengthType.java index 53eb711a..bc1da4ee 100644 --- a/domain/domain-module/src/main/java/org/domainmodule/postgroup/entity/type/PostGroupLengthType.java +++ b/domain/domain-module/src/main/java/org/domainmodule/postgroup/entity/type/PostGroupLengthType.java @@ -6,9 +6,9 @@ @Getter @AllArgsConstructor public enum PostGroupLengthType { - SHORT(200, "짧은 게시물"), - MEDIUM(400, "보통 게시물"), - LONG(600, "긴 게시물"); + SHORT(140, "짧은 게시물"), + MEDIUM(300, "보통 게시물"), + LONG(1000, "긴 게시물"); private final int maxLength; private final String description;