Skip to content

Commit

Permalink
Merge pull request #13 from f-lab-edu/issue10-comment
Browse files Browse the repository at this point in the history
[Issue10] 댓글 기능 구현
  • Loading branch information
misim3 authored May 2, 2024
2 parents 0d86d34 + a6b743c commit d7fc833
Show file tree
Hide file tree
Showing 42 changed files with 788 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public WebSecurityCustomizer webSecurityCustomizer() {
"/terms/**",
"/videos/**",
"/comments/**",
"/channels/**",
"/home"
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.misim.controller;

import com.misim.controller.model.Request.SubscribingRequest;
import com.misim.exception.CommonResponse;
import com.misim.service.SubscriptionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "채널 API", description = "채널 정보 관련 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/channels")
public class ChannelController {

private final SubscriptionService subscriptionService;

@Operation(summary = "채널 구독", description = "채널을 구독합니다.")
@Parameter(name = "SubscribingRequest", description = "채널 구독 요청을 위한 정보", required = true)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "채널 구독 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/subscribe")
public void subscribing(@RequestBody SubscribingRequest request) {

request.check();

subscriptionService.subscribing(request.getChannelId(), request.getSubscriberId());
}

@Operation(summary = "채널 구독 취소", description = "채널 구독을 취소합니다.")
@Parameter(name = "SubscribingRequest", description = "채널 구독 취소 요청을 위한 정보", required = true)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "채널 구독 취소 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/unsubscribe")
public void unsubscribing(@RequestBody SubscribingRequest request) {

request.check();

subscriptionService.unsubscribing(request.getChannelId(), request.getSubscriberId());
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package com.misim.controller;

import com.misim.controller.model.CommentDto;
import com.misim.controller.model.Request.CreateCommentRequest;
import com.misim.controller.model.Request.UpdateCommentRequest;
import com.misim.controller.model.Response.CommentListResponse;
import com.misim.controller.model.Response.CommentResponse;
import com.misim.controller.model.Response.CreateCommentResponse;
import com.misim.exception.CommonResponse;
import com.misim.exception.MitubeErrorCode;
import com.misim.exception.MitubeException;
import com.misim.service.CommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.hibernate.sql.Update;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;


@Tag(name = "댓글 API", description = "댓글 정보 관련 API")
@RestController
Expand All @@ -20,45 +35,100 @@ public class CommentController {

private final CommentService commentService;


private final List<String> scroll = Arrays.asList("up", "down");

// idx, sort, scroll direction
// 1th way - idx and scroll direction -> 'idx'
// 2th way - scroll direction ~ idx -> 'idx' : scroll down lowest idx or scroll up highest idx
// scroll down
// select * from comments where parentCommentId is null and videoId = 'videoId' and idx < 'idx' order by sort DESC limit 'pageSize';
// scroll up
// select * from comments where parentCommentId is null and videoId = 'videoId' and idx > 'idx' order by sort ACS limit 'pageSize';
// 1 / '2' 3 4 / '5' 6 7 / '8' 9 10
// commentService 메소드 호출 전, scrollDirection 데이터 검사
@Operation(summary = "댓글 목록 요청", description = "동영상의 댓글 목록 10개 전달")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "댓글 목록 요청 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@GetMapping("/{videoId}")
public CommonResponse<CommentListResponse> getParentComments(@PathVariable Long videoId, @RequestParam int page) {
public CommonResponse<CommentListResponse> getParentComments(@PathVariable @Parameter(name = "videoId", description = "시청할 동영상 식별 정보", required = true) Long videoId, @RequestParam @Parameter(name = "idx", description = "댓글의 인덱스 정보") Long idx, @RequestParam @Parameter(name = "scrollDirection", description = "댓글 목록 스크롤 방향으로 up, down만 가능하다.") String scrollDirection) {

CommentListResponse comments = commentService.getParentComments(videoId, page);
checkRequests(idx, scrollDirection);

CommentListResponse comments = commentService.getParentComments(videoId, idx, scrollDirection);

return CommonResponse.<CommentListResponse>builder()
.body(comments)
.build();
}

@Operation(summary = "대댓글 목록 요청", description = "동영상 댓글의 대댓글 목록 10개 전달")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "대댓글 목록 요청 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@GetMapping("/{videoId}/{parentCommentId}")
public CommonResponse<CommentListResponse> getChildComments(@PathVariable Long videoId, @PathVariable Long parentCommentId, @RequestParam int page) {
public CommonResponse<CommentListResponse> getChildComments(@PathVariable @Parameter(name = "videoId", description = "시청할 동영상 식별 정보", required = true) Long videoId, @PathVariable @Parameter(name = "parentCommentId", description = "대댓글이 달린 댓글의 식별 정보") Long parentCommentId, @RequestParam @Parameter(name = "idx", description = "댓글의 인덱스 정보") Long idx, @RequestParam @Parameter(name = "scrollDirection", description = "댓글 목록 스크롤 방향으로 up, down만 가능하다.") String scrollDirection) {

checkRequests(idx, scrollDirection);

CommentListResponse comments = commentService.getChildComments(videoId, parentCommentId, page);
CommentListResponse comments = commentService.getChildComments(videoId, parentCommentId, idx, scrollDirection);

return CommonResponse.<CommentListResponse>builder()
.body(comments)
.build();
}

@Operation(summary = "댓글 생성", description = "동영상 댓글을 생성합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "댓글 생성 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/create")
public CommonResponse<CreateCommentResponse> createComments(@RequestBody CreateCommentRequest request) {

request.check();

CreateCommentResponse response = commentService.createComments(request);

return CommonResponse.<CreateCommentResponse>builder().body(response).build();
}

@PostMapping("/{commentId}")
public void updateComments(@PathVariable Long commentId, @RequestParam String content) {
commentService.updateComments(commentId, content);
@Operation(summary = "댓글 수정", description = "동영상 댓글을 수정합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "댓글 수정 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/update")
public void updateComments(@RequestBody UpdateCommentRequest request) {

request.check();

commentService.updateComments(request.getCommentId(), request.getContent());
}

// 댓글 삭제 요청을 post, delete 중에 선택
@DeleteMapping("/{commentId}")
public void deleteComments(@PathVariable Long commentId) {
commentService.deleteComments(commentId);
@Operation(summary = "댓글 삭제", description = "동영상 댓글을 삭제합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "댓글 삭제 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/delete")
public void deleteComments(@RequestBody CommentDto request) {

request.check();

commentService.deleteComments(request.getCommentId());
}

private void checkRequests(Long idx, String scrollDirection) {

if (idx < 1) {
throw new MitubeException(MitubeErrorCode.INVALID_COMMENT_INDEX);
}

if (scrollDirection.isBlank() || !scroll.contains(scrollDirection)) {
throw new MitubeException(MitubeErrorCode.INVALID_COMMENT_SCROLL_DIRECTION);
}
}
}
15 changes: 13 additions & 2 deletions mitube-app/src/main/java/com/misim/controller/MainController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
import com.misim.exception.CommonResponse;
import com.misim.service.HomeService;
import com.misim.service.VideoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -17,9 +23,14 @@
public class MainController {

private final HomeService homeService;


@Operation(summary = "메인 화면 데이터 전송", description = "메인 화면에 필요한 데이터 전송")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "메인 화면 데이터 전송 성공"),
@ApiResponse(responseCode = "400", description = "요청이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@GetMapping("/home")
public CommonResponse<HomeResponse> home(@RequestParam Long userId) {
public CommonResponse<HomeResponse> home(@RequestParam @Parameter(name = "userId", description = "Mitube에 접속한 유저 식별 정보로, 비로그인 사용자의 경우 null로 요청된다.") Long userId) {

HomeResponse response = homeService.getHome(userId);

Expand Down
58 changes: 51 additions & 7 deletions mitube-app/src/main/java/com/misim/controller/VideoController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.misim.controller;

import com.misim.controller.model.Request.ReactionRequest;
import com.misim.controller.model.Response.CommentListResponse;
import com.misim.controller.model.Response.CommentResponse;
import com.misim.controller.model.Response.StartWatchingVideoResponse;
Expand All @@ -9,6 +10,7 @@
import com.misim.exception.MitubeErrorCode;
import com.misim.exception.MitubeException;
import com.misim.service.CommentService;
import com.misim.service.ReactionService;
import com.misim.service.VideoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -30,8 +32,10 @@ public class VideoController {

private final VideoService videoService;
private final CommentService commentService;
private final ReactionService reactionService;

@Operation(summary = "동영상 업로드", description = "새로운 동영상을 업로드합니다.")
@Parameter(name = "MultipartFile", description = "MultipartFile 형식의 동영상 데이터")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "동영상 업로드 성공"),
@ApiResponse(responseCode = "400", description = "요청 형식이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
Expand Down Expand Up @@ -76,13 +80,18 @@ public void createVideos(@RequestBody CreateVideoRequest createVideoRequest) {
// 비디오 생성
videoService.createVideos(createVideoRequest);
}


@Operation(summary = "동영상 시청 시작", description = "동영상 시청을 시작합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "동영상 시청 시작 요청 성공"),
@ApiResponse(responseCode = "400", description = "요청 형식이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@GetMapping("/watch/{videoId}")
public CommonResponse<StartWatchingVideoResponse> startWatchingVideo(@PathVariable Long videoId, @RequestParam Long userId) {
public CommonResponse<StartWatchingVideoResponse> startWatchingVideo(@PathVariable @Parameter(name = "videoId", description = "시청할 동영상 식별 정보", required = true) Long videoId, @RequestParam @Parameter(name = "userId", description = "동영상을 시청할 유저의 식별 정보") Long userId) {

StartWatchingVideoResponse response = videoService.startWatchingVideo(videoId, userId);

CommentListResponse commentListResponse = commentService.getParentComments(videoId, 0);
CommentListResponse commentListResponse = commentService.getParentComments(videoId, null, "down");

response.setCommentListResponse(commentListResponse);

Expand All @@ -91,18 +100,53 @@ public CommonResponse<StartWatchingVideoResponse> startWatchingVideo(@PathVariab
.body(response)
.build();
}



@Operation(summary = "동영상 시청 중", description = "유저가 동영상을 계속 시청 중인지 확인하여 동영상 시청 정보를 업데이트합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "동영상 시청 정보 업데이트 성공"),
@ApiResponse(responseCode = "400", description = "요청 형식이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/watch/{videoId}")
public void watchingVideo(@PathVariable Long videoId, @RequestParam Long userId, @RequestParam Long watchingTime) {
public void watchingVideo(@PathVariable @Parameter(name = "videoId", description = "시청할 동영상 식별 정보", required = true) Long videoId, @RequestParam @Parameter(name = "userId", description = "동영상을 시청할 유저의 식별 정보") Long userId, @RequestParam Long watchingTime) {

videoService.updateWatchingVideoInfo(videoId, userId, watchingTime);
}

// 99999 에러 발생
@Operation(summary = "동영상 시청 완료", description = "동영상 시청 완료로 시청 정보를 업데이트합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "동영상 시청 정보 업데이트 성공"),
@ApiResponse(responseCode = "400", description = "요청 형식이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/watch/{videoId}/complete")
public void completeWatchingVideo(@PathVariable Long videoId, @RequestParam Long userId, @RequestParam Long watchingTime) {
public void completeWatchingVideo(@PathVariable @Parameter(name = "videoId", description = "시청할 동영상 식별 정보", required = true) Long videoId, @RequestParam @Parameter(name = "userId", description = "동영상을 시청할 유저의 식별 정보") Long userId, @RequestParam Long watchingTime) {

videoService.updateWatchingVideoInfo(videoId, userId, watchingTime);
}

@Operation(summary = "동영상에 대한 유저의 반응 선택", description = "동영상에 대한 유저의 반응. 좋아요, 싫어요 선택 정보를 저장합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "동영상 반응 정보 업데이트 성공"),
@ApiResponse(responseCode = "400", description = "요청 형식이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/check/reaction")
public void checkVideo(@RequestBody ReactionRequest request) {

request.check();

reactionService.checking(request.getType(), request.getUserId(), request.getVideoId());
}

@Operation(summary = "동영상에 대한 유저의 반응 선택 취소", description = "동영상에 대한 유저의 반응. 좋아요, 싫어요 선택 취소 정보를 저장합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "동영상 반응 정보 업데이트 성공"),
@ApiResponse(responseCode = "400", description = "요청 형식이 올바르지 않습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class)))
})
@PostMapping("/uncheck/reaction")
public void uncheckVideo(@RequestBody ReactionRequest request) {

request.check();

reactionService.unchecking(request.getType(), request.getUserId(), request.getVideoId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.misim.controller.model;

import com.misim.exception.MitubeErrorCode;
import com.misim.exception.MitubeException;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
public class CommentDto implements Checker{

@Schema(name = "commentId", description = "댓글 식별 정보", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
private Long commentId;

@Override
public void check() {

if (commentId == null) {
throw new MitubeException(MitubeErrorCode.INVALID_COMMENT_ID);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.misim.controller.model.Request;

import com.misim.controller.model.Checker;
import com.misim.controller.model.CommentDto;
import com.misim.exception.MitubeErrorCode;
import com.misim.exception.MitubeException;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
@Schema(name = "댓글 생성 요청 DTO")
public class CreateCommentRequest implements Checker {

@Schema(name = "content", description = "댓글 내용", example = "프로그래밍 공부에 도움이 됩니다!", requiredMode = Schema.RequiredMode.REQUIRED)
Expand Down
Loading

0 comments on commit d7fc833

Please sign in to comment.