From 109e9cb0f7daf2295e9e62c7296853cdad0f06b4 Mon Sep 17 00:00:00 2001 From: BaeSuYeon <71921747+ibaesuyeon@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:19:25 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[Docs]=20=EC=9D=B4=EC=8A=88=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...1-\355\205\234\355\224\214\353\246\277.md" | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 ".github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\203\235\354\204\261-\355\205\234\355\224\214\353\246\277.md" diff --git "a/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\203\235\354\204\261-\355\205\234\355\224\214\353\246\277.md" "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\203\235\354\204\261-\355\205\234\355\224\214\353\246\277.md" new file mode 100644 index 0000000..6c24de7 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\203\235\354\204\261-\355\205\234\355\224\214\353\246\277.md" @@ -0,0 +1,26 @@ +--- +name: 이슈 생성 템플릿 +about: 해당 이슈 템플릿을 사용해 이슈를 생성해주세요. +title: '' +labels: '' +assignees: ibaesuyeon + +--- + +## 📄 이슈 내용 + + + +## 📑 상세 내용 + + + +## ✔ 체크리스트 + +- [] +- [] + + +## 📍 Reference + +- From 51725bf3c902551e7aecf59e239011a08ebedddf Mon Sep 17 00:00:00 2001 From: ibaesuyeon Date: Sun, 31 Mar 2024 18:18:10 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[Feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주석 해제 및 환경설정 --- build.gradle | 2 +- .../user/controller/UserFeignClient.java | 2 +- .../domain/user/service/UserServiceImpl.java | 38 ++--- .../global/config/JasyptConfig.java | 66 ++++----- .../config/jwtInterceptor/JwtInterceptor.java | 132 +++++++++--------- .../config/jwtInterceptor/WebMvcConfig.java | 44 +++--- 6 files changed, 142 insertions(+), 142 deletions(-) diff --git a/build.gradle b/build.gradle index ef66dfd..3fe283f 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'io.leopard.boot:leopard-boot-requestmapping:0.9.20' -// implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/mju/management/domain/user/controller/UserFeignClient.java b/src/main/java/com/mju/management/domain/user/controller/UserFeignClient.java index 1d133d3..bd90472 100644 --- a/src/main/java/com/mju/management/domain/user/controller/UserFeignClient.java +++ b/src/main/java/com/mju/management/domain/user/controller/UserFeignClient.java @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -//@FeignClient(name = "user-service", url = "${user-service.url}", configuration = FeignConfig.class) +@FeignClient(name = "user-service", url = "${user-service.url}", configuration = FeignConfig.class) public interface UserFeignClient { @GetMapping("/response_userById/{userId}") ResponseEntity getUser(@PathVariable("userId") Long userId); diff --git a/src/main/java/com/mju/management/domain/user/service/UserServiceImpl.java b/src/main/java/com/mju/management/domain/user/service/UserServiceImpl.java index 79913ff..1e78f24 100644 --- a/src/main/java/com/mju/management/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/mju/management/domain/user/service/UserServiceImpl.java @@ -28,29 +28,29 @@ @Slf4j public class UserServiceImpl { -// private final UserFeignClient userFeignClient; + private final UserFeignClient userFeignClient; public GetUserResponseDto getUser(Long userId){ -// try {return userFeignClient.getUser(userId).getBody();} -// catch (Exception e){return null;} - return null; + try {return userFeignClient.getUser(userId).getBody();} + catch (Exception e){return null;} +// return null; } public String getUsername(Long userId){ -// GetUserResponseDto getUserResponseDto = null; -// try{ -// getUserResponseDto = userFeignClient.getUser(userId).getBody(); -// }catch (FeignException.InternalServerError e){ -// e.printStackTrace(); -// return "(내부 서버 오류)"; -// }catch (RetryableException e){ -// e.printStackTrace(); -// return "(응답 시간 초과)"; -// }catch (Exception e){ -// e.printStackTrace(); -// return "(알 수 없음)"; -// } -// return getUserResponseDto.getName(); - return null; + GetUserResponseDto getUserResponseDto = null; + try{ + getUserResponseDto = userFeignClient.getUser(userId).getBody(); + }catch (FeignException.InternalServerError e){ + e.printStackTrace(); + return "(내부 서버 오류)"; + }catch (RetryableException e){ + e.printStackTrace(); + return "(응답 시간 초과)"; + }catch (Exception e){ + e.printStackTrace(); + return "(알 수 없음)"; + } + return getUserResponseDto.getName(); +// return null; } } diff --git a/src/main/java/com/mju/management/global/config/JasyptConfig.java b/src/main/java/com/mju/management/global/config/JasyptConfig.java index 8c490c4..ce677a1 100644 --- a/src/main/java/com/mju/management/global/config/JasyptConfig.java +++ b/src/main/java/com/mju/management/global/config/JasyptConfig.java @@ -1,33 +1,33 @@ -//package com.mju.management.global.config; -// -//import org.jasypt.encryption.StringEncryptor; -//import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; -//import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -// -//@Configuration -//public class JasyptConfig { -// -// @Value("${jasypt.encryptor.password}") -// private String encryptKey; -// -// @Bean("jasyptStringEncryptor") -// public StringEncryptor stringEncryptor() { -// -// PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); -// SimpleStringPBEConfig config = new SimpleStringPBEConfig(); -// -// config.setPassword(encryptKey); // encrypt key -// config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘 -// config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수 -// config.setPoolSize("1"); // 인스턴스 pool -// config.setProviderName("SunJCE"); -// config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스 -// config.setStringOutputType("base64"); //인코딩 방식 -// encryptor.setConfig(config); -// return encryptor; -// } -// -//} \ No newline at end of file +package com.mju.management.global.config; + +import org.jasypt.encryption.StringEncryptor; +import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; +import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JasyptConfig { + + @Value("${jasypt.encryptor.password}") + private String encryptKey; + + @Bean("jasyptStringEncryptor") + public StringEncryptor stringEncryptor() { + + PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); + SimpleStringPBEConfig config = new SimpleStringPBEConfig(); + + config.setPassword(encryptKey); // encrypt key + config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘 + config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수 + config.setPoolSize("1"); // 인스턴스 pool + config.setProviderName("SunJCE"); + config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스 + config.setStringOutputType("base64"); //인코딩 방식 + encryptor.setConfig(config); + return encryptor; + } + +} \ No newline at end of file diff --git a/src/main/java/com/mju/management/global/config/jwtInterceptor/JwtInterceptor.java b/src/main/java/com/mju/management/global/config/jwtInterceptor/JwtInterceptor.java index 45d4108..70a7c5b 100644 --- a/src/main/java/com/mju/management/global/config/jwtInterceptor/JwtInterceptor.java +++ b/src/main/java/com/mju/management/global/config/jwtInterceptor/JwtInterceptor.java @@ -1,66 +1,66 @@ -//package com.mju.management.global.config.jwtInterceptor; -// -//import com.mju.management.global.model.Exception.ExceptionList; -//import com.mju.management.global.model.Exception.NullJwtTokenException; -//import io.jsonwebtoken.*; -//import jakarta.annotation.PostConstruct; -//import jakarta.servlet.http.HttpServletRequest; -//import jakarta.servlet.http.HttpServletResponse; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.stereotype.Component; -//import org.springframework.util.StringUtils; -//import org.springframework.web.context.request.WebRequestInterceptor; -//import org.springframework.web.cors.CorsUtils; -//import org.springframework.web.servlet.HandlerInterceptor; -//import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; -// -//import java.util.Base64; -// -//@Component -//public class JwtInterceptor implements HandlerInterceptor { -// -// @Value("${jwt.secret}") -// private String jwtSecret; -// -// @PostConstruct -// protected void init() { -// jwtSecret = Base64.getEncoder().encodeToString(jwtSecret.getBytes()); -// } -// -// @Override -// public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { -// if(CorsUtils.isPreFlightRequest(request)) return true; -// -// String jwtToken = request.getHeader("Authorization"); -// -// if(!StringUtils.hasText(jwtToken)) throw new NullJwtTokenException(ExceptionList.NULL_JWT_TOKEN); -// if(!jwtToken.startsWith("Bearer ")) throw new UnsupportedJwtException(""); -// -// String token = jwtToken.substring(7); -// -// Claims claims = Jwts.parser() -// .setSigningKey(jwtSecret) -// .parseClaimsJws(token) -// .getBody(); -// -// Long userId = claims.get("userId", Long.class); -// String username = claims.get("username", String.class); -// String email = claims.getSubject(); -// if(userId==null||!StringUtils.hasText(username)||!StringUtils.hasText(email)) -// throw new MalformedJwtException(""); -// -// -// JwtContextHolder.setUserId(claims.get("userId", Long.class)); -// JwtContextHolder.setUsername(claims.get("username", String.class)); -// JwtContextHolder.setEmail(claims.getSubject()); -// return true; -// } -// -// @Override -// public void afterCompletion(HttpServletRequest request, -// HttpServletResponse response, -// Object handler, -// Exception ex) throws Exception { -// JwtContextHolder.clear(); -// } -//} +package com.mju.management.global.config.jwtInterceptor; + +import com.mju.management.global.model.Exception.ExceptionList; +import com.mju.management.global.model.Exception.NullJwtTokenException; +import io.jsonwebtoken.*; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.WebRequestInterceptor; +import org.springframework.web.cors.CorsUtils; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; + +import java.util.Base64; + +@Component +public class JwtInterceptor implements HandlerInterceptor { + + @Value("${jwt.secret}") + private String jwtSecret; + + @PostConstruct + protected void init() { + jwtSecret = Base64.getEncoder().encodeToString(jwtSecret.getBytes()); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if(CorsUtils.isPreFlightRequest(request)) return true; + + String jwtToken = request.getHeader("Authorization"); + + if(!StringUtils.hasText(jwtToken)) throw new NullJwtTokenException(ExceptionList.NULL_JWT_TOKEN); + if(!jwtToken.startsWith("Bearer ")) throw new UnsupportedJwtException(""); + + String token = jwtToken.substring(7); + + Claims claims = Jwts.parser() + .setSigningKey(jwtSecret) + .parseClaimsJws(token) + .getBody(); + + Long userId = claims.get("userId", Long.class); + String username = claims.get("username", String.class); + String email = claims.getSubject(); + if(userId==null||!StringUtils.hasText(username)||!StringUtils.hasText(email)) + throw new MalformedJwtException(""); + + + JwtContextHolder.setUserId(claims.get("userId", Long.class)); + JwtContextHolder.setUsername(claims.get("username", String.class)); + JwtContextHolder.setEmail(claims.getSubject()); + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) throws Exception { + JwtContextHolder.clear(); + } +} diff --git a/src/main/java/com/mju/management/global/config/jwtInterceptor/WebMvcConfig.java b/src/main/java/com/mju/management/global/config/jwtInterceptor/WebMvcConfig.java index 25792f9..bbfce81 100644 --- a/src/main/java/com/mju/management/global/config/jwtInterceptor/WebMvcConfig.java +++ b/src/main/java/com/mju/management/global/config/jwtInterceptor/WebMvcConfig.java @@ -1,22 +1,22 @@ -//package com.mju.management.global.config.jwtInterceptor; -// -//import lombok.RequiredArgsConstructor; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.core.env.Environment; -//import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -// -//@Configuration -//@RequiredArgsConstructor -//public class WebMvcConfig implements WebMvcConfigurer { -// private final JwtInterceptor jwtInterceptor; -// private final Environment environment; -// @Override -// public void addInterceptors(InterceptorRegistry registry) { -// String[] activeProfiles = environment.getActiveProfiles(); -// if(activeProfiles != null && activeProfiles.length > 0 && !activeProfiles[0].equals("test")) -// registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**"); -// } -//} \ No newline at end of file +package com.mju.management.global.config.jwtInterceptor; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebMvcConfig implements WebMvcConfigurer { + private final JwtInterceptor jwtInterceptor; + private final Environment environment; + @Override + public void addInterceptors(InterceptorRegistry registry) { + String[] activeProfiles = environment.getActiveProfiles(); + if(activeProfiles != null && activeProfiles.length > 0 && !activeProfiles[0].equals("test")) + registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**"); + } +} \ No newline at end of file From ae4f861c5e7b29caa5c48cc919f832c0d2092b86 Mon Sep 17 00:00:00 2001 From: ibaesuyeon Date: Sun, 7 Apr 2024 10:41:24 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[Fix]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 해당 게시글의 댓글을 삭제하는 코드가 비어있어 구현 --- .../comment/infrastructure/CommentJpaRepository.java | 3 ++- .../comment/infrastructure/CommentRepositoryImpl.java | 8 ++++---- .../management/domain/post/service/PostServiceImpl.java | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/mju/management/domain/comment/infrastructure/CommentJpaRepository.java b/src/main/java/com/mju/management/domain/comment/infrastructure/CommentJpaRepository.java index 72c75c5..1887eeb 100644 --- a/src/main/java/com/mju/management/domain/comment/infrastructure/CommentJpaRepository.java +++ b/src/main/java/com/mju/management/domain/comment/infrastructure/CommentJpaRepository.java @@ -1,10 +1,11 @@ package com.mju.management.domain.comment.infrastructure; +import com.mju.management.domain.comment.domain.Comment; import com.mju.management.domain.post.domain.Post; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface CommentJpaRepository extends JpaRepository { - List findByPost(Post post); + List findByPost(Post post); } diff --git a/src/main/java/com/mju/management/domain/comment/infrastructure/CommentRepositoryImpl.java b/src/main/java/com/mju/management/domain/comment/infrastructure/CommentRepositoryImpl.java index 9e5b66d..f5b21ac 100644 --- a/src/main/java/com/mju/management/domain/comment/infrastructure/CommentRepositoryImpl.java +++ b/src/main/java/com/mju/management/domain/comment/infrastructure/CommentRepositoryImpl.java @@ -32,10 +32,10 @@ public void delete(Comment comment) { @Override public void deleteAll(Post post) { -// List commentList = commentJpaRepository.findByPost(post); -// for(CommentEntity comment : commentList){ -// -// } + List commentList = commentJpaRepository.findByPost(post); + for(Comment comment : commentList){ + this.delete(comment); + } } diff --git a/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java b/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java index 67e40de..2716dbd 100644 --- a/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java +++ b/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java @@ -34,8 +34,8 @@ public class PostServiceImpl { private final PostRepository postRepository; private final ProjectRepository projectRepository; private final UserServiceImpl userService; - private final ResponseService responseService; + private final CommentRepository commentRepository; public CommonResult createPost(CreatePostRequestServiceDto dto) { Optional optionalProject = projectRepository.findById(dto.projectId()); @@ -128,7 +128,6 @@ public CommonResult deletePost(DeletePostRequestServiceDto dto) { return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 삭제에 성공하였습니다."); } - private static CommentRepository commentRepository; private void checkMemberAuthorization(Project project, Long userId){ if(!project.isLeaderOrMember(userId)) From 4976138c72d5a21b6f19442e8f47157e99e05754 Mon Sep 17 00:00:00 2001 From: ibaesuyeon Date: Sun, 7 Apr 2024 19:22:27 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[Feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=EA=B0=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 게시글 수정시간 값 추가 및 반환 --- .../post/controller/response/PostDetailResponse.java | 9 +++++++++ .../domain/post/controller/response/PostResponse.java | 9 +++++++++ .../java/com/mju/management/domain/post/domain/Post.java | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/src/main/java/com/mju/management/domain/post/controller/response/PostDetailResponse.java b/src/main/java/com/mju/management/domain/post/controller/response/PostDetailResponse.java index 9cd6891..9f498b3 100644 --- a/src/main/java/com/mju/management/domain/post/controller/response/PostDetailResponse.java +++ b/src/main/java/com/mju/management/domain/post/controller/response/PostDetailResponse.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Getter @@ -40,6 +41,12 @@ public class PostDetailResponse { @Schema(description = "카테고리") Category category; + @Schema(description = "작성일") + private LocalDateTime createdAt; + + @Schema(description = "수정일") + private LocalDateTime updatedAt; + public static PostDetailResponse from(Post post, String userName){ return PostDetailResponse.builder() @@ -51,6 +58,8 @@ public static PostDetailResponse from(Post post, String userName){ .startDate(post.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH:mm:ss"))) .commentSum(post.getCommentList().size()) .category(post.getCategory()) + .createdAt(post.getCreatedAt()) + .updatedAt(post.getUpdatedAt()) .build(); } } diff --git a/src/main/java/com/mju/management/domain/post/controller/response/PostResponse.java b/src/main/java/com/mju/management/domain/post/controller/response/PostResponse.java index 121bcb9..da37cf6 100644 --- a/src/main/java/com/mju/management/domain/post/controller/response/PostResponse.java +++ b/src/main/java/com/mju/management/domain/post/controller/response/PostResponse.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Getter @@ -33,6 +34,12 @@ public class PostResponse { @Schema(description = "댓글 갯수") private long commentSum; + @Schema(description = "작성일") + private LocalDateTime createdAt; + + @Schema(description = "수정일") + private LocalDateTime updatedAt; + public static PostResponse from(Post post, String userName){ return PostResponse.builder() @@ -42,6 +49,8 @@ public static PostResponse from(Post post, String userName){ .userName(userName) .startDate(post.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH:mm:ss"))) .commentSum(post.getCommentList().size()) + .createdAt(post.getCreatedAt()) + .updatedAt(post.getUpdatedAt()) .build(); } } diff --git a/src/main/java/com/mju/management/domain/post/domain/Post.java b/src/main/java/com/mju/management/domain/post/domain/Post.java index bfa13a9..bef8993 100644 --- a/src/main/java/com/mju/management/domain/post/domain/Post.java +++ b/src/main/java/com/mju/management/domain/post/domain/Post.java @@ -9,6 +9,7 @@ import jakarta.persistence.*; import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -46,6 +47,10 @@ public class Post { private Long writerId; + @LastModifiedDate + @Column(nullable = true, updatable = true) + private LocalDateTime updatedAt; + // TODO: 프로젝트의 팀원일때만, 게시글을 작성할 수 있도록 확인 @ManyToOne @JoinColumn(name = "project_index") From add127c919b5695756ada217867b5bc3a896cc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=8A=B9=EC=9A=B0?= Date: Fri, 12 Apr 2024 03:50:19 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[CHORE]:=20gitignore=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - .../global/config/JasyptConfig.java | 66 +++++++++---------- src/main/resources/application-local.yml | 52 --------------- src/main/resources/application-prod.yml | 37 ----------- src/main/resources/application-test.yml | 33 ---------- src/main/resources/application.yml | 22 +++++++ 6 files changed, 55 insertions(+), 156 deletions(-) delete mode 100644 src/main/resources/application-local.yml delete mode 100644 src/main/resources/application-prod.yml delete mode 100644 src/main/resources/application-test.yml create mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore index e18130a..635313f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ out/ .vscode/ ### application.yml ### -application.yml application-local.yml application-prod.yml application-test.yml diff --git a/src/main/java/com/mju/management/global/config/JasyptConfig.java b/src/main/java/com/mju/management/global/config/JasyptConfig.java index ce677a1..8c490c4 100644 --- a/src/main/java/com/mju/management/global/config/JasyptConfig.java +++ b/src/main/java/com/mju/management/global/config/JasyptConfig.java @@ -1,33 +1,33 @@ -package com.mju.management.global.config; - -import org.jasypt.encryption.StringEncryptor; -import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; -import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class JasyptConfig { - - @Value("${jasypt.encryptor.password}") - private String encryptKey; - - @Bean("jasyptStringEncryptor") - public StringEncryptor stringEncryptor() { - - PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); - SimpleStringPBEConfig config = new SimpleStringPBEConfig(); - - config.setPassword(encryptKey); // encrypt key - config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘 - config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수 - config.setPoolSize("1"); // 인스턴스 pool - config.setProviderName("SunJCE"); - config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스 - config.setStringOutputType("base64"); //인코딩 방식 - encryptor.setConfig(config); - return encryptor; - } - -} \ No newline at end of file +//package com.mju.management.global.config; +// +//import org.jasypt.encryption.StringEncryptor; +//import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; +//import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +//@Configuration +//public class JasyptConfig { +// +// @Value("${jasypt.encryptor.password}") +// private String encryptKey; +// +// @Bean("jasyptStringEncryptor") +// public StringEncryptor stringEncryptor() { +// +// PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); +// SimpleStringPBEConfig config = new SimpleStringPBEConfig(); +// +// config.setPassword(encryptKey); // encrypt key +// config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘 +// config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수 +// config.setPoolSize("1"); // 인스턴스 pool +// config.setProviderName("SunJCE"); +// config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스 +// config.setStringOutputType("base64"); //인코딩 방식 +// encryptor.setConfig(config); +// return encryptor; +// } +// +//} \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml deleted file mode 100644 index 4590483..0000000 --- a/src/main/resources/application-local.yml +++ /dev/null @@ -1,52 +0,0 @@ -#eureka: -# client: -# register-with-eureka: true -# fetch-registry: true -# service-url: -# defaultZone: http://localhost:8761/eureka - -spring: - config: - activate: - on-profile: local - application: - name: api - -# datasource: -# url: jdbc:mysql://localhost:33061/iceAmericano -# driver-class-name: com.mysql.cj.jdbc.Driver -# username: root -# password: iceAmericano - datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:tcp://localhost/~/manageServ;NON_KEYWORDS=USER - username: sa - password: - -# jpa: -# hibernate: -# ddl-auto: create -# show-sql: true -# properties: -# hibernate: -# format_sql: true -# dialect: org.hibernate.dialect.MySQLDialect - - #JPA 설정 - jpa: - database-platform: - hibernate: - ddl-auto: create # create or validate - - #배포 할 때는 open-in-view false 설정 - open-in-view: false - properties: - hibernate: - show_sql: true - format_sql: true - -#jwt: -# secret: ENC(acbDZy8Gz9X1OSL3fjOHVg==) - -#user-service: -# url: http://localhost:8081/user-service diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml deleted file mode 100644 index f7c7f4b..0000000 --- a/src/main/resources/application-prod.yml +++ /dev/null @@ -1,37 +0,0 @@ -spring: - config: - activate: - on-profile: prod - application: - name: Management-Service - - datasource: - url: ENC(vK3ggYCmRnJlODS6cbBXJAi+7Mw7qpO3mKVnWz9PV+Mi+aQrVJBY++5ecNXwrnQxgbCgoivoVVDJjDe554GHnyoj8f2rUbkyNbgN/QmNAZa+Cc+Gp1x7huLrQD7BCeLC) - driver-class-name: com.mysql.cj.jdbc.Driver - username: ENC(Oywg8yRSKPs0TXgE6rbNaA==) - password: ENC(R6oW03mOpylO0Fj2BJzvOeMuCiLGT568) - - jpa: - hibernate: - ddl-auto: update - show-sql: true - properties: - hibernate: - format_sql: true - dialect: org.hibernate.dialect.MySQLDialect - -eureka: - instance: - hostname : 3.35.179.234 - instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}} - client: - register-with-eureka: true - fetch-registry: true - service-url: - defaultZone: http://13.125.181.139:8761/eureka - -jwt: - secret: ENC(acbDZy8Gz9X1OSL3fjOHVg==) - -user-service: - url: http://52.78.155.253:8081/user-service diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml deleted file mode 100644 index bbb42b4..0000000 --- a/src/main/resources/application-test.yml +++ /dev/null @@ -1,33 +0,0 @@ -spring: - config: - activate: - on-profile: test - application: - name: api - - datasource: - driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:mysql:8://testDB - username: root - password: 1234 - - jpa: - hibernate: - ddl-auto: create-drop - show-sql: true - properties: - hibernate: - format_sql: true - dialect: org.hibernate.dialect.MySQLDialect - -eureka: - client: - register-with-eureka: false - fetch-registry: false - -jwt: - secret: ENC(acbDZy8Gz9X1OSL3fjOHVg==) - -user-service: - url: http://localhost:8081/user-service - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..270dad8 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,22 @@ +spring: + profiles: + active: local + +--- + +# Swagger +springdoc: + default-consumes-media-type: application/json + default-produces-media-type: application/json + api-docs: + groups: + enabled: true + swagger-ui: + operations-sorter: method # alpha(알파벳 오름차순), method(HTTP메소드순) + tags-sorter: alpha # 태그 정렬 기준 + path: /swagger # html 문서 접속 경로 + disable-swagger-default-url: true + display-query-params-without-oauth2: true + doc-expansion: none # tag, operation 펼치는 방식 + paths-to-match: + - /** From 4f52d277761f0cdb4e94d49e5c3a8f186d5c8ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=8A=B9=EC=9A=B0?= Date: Sat, 13 Apr 2024 17:03:29 +0900 Subject: [PATCH 06/12] [FEAT]: CICD Test --- .github/workflows/deploy.yml | 77 ++++++++++++++++++++++++++++ .github/workflows/gradle-cd.yml | 77 ---------------------------- .github/workflows/gradle-ci.yml | 82 ------------------------------ Dockerfile | 4 -- appspec.yml | 23 +++++++++ docker-compose.yml | 16 ------ scripts/start.sh | 21 ++++++++ scripts/stop.sh | 19 +++++++ src/main/resources/application.yml | 2 +- 9 files changed, 141 insertions(+), 180 deletions(-) create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/gradle-cd.yml delete mode 100644 .github/workflows/gradle-ci.yml delete mode 100644 Dockerfile create mode 100644 appspec.yml delete mode 100644 docker-compose.yml create mode 100644 scripts/start.sh create mode 100644 scripts/stop.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..91c72cb --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,77 @@ +name: CI and Deploy to Amazon EC2 + +on: + # Triggers the workflow on push events but only for the "main" branch + push: + branches: [ "main", "develop" ] + +# 본인이 설정한 값을 여기서 채워넣습니다. +# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름 +env: + AWS_REGION: ap-northeast-2 + S3_BUCKET_NAME: github-actions-s3-management + CODE_DEPLOY_APPLICATION_NAME: codedeploy-management-app + CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: codedeploy-deployment-group + +permissions: + contents: read + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + environment: production + + steps: + # (1) 기본 체크아웃 + - name: Checkout + uses: actions/checkout@v3 + + # (2) JDK 17 세팅 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + ## create application-rds.yml && application-s3.yml && application-auth.yml && forbidden-words.txt + - name: create application.properties file + run: | + touch ./src/main/resources/application-prod.yml + echo "${{ secrets.DATABASE_YML }}" | base64 --decode > src/main/resources/application-prod.yml + + #추가 + - name: Make Gradle Wrapper script executable + run: chmod +x /home/runner/work/STUDIO-EYE-MANAGEMENT-SERVICE/STUDIO-EYE-MANAGEMENT-SERVICE/gradlew + + # (3) Gradle build (Test 제외) + - name: Build with Gradle + uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee + with: + arguments: clean build -x test + + # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용) + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + # (5) 빌드 결과물을 S3 버킷에 업로드 + - name: Upload to AWS S3 + run: | + aws deploy push \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --ignore-hidden-files \ + --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \ + --source . + + # (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행 + - name: Deploy to AWS EC2 from S3 + run: | + aws deploy create-deployment \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \ + --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip \ No newline at end of file diff --git a/.github/workflows/gradle-cd.yml b/.github/workflows/gradle-cd.yml deleted file mode 100644 index 8e4272b..0000000 --- a/.github/workflows/gradle-cd.yml +++ /dev/null @@ -1,77 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - -# Workflow 이름 -name: Spring Boot & Gradle CD - -on: - push: - branches: [ "main" ] -# pull_request: -# branches: [ "main" ] - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - # JDK 17 설치 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - # github secret 환경변수로 적어둔 APPLICATION_YML으로 application.yml파일을 생성합니다. - # 환경변수가 지나치게 많아짐을 방지하기 위해 APPLICATION_YML 변수를 만들었습니다. - - name: make application.yml - run: | - cd ./src/main/resources - touch ./application.yml - echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml - shell: bash - - # gradlew에 실행 권한을 부여합니다. - - name: Grant execute permisson for gradlew - run: chmod +x gradlew - - # test는 CI 과정에서 수행되므로 여기서는 `-x`로 테스트를 생략했습니다. - # `--stacktrace`로 더 자세한 로그가 출력되게 해줍니다. - - name: Build with Gradle (without Test) - run: ./gradlew clean build -x test --stacktrace - - # docker hub에 로그인하고 이미지를 빌드합니다. 이후에 push를 진행합니다. - # docker_username을 적지 않으면 push 시에 요청이 거부될 수 있습니다. - - name: Docker Hub build & push - run: | - docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} - docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} . - docker images - docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} - - # EC2에 접속하고 배포합니다. - - name: Deploy - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.EC2_HOST }} - username: ${{ secrets.EC2_USERNAME }} - key: ${{ secrets.EC2_KEY }} - port: ${{ secrets.EC2_SSH_PORT }} - - script: | - sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} - sudo docker rm -f $(sudo docker ps -qa) - - sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} - sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest - diff --git a/.github/workflows/gradle-ci.yml b/.github/workflows/gradle-ci.yml deleted file mode 100644 index 25f5e3d..0000000 --- a/.github/workflows/gradle-ci.yml +++ /dev/null @@ -1,82 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - -# Workflow 이름 -name: Spring Boot & Gradle CI - -on: -# push: -# branches: [ "main" ] - pull_request: - branches: [ "main" ] - -permissions: write-all - -jobs: - test: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - # JDK 17 설치 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - # github secret 환경변수로 적어둔 APPLICATION_YML으로 application.yml파일을 생성합니다. - # 환경변수가 지나치게 많아짐을 방지하기 위해 APPLICATION_YML 변수를 만들었습니다. - - name: make application.yml - run: | - cd ./src/main/resources - touch ./application.yml - echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml - shell: bash - - # gradlew에 실행 권한을 부여합니다. - - name: Grant execute permisson for gradlew - run: chmod +x gradlew - - # 테스트를 진행합니다. - - name: Test with Gradle - run: ./gradlew --info test - - # 테스트 후 Result를 보기위해 Publish Unit Test Results step 추가 - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: ${{ always() }} # 테스트가 실패하여도 Report를 보기 위해 `always`로 설정 - with: - files: build/test-results/**/*.xml - - # 테스트 후 코드 커버리지 결과 코멘트 등록 - - name: Add coverage to PR - id: jacoco - uses: madrapps/jacoco-report@v1.6.1 - with: - paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml - token: ${{ secrets.GITHUB_TOKEN }} - title: "Coverage Report" - min-coverage-overall: 50 - min-coverage-changed-files: 50 - - # 테스트 결과 레포트를 깃허브에 업로드 - - name: Upload Test Result Report - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: test-report # 결과물의 이름 - path: build/reports/tests/test # upload하고자 하는 파일 또는 디렉토리 경로 - - # 테스트 커버리지 레포트를 깃허브에 업로드 - - name: Upload Jacoco Report - uses: actions/upload-artifact@v3 - with: - name: jacoco-report # 결과물의 이름 - path: build/reports/jacoco/test/html # upload하고자 하는 파일 또는 디렉토리 경로 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1faad58..0000000 --- a/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM openjdk:17 -ARG JAR_FILE=build/libs/*.jar -COPY ${JAR_FILE} app.jar -ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..17c761f --- /dev/null +++ b/appspec.yml @@ -0,0 +1,23 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/app + overwrite: yes + +permissions: + - object: / + pattern: "**" + owner: ubuntu + group: ubuntu + +hooks: + AfterInstall: + - location: scripts/stop.sh + timeout: 60 + runas: root + ApplicationStart: + - location: scripts/start.sh + timeout: 60 + runas: root \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index dfa982c..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: '3.8' - -services: - # mysql - mysql: - container_name: IA_mysql - image: mysql:8.0 - env_file: - - .env\ - environment: - TZ: Asia/Seoul - ports: - - "33061:3306" - command: - - '--character-set-server=utf8mb4' - - '--collation-server=utf8mb4_unicode_ci' \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..46189ba --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/app/STUDIO-EYE-MANAGEMENT-SERVICE" +JAR_FILE="$PROJECT_ROOT/spring-webapp.jar" + +APP_LOG="$PROJECT_ROOT/application.log" +ERROR_LOG="$PROJECT_ROOT/error.log" +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +# build 파일 복사 +echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG +cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE + +# jar 파일 실행 +echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG +nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG & + +CURRENT_PID=$(pgrep -f $JAR_FILE) +echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG \ No newline at end of file diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100644 index 0000000..38e1e40 --- /dev/null +++ b/scripts/stop.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/app/STUDIO-EYE-MANAGEMENT-SERVICE" +JAR_FILE="$PROJECT_ROOT/spring-webapp.jar" + +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +# 현재 구동 중인 애플리케이션 pid 확인 +CURRENT_PID=$(pgrep -f $JAR_FILE) + +# 프로세스가 켜져 있으면 종료 +if [ -z $CURRENT_PID ]; then + echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG +else + echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG + kill -15 $CURRENT_PID +fi \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 270dad8..7a16e86 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: profiles: - active: local + active: prod --- From b5d205946544c8ac6fd42eef7ee27da6afcbae01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=8A=B9=EC=9A=B0?= Date: Sat, 13 Apr 2024 23:30:59 +0900 Subject: [PATCH 07/12] [FEAT]: SonarQube Test --- .github/workflows/sonarqube.yml | 46 +++++++++++++++++++++++++++++++++ build.gradle | 18 +++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 .github/workflows/sonarqube.yml diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 0000000..5b270dc --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,46 @@ +name: Build + +on: + push: + branches: + - test + + +jobs: + build: + name: Build + runs-on: ubuntu-latest + permissions: read-all + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Cache SonarQube packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache Gradle packages + uses: actions/cache@v1 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + #추가 + - name: Make Gradle Wrapper script executable + run: chmod +x ./gradlew + + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: ./gradlew build -x test sonar --info \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3fe283f..aaba36e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id "org.sonarqube" version "4.4.1.3373" id 'org.springframework.boot' version '3.1.3' id 'io.spring.dependency-management' version '1.1.3' id 'jacoco' @@ -66,6 +67,9 @@ dependencies { implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'javax.xml.bind:jaxb-api:2.3.0' + // SonarQube + implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' + // test container testImplementation "org.testcontainers:junit-jupiter:1.17.2" testImplementation "org.testcontainers:mysql:1.17.2" @@ -74,6 +78,20 @@ dependencies { testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock") } +sonarqube { + properties { + property "sonar.projectKey", "sonar-user" + property "sonar.projectName", "sonar-user" +// property "sonar.sources", "src" // 소스 경로 +// property "sonar.language", "java" // 언어 +// property "sonar.java.source", "17" +// property "sonar.sourceEncoding", "UTF-8" +// property "sonar.profile", "Sonar way" // SonarQube 에서 분석할 때 적용할 프로필(분석할 수준 설정) +// property "sonar.java.binaries", "${buildDir}/classes" // 자바 클래스 파일위치 +// property "sonar.test.inclusions", "**/*Test.java" // 코드 분석에 사용할 테스트 소스 + } +} + dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" From 5f121449c57108d3b6cea5a40384354b793f6833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=8A=B9=EC=9A=B0?= Date: Sat, 13 Apr 2024 23:36:13 +0900 Subject: [PATCH 08/12] [FIX]: project sonarqube --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index aaba36e..60a60b2 100644 --- a/build.gradle +++ b/build.gradle @@ -80,8 +80,8 @@ dependencies { sonarqube { properties { - property "sonar.projectKey", "sonar-user" - property "sonar.projectName", "sonar-user" + property "sonar.projectKey", "sonar-management" + property "sonar.projectName", "sonar-management" // property "sonar.sources", "src" // 소스 경로 // property "sonar.language", "java" // 언어 // property "sonar.java.source", "17" From d42810ed1ac0eb38265bc35e63f0820388e5dd8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=8A=B9=EC=9A=B0?= Date: Sun, 14 Apr 2024 01:07:18 +0900 Subject: [PATCH 09/12] [CHORE]: add develop --- .github/workflows/sonarqube.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 5b270dc..bc0126f 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -3,8 +3,8 @@ name: Build on: push: branches: - - test - + - main + - develop jobs: build: From b4b0079003bb3316e46d1171ca6add7309c93d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=8A=B9=EC=9A=B0?= Date: Sun, 14 Apr 2024 04:30:01 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[CHORE]:=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/start.sh | 2 +- scripts/stop.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/start.sh b/scripts/start.sh index 46189ba..126c2a2 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PROJECT_ROOT="/home/ubuntu/app/STUDIO-EYE-MANAGEMENT-SERVICE" +PROJECT_ROOT="/home/ubuntu/app" JAR_FILE="$PROJECT_ROOT/spring-webapp.jar" APP_LOG="$PROJECT_ROOT/application.log" diff --git a/scripts/stop.sh b/scripts/stop.sh index 38e1e40..755d347 100644 --- a/scripts/stop.sh +++ b/scripts/stop.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PROJECT_ROOT="/home/ubuntu/app/STUDIO-EYE-MANAGEMENT-SERVICE" +PROJECT_ROOT="/home/ubuntu/app" JAR_FILE="$PROJECT_ROOT/spring-webapp.jar" DEPLOY_LOG="$PROJECT_ROOT/deploy.log" From 709315f33e65377563c3b488f41c7261f8bfc263 Mon Sep 17 00:00:00 2001 From: ibaesuyeon Date: Sun, 14 Apr 2024 05:03:05 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[Feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=EC=88=98=EC=A0=95,=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=8B=9C=20=ED=8C=8C=EC=9D=BC=20=EC=A0=80=EC=9E=A5,=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 게시글 생성, 수정 시 S3에 파일 저장, 수정 삭제 시 S3 및 데이터베이스에서 파일 삭제 --- build.gradle | 3 + .../post/controller/PostController.java | 22 +++++-- .../management/domain/post/domain/Post.java | 4 ++ .../domain/post/domain/PostFile.java | 29 ++++++++ .../infrastructure/PostFileRepository.java | 13 ++++ .../request/UpdatePostRequestServiceDto.java | 5 -- .../domain/post/service/PostServiceImpl.java | 65 ++++++++++++++---- .../management/global/config/S3Config.java | 36 ++++++++++ .../global/model/Exception/ExceptionList.java | 1 + .../management/global/service/S3Service.java | 66 +++++++++++++++++++ 10 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/mju/management/domain/post/domain/PostFile.java create mode 100644 src/main/java/com/mju/management/domain/post/infrastructure/PostFileRepository.java create mode 100644 src/main/java/com/mju/management/global/config/S3Config.java create mode 100644 src/main/java/com/mju/management/global/service/S3Service.java diff --git a/build.gradle b/build.gradle index 3fe283f..19ef4db 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,9 @@ dependencies { //wire mock testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock") + + //aws s3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } dependencyManagement { diff --git a/src/main/java/com/mju/management/domain/post/controller/PostController.java b/src/main/java/com/mju/management/domain/post/controller/PostController.java index 624644e..aa7542b 100644 --- a/src/main/java/com/mju/management/domain/post/controller/PostController.java +++ b/src/main/java/com/mju/management/domain/post/controller/PostController.java @@ -11,6 +11,11 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; @Tag(name = "[기획 /제작/ 편집] 게시글 작성, 수정, 삭제, 상세 조회 API") @RestController @@ -21,24 +26,31 @@ public class PostController { private final PostServiceImpl postServiceImpl; +// @ExceptionHandler(MaxUploadSizeExceededException.class) @Operation(summary = "기획/제작/편집 게시글 작성 API") @PostMapping - public CommonResult createPost(/* @AuthenticationPrincipal User user, */ @Valid @RequestBody CreatePostRequestDto createPostDto){ - return postServiceImpl.createPost(/* user, */ createPostDto.toServiceRequest()); + public CommonResult createPost(/* @AuthenticationPrincipal User user,*/ + @Valid @RequestPart CreatePostRequestDto createPostDto, + @RequestPart(value = "files", required = false) List files) throws IOException { + return postServiceImpl.createPost(/* user, */ createPostDto.toServiceRequest(), files); } @Operation(summary = "기획/제작/편집 게시글 상세 조회 API") @GetMapping - public CommonResult retrieveDetailPost(/* @AuthenticationPrincipal User user */ @Valid RetrieveDetailPostRequestDto retrieveDetailPostRequestDto ){ + public CommonResult retrieveDetailPost(/* @AuthenticationPrincipal User user */ + @Valid RetrieveDetailPostRequestDto retrieveDetailPostRequestDto ){ System.out.println("sdfslakfjklafsjlkjflad" + retrieveDetailPostRequestDto); return postServiceImpl.retrieveDetailPost(/* user, */ retrieveDetailPostRequestDto.toServiceRequest()); } +// @ExceptionHandler(MaxUploadSizeExceededException.class) @Operation(summary = "기획/제작/편집 게시글 수정 API") @PutMapping - public CommonResult updatePost(/* @AuthenticationPrincipal User user */ @Valid @RequestBody UpdatePostRequestDto updatePostRequestDto){ - return postServiceImpl.updatePost(/* user, */ updatePostRequestDto.toServiceRequest()); + public CommonResult updatePost(/* @AuthenticationPrincipal User user */ + @Valid @RequestPart UpdatePostRequestDto updatePostRequestDto, + @RequestPart(value = "files", required = false) List files) throws IOException { + return postServiceImpl.updatePost(/* user, */ updatePostRequestDto.toServiceRequest(), files); } @Operation(summary = "기획/제작/편집 게시글 삭제 API") diff --git a/src/main/java/com/mju/management/domain/post/domain/Post.java b/src/main/java/com/mju/management/domain/post/domain/Post.java index bef8993..1cc5a15 100644 --- a/src/main/java/com/mju/management/domain/post/domain/Post.java +++ b/src/main/java/com/mju/management/domain/post/domain/Post.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import com.mju.management.domain.comment.domain.Comment; @@ -60,6 +61,9 @@ public class Post { @OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE) private List commentList = new ArrayList<>(); +// @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE) + private List postFiles; @Builder public Post(String title, String content, Category category, Long writerId) { diff --git a/src/main/java/com/mju/management/domain/post/domain/PostFile.java b/src/main/java/com/mju/management/domain/post/domain/PostFile.java new file mode 100644 index 0000000..324f70d --- /dev/null +++ b/src/main/java/com/mju/management/domain/post/domain/PostFile.java @@ -0,0 +1,29 @@ +package com.mju.management.domain.post.domain; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class PostFile { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String fileName; + + private String filePath; + + private String s3key; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; +} diff --git a/src/main/java/com/mju/management/domain/post/infrastructure/PostFileRepository.java b/src/main/java/com/mju/management/domain/post/infrastructure/PostFileRepository.java new file mode 100644 index 0000000..f55320b --- /dev/null +++ b/src/main/java/com/mju/management/domain/post/infrastructure/PostFileRepository.java @@ -0,0 +1,13 @@ +package com.mju.management.domain.post.infrastructure; + +import com.mju.management.domain.post.domain.PostFile; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface PostFileRepository extends JpaRepository { + + List findByPostId(Long postId); +} diff --git a/src/main/java/com/mju/management/domain/post/model/dto/request/UpdatePostRequestServiceDto.java b/src/main/java/com/mju/management/domain/post/model/dto/request/UpdatePostRequestServiceDto.java index e411975..57f6eb2 100644 --- a/src/main/java/com/mju/management/domain/post/model/dto/request/UpdatePostRequestServiceDto.java +++ b/src/main/java/com/mju/management/domain/post/model/dto/request/UpdatePostRequestServiceDto.java @@ -2,11 +2,6 @@ import com.mju.management.domain.post.infrastructure.Category; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; - public record UpdatePostRequestServiceDto( Long projectId, Long postId, diff --git a/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java b/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java index 2716dbd..779aa84 100644 --- a/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java +++ b/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java @@ -1,12 +1,10 @@ package com.mju.management.domain.post.service; -import static com.mju.management.global.model.Exception.ExceptionList.*; - -import java.util.Optional; - import com.mju.management.domain.comment.service.port.CommentRepository; import com.mju.management.domain.post.controller.response.PostDetailResponse; import com.mju.management.domain.post.domain.Post; +import com.mju.management.domain.post.domain.PostFile; +import com.mju.management.domain.post.infrastructure.PostFileRepository; import com.mju.management.domain.post.infrastructure.PostRepository; import com.mju.management.domain.post.model.dto.request.CreatePostRequestServiceDto; import com.mju.management.domain.post.model.dto.request.DeletePostRequestServiceDto; @@ -14,17 +12,24 @@ import com.mju.management.domain.post.model.dto.request.UpdatePostRequestServiceDto; import com.mju.management.domain.project.infrastructure.Project; import com.mju.management.domain.project.infrastructure.ProjectRepository; -import com.mju.management.domain.user.dto.GetUserResponseDto; import com.mju.management.domain.user.service.UserServiceImpl; import com.mju.management.global.config.jwtInterceptor.JwtContextHolder; import com.mju.management.global.model.Exception.ExceptionList; import com.mju.management.global.model.Exception.UnauthorizedAccessException; import com.mju.management.global.model.Result.CommonResult; import com.mju.management.global.service.ResponseService; - +import com.mju.management.global.service.S3Service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static com.mju.management.global.model.Exception.ExceptionList.*; @Service @Transactional @@ -36,8 +41,10 @@ public class PostServiceImpl { private final UserServiceImpl userService; private final ResponseService responseService; private final CommentRepository commentRepository; + private final PostFileRepository postFileRepository; + private final S3Service s3Service; - public CommonResult createPost(CreatePostRequestServiceDto dto) { + public CommonResult createPost(CreatePostRequestServiceDto dto, List files) throws IOException { Optional optionalProject = projectRepository.findById(dto.projectId()); if (optionalProject.isEmpty()){ return responseService.getFailResult(INVALID_PROJECT_ID.getCode(), INVALID_PROJECT_ID.getMessage()); @@ -51,6 +58,21 @@ public CommonResult createPost(CreatePostRequestServiceDto dto) { Post post = dto.toEntity(userId); project.createPost(post); postRepository.save(post); + // 파일 업로드 + List postFiles = new ArrayList<>(); + + for (MultipartFile file : files) { + String s3key = s3Service.uploadFile(file); + + postFiles.add(PostFile.builder() + .fileName(file.getOriginalFilename()) + .filePath(s3Service.getUrl(s3key)) + .s3key(s3key) + .post(post) + .build()); + } + postFileRepository.saveAll(postFiles); + return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 작성에 성공하였습니다."); } @@ -74,7 +96,7 @@ public CommonResult retrieveDetailPost(RetrieveDetailPostRequestServiceDto dto) return responseService.getSingleResult(PostDetailResponse.from(post, userService.getUsername(post.getWriterId()))); } - public CommonResult updatePost(UpdatePostRequestServiceDto dto) { + public CommonResult updatePost(UpdatePostRequestServiceDto dto, List newFiles ) throws IOException{ Optional optionalProject = projectRepository.findById(dto.projectId()); if (optionalProject.isEmpty()){ return responseService.getFailResult(INVALID_PROJECT_ID.getCode(), INVALID_PROJECT_ID.getMessage()); @@ -96,6 +118,24 @@ public CommonResult updatePost(UpdatePostRequestServiceDto dto) { } post.update(dto); + // 파일 삭제 + List oldFiles = postFileRepository.findByPostId(post.getId()); + for(PostFile file : oldFiles) { + s3Service.deleteFile(file.getS3key()); //s3 삭제 + postFileRepository.deleteById(file.getId()); //엔티티 삭제 + } + // 파일 다시 새로 업로드 + List postFiles = new ArrayList<>(); + for (MultipartFile file : newFiles) { + String s3key = s3Service.uploadFile(file); + postFiles.add(PostFile.builder() + .fileName(file.getOriginalFilename()) + .filePath(s3Service.getUrl(s3key)) + .s3key(s3key) + .post(post) + .build()); + } + postFileRepository.saveAll(postFiles); return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 수정에 성공하였습니다."); } @@ -123,7 +163,12 @@ public CommonResult deletePost(DeletePostRequestServiceDto dto) { // 댓글들 삭제 commentRepository.deleteAll(post); - + // 파일 삭제 + List oldFiles = postFileRepository.findByPostId(post.getId()); + for(PostFile file : oldFiles) { + s3Service.deleteFile(file.getS3key()); //s3 삭제 + postFileRepository.deleteById(file.getId()); //엔티티 삭제 + } postRepository.delete(post); return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 삭제에 성공하였습니다."); } @@ -134,6 +179,4 @@ private void checkMemberAuthorization(Project project, Long userId){ throw new UnauthorizedAccessException(ExceptionList.UNAUTHORIZED_ACCESS); } - - } diff --git a/src/main/java/com/mju/management/global/config/S3Config.java b/src/main/java/com/mju/management/global/config/S3Config.java new file mode 100644 index 0000000..1fa0507 --- /dev/null +++ b/src/main/java/com/mju/management/global/config/S3Config.java @@ -0,0 +1,36 @@ +package com.mju.management.global.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3Client() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } + + +} diff --git a/src/main/java/com/mju/management/global/model/Exception/ExceptionList.java b/src/main/java/com/mju/management/global/model/Exception/ExceptionList.java index 7a36f94..3c070af 100644 --- a/src/main/java/com/mju/management/global/model/Exception/ExceptionList.java +++ b/src/main/java/com/mju/management/global/model/Exception/ExceptionList.java @@ -9,6 +9,7 @@ public enum ExceptionList { UNKNOWN(-9999, "알 수 없는 오류가 발생하였습니다."), + INVALID_PARAMETER(-5000, "인자가 잘못 전달되었거나 없습니다."), EMPTY_USER(-5051, "유저 정보를 입력해 주세요."), NOT_CORRECT_USER(-5052, "수강생이 아닙니다. 수강생으로 로그인 다시 부탁드립니다."), NOT_ACCESS_USER(-5053, "접근할 수 없는 유저 입니다."), diff --git a/src/main/java/com/mju/management/global/service/S3Service.java b/src/main/java/com/mju/management/global/service/S3Service.java new file mode 100644 index 0000000..ce15311 --- /dev/null +++ b/src/main/java/com/mju/management/global/service/S3Service.java @@ -0,0 +1,66 @@ +package com.mju.management.global.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.DeleteObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.mju.management.global.model.Exception.ExceptionList; +import com.mju.management.global.model.Exception.NonExistentException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class S3Service { + @Value("${cloud.aws.s3.bucket}") + private String BUCKET; + + private final AmazonS3 amazonS3; + + @Transactional + public String uploadFile(MultipartFile file) throws IOException { + if(file == null){ + throw new NonExistentException(ExceptionList.INVALID_PARAMETER); + } + String fileName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf(".")) + + "-" + convertToRandomName(file.getOriginalFilename()); + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(file.getSize()); + objectMetadata.setContentType(file.getContentType()); + InputStream inputStream = file.getInputStream(); + + String filePath = BUCKET; + amazonS3.putObject(new PutObjectRequest(filePath, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + return fileName; + } + @Transactional + public void deleteFile(String s3key) { + if(s3key == null){ + throw new NonExistentException(ExceptionList.INVALID_PARAMETER); + } + amazonS3.deleteObject(new DeleteObjectRequest(BUCKET, s3key)); + } + + public String convertToRandomName(String originalFileName) { + String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")); + return UUID.randomUUID().toString().concat(fileExtension); + } + + public String getUrl(String s3key) { + if(s3key == null){ + throw new NonExistentException(ExceptionList.INVALID_PARAMETER); + } + return amazonS3.getUrl(BUCKET, s3key).toString(); + } +} From 17180103d5255932b5e4bb637d7758de7bf38241 Mon Sep 17 00:00:00 2001 From: ibaesuyeon Date: Sun, 14 Apr 2024 09:49:42 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[Feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit projectId, postJd를 받아 파일리스트 반환 --- .../post/controller/PostController.java | 10 ++++++--- .../domain/post/domain/PostFile.java | 2 ++ .../domain/post/service/PostServiceImpl.java | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mju/management/domain/post/controller/PostController.java b/src/main/java/com/mju/management/domain/post/controller/PostController.java index aa7542b..5d7833e 100644 --- a/src/main/java/com/mju/management/domain/post/controller/PostController.java +++ b/src/main/java/com/mju/management/domain/post/controller/PostController.java @@ -6,6 +6,7 @@ import com.mju.management.domain.post.model.dto.request.UpdatePostRequestDto; import com.mju.management.global.model.Result.CommonResult; import com.mju.management.domain.post.service.PostServiceImpl; +import com.mju.management.global.model.Result.ListResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -35,15 +36,18 @@ public CommonResult createPost(/* @AuthenticationPrincipal User user,*/ return postServiceImpl.createPost(/* user, */ createPostDto.toServiceRequest(), files); } - @Operation(summary = "기획/제작/편집 게시글 상세 조회 API") + @Operation(summary = "기획/제작/편집 게시글 상세 - 내용 조회 API") @GetMapping public CommonResult retrieveDetailPost(/* @AuthenticationPrincipal User user */ @Valid RetrieveDetailPostRequestDto retrieveDetailPostRequestDto ){ - System.out.println("sdfslakfjklafsjlkjflad" + retrieveDetailPostRequestDto); return postServiceImpl.retrieveDetailPost(/* user, */ retrieveDetailPostRequestDto.toServiceRequest()); } - + @Operation(summary = "기획/제작/편집 게시글 상세 조회 - 파일 리스트 조회 API") + @GetMapping("/files") + public CommonResult retrieveDetailPostFiles(@Valid RetrieveDetailPostRequestDto retrieveDetailPostRequestDto ){ + return postServiceImpl.retrieveDetailPostFiles(retrieveDetailPostRequestDto.toServiceRequest()); + } // @ExceptionHandler(MaxUploadSizeExceededException.class) @Operation(summary = "기획/제작/편집 게시글 수정 API") @PutMapping diff --git a/src/main/java/com/mju/management/domain/post/domain/PostFile.java b/src/main/java/com/mju/management/domain/post/domain/PostFile.java index 324f70d..ce14cd3 100644 --- a/src/main/java/com/mju/management/domain/post/domain/PostFile.java +++ b/src/main/java/com/mju/management/domain/post/domain/PostFile.java @@ -1,5 +1,6 @@ package com.mju.management.domain.post.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -25,5 +26,6 @@ public class PostFile { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") + @JsonIgnore private Post post; } diff --git a/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java b/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java index 779aa84..44ecf95 100644 --- a/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java +++ b/src/main/java/com/mju/management/domain/post/service/PostServiceImpl.java @@ -17,6 +17,7 @@ import com.mju.management.global.model.Exception.ExceptionList; import com.mju.management.global.model.Exception.UnauthorizedAccessException; import com.mju.management.global.model.Result.CommonResult; +import com.mju.management.global.model.Result.ListResult; import com.mju.management.global.service.ResponseService; import com.mju.management.global.service.S3Service; import lombok.RequiredArgsConstructor; @@ -96,6 +97,26 @@ public CommonResult retrieveDetailPost(RetrieveDetailPostRequestServiceDto dto) return responseService.getSingleResult(PostDetailResponse.from(post, userService.getUsername(post.getWriterId()))); } + @Transactional(readOnly = true) + public CommonResult retrieveDetailPostFiles(RetrieveDetailPostRequestServiceDto dto) { + Optional optionalProject = projectRepository.findById(dto.projectId()); +// if (optionalProject.isEmpty()){ +// return responseService.getFailResult(INVALID_PROJECT_ID.getCode(), INVALID_PROJECT_ID.getMessage()); +// } + Project project = optionalProject.get(); + + // 요청자가 해당 프로젝트의 팀원인지 확인 + checkMemberAuthorization(project, JwtContextHolder.getUserId()); + + Optional optionalPost = postRepository.findById(dto.postId()); +// if(optionalPost.isEmpty()){ +// return responseService.getFailResult(INVALID_POST_ID.getCode(), INVALID_POST_ID.getMessage()); +// } + + Post post = optionalPost.get(); + List files = post.getPostFiles(); + return responseService.getListResult(files); + } public CommonResult updatePost(UpdatePostRequestServiceDto dto, List newFiles ) throws IOException{ Optional optionalProject = projectRepository.findById(dto.projectId()); if (optionalProject.isEmpty()){