diff --git a/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java b/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java index 93574ada..58995dcb 100644 --- a/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java +++ b/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java @@ -8,7 +8,8 @@ public enum BBSErrorCode implements ErrorCode { FAILED_GET_BBS_LIST(HttpStatus.NO_CONTENT.value(), "BMC001", "게시판 목록을 찾을 수 없음."), - FAILED_GET_BBS_VIEW(HttpStatus.NO_CONTENT.value(), "BMC002", "게시글을 찾을 수 없음."); + FAILED_GET_BBS_VIEW(HttpStatus.NO_CONTENT.value(), "BMC002", "게시글을 찾을 수 없음."), + UNSUPPORTED_API_VERSION(HttpStatus.BAD_REQUEST.value(), "BMC003", "지원하지않는 API버전. API 버전을 다시 확인하세요."); private final int status; private final String code; diff --git a/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java b/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java index 4c65732d..b483fc70 100644 --- a/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java +++ b/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java @@ -8,54 +8,76 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @RequiredArgsConstructor +@RequestMapping("/bbs") public class BBSController { private final BBSService bbsService; - @GetMapping("/bbs") - public ResponseEntity>> getBBS( + @GetMapping("/v1") + public ResponseEntity>> getBBSv1( @RequestParam(value = "type", defaultValue = "") String type, - @RequestParam(value = "keyword", required = false) String keyword, Pageable pageable + @RequestParam(value = "keyword", required = false) String keyword, + Pageable pageable ){ - Page result = bbsService.getBBSWithCondition(type, keyword, pageable); - return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS, result); + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS, bbsService.getBBSWithConditionByPage(type, keyword, pageable)); } - @GetMapping("/bbs/{bbsNo}") + @GetMapping("/v2") + public ResponseEntity>> getBBSv2( + @RequestParam(value = "type", defaultValue = "") String type, + @RequestParam(value = "keyword", required = false) String keyword, + Pageable pageable + ){ + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS, bbsService.getBBSWithConditionBySlice(type, keyword, pageable)); + } + + @GetMapping("/{bbsNo}") public ResponseEntity> getBBSView(@PathVariable Long bbsNo){ return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS_VIEW, bbsService.getBBSView(bbsNo)); } - @GetMapping("/bbs/reply") + @GetMapping("/reply") public ResponseEntity>> getBBSReply(@RequestParam Long upperNo, Pageable pageable){ return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS_VIEW, bbsService.getBBSReply(upperNo, pageable)); } - @PostMapping("/bbs") - public ResponseEntity> saveBBS(@RequestBody BBSDto bbsDto, @RequestHeader("Id") String id){ - - bbsService.saveBBS(bbsDto, id); + @PostMapping("") + public ResponseEntity> saveBBS( + @RequestPart BBSDto bbsDto, + @RequestHeader("Id") String id, + @RequestPart(required = false) List files + ){ + bbsService.saveBBS(bbsDto, id, files); return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_CREATED_BBS); } - @PatchMapping("/bbs") - public ResponseEntity> modifyBBS(@RequestBody BBSDto bbsDto, @RequestHeader("Id") String id){ + @PatchMapping("") + public ResponseEntity> modifyBBS( + @RequestPart BBSDto bbsDto, + @RequestHeader("Id") String id, + @RequestPart(required = false) List files + ){ - bbsService.saveBBS(bbsDto, id); + bbsService.saveBBS(bbsDto, id, files); return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_MODIFIED_BBS); } - @DeleteMapping("/bbs") - public ResponseEntity> deleteBBS(@RequestParam Long bbsNo){ + @DeleteMapping("/{bbsNo}") + public ResponseEntity> deleteBBS(@PathVariable Long bbsNo){ bbsService.deleteBBS(bbsNo); diff --git a/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java b/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java index fc3f7b33..657a8788 100644 --- a/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java +++ b/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java @@ -1,14 +1,17 @@ package com.kernel360.bbs.dto; import com.kernel360.bbs.entity.BBS; +import com.kernel360.file.entity.File; import com.kernel360.member.dto.MemberDto; import java.time.LocalDateTime; +import java.util.List; /** * DTO for {@link com.kernel360.bbs.entity.BBS} */ public record BBSDto( + Long bbsNo, Long upperNo, String type, @@ -20,7 +23,8 @@ public record BBSDto( LocalDateTime modifiedAt, String modifiedBy, Long viewCount, - MemberDto memberDto + MemberDto memberDto, + List files ) { public static BBSDto of( @@ -35,7 +39,8 @@ public static BBSDto of( LocalDateTime modifiedAt, String modifiedBy, Long viewCount, - MemberDto memberDto + MemberDto memberDto, + List files ){ return new BBSDto( bbsNo, @@ -49,11 +54,12 @@ public static BBSDto of( modifiedAt, modifiedBy, viewCount, - memberDto + memberDto, + files ); } - public static BBSDto from(BBS entity){ + public static BBSDto from(BBS entity, List byReferenceTypeAndReferenceNo){ return new BBSDto( entity.getBbsNo(), entity.getUpperNo(), @@ -66,21 +72,9 @@ public static BBSDto from(BBS entity){ entity.getModifiedAt(), entity.getModifiedBy(), entity.getViewCount(), - MemberDto.from(entity.getMember()) + MemberDto.from(entity.getMember()), + byReferenceTypeAndReferenceNo ); } - -// public BBS toEntity() { -// return BBS.create( -// this.bbsNo(), -// this.upperNo(), -// this.title(), -// this.contents() -// -// ); -// } - - - } \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java index b60ae6c4..3d776c6f 100644 --- a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java @@ -4,9 +4,11 @@ import com.kernel360.bbs.entity.BBS; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; public interface BBSRepository extends BBSRepositoryJPA, BBSRepositoryDSL { - Page getBBSWithCondition(String type, String keyword, Pageable pageable); + Page getBBSWithConditionByPage(String type, String keyword, Pageable pageable); + Slice getBBSWithConditionBySlice(String type, String keyword, Pageable pageable); BBS findOneByBbsNo(Long bbsNo); diff --git a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java index 6adfa692..3654ce39 100644 --- a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java @@ -3,7 +3,10 @@ import com.kernel360.bbs.dto.BBSListDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; public interface BBSRepositoryDSL { - Page getBBSWithCondition(String bbsType, String keyword, Pageable pageable); + Page getBBSWithConditionByPage(String bbsType, String keyword, Pageable pageable); + + Slice getBBSWithConditionBySlice(String bbsType, String keyword, Pageable pageable); } diff --git a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java index c6b2d1d3..122d099b 100644 --- a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java @@ -7,12 +7,9 @@ import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.*; import java.util.List; -import java.util.Objects; import static com.kernel360.bbs.entity.QBBS.bBS; import static com.kernel360.member.entity.QMember.member; @@ -25,20 +22,20 @@ public class BBSRepositoryDSLImpl implements BBSRepositoryDSL { private final JPAQueryFactory queryFactory; @Override - public Page getBBSWithCondition(String type, String keyword, Pageable pageable) { + public Page getBBSWithConditionByPage(String type, String keyword, Pageable pageable) { Predicate finalPredicate = bBS.isVisible.eq(true) - .and(bBS.type.eq(BBSType.valueOf(type).name())); + .and(bBS.type.eq(BBSType.valueOf(type).name())); List bbs = getBBSListWithMember(). - where( - finalPredicate, - keywordContains(keyword) - ) - .orderBy(bBS.createdAt.desc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + where( + finalPredicate, + keywordContains(keyword) + ) + .orderBy(bBS.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + .fetch(); Long totalCount = queryFactory .select(bBS.count()) @@ -52,6 +49,32 @@ public Page getBBSWithCondition(String type, String keyword, Pageabl return new PageImpl<>(bbs, pageable, totalCount); } + @Override + public Slice getBBSWithConditionBySlice(String type, String keyword, Pageable pageable) { + + Predicate finalPredicate = bBS.isVisible.eq(true) + .and(bBS.type.eq(BBSType.valueOf(type).name())); + + List bbs = getBBSListWithMember(). + where( + finalPredicate, + keywordContains(keyword) + ) + .orderBy(bBS.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + .fetch(); + + boolean hasNext = false; + int pageSize = pageable.getPageSize(); + if(bbs.size() > pageSize){ + bbs.remove(pageSize); + hasNext = true; + } + + return new SliceImpl<>(bbs, pageable, hasNext); + } + private JPAQuery getBBSListWithMember() { return queryFactory .select(fields(BBSListDto.class, @@ -61,8 +84,8 @@ private JPAQuery getBBSListWithMember() { bBS.createdAt, bBS.createdBy, bBS.viewCount, - bBS.member.memberNo, - bBS.member.id + member.memberNo, + member.id ) ) diff --git a/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java b/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java index bab40728..7bd5f0e8 100644 --- a/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java +++ b/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java @@ -1,48 +1,96 @@ package com.kernel360.bbs.service; +import com.kernel360.bbs.code.BBSErrorCode; import com.kernel360.bbs.dto.BBSDto; import com.kernel360.bbs.dto.BBSListDto; import com.kernel360.bbs.entity.BBS; import com.kernel360.bbs.repository.BBSRepository; +import com.kernel360.exception.BusinessException; +import com.kernel360.file.entity.File; +import com.kernel360.file.entity.FileReferType; +import com.kernel360.file.repository.FileRepository; +import com.kernel360.member.code.MemberErrorCode; import com.kernel360.member.dto.MemberDto; import com.kernel360.member.service.MemberService; +import com.kernel360.utils.file.FileUtils; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Slf4j @Service @RequiredArgsConstructor public class BBSService { + private final FileUtils fileUtils; private final BBSRepository bbsRepository; private final MemberService memberService; + private final FileRepository fileRepository; + + @Value("${aws.s3.bucket.url}") + private String bucketUrl; + private static final String REFERENCE_TYPE = "BBS"; + + public Page getBBSWithConditionByPage(String type, String keyword, Pageable pageable) { - public Page getBBSWithCondition(String type, String keyword, Pageable pageable) { + return bbsRepository.getBBSWithConditionByPage(type, keyword, pageable); + } + + public Slice getBBSWithConditionBySlice(String type, String keyword, Pageable pageable) { - return bbsRepository.getBBSWithCondition(type, keyword, pageable); + return bbsRepository.getBBSWithConditionBySlice(type, keyword, pageable); } public BBSDto getBBSView(Long bbsNo) { + BBS entity = Optional.of(bbsRepository.findOneByBbsNo(bbsNo)) + .orElseThrow(() -> new BusinessException(BBSErrorCode.FAILED_GET_BBS_VIEW)); + + BBSDto result = BBSDto.from(entity, fileRepository.findByReferenceTypeAndReferenceNo(REFERENCE_TYPE, entity.getBbsNo())); - return BBSDto.from(bbsRepository.findOneByBbsNo(bbsNo)); + return result; } public Page getBBSReply(Long upperNo, Pageable pageable) { - return bbsRepository.findAllByUpperNo(upperNo, pageable).map(BBSDto::from); + return bbsRepository.findAllByUpperNo(upperNo, pageable).map(entity -> BBSDto.from(entity, fileRepository.findByReferenceTypeAndReferenceNo(REFERENCE_TYPE, entity.getBbsNo()))); } @Transactional - public void saveBBS(BBSDto bbsDto, String id) { + public void saveBBS(BBSDto bbsDto, String id, List files) { - MemberDto memberDto = memberService.findByMemberId(id); + MemberDto memberDto = Optional.of(memberService.findByMemberId(id)) + .orElseThrow(() -> new BusinessException(MemberErrorCode.FAILED_FIND_MEMBER_INFO)); - bbsRepository.save(BBS.save(bbsDto.bbsNo(), bbsDto.upperNo(), bbsDto.type(), bbsDto.title(), bbsDto.contents(), true, 0L, memberDto.toEntity())); + BBS bbs = bbsRepository.save(BBS.save(bbsDto.bbsNo(), bbsDto.upperNo(), bbsDto.type(), bbsDto.title(), bbsDto.contents(), true, 0L, memberDto.toEntity())); + + if(Objects.nonNull(files)){ + uploadFiles(files, bbs.getBbsNo()); + } } @Transactional public void deleteBBS(Long bbsNo) { + bbsRepository.deleteByBbsNo(bbsNo); } + + private void uploadFiles(List files, Long bbsNo) { + files.stream().forEach(file -> { + String path = String.join("/", FileReferType.BBS.getDomain(), bbsNo.toString()); + String fileKey = fileUtils.upload(path, file); + String fileUrl = String.join("/", bucketUrl, fileKey); + + File fileInfo = fileRepository.save(File.of(null, file.getOriginalFilename(), fileKey, fileUrl, FileReferType.BBS.getCode(), bbsNo)); + log.info("게시판 파일 등록 -> file_no {}", fileInfo.getFileNo()); + }); + } } diff --git a/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java b/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java index 8f1887f4..5765e04d 100644 --- a/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java +++ b/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java @@ -5,7 +5,8 @@ @RequiredArgsConstructor public enum FileReferType { REVIEW("review", "RV"), - WASHZONE_REVIEW("washzone-review", "WZRV"); + WASHZONE_REVIEW("washzone-review", "WZRV"), + BBS("bbs", "BBS"); private final String domain; private final String code;