Skip to content

Commit

Permalink
Merge pull request #39 from softeerbootcamp4th/feature/37-admin-comme…
Browse files Browse the repository at this point in the history
…nt-act

[feat] 관리자 이벤트 댓글 조회 & 삭제 (#37)
  • Loading branch information
blaxsior authored Aug 7, 2024
2 parents 9e25f62 + acb939f commit b945d50
Show file tree
Hide file tree
Showing 15 changed files with 322 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package hyundai.softeer.orange.admin.controller;
import hyundai.softeer.orange.comment.dto.DeleteCommentsDto;
import hyundai.softeer.orange.comment.dto.ResponseCommentsDto;
import hyundai.softeer.orange.comment.service.CommentService;
import hyundai.softeer.orange.core.auth.Auth;
import hyundai.softeer.orange.core.auth.AuthRole;
import io.swagger.v3.oas.annotations.Operation;
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.security.SecurityRequirement;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/api/v1/admin/comments")
@RequiredArgsConstructor
@RestController
@Auth({AuthRole.admin})
public class AdminCommentController {
private final CommentService commentService;

/**
* @param eventId 댓글을 검색할 이벤트 id
* @param page 댓글 페이지. default = 0
* @param size 한번에 읽어 올 댓글 개수. default = 10
* @return 대상 이벤트에 대해 검색된 댓글 목록
*/
@Operation(summary = "관리자가 이벤트에 대한 댓글 목록 조회", description = "이벤트에 대한 댓글 목록을 조회한다.", responses = {
@ApiResponse(responseCode = "200", description = "이벤트에 대한 댓글 목록 조회 성공")
})
@GetMapping
public ResponseEntity<ResponseCommentsDto> findEventComments(
@RequestParam String eventId,
@RequestParam(required = false, defaultValue = "0") Integer page,
@RequestParam(required = false, defaultValue = "10") Integer size
) {
var comments = commentService.searchComments(eventId, page, size);
return ResponseEntity.ok(comments);
}

@Operation(summary = "관리가 댓글 목록 삭제", description = "관리자가 이벤트에 대한 댓글 목록을 삭제한다.", responses = {
@ApiResponse(responseCode = "200", description = "댓글 삭제 성공")
})
@DeleteMapping
public ResponseEntity<Void> deleteEventComments(@Valid @RequestBody DeleteCommentsDto dto) {
commentService.deleteComments(dto.getCommentIds());
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package hyundai.softeer.orange.comment.dto;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;

import java.util.List;

@Getter
public class DeleteCommentsDto {
@NotNull
private List<Long> commentIds;

public DeleteCommentsDto(List<Long> commentIds) {
this.commentIds = commentIds;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package hyundai.softeer.orange.comment.repository;

import hyundai.softeer.orange.comment.entity.Comment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -16,4 +18,12 @@ public interface CommentRepository extends JpaRepository<Comment, Long> {
// 오늘 날짜 기준으로 이미 유저의 기대평이 등록되어 있는지 확인
@Query(value = "SELECT COUNT(*) FROM comment WHERE event_user_id = :eventUserId AND DATE(createdAt) = CURDATE()", nativeQuery = true)
boolean existsByCreatedDateAndEventUser(@Param("eventUserId") Long eventUserId);

@Query(value = "SELECT c.* FROM comment c " +
"JOIN event_frame ef ON c.event_frame_id = ef.id " +
"JOIN event_metadata e ON ef.id = e.event_frame_id " +
"WHERE e.event_id = :eventId",
countProjection = "c.id", // 어떤 값으로 count 셀건지 지정. 지정 안하면 count(c.*)가 되어 문제 발생.
nativeQuery = true)
Page<Comment> findAllByEventId(@Param("eventId") String eventId, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -24,7 +25,6 @@
@RequiredArgsConstructor
@Service
public class CommentService {

private final CommentRepository commentRepository;
private final EventFrameRepository eventFrameRepository;
private final EventUserRepository eventUserRepository;
Expand Down Expand Up @@ -68,4 +68,17 @@ public Long deleteComment(Long commentId) {
commentRepository.deleteById(commentId);
return commentId;
}

@Transactional
public void deleteComments(List<Long> commentIds) {
commentRepository.deleteAllById(commentIds);
}

public ResponseCommentsDto searchComments(String eventId, Integer page, Integer size) {
PageRequest pageInfo = PageRequest.of(page, size);

var comments = commentRepository.findAllByEventId(eventId,pageInfo)
.getContent().stream().map(ResponseCommentDto::from).toList();
return new ResponseCommentsDto(comments);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -34,6 +36,15 @@ public ResponseEntity<Map<String, String>> handleInValidRequestException(MethodA
return ResponseEntity.badRequest().body(errors);
}

// TODO: messages.properties에 예외 메시지 커스터마이징할 수 있게 방법 찾아보기
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Map<String, String>> handleInValidRequestException(MethodArgumentTypeMismatchException e) {
Map<String, String> errors = new HashMap<>();
errors.put(e.getName(), e.getLocalizedMessage());
return ResponseEntity.badRequest().body(errors);
}

@ExceptionHandler({CommentException.class, AdminException.class, EventUserException.class, FcfsEventException.class, UrlException.class, InternalServerException.class})
public ResponseEntity<ErrorResponse> handleException(BaseException e) {
return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(ErrorResponse.from(e.getErrorCode()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import hyundai.softeer.orange.core.auth.Auth;
import hyundai.softeer.orange.core.auth.AuthRole;
import hyundai.softeer.orange.event.common.entity.EventMetadata;
import hyundai.softeer.orange.event.common.service.EventService;
import hyundai.softeer.orange.event.dto.BriefEventDto;
import hyundai.softeer.orange.event.dto.EventDto;
import hyundai.softeer.orange.event.dto.EventFrameCreateRequest;
import hyundai.softeer.orange.event.dto.EventSearchHintDto;
import hyundai.softeer.orange.event.dto.group.EventEditGroup;
import io.swagger.v3.oas.annotations.Operation;
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 jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -49,7 +49,8 @@ public ResponseEntity<List<BriefEventDto>> getEvents(
@RequestParam(required = false) String search,
@RequestParam(required = false) String sort,
@RequestParam(required = false) Integer page,
@RequestParam(required = false) Integer size) {
@RequestParam(required = false) Integer size
) {
List<BriefEventDto> events = eventService.searchEvents(search, sort, page, size);
return ResponseEntity.ok(events);
}
Expand Down Expand Up @@ -112,5 +113,14 @@ public ResponseEntity<Void> createEventFrame(@Valid @RequestBody EventFrameCreat
return ResponseEntity.status(HttpStatus.CREATED).build();
}

@Auth({AuthRole.admin})
@GetMapping("/hints")
@Operation(summary="이벤트 힌트 목록 얻기", description = "관리자가 이벤트 댓글 열람을 위해 검색할 때 반환하는 (이벤트 id / 이름 ) 정보 목록을 얻는다.", responses = {
@ApiResponse(responseCode = "200", description = "이벤트 힌트 목록 획득")
})
public ResponseEntity<List<EventSearchHintDto>> findEventSearchHints(@RequestParam("search") String search) {
var searchHints = eventService.searchHints(search);
return ResponseEntity.ok(searchHints);
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
package hyundai.softeer.orange.event.common.repository;

import hyundai.softeer.orange.event.common.entity.EventMetadata;
import jakarta.persistence.criteria.Predicate;
import hyundai.softeer.orange.event.common.enums.EventType;
import org.springframework.data.jpa.domain.Specification;

public class EventSpecification {
public static Specification<EventMetadata> withSearch(String search) {

public static Specification<EventMetadata> searchOnName(String search) {
return searchOnName(search, true);
}

public static Specification<EventMetadata> searchOnName(String search, boolean conjunctionOnNull) {
return (metadata, query, cb) -> {
if (search == null || search.isEmpty()) return cb.conjunction();
if (search == null || search.isEmpty()) return (conjunctionOnNull ? cb.conjunction() : cb.disjunction());
return cb.like(metadata.get("name"), "%" + search + "%");
};
}

Predicate searchName = cb.like(metadata.get("name"), "%" + search + "%");
Predicate searchEventId = cb.like(metadata.get("eventId"), "%" + search + "%");
return cb.or(searchName, searchEventId);
public static Specification<EventMetadata> searchOnEventId(String search) {
return searchOnEventId(search, true);
}

public static Specification<EventMetadata> searchOnEventId(String search, boolean conjunctionOnNull) {
return (metadata, query, cb) -> {
if (search == null || search.isEmpty()) return (conjunctionOnNull ? cb.conjunction() : cb.disjunction());
return cb.like(metadata.get("eventId"), "%" + search + "%");
};
}

public static Specification<EventMetadata> isEventTypeOf(EventType eventType) {
return (metadata, query, cb) -> cb.equal(metadata.get("eventType"), eventType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import hyundai.softeer.orange.event.component.EventKeyGenerator;
import hyundai.softeer.orange.event.dto.BriefEventDto;
import hyundai.softeer.orange.event.dto.EventDto;
import hyundai.softeer.orange.event.dto.EventSearchHintDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -152,26 +153,40 @@ public List<BriefEventDto> searchEvents(String search, String sortQuery, Integer
break;
}
}
// findBy를 이용하려면 Sort와 Page를 하나로 몰아넣으면 안된다.
Sort sort = Sort.by(orders);

PageRequest pageInfo = PageRequest.of(
page != null ? page : EventConst.EVENT_DEFAULT_PAGE,
size != null ? size : EventConst.EVENT_DEFAULT_SIZE,
sort
size != null ? size : EventConst.EVENT_DEFAULT_SIZE
);

var withSearch = EventSpecification.withSearch(search);
Page<EventMetadata> eventPage = emRepository.findAll(withSearch, pageInfo);
List<EventMetadata> events = eventPage.getContent();

return events.stream().map(
it -> BriefEventDto.of(
it.getEventId(),
it.getName(),
it.getStartTime(),
it.getEndTime(),
it.getEventType()
)).toList();
var searchOnName = EventSpecification.searchOnName(search);
var searchOnEventId = EventSpecification.searchOnEventId(search);

Page<BriefEventDto> eventPage = emRepository.findBy(
searchOnName.or(searchOnEventId),
(p) -> p.as(BriefEventDto.class)
.sortBy(sort)
.page(pageInfo)
);

return eventPage.getContent();
}

/**
* 이벤트 힌트 정보를 받는다. 관리자는 이벤트 힌트 정보를 기반으로 이벤트에 대한 추가적인 정보를 조회할 수 있다.
* @param search 이벤트 검색어
* @return 검색을 위한 힌트 정보 ( id, 이름 )
*/
public List<EventSearchHintDto> searchHints(String search) {
var searchOnEventIdDefaultReject = EventSpecification.searchOnEventId(search, false);
var isDrawEvent = EventSpecification.isEventTypeOf(EventType.draw);
// 내부적으로는 모든 데이터를 fetch하는 문제가 여전히 존재.

return emRepository.findBy(
searchOnEventIdDefaultReject.and(isDrawEvent),
(q) -> q.as(EventSearchHintDto.class).all()
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public class DrawEvent {
fetch = FetchType.EAGER) // 추첨 이벤트는 항상 metadata와 함께 사용되므로 EAGER로 설정
private List<DrawEventMetadata> metadataList = new ArrayList<>();

@OneToMany(mappedBy ="drawEvent")
private List<EventParticipationInfo> participationInfoList = new ArrayList<>();


@OneToMany(mappedBy ="drawEvent")
private List<DrawEventWinningInfo> winningInfoList = new ArrayList<>();
}
23 changes: 6 additions & 17 deletions src/main/java/hyundai/softeer/orange/event/dto/BriefEventDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,28 @@
/**
* 이벤트 리스트를 위한 정보만 담고 있는 객체
*/
@Getter
public class BriefEventDto {
public interface BriefEventDto {
/**
* HD000000_000 형식으로 구성된 id 값
*/
String eventId;
String getEventId();
/**
* 이벤트의 이름
*/
private String name;
String getName();

/**
* 이벤트 시작 시간
*/
private LocalDateTime startTime;
LocalDateTime getStartTime();

/**
* 이벤트 종료 시간
*/
private LocalDateTime endTime;
LocalDateTime getEndTime();

/**
* 이벤트의 타입
*/
private EventType eventType;

public static BriefEventDto of(String eventId, String name, LocalDateTime startTime, LocalDateTime endTime, EventType eventType) {
BriefEventDto briefEventDto = new BriefEventDto();
briefEventDto.eventId = eventId;
briefEventDto.name = name;
briefEventDto.startTime = startTime;
briefEventDto.endTime = endTime;
briefEventDto.eventType = eventType;
return briefEventDto;
}
EventType getEventType();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package hyundai.softeer.orange.event.dto;

import lombok.Getter;

/**
* 관리자가 이벤트 댓글 검색 시 자동완성 영역에 제공되는 데이터
*/
public interface EventSearchHintDto {
String getEventId();
String getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import hyundai.softeer.orange.comment.entity.Comment;
import hyundai.softeer.orange.event.common.entity.EventFrame;
import hyundai.softeer.orange.event.draw.entity.DrawEventWinningInfo;
import hyundai.softeer.orange.event.draw.entity.EventParticipationInfo;
import hyundai.softeer.orange.event.fcfs.entity.FcfsEventWinningInfo;
import jakarta.persistence.*;
import lombok.AccessLevel;
Expand Down Expand Up @@ -40,6 +41,9 @@ public class EventUser {
@OneToMany(mappedBy = "eventUser")
private List<Comment> commentList = new ArrayList<>();

@OneToMany(mappedBy = "eventUser")
private List<EventParticipationInfo> participationInfoList = new ArrayList<>();

@OneToMany(mappedBy = "eventUser")
private List<DrawEventWinningInfo> drawEventWinningInfoList = new ArrayList<>();

Expand Down
Loading

0 comments on commit b945d50

Please sign in to comment.