diff --git a/module-api/src/main/java/com/kernel360/bbs/code/BBSBusinessCode.java b/module-api/src/main/java/com/kernel360/bbs/code/BBSBusinessCode.java new file mode 100644 index 00000000..e6989293 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/code/BBSBusinessCode.java @@ -0,0 +1,32 @@ +package com.kernel360.bbs.code; + +import com.kernel360.code.BusinessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum BBSBusinessCode implements BusinessCode { + + SUCCESS_REQUEST_GET_BBS(HttpStatus.OK.value(), "BBC001", "게시판 목록 조회 성공"), + SUCCESS_REQUEST_GET_BBS_VIEW(HttpStatus.OK.value(), "BBC002", "게시판 상세 조회 성공"), + SUCCESS_REQUEST_CREATED_BBS(HttpStatus.CREATED.value(), "BBC003", "게시글 작성 성공"), + SUCCESS_REQUEST_MODIFIED_BBS(HttpStatus.OK.value(), "BBC004", "게시글 수정 성공"), + SUCCESS_REQUEST_DELETE_BBS(HttpStatus.OK.value(), "BBC005", "게시글 삭제 성공"); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { return code; } + + @Override + public String getMessage() { + return message; + } +} 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 new file mode 100644 index 00000000..93574ada --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java @@ -0,0 +1,31 @@ +package com.kernel360.bbs.code; + +import com.kernel360.code.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum BBSErrorCode implements ErrorCode { + + FAILED_GET_BBS_LIST(HttpStatus.NO_CONTENT.value(), "BMC001", "게시판 목록을 찾을 수 없음."), + FAILED_GET_BBS_VIEW(HttpStatus.NO_CONTENT.value(), "BMC002", "게시글을 찾을 수 없음."); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} 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 new file mode 100644 index 00000000..4c65732d --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java @@ -0,0 +1,64 @@ +package com.kernel360.bbs.controller; + +import com.kernel360.bbs.code.BBSBusinessCode; +import com.kernel360.bbs.dto.BBSDto; +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.service.BBSService; +import com.kernel360.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +public class BBSController { + private final BBSService bbsService; + + @GetMapping("/bbs") + public ResponseEntity>> getBBS( + @RequestParam(value = "type", defaultValue = "") String type, + @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); + } + + @GetMapping("/bbs/{bbsNo}") + public ResponseEntity> getBBSView(@PathVariable Long bbsNo){ + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS_VIEW, bbsService.getBBSView(bbsNo)); + } + + @GetMapping("/bbs/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); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_CREATED_BBS); + } + + @PatchMapping("/bbs") + public ResponseEntity> modifyBBS(@RequestBody BBSDto bbsDto, @RequestHeader("Id") String id){ + + bbsService.saveBBS(bbsDto, id); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_MODIFIED_BBS); + } + + @DeleteMapping("/bbs") + public ResponseEntity> deleteBBS(@RequestParam Long bbsNo){ + + bbsService.deleteBBS(bbsNo); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_DELETE_BBS); + } +} 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 new file mode 100644 index 00000000..fc3f7b33 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java @@ -0,0 +1,86 @@ +package com.kernel360.bbs.dto; + +import com.kernel360.bbs.entity.BBS; +import com.kernel360.member.dto.MemberDto; + +import java.time.LocalDateTime; + +/** + * DTO for {@link com.kernel360.bbs.entity.BBS} + */ +public record BBSDto( + Long bbsNo, + Long upperNo, + String type, + String title, + String contents, + Boolean isVisible, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + Long viewCount, + MemberDto memberDto + ) { + + public static BBSDto of( + Long bbsNo, + Long upperNo, + String type, + String title, + String contents, + Boolean isVisible, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + Long viewCount, + MemberDto memberDto + ){ + return new BBSDto( + bbsNo, + upperNo, + type, + title, + contents, + isVisible, + createdAt, + createdBy, + modifiedAt, + modifiedBy, + viewCount, + memberDto + ); + } + + public static BBSDto from(BBS entity){ + return new BBSDto( + entity.getBbsNo(), + entity.getUpperNo(), + entity.getType(), + entity.getTitle(), + entity.getContents(), + entity.getIsVisible(), + entity.getCreatedAt(), + entity.getCreatedBy(), + entity.getModifiedAt(), + entity.getModifiedBy(), + entity.getViewCount(), + MemberDto.from(entity.getMember()) + ); + } + + +// 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/dto/BBSListDto.java b/module-api/src/main/java/com/kernel360/bbs/dto/BBSListDto.java new file mode 100644 index 00000000..ca905667 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/dto/BBSListDto.java @@ -0,0 +1,19 @@ +package com.kernel360.bbs.dto; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@RequiredArgsConstructor +public class BBSListDto { + Long bbsNo; + String type; + String title; + LocalDateTime createdAt; + String createdBy; + Long viewCount; + Long memberNo; + String id; +} diff --git a/module-api/src/main/java/com/kernel360/bbs/enumset/BBSType.java b/module-api/src/main/java/com/kernel360/bbs/enumset/BBSType.java new file mode 100644 index 00000000..54fceb00 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/enumset/BBSType.java @@ -0,0 +1,10 @@ +package com.kernel360.bbs.enumset; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum BBSType { + QNA, FREE, BOAST, RECOMMAND, NOTICE, REPLY; + + String type; +} 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 new file mode 100644 index 00000000..b60ae6c4 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java @@ -0,0 +1,16 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.entity.BBS; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BBSRepository extends BBSRepositoryJPA, BBSRepositoryDSL { + Page getBBSWithCondition(String type, String keyword, Pageable pageable); + + BBS findOneByBbsNo(Long bbsNo); + + Page findAllByUpperNo(Long upperNo, Pageable pageable); + + void deleteByBbsNo(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 new file mode 100644 index 00000000..6adfa692 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java @@ -0,0 +1,9 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.dto.BBSListDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BBSRepositoryDSL { + Page getBBSWithCondition(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 new file mode 100644 index 00000000..c6b2d1d3 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java @@ -0,0 +1,81 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.enumset.BBSType; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +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 java.util.List; +import java.util.Objects; + +import static com.kernel360.bbs.entity.QBBS.bBS; +import static com.kernel360.member.entity.QMember.member; +import static com.querydsl.core.types.Projections.fields; +import static org.springframework.util.StringUtils.hasText; + +@RequiredArgsConstructor +public class BBSRepositoryDSLImpl implements BBSRepositoryDSL { + + private final JPAQueryFactory queryFactory; + + @Override + public Page getBBSWithCondition(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()) + .fetch(); + + Long totalCount = queryFactory + .select(bBS.count()) + .from(bBS) + .where( + finalPredicate, + keywordContains(keyword) + ) + .fetchOne(); + + return new PageImpl<>(bbs, pageable, totalCount); + } + + private JPAQuery getBBSListWithMember() { + return queryFactory + .select(fields(BBSListDto.class, + bBS.bbsNo, + bBS.title, + bBS.type, + bBS.createdAt, + bBS.createdBy, + bBS.viewCount, + bBS.member.memberNo, + bBS.member.id + ) + + ) + .from(bBS) + .join(member).on(bBS.member.memberNo.eq(member.memberNo)); + } + + private BooleanExpression keywordContains(String keyword) { //우선 참이 된다면 하위 조건 결과는 포함 안됨. + return hasText(keyword) ? + bBS.title.contains(keyword) + .or(bBS.contents.contains(keyword)) + .or(bBS.createdBy.eq(keyword)) + : null; + } + +} 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 new file mode 100644 index 00000000..bab40728 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java @@ -0,0 +1,48 @@ +package com.kernel360.bbs.service; + +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.member.dto.MemberDto; +import com.kernel360.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class BBSService { + private final BBSRepository bbsRepository; + private final MemberService memberService; + + public Page getBBSWithCondition(String type, String keyword, Pageable pageable) { + + return bbsRepository.getBBSWithCondition(type, keyword, pageable); + } + + public BBSDto getBBSView(Long bbsNo) { + + return BBSDto.from(bbsRepository.findOneByBbsNo(bbsNo)); + } + + public Page getBBSReply(Long upperNo, Pageable pageable) { + + return bbsRepository.findAllByUpperNo(upperNo, pageable).map(BBSDto::from); + } + + @Transactional + public void saveBBS(BBSDto bbsDto, String id) { + + MemberDto memberDto = memberService.findByMemberId(id); + + bbsRepository.save(BBS.save(bbsDto.bbsNo(), bbsDto.upperNo(), bbsDto.type(), bbsDto.title(), bbsDto.contents(), true, 0L, memberDto.toEntity())); + } + + @Transactional + public void deleteBBS(Long bbsNo) { + bbsRepository.deleteByBbsNo(bbsNo); + } +} diff --git a/module-api/src/main/java/com/kernel360/member/config/AuditConfig.java b/module-api/src/main/java/com/kernel360/global/config/AuditConfig.java similarity index 83% rename from module-api/src/main/java/com/kernel360/member/config/AuditConfig.java rename to module-api/src/main/java/com/kernel360/global/config/AuditConfig.java index 802402cf..20589539 100644 --- a/module-api/src/main/java/com/kernel360/member/config/AuditConfig.java +++ b/module-api/src/main/java/com/kernel360/global/config/AuditConfig.java @@ -1,4 +1,4 @@ -package com.kernel360.member.config; +package com.kernel360.global.config; import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Configuration; @@ -13,7 +13,7 @@ public class AuditConfig implements AuditorAware { @Override public Optional getCurrentAuditor() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); - String createId = Optional.ofNullable(request.getParameter("id")).orElse("admin"); + String createId = Optional.ofNullable(request.getHeader("Id")).orElse("admin"); return Optional.of(createId); } diff --git a/module-api/src/main/java/com/kernel360/member/controller/MemberController.java b/module-api/src/main/java/com/kernel360/member/controller/MemberController.java index 531537a4..f6249e25 100644 --- a/module-api/src/main/java/com/kernel360/member/controller/MemberController.java +++ b/module-api/src/main/java/com/kernel360/member/controller/MemberController.java @@ -92,7 +92,7 @@ public ResponseEntity> sendMemberIdByEmail(@RequestBody Memb @PostMapping("/find-password") public ResponseEntity> sendPasswordResetUriByEmail(@RequestBody MemberCredentialDto dto) { //--입력받은 아이디를 데이터베이스에 조회, 없으면 예외 발생--/ - MemberDto memberDto = memberService.findByMemberId(dto.memberId()); + MemberDto memberDto = memberService.findOneByIdForAccountTypeByPlatform(dto.memberId()); //--유효성이 검증된 아이디에 대해서 만료시간이 있는 비밀번호 초기화 (호스트 + UUID) 링크 생성 --// String resetUri = findCredentialService.generatePasswordResetPageUri(memberDto); //-- 가입시 입력한 이메일로 비밀번호 초기화 이메일 발송 --// diff --git a/module-api/src/main/java/com/kernel360/member/service/MemberService.java b/module-api/src/main/java/com/kernel360/member/service/MemberService.java index d833f3e8..0792169d 100644 --- a/module-api/src/main/java/com/kernel360/member/service/MemberService.java +++ b/module-api/src/main/java/com/kernel360/member/service/MemberService.java @@ -238,7 +238,7 @@ public MemberDto findByEmail(String email) { } @Transactional(readOnly = true) - public MemberDto findByMemberId(String memberId) { + public MemberDto findOneByIdForAccountTypeByPlatform(String memberId) { Member member = memberRepository.findOneByIdForAccountTypeByPlatform(memberId); if (member == null) { throw new BusinessException(MemberErrorCode.FAILED_FIND_MEMBER_INFO); @@ -284,4 +284,9 @@ public boolean validatePassword(String password, String token) { return member.getPassword().equals(ConvertSHA256.convertToSHA256(password)); } + + public MemberDto findByMemberId(String id) { + Member member = memberRepository.findOneById(id); + return MemberDto.from(member); + } } diff --git a/module-api/src/main/resources/db/migration/V1.0.15__create_BBS.sql b/module-api/src/main/resources/db/migration/V1.0.15__create_BBS.sql new file mode 100644 index 00000000..8384b6e9 --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.15__create_BBS.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS bbs ( + bbs_no BIGSERIAL PRIMARY KEY, + upper_no BIGINT, + member_no BIGINT NOT NULL, + type VARCHAR NOT NULL, + title VARCHAR NOT NULL, + contents VARCHAR NOT NULL, + is_visible BOOL NOT NULL DEFAULT TRUE, + view_count BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL, + created_by VARCHAR NOT NULL, + modified_at TIMESTAMP, + modified_by VARCHAR, + FOREIGN KEY (member_no) REFERENCES Member (member_no) + ); + +AlTER SEQUENCE bbs_bbs_no_seq increment by 50; diff --git a/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java b/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java index 12bde2d9..6899198d 100644 --- a/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java +++ b/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java @@ -148,7 +148,7 @@ class MemberControllerTest extends ControllerTest { MemberCredentialDto credentialDto = MemberCredentialDto.of(null, null, "kernel360", null); MemberDto memberDto = MemberDto.of("testMemberId", "testPassword001"); - given(memberService.findByMemberId(credentialDto.memberId())).willReturn(memberDto); + given(memberService.findOneByIdForAccountTypeByPlatform(credentialDto.memberId())).willReturn(memberDto); given(findCredentialService.generatePasswordResetPageUri(memberDto)).willReturn("테스트 URI"); ObjectMapper objectMapper = new ObjectMapper(); diff --git a/module-domain/src/main/java/com/kernel360/bbs/entity/BBS.java b/module-domain/src/main/java/com/kernel360/bbs/entity/BBS.java new file mode 100644 index 00000000..d250ce54 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/bbs/entity/BBS.java @@ -0,0 +1,52 @@ +package com.kernel360.bbs.entity; + +import com.kernel360.base.BaseEntity; +import com.kernel360.member.entity.Member; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "bbs") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BBS extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bbs_id_gen") + @SequenceGenerator(name = "bbs_id_gen", sequenceName = "bbs_bbs_no_seq") + private Long bbsNo; + + private Long upperNo; + + private String type; + + private String title; + + private String contents; + + private Boolean isVisible; + + private Long viewCount; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "member_no", nullable = false, updatable = false) + private Member member; + + private BBS(Long bbsNo, Long upperNo, String type, String title, String contents, Boolean isVisible, Long viewConut, Member member) { + this.bbsNo = bbsNo; + this.upperNo = upperNo; + this.type = type; + this.title = title; + this.contents = contents; + this.isVisible = isVisible; + this.member = member; + this.viewCount = viewConut; + } + + public static BBS save(Long bbsNo, Long upperNo, String type, String title, String contents, Boolean isVisible, Long viewCount, Member member){ + + return new BBS (bbsNo, upperNo, type, title, contents, isVisible, viewCount, member); + } + +} diff --git a/module-domain/src/main/java/com/kernel360/bbs/repository/BBSRepositoryJPA.java b/module-domain/src/main/java/com/kernel360/bbs/repository/BBSRepositoryJPA.java new file mode 100644 index 00000000..b8d0a451 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/bbs/repository/BBSRepositoryJPA.java @@ -0,0 +1,16 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.entity.BBS; +import jakarta.persistence.Id; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BBSRepositoryJPA extends JpaRepository { + BBS findOneByBbsNo(Long bbsNo); + + Page findAllByUpperNo(Long upperNo, Pageable pageable); + + + void deleteByBbsNo(Long bbsNo); +}