Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[배포] 후기 - 상세조회 작성, 수정, 삭제, 좋아요 & 댓글 - 작성, 수정, 삭제, 좋아요 & S3 이미지 구현 #34

Merged
merged 22 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f68a385
feat: [review] PUT/api/v1/reviews/{reviewId} - 후기 수정
k9want Jun 14, 2024
356b75b
modified: [review] 후기 작성 - url 변경 및 articleId request로 입력받도록 수정, Erro…
k9want Jun 14, 2024
cb31dd3
Merge pull request #27 from TravelLaboratory/feature/review_update_v1
k9want Jun 14, 2024
72dd0b8
feat: [review] PATCH /api/v1/reviews/{reviewId}/status - 후기 삭제 기능 구현 …
k9want Jun 14, 2024
4907022
Merge pull request #28 from TravelLaboratory/feature/review_delete_v1
k9want Jun 14, 2024
8427c83
rename: userslikerereview를 userlikereview로 패키지 이름 변경
k9want Jun 14, 2024
3f58780
feat: [review] PUT /api/v1/reviews/{reviewId}/likes - 후기 좋아요 기능 구현
k9want Jun 14, 2024
2956ec9
Merge pull request #29 from TravelLaboratory/feature/userlikereview_v1
k9want Jun 14, 2024
5c6a2e5
modified: [config] GlobalExceptionHandler - Profile 제거, application d…
k9want Jun 14, 2024
5046d1a
feat: [comment] Comment, UserLikeComment 추가
k9want Jun 14, 2024
b14ab3e
rename: userLikeComment를 userlikecomment 패키지명 변경
k9want Jun 14, 2024
f4a3994
feat: [comment] 댓글 작성, 수정, 삭제, 좋아요 기능 구현
k9want Jun 15, 2024
ec93db9
Merge pull request #31 from TravelLaboratory/feature/comment_v1_save_…
k9want Jun 15, 2024
bd75e23
feat: [review] 후기 수정 - Put을 Patch로 수정
k9want Jun 15, 2024
fc2317a
Merge pull request #32 from TravelLaboratory/feature/comment_v1_save_…
k9want Jun 15, 2024
998a92d
feat : [S3] 업로드 및 프로필 수정 기능 구현 (#33)
SeonJuuuun Jun 15, 2024
50945ec
feat: [ARTICLE] 검색 기능 구현 (#30)
SeonJuuuun Jun 15, 2024
e3b49c3
chore: 환경변수 설정 (#35)
SeonJuuuun Jun 15, 2024
9462a50
feat: [review] 후기 상세 조회(1) - (feat. 후기 내용 + 후기를 쓴 유저 정보) 기능 구현
k9want Jun 15, 2024
aa6a695
Merge pull request #36 from TravelLaboratory/feature/review_read_v1
k9want Jun 15, 2024
5c2fbae
feat: [config] CD - 이미지 저장을 위한 s3 설정 추가
k9want Jun 15, 2024
e7f8829
Merge pull request #37 from TravelLaboratory/feature/config_s3_v1
k9want Jun 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/main-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ jobs:
jwt.secret-key: ${{ secrets.JWT_SECRET_KEY }}
jwt.access-token.plus-hour: ${{ secrets.JWT_ACCESS_TOKEN_PLUS_HOUR }}
jwt.refresh-token.plus-hour: ${{ secrets.JWT_REFRESH_TOKEN_PLUS_HOUR }}
servers.url: ${{ secrets.SERVERS_URL}}
servers.url: ${{ secrets.SERVERS_URL }}
cloud.aws.s3.bucket: ${{ secrets.CLOUD_AWS_S3_BUCKET }}
cloud.aws.credentials.access-key: ${{ secrets.CLOUD_AWS_CREDENTIALS_ACCESS_KEY }}
cloud.aws.credentials.secret-key: ${{ secrets.CLOUD_AWS_CREDENTIALS_SECRET_KEY }}
cloud.aws.region.static: ${{ secrets.CLOUD_AWS_REGION_STATIC }}


- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand Down
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ dependencies {
//swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

//s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand All @@ -61,4 +64,4 @@ bootJar {

jar {
enabled = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,39 @@ public enum ErrorCodes {
PASSWORD_INVALID_EMAIL("유효하지 않은 이메일", 2011L),
PASSWORD_INQUIRY_INVALID_ANSWER("올바르지 않은 답변", 2012L),

// 유저 관련
USER_NOT_FOUND("존재하지 않는 유저", 3000L),


// 후기 (review)
REVIEW_INVALID_ARTICLE("유효하지 않은 여행 계획 ID", 4000L),
REVIEW_NOT_USER_ARTICLE("여행 계획 작성자만 해당 여행 계획의 후기를 작성할 수 있습니다.", 4001L),
REVIEW_EXIST_USER_ARTICLE("각 여행 계획에 대한 후기는 한 개만 작성할 수 있습니다.", 4002L),
// 후기 작성
REVIEW_POST_INVALID("[후기 작성] - 유효하지 않은 여행 계획 ID", 4000L),
REVIEW_POST_NOT_USER("[후기 작성] - 여행 계획 작성자만 해당 여행 계획의 후기를 작성할 수 있습니다.", 4001L),
REVIEW_POST_EXIST("[후기 작성] - 각 여행 계획에 대한 후기는 한 개만 작성할 수 있습니다.", 4002L),
// 후기 수정
REVIEW_UPDATE_INVALID("[후기 수정] - 유효하지 않은 후기 ID", 4003L),
REVIEW_UPDATE_NOT_USER("[후기 수정] - 본인의 후기가 아닙니다.", 4004L),
// 후기 삭제
REVIEW_DELETE_INVALID("[후기 삭제] - 유효하지 않은 후기 ID", 4005L),
REVIEW_DELETE_NOT_USER("[후기 삭제] - 본인의 후기가 아닙니다.", 4006L),
// 후기 좋아요
REVIEW_LIKE_INVALID("[후기 좋아요] - 유효하지 않은 후기 ID", 4007L),
// 후기 상세 조회
REVIEW_READ_DETAIL_INVALID("[후기 상세 조회] - 유효하지 않은 후기 ID", 4040L),
REVIEW_READ_DETAIL_NOT_AUTHORIZATION("[후기 상세 조회] - 해당 후기에 접근 권한 없음", 4041L),


// 댓글
// 댓글 작성
COMMENT_POST_INVALID("[댓글 작성] - 유효하지 않은 후기 ID", 5000L),
// 댓글 수정
COMMENT_UPDATE_INVALID("[댓글 수정] - 유효하지 않은 댓글 ID", 5010L),
COMMENT_UPDATE_NOT_USER("[댓글 수정] - 본인의 댓글이 아닙니다.", 5011L),
// 댓글 삭제
COMMENT_DELETE_INVALID("[댓글 삭제] - 유효하지 않은 댓글 ID", 5020L),
COMMENT_DELETE_NOT_USER("[댓글 삭제] - 본인의 댓글가 아닙니다.", 5021L),
// 댓글 좋아요
COMMENT_LIKE_INVALID("[댓글 좋아요] - 유효하지 않은 댓글 ID", 5030L),

BAD_REQUEST("BAD_REQUEST", 9404L),
BAD_REQUEST_JSON_PARSE_ERROR("[BAD_REQUEST] JSON_PARSE_ERROR - 올바른 JSON 형식이 아님", 9405L),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -17,7 +16,6 @@
import site.travellaboratory.be.common.exception.ErrorCodes;
import site.travellaboratory.be.common.response.ApiErrorResponse;

@Profile("prod")
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/site/travellaboratory/be/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package site.travellaboratory.be.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {

@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder
.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package site.travellaboratory.be.controller.article;

import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -13,6 +14,8 @@
import site.travellaboratory.be.common.annotation.UserId;
import site.travellaboratory.be.controller.article.dto.ArticleRegisterRequest;
import site.travellaboratory.be.controller.article.dto.ArticleResponse;
import site.travellaboratory.be.controller.article.dto.ArticleSearchRequest;
import site.travellaboratory.be.controller.article.dto.ArticleSearchResponse;
import site.travellaboratory.be.service.ArticleService;

@RestController
Expand Down Expand Up @@ -44,4 +47,11 @@ public ResponseEntity<ArticleResponse> findArticle(
final ArticleResponse articleResponse = articleService.findByUserArticle(articleId);
return ResponseEntity.ok(articleResponse);
}

@GetMapping("/article/search")
public ResponseEntity<List<ArticleSearchResponse>> searchArticle(
@Valid @RequestBody final ArticleSearchRequest articleSearchRequest) {
final List<ArticleSearchResponse> response = articleService.searchArticlesByKeyWord(articleSearchRequest);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package site.travellaboratory.be.controller.article.dto;

import jakarta.validation.constraints.NotBlank;

public record ArticleSearchRequest (
@NotBlank
String keyWord
){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package site.travellaboratory.be.controller.article.dto;

import java.time.LocalDateTime;
import java.util.List;
import site.travellaboratory.be.domain.article.Article;

public record ArticleSearchResponse(
String title,
List<String> location,
LocalDateTime startAt,
LocalDateTime endAt,
String expense,
List<String> travelCompanion,
List<String> style,
String nickname
) {

public static List<ArticleSearchResponse> from(final List<Article> articles) {
return articles.stream()
.map(article -> new ArticleSearchResponse(
article.getTitle(),
article.getLocation(),
article.getStartAt(),
article.getEndAt(),
article.getExpense(),
article.getTravelCompanions(),
article.getTravelStyles(),
article.getNickname()
))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package site.travellaboratory.be.controller.comment;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RestController;
import site.travellaboratory.be.common.annotation.UserId;
import site.travellaboratory.be.controller.comment.dto.CommentDeleteResponse;
import site.travellaboratory.be.controller.comment.dto.CommentSaveRequest;
import site.travellaboratory.be.controller.comment.dto.CommentSaveResponse;
import site.travellaboratory.be.controller.comment.dto.CommentUpdateRequest;
import site.travellaboratory.be.controller.comment.dto.CommentUpdateResponse;
import site.travellaboratory.be.controller.comment.dto.userlikecomment.CommentToggleLikeResponse;
import site.travellaboratory.be.service.CommentService;

@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class CommentController {

private final CommentService commentService;

@PostMapping("/comment")
public ResponseEntity<CommentSaveResponse> saveComment(
@UserId Long userId,
@Valid @RequestBody CommentSaveRequest commentSaveRequest
) {
CommentSaveResponse response = commentService.saveComment(userId, commentSaveRequest);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}

@PatchMapping("/comments/{commentId}")
public ResponseEntity<CommentUpdateResponse> updateComment(
@UserId Long userId,
@PathVariable(name = "commentId") Long commentId,
@Valid @RequestBody CommentUpdateRequest commentUpdateRequest
) {
CommentUpdateResponse response = commentService.updateComment(userId, commentId, commentUpdateRequest);
return ResponseEntity.ok(response);
}

@PatchMapping("/comments/{commentId}/status")
public ResponseEntity<CommentDeleteResponse> deleteComment(
@UserId Long userId,
@PathVariable(name = "commentId") Long commentId
) {
CommentDeleteResponse response = commentService.deleteComment(userId, commentId);
return ResponseEntity.ok(response);
}

@PutMapping("/comments/{commentId}/likes")
public ResponseEntity<CommentToggleLikeResponse> toggleLikeComment(
@UserId Long userId,
@PathVariable(name = "commentId") Long commentId
) {
CommentToggleLikeResponse response = commentService.toggleLikeComment(userId,
commentId);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package site.travellaboratory.be.controller.comment.dto;

public record CommentDeleteResponse(
Boolean isDelete
) {
public static CommentDeleteResponse from(Boolean isDelete) {
return new CommentDeleteResponse(
isDelete
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package site.travellaboratory.be.controller.comment.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record CommentSaveRequest(
@NotNull
Long reviewId,
@NotBlank
String replyComment
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package site.travellaboratory.be.controller.comment.dto;

public record CommentSaveResponse(
Long commentId
) {
public static CommentSaveResponse from(Long commentId) {
return new CommentSaveResponse(commentId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package site.travellaboratory.be.controller.comment.dto;

public record CommentUpdateRequest(
String replyComment
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package site.travellaboratory.be.controller.comment.dto;

public record CommentUpdateResponse(
Long commentId
) {

public static CommentUpdateResponse from(Long commentId) {
return new CommentUpdateResponse(commentId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package site.travellaboratory.be.controller.comment.dto.userlikecomment;

import site.travellaboratory.be.domain.userlikecomment.UserLikeCommentStatus;

public record CommentToggleLikeResponse(
UserLikeCommentStatus status
) {
public static CommentToggleLikeResponse from(UserLikeCommentStatus status) {
return new CommentToggleLikeResponse(status);
}
}
Loading
Loading