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

develop -> prod 머지 #63

Merged
merged 6 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .github/workflows/showpot-dev-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
spotify.client-id: ${{ secrets.SPOTIFY_CLIENT_ID }}
spotify.client-secret: ${{ secrets.SPOTIFY_CLIENT_SECRET }}
alarm.api-url: ${{ secrets.ALARM_SERVER_API_URL }}
file.root-url: ${{ secrets.FILE_ROOT_API_URL_DEV }}

- name: Build with Gradle Wrapper
run: ./gradlew clean build -Dspring.profiles.active=dev
Expand Down Expand Up @@ -87,7 +88,7 @@ jobs:
script: |
cd /home/ec2-user/deployment/
docker-compose -f docker-compose-dev.yml down
docker system prune -f --volumes
docker system prune -a -f
docker-compose -f docker-compose-dev.yml up -d --build

- name: Remove Github Actions IP from Security Group
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/showpot-prod-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
spotify.client-id: ${{ secrets.SPOTIFY_CLIENT_ID }}
spotify.client-secret: ${{ secrets.SPOTIFY_CLIENT_SECRET }}
alarm.api-url: ${{ secrets.ALARM_SERVER_API_URL_PROD }}
file.root-url: ${{ secrets.FILE_ROOT_API_URL_PROD }}

- name: Build with Gradle Wrapper
run: ./gradlew clean build -Dspring.profiles.active=prod
Expand Down Expand Up @@ -99,8 +100,7 @@ jobs:
docker ps -q --filter "name=core" | xargs -r docker stop
docker ps -aq --filter "name=core" | xargs -r docker rm
aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }}
docker image prune -f -a
docker volume prune -f -a
docker system prune -a -f
docker pull ${{ env.IMAGE_URI }}
docker run -d --name core -p 8080:8080 -e ENVIRONMENT=prod --network prod-network ${{ env.IMAGE_URI }}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ private RequestMatcher getMatcherForAnyone() {
antMatcher(HttpMethod.GET, "/api/v1/shows/search/**"),
antMatcher(HttpMethod.GET, "/api/v1/artists/filter"),
antMatcher(HttpMethod.GET, "/api/v1/artists/filter-total-count"),
antMatcher(HttpMethod.GET, "/api/v1/artists/unsubscriptions")
antMatcher(HttpMethod.GET, "/api/v1/artists/unsubscriptions"),
antMatcher(HttpMethod.GET, "/api/v1/files/profile-image/{id}")
);
}

Expand Down Expand Up @@ -143,7 +144,11 @@ private RequestMatcher getMatcherForUserAndAdmin() {
antMatcher(HttpMethod.POST, "/api/v1/artists/unsubscribe"),
antMatcher(HttpMethod.GET, "/api/v1/artists/subscriptions"),
antMatcher(HttpMethod.GET, "/api/v1/users/notifications"),
antMatcher(HttpMethod.GET, "/api/v1/users/notifications/exist")
antMatcher(HttpMethod.GET, "/api/v1/users/notifications/exist"),
antMatcher(HttpMethod.POST, "/api/v1/comments"),
antMatcher(HttpMethod.DELETE, "/api/v1/comments/{commentId}"),
antMatcher(HttpMethod.POST, "/api/v1/comments/{commentId}/report"),
antMatcher(HttpMethod.GET, "/api/v1/comments/{refId}/**")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ public static CursorApiResponse noneCursor() {
public static <T> T getLastElement(List<T> list) {
return list.isEmpty() ? null : list.get(list.size() - 1);
}

public static <T> T getFirstElement(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected ResponseEntity<ErrorResponse> handleNoSuchElementException(final NoSuc
String errorId = UUID.randomUUID().toString();
ErrorResponse response = ErrorResponse.messageCustomErrorResponseBuilder()
.errorId(errorId)
.message(e.getMessage())
.message(GlobalError.ELEMENT_NOT_FOUND.getClientMessage())
.error(GlobalError.ELEMENT_NOT_FOUND)
.build();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.example.comment.controller;

import com.example.comment.controller.dto.param.CommentApiParam;
import com.example.comment.controller.dto.request.CommentPaginationApiRequest;
import com.example.comment.controller.dto.request.CommentReportApiRequest;
import com.example.comment.controller.dto.request.CommentWriteApiRequest;
import com.example.comment.service.CommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.example.dto.response.CursorApiResponse;
import org.example.dto.response.PaginationApiResponse;
import org.example.dto.response.SuccessResponse;
import org.example.dto.response.SuccessResponse.Empty;
import org.example.security.dto.AuthenticatedInfo;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/comments")
@Tag(name = "댓글")
public class CommentController {

private final CommentService commentService;

@ResponseStatus(HttpStatus.OK)
@PostMapping
@Operation(summary = "댓글 작성")
public SuccessResponse<Empty> writeComment(
@AuthenticationPrincipal AuthenticatedInfo info,
@RequestBody @Valid CommentWriteApiRequest request
) {
commentService.writeComment(request.toServiceRequest(), info.userId());

return SuccessResponse.emptyData();
}

@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/{commentId}")
@Operation(summary = "댓글 삭제")
public SuccessResponse<Empty> deleteComment(
@AuthenticationPrincipal AuthenticatedInfo info,
@PathVariable UUID commentId
) {
commentService.deleteComment(commentId, info.userId());

return SuccessResponse.emptyData();
}

@ResponseStatus(HttpStatus.OK)
@PostMapping("/{commentId}/report")
@Operation(summary = "댓글 신고/차단")
public SuccessResponse<Empty> reportComment(
@AuthenticationPrincipal AuthenticatedInfo info,
@PathVariable UUID commentId,
@RequestBody @Valid CommentReportApiRequest request
) {
commentService.reportComment(request.toServiceRequest(), commentId, info.userId());

return SuccessResponse.emptyData();
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/{refId}")
@Operation(summary = "댓글 목록 조회")
public SuccessResponse<PaginationApiResponse<CommentApiParam>> readComments(
@AuthenticationPrincipal AuthenticatedInfo info,
@PathVariable UUID refId,
@Valid @ParameterObject CommentPaginationApiRequest request
) {
var commentPagination = commentService.getComments(request.toServiceRequest(refId, info.userId()));

CursorApiResponse cursor;
if (request.isInverted()) {
cursor = Optional.ofNullable(CursorApiResponse.getLastElement(commentPagination.data()))
.map(element -> CursorApiResponse.toCursorResponse(element.commentId(), element.createdAt()))
.orElse(CursorApiResponse.noneCursor());
} else {
cursor = Optional.ofNullable(CursorApiResponse.getFirstElement(commentPagination.data()))
.map(element -> CursorApiResponse.toCursorResponse(element.commentId(), element.createdAt()))
.orElse(CursorApiResponse.noneCursor());
}

return SuccessResponse.ok(
PaginationApiResponse.<CommentApiParam>builder()
.data(commentPagination.data())
.hasNext(commentPagination.hasNext())
.cursor(cursor)
.build()
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.comment.controller.dto.param;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.UUID;
import lombok.Builder;

@Builder
public record CommentApiParam(

@Schema(description = "댓글 ID")
UUID commentId,

@Schema(description = "부모 댓글 ID")
UUID parentId,

@Schema(description = "댓글 내용")
String content,

@Schema(description = "차단 여부")
boolean isBlocked,

@Schema(description = "사용자 프로필 URL")
String profileURL,

@Schema(description = "사용자 이름")
String userName,

@Schema(description = "댓글 생성 시간")
String createdAt
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.example.comment.controller.dto.request;

import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.constraints.Max;
import java.time.LocalDateTime;
import java.util.UUID;
import org.example.dto.comment.request.CommentPaginationDomainRequest;
import org.example.vo.CommentType;

public record CommentPaginationApiRequest(
@Parameter(description = "댓글 타입", required = true)
CommentType type,

@Parameter(description = "스크롤 방향", required = true)
boolean isInverted,

@Parameter(description = "이전 페이지네이션의 cursorId / 최초 조회라면 null")
UUID cursorId,

@Parameter(description = "이전 페이지네이션의 cursorValue/ 최초 조회라면 null")
LocalDateTime cursorValue,

@Parameter(example = "30")
@Max(value = 30, message = "조회하는 데이터의 최대 개수는 30입니다.")
Integer size
) {
public CommentPaginationApiRequest {
if (size == null) {
size = 30;
}
}

public CommentPaginationDomainRequest toServiceRequest(UUID refId, UUID userId) {
return CommentPaginationDomainRequest.builder()
.type(type)
.refId(refId)
.userId(userId)
.isInverted(isInverted)
.cursorId(cursorId)
.cursorValue(cursorValue)
.size(size)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.comment.controller.dto.request;

import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.example.dto.comment.request.CommentReportDomainRequest;
import org.example.vo.ReportType;

public record CommentReportApiRequest(
@Parameter(
example = "(GRAFFITI, PORNOGRAPHY, COMMERCIAL_AD, IMPERSONATION, PROFANITY, ETC, BLOCKING) 중 하나",
description = "신고 타입")
@NotNull(message = "신고 타입을 입력해주세요.")
ReportType reportType,

@Size(max = 255, message = "직접 입력 내용은 최대 255자까지 입력 가능합니다.")
@NotBlank
@NotEmpty
String directInput
) {
public CommentReportDomainRequest toServiceRequest() {
if (directInput != null) {
return new CommentReportDomainRequest(reportType, directInput);
}

return new CommentReportDomainRequest(reportType, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.comment.controller.dto.request;

import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import org.example.dto.comment.request.CommentWriteDomainRequest;
import org.example.vo.CommentType;

public record CommentWriteApiRequest(
@Parameter(description = "부모 댓글의 ID / 대댓글 작성 시 필수")
UUID parentId,

@Parameter(description = "댓글 내용")
@NotBlank(message = "댓글 내용을 입력해주세요.")
String content,

@Parameter(example = "SHOW", description = "댓글 타입")
@NotNull(message = "댓글 타입을 입력해주세요.")
CommentType commentType,

@Parameter(description = "참조 ID / 댓글 타입이 SHOW일 경우에는 SHOW ID")
@NotNull(message = "참조 ID를 입력해주세요.")
UUID refId
) {

public CommentWriteDomainRequest toServiceRequest() {
return new CommentWriteDomainRequest(
parentId,
content,
commentType,
refId
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.example.comment.error;

import org.example.exception.BusinessError;

public enum CommentError implements BusinessError {

COMMENT_AUTHOR_REPORT_ERROR {
@Override
public int getHttpStatus() {
return 400;
}

@Override
public String getErrorCode() {
return "CMT-001";
}

@Override
public String getClientMessage() {
return "댓글 저자는 자신의 댓글을 신고/차단할 수 없습니다.";
}

@Override
public String getLogMessage() {
return "댓글 저자가 본인의 댓글을 신고/차단 요청함";
}
},

COMMENT_AUTHOR_DIFFERENT_ERROR {
@Override
public int getHttpStatus() {
return 403;
}

@Override
public String getErrorCode() {
return "CMT-002";
}

@Override
public String getClientMessage() {
return "댓글 저자가 아닙니다.";
}

@Override
public String getLogMessage() {
return "댓글 저자가 아닌 신원이 요청함";
}
}
}
Loading
Loading