diff --git a/module-auth/src/main/java/com/inhabas/api/auth/domain/oauth2/member/domain/entity/Member.java b/module-auth/src/main/java/com/inhabas/api/auth/domain/oauth2/member/domain/entity/Member.java index a35aa7ca..4c73b7ef 100644 --- a/module-auth/src/main/java/com/inhabas/api/auth/domain/oauth2/member/domain/entity/Member.java +++ b/module-auth/src/main/java/com/inhabas/api/auth/domain/oauth2/member/domain/entity/Member.java @@ -22,7 +22,7 @@ @Entity @Table( - name = "user", + name = "USER", uniqueConstraints = { @UniqueConstraint( name = "UNIQUE_PROVIDER_UID", diff --git a/resource-server/src/main/java/com/inhabas/api/config/SwaggerConfig.java b/resource-server/src/main/java/com/inhabas/api/config/SwaggerConfig.java index aeaf9386..b626b410 100644 --- a/resource-server/src/main/java/com/inhabas/api/config/SwaggerConfig.java +++ b/resource-server/src/main/java/com/inhabas/api/config/SwaggerConfig.java @@ -98,6 +98,15 @@ public GroupedOpenApi getIBASApi() { .build(); } + @Bean + public GroupedOpenApi getBoardApi() { + + return GroupedOpenApi.builder() + .group("게시판 관련") + .pathsToMatch("/board/**", "/**/**/**/comment/**", "/**/**/**/comments") + .build(); + } + @Bean @Profile("local") public OpenAPI localOpenAPI() { diff --git a/resource-server/src/main/java/com/inhabas/api/config/WebSecurityConfig.java b/resource-server/src/main/java/com/inhabas/api/config/WebSecurityConfig.java index d62717ad..83374417 100644 --- a/resource-server/src/main/java/com/inhabas/api/config/WebSecurityConfig.java +++ b/resource-server/src/main/java/com/inhabas/api/config/WebSecurityConfig.java @@ -59,6 +59,7 @@ public class WebSecurityConfig { private static final String[] AUTH_WHITELIST_CLUB_ACTIVITY = { "/club/activity/**", "/club/activities" }; + private static final String[] AUTH_WHITELIST_NORMAL_BOARD = {"/board/count"}; @Order(1) @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) @@ -85,6 +86,7 @@ public void configure(WebSecurity web) throws Exception { .antMatchers(HttpMethod.GET, AUTH_WHITELIST_POLICY) .antMatchers(HttpMethod.GET, AUTH_WHITELIST_SIGNUP) .antMatchers(HttpMethod.GET, AUTH_WHITELIST_CLUB) + .antMatchers(HttpMethod.GET, AUTH_WHITELIST_NORMAL_BOARD) .antMatchers(AUTH_WHITELIST_SWAGGER) .antMatchers(AUTH_WHITELIST_STATIC) .antMatchers(AUTH_WHITELIST_PATH); diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/domain/AlbumBoard.java b/resource-server/src/main/java/com/inhabas/api/domain/board/domain/AlbumBoard.java index e39d42d9..18430333 100644 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/domain/AlbumBoard.java +++ b/resource-server/src/main/java/com/inhabas/api/domain/board/domain/AlbumBoard.java @@ -21,6 +21,7 @@ @Table(name = "ALBUM_BOARD") @NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) +@Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorValue("ALBUM") public class AlbumBoard extends BaseBoard { diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/domain/NormalBoard.java b/resource-server/src/main/java/com/inhabas/api/domain/board/domain/NormalBoard.java deleted file mode 100644 index a64a6d9a..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/domain/NormalBoard.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.inhabas.api.domain.board.domain; - -import java.util.*; - -import javax.persistence.*; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; -import com.inhabas.api.domain.board.domain.valueObject.Content; -import com.inhabas.api.domain.board.domain.valueObject.Title; -import com.inhabas.api.domain.board.exception.OnlyWriterModifiableException; -import com.inhabas.api.domain.comment.domain.Comment; -import com.inhabas.api.domain.file.domain.BoardFile; - -@Entity -@Table(name = "normal_board") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@EntityListeners(AuditingEntityListener.class) -@Inheritance(strategy = InheritanceType.JOINED) -@DiscriminatorValue("NORMAL") -public class NormalBoard extends BaseBoard { - - @Embedded protected Content content; - - @OneToMany(mappedBy = "parentBoard", cascade = CascadeType.ALL, orphanRemoval = true) - protected List comments = new ArrayList<>(); - - /* constructor */ - - public NormalBoard(String title, String contents) { - this.title = new Title(title); - this.content = new Content(contents); - } - - /* getter */ - - public String getContent() { - return content.getValue(); - } - - public List getFiles() { - return Collections.unmodifiableList(files); - } - - /* relation method */ - - public NormalBoard addFiles(Set UploadFiles) { - if (Objects.nonNull(UploadFiles)) UploadFiles.forEach(this::addFile); - - return this; - } - - public void addFile(BoardFile uploadFile) { - files.add(uploadFile); - uploadFile.toBoard(this); - } - - public void addComment(Comment newComment) { - comments.add(newComment); - } - - public void modify(String title, String contents, Member writer) { - - if (cannotModifiableBy(writer)) { - throw new OnlyWriterModifiableException(); - } - - this.title = new Title(title); - this.content = new Content(contents); - } - - public boolean cannotModifiableBy(Member writer) { - return !this.writer.equals(writer); - } -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/BoardCountDto.java b/resource-server/src/main/java/com/inhabas/api/domain/board/dto/BoardCountDto.java new file mode 100644 index 00000000..e40344c3 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/board/dto/BoardCountDto.java @@ -0,0 +1,23 @@ +package com.inhabas.api.domain.board.dto; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class BoardCountDto { + + @NotNull private String menuName; + + @NotNull @PositiveOrZero private Integer count; + + @Builder + public BoardCountDto(String menuName, Integer count) { + this.menuName = menuName; + this.count = count; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/BoardDto.java b/resource-server/src/main/java/com/inhabas/api/domain/board/dto/BoardDto.java deleted file mode 100644 index a06da371..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/BoardDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.inhabas.api.domain.board.dto; - -import java.time.LocalDateTime; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.inhabas.api.domain.menu.domain.valueObject.MenuId; - -@Getter -@AllArgsConstructor -public class BoardDto { - private Integer id; - private String title; - private String contents; - private String writerName; - @JsonUnwrapped private MenuId menuId; - - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") - private LocalDateTime created; - - private LocalDateTime updated; -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/SaveBoardDto.java b/resource-server/src/main/java/com/inhabas/api/domain/board/dto/SaveBoardDto.java deleted file mode 100644 index 84afb64d..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/SaveBoardDto.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.inhabas.api.domain.board.dto; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import com.inhabas.api.domain.menu.domain.valueObject.MenuId; -import org.hibernate.validator.constraints.Length; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class SaveBoardDto { - @NotBlank(message = "제목을 입력하세요.") - @Length(max = 100, message = "제목은 최대 100자입니다.") - private String title; - - @NotBlank(message = "본문을 입력하세요.") - private String contents; - - @NotNull private MenuId menuId; - - public SaveBoardDto(String title, String contents, MenuId menuId) { - this.title = title; - this.contents = contents; - this.menuId = menuId; - } -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/UpdateBoardDto.java b/resource-server/src/main/java/com/inhabas/api/domain/board/dto/UpdateBoardDto.java deleted file mode 100644 index 8f5aaec8..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/dto/UpdateBoardDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.inhabas.api.domain.board.dto; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -public class UpdateBoardDto { - @NotNull private Long id; - - @NotBlank(message = "제목을 입력하세요.") - @Size(max = 100, message = "제목은 최대 100자입니다.") - private String title; - - @NotBlank(message = "본문을 입력하세요") - private String contents; - - public UpdateBoardDto(Long id, String title, String contents) { - this.id = id; - this.title = title; - this.contents = contents; - } -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepository.java b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepository.java index 8fae3619..b6288f53 100644 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepository.java +++ b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepository.java @@ -4,4 +4,5 @@ import com.inhabas.api.domain.board.domain.BaseBoard; -public interface BaseBoardRepository extends JpaRepository {} +public interface BaseBoardRepository + extends JpaRepository, BaseBoardRepositoryCustom {} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepositoryCustom.java b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepositoryCustom.java new file mode 100644 index 00000000..daa552f2 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.inhabas.api.domain.board.repository; + +import java.util.List; + +import com.inhabas.api.domain.board.dto.BoardCountDto; + +public interface BaseBoardRepositoryCustom { + + List countRowsGroupByMenuName(Integer menuGroupId); +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepositoryImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepositoryImpl.java new file mode 100644 index 00000000..8b8cb250 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/BaseBoardRepositoryImpl.java @@ -0,0 +1,32 @@ +package com.inhabas.api.domain.board.repository; + +import static com.inhabas.api.domain.board.domain.QBaseBoard.baseBoard; +import static com.inhabas.api.domain.menu.domain.QMenu.*; + +import java.util.List; + +import lombok.RequiredArgsConstructor; + +import com.inhabas.api.domain.board.dto.BoardCountDto; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; + +@RequiredArgsConstructor +public class BaseBoardRepositoryImpl implements BaseBoardRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List countRowsGroupByMenuName(Integer menuGroupId) { + return queryFactory + .select( + Projections.constructor( + BoardCountDto.class, menu.name.value, baseBoard.id.count().intValue())) + .from(menu) + .leftJoin(baseBoard) + .on(menu.id.eq(baseBoard.menu.id)) + .where(menu.menuGroup.id.eq(menuGroupId)) + .groupBy(menu.name) + .fetch(); + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryCustom.java b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryCustom.java deleted file mode 100644 index 90c5fedb..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryCustom.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.inhabas.api.domain.board.repository; - -import java.util.Optional; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import com.inhabas.api.domain.board.dto.BoardDto; - -public interface NormalBoardRepositoryCustom { - - Page findAllByMenuId(Integer menuId, Pageable pageable); - - Optional findDtoById(Long id); -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryImpl.java deleted file mode 100644 index 59d9df4a..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.inhabas.api.domain.board.repository; - -import static com.inhabas.api.auth.domain.oauth2.member.domain.entity.QMember.member; -import static com.inhabas.api.domain.board.domain.QNormalBoard.normalBoard; - -import java.util.List; -import java.util.Optional; - -import lombok.RequiredArgsConstructor; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; - -import com.inhabas.api.domain.board.dto.BoardDto; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.jpa.impl.JPAQueryFactory; - -@RequiredArgsConstructor -public class NormalBoardRepositoryImpl implements NormalBoardRepositoryCustom { - - private final JPAQueryFactory queryFactory; - - @Override - public Page findAllByMenuId(Integer menuId, Pageable pageable) { - List results = - queryFactory - .select( - Projections.constructor( - BoardDto.class, - normalBoard.id, - normalBoard.title.value, - Expressions.asString("").as("content"), - member.name.value, - normalBoard.menu.id, - normalBoard.dateCreated, - normalBoard.dateUpdated)) - .from(normalBoard) - .innerJoin(member) - .on(eqMemberId()) - .where(eqMenuId(menuId)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(normalBoard.dateCreated.desc()) - .fetch(); - - return new PageImpl<>(results, pageable, this.getCount(menuId)); - } - - private BooleanExpression eqMenuId(Integer menuId) { - return normalBoard.menu.id.eq(menuId); - } - - // 캐시 필요함. - private Integer getCount(Integer menuId) { - return queryFactory.selectFrom(normalBoard).where(eqMenuId(menuId)).fetch().size(); - } - - @Override - public Optional findDtoById(Long id) { - - BoardDto target = - queryFactory - .select( - Projections.constructor( - BoardDto.class, - Expressions.asNumber(id).as("id"), - normalBoard.title.value, - normalBoard.content.value, - member.name.value, - normalBoard.menu.id, - normalBoard.dateCreated, - normalBoard.dateUpdated)) - .from(normalBoard) - .innerJoin(member) - .on(eqMemberId()) - .where(normalBoard.id.eq(id)) - .fetchOne(); - - return Optional.ofNullable(target); - } - - private BooleanExpression eqMemberId() { - return normalBoard.writer.id.eq(member.id); - } -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/usecase/BoardService.java b/resource-server/src/main/java/com/inhabas/api/domain/board/usecase/BoardService.java deleted file mode 100644 index ff870c68..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/usecase/BoardService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.inhabas.api.domain.board.usecase; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import com.inhabas.api.domain.board.dto.BoardDto; -import com.inhabas.api.domain.board.dto.SaveBoardDto; -import com.inhabas.api.domain.board.dto.UpdateBoardDto; - -public interface BoardService { - Long write(Long memberId, SaveBoardDto saveBoardDto); - - Long update(Long memberId, UpdateBoardDto updateBoardDto); - - void delete(Long memberId, Long boardId); - - BoardDto getBoard(Long boardId); - - Page getBoardList(Integer menuId, Pageable pageable); -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/usecase/BoardServiceImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/board/usecase/BoardServiceImpl.java deleted file mode 100644 index 53bb6fed..00000000 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/usecase/BoardServiceImpl.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.inhabas.api.domain.board.usecase; - -import javax.transaction.Transactional; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -import com.inhabas.api.auth.domain.error.businessException.NotFoundException; -import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; -import com.inhabas.api.auth.domain.oauth2.member.domain.exception.MemberNotFoundException; -import com.inhabas.api.auth.domain.oauth2.member.repository.MemberRepository; -import com.inhabas.api.domain.board.domain.NormalBoard; -import com.inhabas.api.domain.board.dto.BoardDto; -import com.inhabas.api.domain.board.dto.SaveBoardDto; -import com.inhabas.api.domain.board.dto.UpdateBoardDto; -import com.inhabas.api.domain.board.exception.OnlyWriterModifiableException; -import com.inhabas.api.domain.board.repository.NormalBoardRepository; -import com.inhabas.api.domain.menu.repository.MenuRepository; - -@Service -@Slf4j -@Transactional -@RequiredArgsConstructor -public class BoardServiceImpl implements BoardService { - - private final NormalBoardRepository boardRepository; - private final MenuRepository menuRepository; - private final MemberRepository memberRepository; - - @Override - public Long write(Long memberId, SaveBoardDto saveBoardDto) { - - Member writer = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); - - NormalBoard normalBoard = - new NormalBoard(saveBoardDto.getTitle(), saveBoardDto.getContents()) - .writtenBy(writer, NormalBoard.class); - - return boardRepository.save(normalBoard).getId(); - } - - @Override - public Long update(Long memberId, UpdateBoardDto updateBoardDto) { - - NormalBoard savedBoard = - boardRepository.findById(updateBoardDto.getId()).orElseThrow(NotFoundException::new); - Member writer = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); - - savedBoard.modify(updateBoardDto.getTitle(), updateBoardDto.getContents(), writer); - - return boardRepository.save(savedBoard).getId(); - } - - @Override - public void delete(Long memberId, Long boardId) { - - NormalBoard board = boardRepository.findById(boardId).orElseThrow(NotFoundException::new); - Member writer = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); - - if (board.cannotModifiableBy(writer)) { - throw new OnlyWriterModifiableException(); - } - - boardRepository.deleteById(boardId); - } - - @Override - public BoardDto getBoard(Long id) { - - return boardRepository.findDtoById(id).orElseThrow(NotFoundException::new); - } - - @Override - public Page getBoardList(Integer menuId, Pageable pageable) { - - return boardRepository.findAllByMenuId(menuId, pageable); - } -} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/contest/domain/ContestBoard.java b/resource-server/src/main/java/com/inhabas/api/domain/contest/domain/ContestBoard.java index cae468c0..5c9cd840 100644 --- a/resource-server/src/main/java/com/inhabas/api/domain/contest/domain/ContestBoard.java +++ b/resource-server/src/main/java/com/inhabas/api/domain/contest/domain/ContestBoard.java @@ -12,11 +12,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import com.inhabas.api.domain.board.domain.NormalBoard; -import com.inhabas.api.domain.board.domain.valueObject.Content; import com.inhabas.api.domain.board.domain.valueObject.Title; import com.inhabas.api.domain.contest.domain.valueObject.Association; import com.inhabas.api.domain.contest.domain.valueObject.Topic; +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; @Entity @Table(name = "contest_board") @@ -53,7 +52,7 @@ public ContestBoard( LocalDate deadline) { this.title = new Title(title); - this.content = new Content(contents); + // this.content = new Content(contents); this.association = new Association(association); this.topic = new Topic(topic); this.start = start; diff --git a/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberManageServiceImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberManageServiceImpl.java index 80a5f9fe..c5c6ac36 100644 --- a/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberManageServiceImpl.java +++ b/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberManageServiceImpl.java @@ -2,10 +2,7 @@ import static com.inhabas.api.auth.domain.oauth2.member.domain.valueObject.Role.*; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -44,6 +41,7 @@ public class MemberManageServiceImpl implements MemberManageService { private static final String FAIL_STATE = "fail"; private static final String PASS_EMAIL_SUBJECT = "[IBAS] 축하합니다. 동아리에 입부되셨습니다."; private static final String FAIL_EMAIL_SUBJECT = "[IBAS] 입부 신청 결과를 알립니다."; + private static final String ROLE_PREFIX = "ROLE_"; private final MemberRepository memberRepository; private final SMTPService amazonSMTPService; private final AnswerRepository answerRepository; @@ -198,8 +196,7 @@ private void validateMemberRolesToUpdate( List members = memberRepository.findAllById(memberIdList); boolean allApprovedMembers; - if (authentication.getAuthorities().contains("ROLE_" + VICE_CHIEF) - || authentication.getAuthorities().contains("ROLE_" + CHIEF)) { + if (hasRoleToChange(authentication, CHIEF) || hasRoleToChange(authentication, VICE_CHIEF)) { if (!CHIEF_CHANGEABLE_ROLES.contains(role)) { throw new InvalidInputException(); } @@ -217,6 +214,11 @@ private void validateMemberRolesToUpdate( } } + private boolean hasRoleToChange(Authentication authentication, Role role) { + return authentication.getAuthorities().stream() + .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(ROLE_PREFIX + role)); + } + private boolean checkAllMembersHaveAllowedRoles(List members, Set allowedRoles) { return members.stream().allMatch(member -> allowedRoles.contains(member.getRole())); } diff --git a/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberProfileServiceImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberProfileServiceImpl.java index 242a6d9d..5ada11cf 100644 --- a/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberProfileServiceImpl.java +++ b/resource-server/src/main/java/com/inhabas/api/domain/member/usecase/MemberProfileServiceImpl.java @@ -61,7 +61,7 @@ public void updateMyProfileDetail(Long memberId, ProfileDetailDto profileDetailD member.setPhone(profileDetailDto.getPhoneNumber()); if (profileDetailDto.getGrade() != null) member.getSchoolInformation().setGrade(profileDetailDto.getGrade()); - if (profileDetailDto.getGrade() != null) member.setMemberType(profileDetailDto.getType()); + if (profileDetailDto.getType() != null) member.setMemberType(profileDetailDto.getType()); } @Override diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/NormalBoard.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/NormalBoard.java new file mode 100644 index 00000000..6bf9526f --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/NormalBoard.java @@ -0,0 +1,92 @@ +package com.inhabas.api.domain.normalBoard.domain; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.persistence.*; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.inhabas.api.domain.board.domain.BaseBoard; +import com.inhabas.api.domain.board.domain.valueObject.Content; +import com.inhabas.api.domain.board.domain.valueObject.Title; +import com.inhabas.api.domain.comment.domain.Comment; +import com.inhabas.api.domain.file.domain.BoardFile; +import com.inhabas.api.domain.menu.domain.Menu; + +@Entity +@Table(name = "NORMAL_BOARD") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorValue("NORMAL") +public class NormalBoard extends BaseBoard { + + @Embedded private Content content; + + @Column private Boolean isPinned = false; + + @Column(columnDefinition = "DATETIME(0)") + private LocalDateTime datePinExpiration; + + @OneToMany(mappedBy = "parentBoard", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); + + /* constructor */ + + public NormalBoard( + String title, Menu menu, String content, Boolean isPinned, LocalDateTime datePinExpiration) { + super(title, menu); + this.content = new Content(content); + this.isPinned = isPinned; + this.datePinExpiration = datePinExpiration; + } + + /* getter */ + + public Boolean getPinned() { + return isPinned; + } + + public LocalDateTime getDatePinExpiration() { + return datePinExpiration; + } + + public String getContent() { + return content.getValue(); + } + + public List getFiles() { + return Collections.unmodifiableList(files); + } + + /* relation method */ + + public void updateText(String title, String content) { + this.title = new Title(title); + this.content = new Content(content); + } + + public void updateFiles(List files) { + + if (this.files != null) { + this.files.clear(); + } else { + this.files = new ArrayList<>(); + } + + for (BoardFile file : files) { + addFile(file); + } + } + + public void updatePinned(Boolean isPinned, LocalDateTime datePinExpiration) { + this.isPinned = isPinned; + this.datePinExpiration = datePinExpiration; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardType.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardType.java new file mode 100644 index 00000000..f11cc03b --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardType.java @@ -0,0 +1,26 @@ +package com.inhabas.api.domain.normalBoard.domain; + +public enum NormalBoardType { + NOTICE("notice", 4), + FREE("free", 5), + QUESTION("question", 6), + SUGGEST("suggest", 7), + STORAGE("storage", 8), + EXECUTIVE("executive", 9); + + private final String boardType; + private final int menuId; + + NormalBoardType(String boardType, int menuId) { + this.boardType = boardType; + this.menuId = menuId; + } + + public String getBoardType() { + return boardType; + } + + public int getMenuId() { + return menuId; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/PinOption.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/PinOption.java new file mode 100644 index 00000000..312ed7f4 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/domain/PinOption.java @@ -0,0 +1,17 @@ +package com.inhabas.api.domain.normalBoard.domain; + +public enum PinOption { + DISABLED(0), + TEMPORARY(1), + PERMANENT(2); + + private final Integer option; + + PinOption(Integer option) { + this.option = option; + } + + public Integer getOption() { + return option; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDetailDto.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDetailDto.java new file mode 100644 index 00000000..618a221b --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDetailDto.java @@ -0,0 +1,77 @@ +package com.inhabas.api.domain.normalBoard.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.inhabas.api.domain.file.dto.FileDownloadDto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Getter +@NoArgsConstructor +public class NormalBoardDetailDto { + + @NotNull @Positive private Long id; + + @NotBlank private String title; + + @NotBlank private String content; + @NotNull private Long writerId; + + @NotBlank private String writerName; + + @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(type = "string", example = "2024-11-01T00:00:00") + private LocalDateTime datePinExpiration; + + @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(type = "string", example = "2024-11-01T00:00:00") + private LocalDateTime dateCreated; + + @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(type = "string", example = "2024-11-01T00:00:00") + private LocalDateTime dateUpdated; + + @NotNull private List images; + + @NotNull private List otherFiles; + + @NotNull private Boolean isPinned; + + @Builder + public NormalBoardDetailDto( + Long id, + String title, + String content, + Long writerId, + String writerName, + LocalDateTime datePinExpiration, + LocalDateTime dateCreated, + LocalDateTime dateUpdated, + List images, + List otherFiles, + Boolean isPinned) { + this.id = id; + this.title = title; + this.content = content; + this.writerId = writerId; + this.writerName = writerName; + this.datePinExpiration = datePinExpiration; + this.dateCreated = dateCreated; + this.dateUpdated = dateUpdated; + this.images = images; + this.otherFiles = otherFiles; + this.isPinned = isPinned; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDto.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDto.java new file mode 100644 index 00000000..a4efe260 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDto.java @@ -0,0 +1,64 @@ +package com.inhabas.api.domain.normalBoard.dto; + +import java.time.LocalDateTime; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; + +@Getter +@NoArgsConstructor +public class NormalBoardDto { + + @NotNull @Positive private Long id; + + @NotBlank private String title; + + @NotNull private Long writerId; + + @NotBlank private String writerName; + + @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(type = "string", example = "2024-11-01T00:00:00") + private LocalDateTime datePinExpiration; + + @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(type = "string", example = "2024-11-01T00:00:00") + private LocalDateTime dateCreated; + + @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(type = "string", example = "2024-11-01T00:00:00") + private LocalDateTime dateUpdated; + + @NotNull private Boolean isPinned; + + @Builder + public NormalBoardDto( + Long id, + String title, + Long writerId, + String writerName, + LocalDateTime datePinExpiration, + LocalDateTime dateCreated, + LocalDateTime dateUpdated, + Boolean isPinned) { + this.id = id; + this.title = title; + this.writerId = writerId; + this.writerName = writerName; + this.datePinExpiration = datePinExpiration; + this.dateCreated = dateCreated; + this.dateUpdated = dateUpdated; + this.isPinned = isPinned; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/SaveNormalBoardDto.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/SaveNormalBoardDto.java new file mode 100644 index 00000000..fd3ff50d --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/dto/SaveNormalBoardDto.java @@ -0,0 +1,30 @@ +package com.inhabas.api.domain.normalBoard.dto; + +import java.util.List; + +import javax.validation.constraints.NotBlank; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import org.springframework.web.multipart.MultipartFile; + +@Getter +@NoArgsConstructor +public class SaveNormalBoardDto { + @NotBlank private String title; + + @NotBlank private String content; + + private List files; + + private Integer pinOption; + + public SaveNormalBoardDto( + String title, String content, List files, Integer pinOption) { + this.title = title; + this.content = content; + this.files = files; + this.pinOption = pinOption; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepository.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepository.java similarity index 60% rename from resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepository.java rename to resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepository.java index 49122501..fe9b073a 100644 --- a/resource-server/src/main/java/com/inhabas/api/domain/board/repository/NormalBoardRepository.java +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepository.java @@ -1,8 +1,8 @@ -package com.inhabas.api.domain.board.repository; +package com.inhabas.api.domain.normalBoard.repository; import org.springframework.data.jpa.repository.JpaRepository; -import com.inhabas.api.domain.board.domain.NormalBoard; +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; public interface NormalBoardRepository extends JpaRepository, NormalBoardRepositoryCustom {} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryCustom.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryCustom.java new file mode 100644 index 00000000..75f7c569 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryCustom.java @@ -0,0 +1,23 @@ +package com.inhabas.api.domain.normalBoard.repository; + +import java.util.List; +import java.util.Optional; + +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; +import com.inhabas.api.domain.normalBoard.domain.NormalBoardType; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; + +public interface NormalBoardRepositoryCustom { + + List findAllByTypeAndIsPinned(NormalBoardType boardType); + + List findAllByMemberIdAndTypeAndSearch( + Long memberId, NormalBoardType boardType, String search); + + List findAllByTypeAndSearch(NormalBoardType boardType, String search); + + Optional findByMemberIdAndTypeAndId( + Long memberId, NormalBoardType boardType, Long boardId); + + Optional findByTypeAndId(NormalBoardType boardType, Long boardId); +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryImpl.java new file mode 100644 index 00000000..58cab1bf --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryImpl.java @@ -0,0 +1,129 @@ +package com.inhabas.api.domain.normalBoard.repository; + +import static com.inhabas.api.domain.normalBoard.domain.QNormalBoard.normalBoard; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import lombok.RequiredArgsConstructor; + +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; +import com.inhabas.api.domain.normalBoard.domain.NormalBoardType; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; + +@RequiredArgsConstructor +public class NormalBoardRepositoryImpl implements NormalBoardRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAllByTypeAndIsPinned(NormalBoardType boardType) { + return queryFactory + .select( + Projections.constructor( + NormalBoardDto.class, + normalBoard.id, + normalBoard.title.value, + normalBoard.writer.id, + normalBoard.writer.name.value, + normalBoard.datePinExpiration, + normalBoard.dateCreated, + normalBoard.dateUpdated, + normalBoard.isPinned)) + .from(normalBoard) + .where( + eqNormalBoardType(boardType) + .and(normalBoard.isPinned.isTrue()) + .and(normalBoard.datePinExpiration.after(LocalDateTime.now()))) + .orderBy(normalBoard.dateCreated.desc()) + .fetch(); + } + + @Override + public List findAllByMemberIdAndTypeAndSearch( + Long memberId, NormalBoardType boardType, String search) { + return queryFactory + .select( + Projections.constructor( + NormalBoardDto.class, + normalBoard.id, + normalBoard.title.value, + normalBoard.writer.id, + normalBoard.writer.name.value, + normalBoard.datePinExpiration, + normalBoard.dateCreated, + normalBoard.dateUpdated, + normalBoard.isPinned)) + .from(normalBoard) + .where( + eqMemberId(memberId) + .and(eqNormalBoardType(boardType)) + .and(likeTitle(search).or(likeContent(search)))) + .orderBy(normalBoard.dateCreated.desc()) + .fetch(); + } + + @Override + public List findAllByTypeAndSearch(NormalBoardType boardType, String search) { + return queryFactory + .select( + Projections.constructor( + NormalBoardDto.class, + normalBoard.id, + normalBoard.title.value, + normalBoard.writer.id, + normalBoard.writer.name.value, + normalBoard.datePinExpiration, + normalBoard.dateCreated, + normalBoard.dateUpdated, + normalBoard.isPinned)) + .from(normalBoard) + .where(eqNormalBoardType(boardType).and(likeTitle(search).or(likeContent(search)))) + .orderBy(normalBoard.dateCreated.desc()) + .fetch(); + } + + @Override + public Optional findByMemberIdAndTypeAndId( + Long memberId, NormalBoardType boardType, Long boardId) { + return Optional.ofNullable( + queryFactory + .selectFrom(normalBoard) + .where( + eqMemberId(memberId) + .and(eqNormalBoardType(boardType)) + .and(normalBoard.id.eq(boardId))) + .orderBy(normalBoard.dateCreated.desc()) + .fetchOne()); + } + + @Override + public Optional findByTypeAndId(NormalBoardType boardType, Long boardId) { + return Optional.ofNullable( + queryFactory + .selectFrom(normalBoard) + .where((eqNormalBoardType(boardType)).and(normalBoard.id.eq(boardId))) + .orderBy(normalBoard.dateCreated.desc()) + .fetchOne()); + } + + private BooleanExpression eqMemberId(Long memberId) { + return normalBoard.writer.id.eq(memberId); + } + + private BooleanExpression eqNormalBoardType(NormalBoardType normalBoardType) { + return normalBoard.menu.id.eq(normalBoardType.getMenuId()); + } + + private BooleanExpression likeTitle(String search) { + return normalBoard.title.value.like("%" + search + "%"); + } + + private BooleanExpression likeContent(String search) { + return normalBoard.content.value.like("%" + search + "%"); + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardService.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardService.java new file mode 100644 index 00000000..0f5acd35 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardService.java @@ -0,0 +1,23 @@ +package com.inhabas.api.domain.normalBoard.usecase; + +import java.util.List; + +import com.inhabas.api.domain.normalBoard.domain.NormalBoardType; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDetailDto; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.inhabas.api.domain.normalBoard.dto.SaveNormalBoardDto; + +public interface NormalBoardService { + + List getPinned(NormalBoardType boardType); + + List getPosts(NormalBoardType boardType, String search); + + NormalBoardDetailDto getPost(Long memberId, NormalBoardType boardType, Long boardId); + + Long write(Long memberId, NormalBoardType normalBoardType, SaveNormalBoardDto saveNormalBoardDto); + + void update(Long boardId, NormalBoardType normalBoardType, SaveNormalBoardDto saveNormalBoardDto); + + void delete(Long boardId); +} diff --git a/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardServiceImpl.java b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardServiceImpl.java new file mode 100644 index 00000000..b0c21d02 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardServiceImpl.java @@ -0,0 +1,204 @@ +package com.inhabas.api.domain.normalBoard.usecase; + +import static com.inhabas.api.domain.normalBoard.domain.NormalBoardType.*; +import static com.inhabas.api.domain.normalBoard.domain.NormalBoardType.EXECUTIVE; +import static com.inhabas.api.domain.normalBoard.domain.NormalBoardType.NOTICE; +import static com.inhabas.api.domain.normalBoard.domain.PinOption.*; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import javax.transaction.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import com.inhabas.api.auth.domain.error.authException.InvalidAuthorityException; +import com.inhabas.api.auth.domain.error.businessException.InvalidInputException; +import com.inhabas.api.auth.domain.error.businessException.NotFoundException; +import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; +import com.inhabas.api.auth.domain.oauth2.member.domain.exception.MemberNotFoundException; +import com.inhabas.api.auth.domain.oauth2.member.repository.MemberRepository; +import com.inhabas.api.domain.board.exception.S3UploadFailedException; +import com.inhabas.api.domain.file.domain.BoardFile; +import com.inhabas.api.domain.file.usecase.S3Service; +import com.inhabas.api.domain.menu.domain.Menu; +import com.inhabas.api.domain.menu.repository.MenuRepository; +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; +import com.inhabas.api.domain.normalBoard.domain.NormalBoardType; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDetailDto; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.inhabas.api.domain.normalBoard.dto.SaveNormalBoardDto; +import com.inhabas.api.domain.normalBoard.repository.NormalBoardRepository; +import com.inhabas.api.global.util.ClassifiedFiles; +import com.inhabas.api.global.util.ClassifyFiles; +import com.inhabas.api.global.util.FileUtil; + +@Service +@Slf4j +@Transactional +@RequiredArgsConstructor +public class NormalBoardServiceImpl implements NormalBoardService { + + private final NormalBoardRepository normalBoardRepository; + private final MenuRepository menuRepository; + private final MemberRepository memberRepository; + private final S3Service s3Service; + + private static final Set hasPinnedBoardTypeSet = + new HashSet<>(Arrays.asList(NOTICE, EXECUTIVE)); + private static final LocalDateTime PERMANENT_DATE = LocalDateTime.of(2200, 1, 1, 0, 0, 0); + private static final Integer TEMPORARY_DAYS = 14; + + @Override + public List getPinned(NormalBoardType boardType) { + List normalBoardList = new ArrayList<>(); + if (boardType.equals(NOTICE) || boardType.equals(EXECUTIVE)) { + normalBoardList = normalBoardRepository.findAllByTypeAndIsPinned(boardType); + } + return normalBoardList; + } + + @Override + public List getPosts(NormalBoardType boardType, String search) { + List normalBoardList = new ArrayList<>(); + if (boardType.equals(SUGGEST)) { + if (SecurityContextHolder.getContext() == null) { + throw new InvalidAuthorityException(); + } + Long memberId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + normalBoardList.addAll( + normalBoardRepository.findAllByMemberIdAndTypeAndSearch(memberId, boardType, search)); + } else { + normalBoardList.addAll(normalBoardRepository.findAllByTypeAndSearch(boardType, search)); + } + return normalBoardList; + } + + @Override + public NormalBoardDetailDto getPost(Long memberId, NormalBoardType boardType, Long boardId) { + NormalBoard normalBoard; + if (boardType.equals(SUGGEST)) { + normalBoard = + normalBoardRepository + .findByMemberIdAndTypeAndId(memberId, boardType, boardId) + .orElseThrow(NotFoundException::new); + } else { + normalBoard = + normalBoardRepository + .findByTypeAndId(boardType, boardId) + .orElseThrow(NotFoundException::new); + } + + ClassifiedFiles classifiedFiles = ClassifyFiles.classifyFiles(normalBoard.getFiles()); + + return NormalBoardDetailDto.builder() + .id(normalBoard.getId()) + .title(normalBoard.getTitle()) + .content(normalBoard.getContent()) + .writerId(normalBoard.getWriter().getId()) + .writerName(normalBoard.getWriter().getName()) + .datePinExpiration(normalBoard.getDatePinExpiration()) + .dateCreated(normalBoard.getDateCreated()) + .dateUpdated(normalBoard.getDateUpdated()) + .images(classifiedFiles.getImages()) + .otherFiles(classifiedFiles.getOtherFiles()) + .isPinned(normalBoard.getPinned()) + .build(); + } + + @Override + public Long write( + Long memberId, NormalBoardType boardType, SaveNormalBoardDto saveNormalBoardDto) { + + Member writer = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); + Menu menu = menuRepository.findById(boardType.getMenuId()).orElseThrow(NotFoundException::new); + + NormalBoard normalBoard = + new NormalBoard( + saveNormalBoardDto.getTitle(), menu, saveNormalBoardDto.getContent(), false, null) + .writtenBy(writer, NormalBoard.class); + + updateNormalBoardPinned(saveNormalBoardDto, boardType, normalBoard); + return updateNormalBoardFiles(saveNormalBoardDto, boardType, normalBoard); + } + + @Override + public void update( + Long boardId, NormalBoardType boardType, SaveNormalBoardDto saveNormalBoardDto) { + + NormalBoard normalBoard = + normalBoardRepository.findById(boardId).orElseThrow(NotFoundException::new); + updateNormalBoardPinned(saveNormalBoardDto, boardType, normalBoard); + updateNormalBoardFiles(saveNormalBoardDto, boardType, normalBoard); + } + + @Override + public void delete(Long boardId) { + normalBoardRepository.deleteById(boardId); + } + + private Long updateNormalBoardFiles( + SaveNormalBoardDto saveNormalBoardDto, NormalBoardType boardType, NormalBoard normalBoard) { + final String DIR_NAME = boardType.getBoardType() + "/"; + List updateFiles = new ArrayList<>(); + List urlListForDelete = new ArrayList<>(); + + if (saveNormalBoardDto.getFiles() != null) { + normalBoard.updateText(saveNormalBoardDto.getTitle(), saveNormalBoardDto.getContent()); + try { + updateFiles = + saveNormalBoardDto.getFiles().stream() + .map( + file -> { + String path = FileUtil.generateFileName(file, DIR_NAME); + String url = s3Service.uploadS3File(file, path); + urlListForDelete.add(url); + return BoardFile.builder() + .name(file.getOriginalFilename()) + .url(url) + .board(normalBoard) + .build(); + }) + .collect(Collectors.toList()); + + } catch (RuntimeException e) { + for (String url : urlListForDelete) { + s3Service.deleteS3File(url); + } + throw new S3UploadFailedException(); + } + } + + normalBoard.updateFiles(updateFiles); + return normalBoardRepository.save(normalBoard).getId(); + } + + private void updateNormalBoardPinned( + SaveNormalBoardDto saveNormalBoardDto, NormalBoardType boardType, NormalBoard normalBoard) { + boolean isPinned = false; + LocalDateTime datePinExpiration = null; + + if (hasPinned(boardType)) { + if (saveNormalBoardDto.getPinOption() == null) { + throw new InvalidInputException(); + } else if (saveNormalBoardDto.getPinOption().equals(TEMPORARY.getOption())) { + isPinned = true; + datePinExpiration = LocalDateTime.now().plusDays(TEMPORARY_DAYS); + } else if (saveNormalBoardDto.getPinOption().equals(PERMANENT.getOption())) { + isPinned = true; + datePinExpiration = PERMANENT_DATE; + } + } + + normalBoard.updatePinned(isPinned, datePinExpiration); + } + + private boolean hasPinned(NormalBoardType boardType) { + return hasPinnedBoardTypeSet.contains(boardType); + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/global/dto/PagedPinnedResponseDto.java b/resource-server/src/main/java/com/inhabas/api/global/dto/PagedPinnedResponseDto.java new file mode 100644 index 00000000..30851468 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/global/dto/PagedPinnedResponseDto.java @@ -0,0 +1,27 @@ +package com.inhabas.api.global.dto; + +import java.util.List; + +import javax.validation.constraints.NotNull; + +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class PagedPinnedResponseDto { + + @NotNull private PageInfoDto pageInfo; + + private List pinnedData; + + @NotNull private List data; + + @Builder + public PagedPinnedResponseDto(PageInfoDto pageInfo, List pinnedData, List data) { + this.pageInfo = pageInfo; + this.pinnedData = pinnedData; + this.data = data; + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/global/dto/PagedMemberResponseDto.java b/resource-server/src/main/java/com/inhabas/api/global/dto/PagedResponseDto.java similarity index 76% rename from resource-server/src/main/java/com/inhabas/api/global/dto/PagedMemberResponseDto.java rename to resource-server/src/main/java/com/inhabas/api/global/dto/PagedResponseDto.java index 1b051df0..cf74a59d 100644 --- a/resource-server/src/main/java/com/inhabas/api/global/dto/PagedMemberResponseDto.java +++ b/resource-server/src/main/java/com/inhabas/api/global/dto/PagedResponseDto.java @@ -10,14 +10,14 @@ @Data @NoArgsConstructor -public class PagedMemberResponseDto { +public class PagedResponseDto { @NotNull private PageInfoDto pageInfo; @NotNull private List data; @Builder - public PagedMemberResponseDto(PageInfoDto pageInfo, List data) { + public PagedResponseDto(PageInfoDto pageInfo, List data) { this.pageInfo = pageInfo; this.data = data; } diff --git a/resource-server/src/main/java/com/inhabas/api/web/BoardController.java b/resource-server/src/main/java/com/inhabas/api/web/BoardController.java deleted file mode 100644 index 3b9e9b28..00000000 --- a/resource-server/src/main/java/com/inhabas/api/web/BoardController.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.inhabas.api.web; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import org.springframework.web.bind.annotation.*; - -import io.swagger.v3.oas.annotations.tags.Tag; - -@Slf4j -@Tag(name = "게시글 관리") -@RestController -@RequiredArgsConstructor -public class BoardController { - - // private final BoardService boardService; - - // @Operation(summary = "게시글 단일 조회") - // @GetMapping("/board/{id}") - // @ApiResponses({ - // @ApiResponse(responseCode = "200"), - // @ApiResponse(responseCode = "400", description = "잘못된 게시글 조회 URL 요청"), - // @ApiResponse(responseCode = "403", description = "클라이언트의 접근 권한이 없음") - // }) - // public ResponseEntity getBoard(@PathVariable Integer id) { - // - // return new ResponseEntity<>(boardService.getBoard(id), HttpStatus.OK); - // } - - // @Operation(summary = "게시글 목록 조회") - // @GetMapping("/boards") - // @ApiResponses({ - // @ApiResponse(responseCode = "200"), - // @ApiResponse(responseCode = "400", description = "잘못된 게시글 목록 조회 URL 요청"), - // @ApiResponse(responseCode = "403", description = "클라이언트의 접근 권한이 없음") - // }) - // public ResponseEntity> getBoardList( - // @PageableDefault(size = 15, direction = Direction.DESC, sort = "created") Pageable - // pageable, - // @RequestParam("menu_id") MenuId menuId) { - // - // return new ResponseEntity<>(boardService.getBoardList(menuId, pageable), HttpStatus.OK); - // } - - // @Operation(summary = "게시글 추가") - // @PostMapping("/board") - // @ApiResponses({ - // @ApiResponse(responseCode = "201"), - // @ApiResponse(responseCode = "400", description = "잘못된 게시글 폼 데이터 요청"), - // @ApiResponse(responseCode = "403", description = "클라이언트의 접근 권한이 없음") - // }) - // public ResponseEntity addBoard( - // @Authenticated Long memberId, @Valid @RequestBody SaveBoardDto saveBoardDto) { - // - // return new ResponseEntity<>(boardService.write(memberId, saveBoardDto), - // HttpStatus.CREATED); - // } - // - // @Operation(summary = "게시글 수정") - // @PutMapping("/board") - // @ApiResponses({ - // @ApiResponse(responseCode = "200"), - // @ApiResponse(responseCode = "400", description = "잘못된 게시글 폼 데이터 요청"), - // @ApiResponse(responseCode = "403", description = "클라이언트의 접근 권한이 없음") - // }) - // public ResponseEntity updateBoard( - // @Authenticated Long memberId, @Valid @RequestBody UpdateBoardDto updateBoardDto) { - // - // return new ResponseEntity<>(boardService.update(memberId, updateBoardDto), - // HttpStatus.OK); - // } - // - // @Operation(description = "게시글 삭제") - // @DeleteMapping("/board/{id}") - // @ApiResponses({ - // @ApiResponse(responseCode = "204"), - // @ApiResponse(responseCode = "400", description = "잘못된 게시글 삭제 요청"), - // @ApiResponse(responseCode = "403", description = "클라이언트의 접근 권한이 없음") - // }) - // public ResponseEntity deleteBoard( - // @Authenticated Long memberId, @PathVariable Long id) { - // - // boardService.delete(memberId, id); - // return new ResponseEntity<>(HttpStatus.NO_CONTENT); - // } -} diff --git a/resource-server/src/main/java/com/inhabas/api/web/ClubActivityController.java b/resource-server/src/main/java/com/inhabas/api/web/ClubActivityController.java index d28ee61b..971404b7 100644 --- a/resource-server/src/main/java/com/inhabas/api/web/ClubActivityController.java +++ b/resource-server/src/main/java/com/inhabas/api/web/ClubActivityController.java @@ -21,7 +21,7 @@ import com.inhabas.api.domain.club.dto.SaveClubActivityDto; import com.inhabas.api.domain.club.usecase.ClubActivityService; import com.inhabas.api.global.dto.PageInfoDto; -import com.inhabas.api.global.dto.PagedMemberResponseDto; +import com.inhabas.api.global.dto.PagedResponseDto; import com.inhabas.api.global.util.PageUtil; import com.inhabas.api.web.argumentResolver.Authenticated; import io.swagger.v3.oas.annotations.Operation; @@ -46,13 +46,13 @@ public class ClubActivityController { value = { @ApiResponse( responseCode = "200", - content = {@Content(schema = @Schema(implementation = PagedMemberResponseDto.class))}), + content = {@Content(schema = @Schema(implementation = PagedResponseDto.class))}), }) @SecurityRequirements(value = {}) @GetMapping("/club/activities") @PreAuthorize( "@boardSecurityChecker.checkMenuAccess(2, T(com.inhabas.api.domain.board.usecase.BoardSecurityChecker).READ_BOARD_LIST)") - public ResponseEntity> getClubActivities( + public ResponseEntity> getClubActivities( @Parameter(description = "페이지", example = "0") @RequestParam(name = "page", defaultValue = "0") int page, @@ -68,7 +68,7 @@ public ResponseEntity> getClubActivities new PageImpl<>(pagedDtos, pageable, allDtos.size()); PageInfoDto pageInfoDto = new PageInfoDto(ClubActivityDtoPage); - return ResponseEntity.ok(new PagedMemberResponseDto<>(pageInfoDto, pagedDtos)); + return ResponseEntity.ok(new PagedResponseDto<>(pageInfoDto, pagedDtos)); } @Operation(summary = "동아리 활동 글 생성", description = "동아리 활동 글 생성 (운영진 이상)") @@ -100,7 +100,7 @@ public ResponseEntity writeClubActivity( Long newClubActivityId = clubActivityService.writeClubActivity(memberId, saveClubActivityDto); URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/club/activity/{boardId}") + .path("/{boardId}") .buildAndExpand(newClubActivityId) .toUri(); diff --git a/resource-server/src/main/java/com/inhabas/api/web/ClubHistoryController.java b/resource-server/src/main/java/com/inhabas/api/web/ClubHistoryController.java index e0d10fe9..78773eb6 100644 --- a/resource-server/src/main/java/com/inhabas/api/web/ClubHistoryController.java +++ b/resource-server/src/main/java/com/inhabas/api/web/ClubHistoryController.java @@ -106,7 +106,7 @@ public ResponseEntity writeClubHistory( Long newClubHistoryId = clubHistoryService.writeClubHistory(memberId, saveClubHistoryDto); URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/club/history/{clubHistoryId}") + .path("/{clubHistoryId}") .buildAndExpand(newClubHistoryId) .toUri(); return ResponseEntity.created(location).build(); diff --git a/resource-server/src/main/java/com/inhabas/api/web/CommentController.java b/resource-server/src/main/java/com/inhabas/api/web/CommentController.java index 0fe95e83..114acbcf 100644 --- a/resource-server/src/main/java/com/inhabas/api/web/CommentController.java +++ b/resource-server/src/main/java/com/inhabas/api/web/CommentController.java @@ -116,7 +116,7 @@ public ResponseEntity createNewComment( commentService.create(commentSaveDto, menuId, boardId, memberId); URI location = - ServletUriComponentsBuilder.fromCurrentRequest() + ServletUriComponentsBuilder.fromCurrentContextPath() .path("/board/{menuId}/{boardId}/comments") .buildAndExpand(menuId, boardId) .toUri(); diff --git a/resource-server/src/main/java/com/inhabas/api/web/ExceptionController.java b/resource-server/src/main/java/com/inhabas/api/web/ExceptionController.java index 79e25367..423b90e4 100644 --- a/resource-server/src/main/java/com/inhabas/api/web/ExceptionController.java +++ b/resource-server/src/main/java/com/inhabas/api/web/ExceptionController.java @@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; import com.inhabas.api.auth.domain.error.ErrorResponse; @@ -114,4 +115,11 @@ protected ResponseEntity processValidationError(MethodArgumentNotValidEx final ErrorResponse response = ErrorResponse.of(INVALID_INPUT_VALUE); return new ResponseEntity<>(response, BAD_REQUEST); } + + @ExceptionHandler(MissingServletRequestPartException.class) + protected ResponseEntity processRequestPartError(MissingServletRequestPartException e) { + log.error("RequestPart validation failed", e); + final ErrorResponse response = ErrorResponse.of(INVALID_INPUT_VALUE); + return new ResponseEntity<>(response, BAD_REQUEST); + } } diff --git a/resource-server/src/main/java/com/inhabas/api/web/MemberController.java b/resource-server/src/main/java/com/inhabas/api/web/MemberController.java index 981e25ba..53dc29b3 100644 --- a/resource-server/src/main/java/com/inhabas/api/web/MemberController.java +++ b/resource-server/src/main/java/com/inhabas/api/web/MemberController.java @@ -17,7 +17,7 @@ import com.inhabas.api.domain.signUp.dto.ApplicationDetailDto; import com.inhabas.api.domain.signUp.usecase.AnswerService; import com.inhabas.api.global.dto.PageInfoDto; -import com.inhabas.api.global.dto.PagedMemberResponseDto; +import com.inhabas.api.global.dto.PagedResponseDto; import com.inhabas.api.global.util.PageUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -43,20 +43,19 @@ public class MemberController { value = { @ApiResponse( responseCode = "200", - content = {@Content(schema = @Schema(implementation = PagedMemberResponseDto.class))}), + content = {@Content(schema = @Schema(implementation = PagedResponseDto.class))}), }) @GetMapping("/members/unapproved") - public ResponseEntity> - getUnapprovedMembers( - @Parameter(description = "페이지", example = "0") - @RequestParam(name = "page", defaultValue = "0") - int page, - @Parameter(description = "페이지당 개수", example = "10") - @RequestParam(name = "size", defaultValue = "10") - int size, - @Parameter(description = "검색어 (학번 or 이름)", example = "홍길동") - @RequestParam(name = "search", defaultValue = "") - String search) { + public ResponseEntity> getUnapprovedMembers( + @Parameter(description = "페이지", example = "0") + @RequestParam(name = "page", defaultValue = "0") + int page, + @Parameter(description = "페이지당 개수", example = "10") + @RequestParam(name = "size", defaultValue = "10") + int size, + @Parameter(description = "검색어 (학번 or 이름)", example = "홍길동") + @RequestParam(name = "search", defaultValue = "") + String search) { Pageable pageable = PageRequest.of(page, size); List allDtos = @@ -67,7 +66,7 @@ public class MemberController { new PageImpl<>(pagedDtos, pageable, allDtos.size()); PageInfoDto pageInfoDto = new PageInfoDto(newMemberManagementDtoPage); - return ResponseEntity.ok(new PagedMemberResponseDto<>(pageInfoDto, pagedDtos)); + return ResponseEntity.ok(new PagedResponseDto<>(pageInfoDto, pagedDtos)); } @Operation( @@ -124,10 +123,10 @@ public ResponseEntity getUnapprovedMemberApplication( value = { @ApiResponse( responseCode = "200", - content = {@Content(schema = @Schema(implementation = PagedMemberResponseDto.class))}), + content = {@Content(schema = @Schema(implementation = PagedResponseDto.class))}), }) @GetMapping("/members/graduated") - public ResponseEntity> getGraduatedMembers( + public ResponseEntity> getGraduatedMembers( @Parameter(description = "페이지", example = "0") @RequestParam(name = "page", defaultValue = "0") int page, @@ -147,7 +146,7 @@ public ResponseEntity> getGr new PageImpl<>(pagedDtos, pageable, allDtos.size()); PageInfoDto pageInfoDto = new PageInfoDto(graduatedMemberManagementDtoPage); - return ResponseEntity.ok(new PagedMemberResponseDto<>(pageInfoDto, pagedDtos)); + return ResponseEntity.ok(new PagedResponseDto<>(pageInfoDto, pagedDtos)); } @Operation(summary = "비활동 이상 졸업자 아닌 멤버 목록 조회", description = "이름, 학번 검색 가능") @@ -155,10 +154,10 @@ public ResponseEntity> getGr value = { @ApiResponse( responseCode = "200", - content = {@Content(schema = @Schema(implementation = PagedMemberResponseDto.class))}), + content = {@Content(schema = @Schema(implementation = PagedResponseDto.class))}), }) @GetMapping("/members/notGraduated") - public ResponseEntity> getNotGraduatedMembers( + public ResponseEntity> getNotGraduatedMembers( @Parameter(description = "페이지", example = "0") @RequestParam(name = "page", defaultValue = "0") int page, @@ -178,7 +177,7 @@ public ResponseEntity> getNo new PageImpl<>(pagedDtos, pageable, allDtos.size()); PageInfoDto pageInfoDto = new PageInfoDto(oldMemberManagementDtoPage); - return ResponseEntity.ok(new PagedMemberResponseDto<>(pageInfoDto, pagedDtos)); + return ResponseEntity.ok(new PagedResponseDto<>(pageInfoDto, pagedDtos)); } @Operation( diff --git a/resource-server/src/main/java/com/inhabas/api/web/MyProfileController.java b/resource-server/src/main/java/com/inhabas/api/web/MyProfileController.java index 59f9765f..cf331b19 100644 --- a/resource-server/src/main/java/com/inhabas/api/web/MyProfileController.java +++ b/resource-server/src/main/java/com/inhabas/api/web/MyProfileController.java @@ -19,7 +19,7 @@ import com.inhabas.api.auth.domain.oauth2.member.dto.*; import com.inhabas.api.domain.member.usecase.MemberProfileService; import com.inhabas.api.global.dto.PageInfoDto; -import com.inhabas.api.global.dto.PagedMemberResponseDto; +import com.inhabas.api.global.dto.PagedResponseDto; import com.inhabas.api.global.util.PageUtil; import com.inhabas.api.web.argumentResolver.Authenticated; import io.swagger.v3.oas.annotations.Operation; @@ -51,7 +51,7 @@ public ResponseEntity getMyProfile(@Authenticated Long memberId) { return ResponseEntity.ok(memberProfileService.getMyProfile(memberId)); } - @Operation(summary = "내 [학과, 학년, 전화번호] 수정", description = "학과, 학년, 전화번호 수정. ") + @Operation(summary = "내 [학과, 학년, 전화번호, 타입] 수정", description = "학과, 학년, 전화번호 수정. ") @ApiResponses( value = { @ApiResponse(responseCode = "204"), @@ -147,7 +147,7 @@ public ResponseEntity requestMyProfileName( content = {@Content(schema = @Schema(implementation = UpdateNameRequestDto.class))}), }) @GetMapping("/myInfo/myRequests") - public ResponseEntity> getMyInfoMyRequests( + public ResponseEntity> getMyInfoMyRequests( @Parameter(description = "페이지", example = "0") @RequestParam(name = "page", defaultValue = "0") int page, @@ -163,7 +163,7 @@ public ResponseEntity> getMyInfoMyR new PageImpl<>(pagedDtos, pageable, allDtos.size()); PageInfoDto pageInfoDto = new PageInfoDto(updateNameRequestDtoPage); - return ResponseEntity.ok(new PagedMemberResponseDto<>(pageInfoDto, pagedDtos)); + return ResponseEntity.ok(new PagedResponseDto<>(pageInfoDto, pagedDtos)); } @Operation(summary = "이름 수정 요청 조회", description = "이름 수정 요청 조회") @@ -174,7 +174,7 @@ public ResponseEntity> getMyInfoMyR content = {@Content(schema = @Schema(implementation = UpdateNameRequestDto.class))}), }) @GetMapping("/myInfo/requests") - public ResponseEntity> getMyInfoRequests( + public ResponseEntity> getMyInfoRequests( @Parameter(description = "페이지", example = "0") @RequestParam(name = "page", defaultValue = "0") int page, @@ -190,7 +190,7 @@ public ResponseEntity> getMyInfoReq new PageImpl<>(pagedDtos, pageable, allDtos.size()); PageInfoDto pageInfoDto = new PageInfoDto(updateNameRequestDtoPage); - return ResponseEntity.ok(new PagedMemberResponseDto<>(pageInfoDto, pagedDtos)); + return ResponseEntity.ok(new PagedResponseDto<>(pageInfoDto, pagedDtos)); } @Operation(summary = "이름 수정 요청 처리", description = "이름 수정 요청 처리") diff --git a/resource-server/src/main/java/com/inhabas/api/web/NormalBoardController.java b/resource-server/src/main/java/com/inhabas/api/web/NormalBoardController.java new file mode 100644 index 00000000..f81671a6 --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/web/NormalBoardController.java @@ -0,0 +1,259 @@ +package com.inhabas.api.web; + +import java.net.URI; +import java.util.List; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.inhabas.api.auth.domain.error.ErrorResponse; +import com.inhabas.api.domain.board.dto.BoardCountDto; +import com.inhabas.api.domain.board.repository.BaseBoardRepository; +import com.inhabas.api.domain.normalBoard.domain.NormalBoardType; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDetailDto; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.inhabas.api.domain.normalBoard.dto.SaveNormalBoardDto; +import com.inhabas.api.domain.normalBoard.usecase.NormalBoardService; +import com.inhabas.api.global.dto.PageInfoDto; +import com.inhabas.api.global.dto.PagedPinnedResponseDto; +import com.inhabas.api.global.dto.PagedResponseDto; +import com.inhabas.api.global.util.PageUtil; +import com.inhabas.api.web.argumentResolver.Authenticated; +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.ExampleObject; +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.security.SecurityRequirements; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Slf4j +@Tag(name = "게시글 관리") +@RestController +@RequiredArgsConstructor +public class NormalBoardController { + + private final NormalBoardService normalBoardService; + private final BaseBoardRepository baseBoardRepository; + + @Operation(summary = "게시판 종류 당 글 개수 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + content = {@Content(schema = @Schema(implementation = BoardCountDto.class))}), + }) + @GetMapping("/board/count") + @SecurityRequirements(value = {}) + public ResponseEntity> getBoardCount() { + return ResponseEntity.ok(baseBoardRepository.countRowsGroupByMenuName(2)); + } + + @Operation(summary = "게시글 목록 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + content = {@Content(schema = @Schema(implementation = PagedResponseDto.class))}), + @ApiResponse( + responseCode = "400 ", + description = "입력값이 없거나, 타입이 유효하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 400, \"code\": \"G003\", \"message\": \"입력값이 없거나, 타입이 유효하지 않습니다.\"}"))), + }) + @GetMapping("/board/{boardType}") + @PreAuthorize( + "@boardSecurityChecker.checkMenuAccess(#boardType.menuId, T(com.inhabas.api.domain.board.usecase.BoardSecurityChecker).READ_BOARD_LIST)") + public ResponseEntity> getBoardList( + @Parameter(description = "페이지", example = "0") + @RequestParam(name = "page", defaultValue = "0") + int page, + @Parameter(description = "페이지당 개수", example = "10") + @RequestParam(name = "size", defaultValue = "10") + int size, + @Parameter(description = "검색어 (작성자 이름 or 제목 or 내용)", example = "") + @RequestParam(name = "search", defaultValue = "") + String search, + @PathVariable NormalBoardType boardType) { + + Pageable pageable = PageRequest.of(page, size); + List allDtoList = normalBoardService.getPosts(boardType, search); + List pinnedDtoList = normalBoardService.getPinned(boardType); + List pagedDtoList = PageUtil.getPagedDtoList(pageable, allDtoList); + + PageImpl normalBoardDtoPage = + new PageImpl<>(pagedDtoList, pageable, allDtoList.size()); + PageInfoDto pageInfoDto = new PageInfoDto(normalBoardDtoPage); + + return ResponseEntity.ok( + new PagedPinnedResponseDto<>(pageInfoDto, pinnedDtoList, pagedDtoList)); + } + + @Operation(summary = "게시글 단일 조회") + @GetMapping("/board/{boardType}/{boardId}") + @PreAuthorize( + "@boardSecurityChecker.checkMenuAccess(#boardType.menuId, T(com.inhabas.api.domain.board.usecase.BoardSecurityChecker).READ_BOARD)") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + content = {@Content(schema = @Schema(implementation = NormalBoardDto.class))}), + @ApiResponse( + responseCode = "400 ", + description = "입력값이 없거나, 타입이 유효하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 400, \"code\": \"G003\", \"message\": \"입력값이 없거나, 타입이 유효하지 않습니다.\"}"))), + @ApiResponse( + responseCode = "404", + description = "데이터가 존재하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 404, \"code\": \"G004\", \"message\": \"데이터가 존재하지 않습니다.\"}"))) + }) + public ResponseEntity getBoard( + @PathVariable Long boardId, + @PathVariable NormalBoardType boardType, + @Authenticated Long memberId) { + + return ResponseEntity.ok(normalBoardService.getPost(memberId, boardType, boardId)); + } + + @Operation(summary = "게시글 추가") + @PostMapping(path = "/board/{boardType}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize( + "@boardSecurityChecker.checkMenuAccess(#boardType.menuId, T(com.inhabas.api.domain.board.usecase.BoardSecurityChecker).CREATE_BOARD)") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "'Location' 헤더에 생성된 리소스의 URI 가 포함됩니다."), + @ApiResponse( + responseCode = "400 ", + description = "입력값이 없거나, 타입이 유효하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 400, \"code\": \"G003\", \"message\": \"입력값이 없거나, 타입이 유효하지 않습니다.\"}"))), + }) + public ResponseEntity addBoard( + @Authenticated Long memberId, + @PathVariable NormalBoardType boardType, + @RequestPart("title") String title, + @RequestPart("content") String content, + @RequestPart(value = "files", required = false) List files, + @RequestParam(value = "pinOption", required = false) Integer pinOption) { + SaveNormalBoardDto saveNormalBoardDto = + new SaveNormalBoardDto(title, content, files, pinOption); + Long newNormalBoardId = normalBoardService.write(memberId, boardType, saveNormalBoardDto); + URI location = + ServletUriComponentsBuilder.fromCurrentRequest() + .replaceQueryParam("pinOption") + .path("/{boardId}") + .buildAndExpand(newNormalBoardId) + .toUri(); + + return ResponseEntity.created(location).build(); + } + + @Operation(summary = "게시글 수정") + @PostMapping( + value = "/board/{boardType}/{boardId}", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @ApiResponses({ + @ApiResponse(responseCode = "200"), + @ApiResponse( + responseCode = "400 ", + description = "입력값이 없거나, 타입이 유효하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 400, \"code\": \"G003\", \"message\": \"입력값이 없거나, 타입이 유효하지 않습니다.\"}"))), + @ApiResponse( + responseCode = "404", + description = "데이터가 존재하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 404, \"code\": \"G004\", \"message\": \"데이터가 존재하지 않습니다.\"}"))) + }) + @PreAuthorize("@boardSecurityChecker.boardWriterOnly(#boardId) or hasRole('VICE_CHIEF')") + public ResponseEntity updateBoard( + @Authenticated Long memberId, + @PathVariable NormalBoardType boardType, + @PathVariable Long boardId, + @RequestPart("title") String title, + @RequestPart("content") String content, + @RequestPart(value = "files", required = false) List files, + @RequestParam(value = "pinOption", required = false) Integer pinOption) { + SaveNormalBoardDto saveNormalBoardDto = + new SaveNormalBoardDto(title, content, files, pinOption); + normalBoardService.update(boardId, boardType, saveNormalBoardDto); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "게시글 삭제") + @DeleteMapping("/board/{boardType}/{boardId}") + @ApiResponses({ + @ApiResponse(responseCode = "204"), + @ApiResponse( + responseCode = "400 ", + description = "입력값이 없거나, 타입이 유효하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 400, \"code\": \"G003\", \"message\": \"입력값이 없거나, 타입이 유효하지 않습니다.\"}"))), + @ApiResponse( + responseCode = "404", + description = "데이터가 존재하지 않습니다.", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = + @ExampleObject( + value = + "{\"status\": 404, \"code\": \"G004\", \"message\": \"데이터가 존재하지 않습니다.\"}"))) + }) + @PreAuthorize("@boardSecurityChecker.boardWriterOnly(#boardId) or hasRole('VICE_CHIEF')") + public ResponseEntity deleteBoard( + @Authenticated Long memberId, + @PathVariable NormalBoardType boardType, + @PathVariable Long boardId) { + + normalBoardService.delete(boardId); + return ResponseEntity.noContent().build(); + } +} diff --git a/resource-server/src/main/java/com/inhabas/api/web/converter/NormalBoardTypeConverter.java b/resource-server/src/main/java/com/inhabas/api/web/converter/NormalBoardTypeConverter.java new file mode 100644 index 00000000..0e34d4fd --- /dev/null +++ b/resource-server/src/main/java/com/inhabas/api/web/converter/NormalBoardTypeConverter.java @@ -0,0 +1,36 @@ +package com.inhabas.api.web.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import com.inhabas.api.auth.domain.error.businessException.InvalidInputException; +import com.inhabas.api.auth.domain.error.businessException.NotFoundException; +import com.inhabas.api.domain.normalBoard.domain.NormalBoardType; + +public class NormalBoardTypeConverter { + + @Component + public static class StringToNormalTypeConverter implements Converter { + + @Override + public NormalBoardType convert(String source) { + if (!StringUtils.hasText(source)) { + throw new InvalidInputException(); + } + try { + return NormalBoardType.valueOf(source.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + throw new NotFoundException(); + } + } + } + + @Component + public static class NormalTypeToStringConverter implements Converter { + @Override + public String convert(NormalBoardType source) { + return source != null ? source.toString().toLowerCase() : null; + } + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/board/domain/NormalBoardTest.java b/resource-server/src/test/java/com/inhabas/api/domain/board/domain/NormalBoardTest.java deleted file mode 100644 index d112c4b6..00000000 --- a/resource-server/src/test/java/com/inhabas/api/domain/board/domain/NormalBoardTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.inhabas.api.domain.board.domain; - -import org.springframework.test.util.ReflectionTestUtils; - -public class NormalBoardTest { - - public static NormalBoard getBoard1() { - return new NormalBoard("이건 제목", "이건 내용입니다."); - } - - public static NormalBoard getBoard2() { - return new NormalBoard("이건 공지", "이건 공지입니다."); - } - - public static NormalBoard getBoard3() { - return new NormalBoard("이건 공지2", "이건 공지2입니다."); - } - - public static NormalBoard getTestBoard(Integer id) { - NormalBoard board = new NormalBoard("이건 공지2", "이건 공지2입니다."); - ReflectionTestUtils.setField(board, "id", id); - return board; - } -} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/board/dto/SaveBoardDtoTest.java b/resource-server/src/test/java/com/inhabas/api/domain/board/dto/SaveBoardDtoTest.java deleted file mode 100644 index 51d93cda..00000000 --- a/resource-server/src/test/java/com/inhabas/api/domain/board/dto/SaveBoardDtoTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.inhabas.api.domain.board.dto; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Set; - -import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; - -import com.inhabas.api.domain.menu.domain.valueObject.MenuId; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class SaveBoardDtoTest { - - private static ValidatorFactory validatorFactory; - private static Validator validator; - - @BeforeAll - public static void init() { - validatorFactory = Validation.buildDefaultValidatorFactory(); - validator = validatorFactory.getValidator(); - } - - @AfterAll - public static void close() { - validatorFactory.close(); - } - - @DisplayName("SaveBoardDto 객체를 정상적으로 생성한다.") - @Test - public void SaveBoardDto_is_OK() { - // given - SaveBoardDto saveBoardDto = new SaveBoardDto("title", "content", new MenuId(1)); - - // when - Set> violations = validator.validate(saveBoardDto); - - // then - assertTrue(violations.isEmpty()); - } - - @DisplayName("SaveBoardDto의 content 필드가 null 이면 validation 실패") - @Test - public void Contents_is_null() { - // given - SaveBoardDto saveBoardDto = new SaveBoardDto("title", null, new MenuId(1)); - - // when - Set> violations = validator.validate(saveBoardDto); - - // then - assertEquals(1, violations.size()); - assertEquals("본문을 입력하세요.", violations.iterator().next().getMessage()); - } - - @DisplayName("게시글의 제목이 100자 이상을 넘긴 경우 validation 통과하지 못함.") - @Test - public void Title_is_too_long() { - // given - String title = "title".repeat(20) + "."; - String contents = "그냥 본문 내용입니다."; - - SaveBoardDto saveBoardDto = new SaveBoardDto(title, contents, new MenuId(1)); - - // when - Set> violations = validator.validate(saveBoardDto); - - // then - assertEquals(1, violations.size()); - assertEquals("제목은 최대 100자입니다.", violations.iterator().next().getMessage()); - } -} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/board/dto/UpdateBoardDtoTest.java b/resource-server/src/test/java/com/inhabas/api/domain/board/dto/UpdateBoardDtoTest.java deleted file mode 100644 index e128a266..00000000 --- a/resource-server/src/test/java/com/inhabas/api/domain/board/dto/UpdateBoardDtoTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.inhabas.api.domain.board.dto; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Set; - -import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class UpdateBoardDtoTest { - - private static ValidatorFactory validatorFactory; - private static Validator validator; - - @BeforeAll - public static void init() { - validatorFactory = Validation.buildDefaultValidatorFactory(); - validator = validatorFactory.getValidator(); - } - - @AfterAll - public static void close() { - validatorFactory.close(); - } - - @DisplayName("UpdateBoardDto를 정상적으로 생성한다. ") - @Test - public void UpdateBoardDto_is_OK() { - // given - UpdateBoardDto updateBoardDto = new UpdateBoardDto(1L, "title", "content"); - - // when - Set> violations = validator.validate(updateBoardDto); - - // then - assertEquals(0, violations.size()); - } - - @DisplayName("본문에 공백이 입력되었을 경우 테스트를 통과하지 못함.") - @Test - public void Contents_is_empty() { - // given - UpdateBoardDto updateBoardDto = new UpdateBoardDto(2L, "title", " "); - - // when - Set> violations = validator.validate(updateBoardDto); - - // then - assertEquals(1, violations.size()); - assertEquals("본문을 입력하세요", violations.iterator().next().getMessage()); - } -} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryTest.java b/resource-server/src/test/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryTest.java deleted file mode 100644 index f5b264cf..00000000 --- a/resource-server/src/test/java/com/inhabas/api/domain/board/repository/NormalBoardRepositoryTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.inhabas.api.domain.board.repository; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; - -import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; -import com.inhabas.api.domain.board.domain.NormalBoard; -import com.inhabas.api.domain.menu.domain.Menu; -import com.inhabas.testAnnotataion.DefaultDataJpaTest; - -@DefaultDataJpaTest -public class NormalBoardRepositoryTest { - - @Autowired NormalBoardRepository boardRepository; - @Autowired TestEntityManager em; - - NormalBoard FREE_BOARD; - NormalBoard NOTICE_BOARD; - NormalBoard NOTICE_BOARD_2; - Menu freeBoardMenu; - Member writer; - - // @BeforeEach - // public void setUp() { - // writer = em.persist(basicMember1()); - // MenuGroup boardMenuGroup = em.persist(new MenuGroup("게시판")); - // Menu noticeBoardMenu = em.persist( - // Menu.builder() - // .menuGroup(boardMenuGroup) - // .priority(1) - // .type(MenuType.LIST) - // .name("공지사항") - // .description("부원이 알아야 할 내용을 게시합니다.") - // .build()); - // freeBoardMenu = em.persist( - // Menu.builder() - // .menuGroup(boardMenuGroup) - // .priority(2) - // .type(MenuType.LIST) - // .name("자유게시판") - // .description("부원이 자유롭게 사용할 수 있는 게시판입니다.") - // .build()); - // - // FREE_BOARD = NormalBoardTest.getBoard1() - // .writtenBy(writer.getId()).inMenu(freeBoardMenu.getId()); - // NOTICE_BOARD = NormalBoardTest.getBoard2() - // .writtenBy(writer.getId()).inMenu(noticeBoardMenu.getId()); - // NOTICE_BOARD_2 = NormalBoardTest.getBoard3() - // .writtenBy(writer.getId()).inMenu(noticeBoardMenu.getId()); - // } - - // @DisplayName("저장 후 반환값이 처음과 같다.") - // @Test - // public void save() { - // Member saveMember = em.find(Member.class, basicMember1().getId()); - // - // //when - // NormalBoard saveBoard = boardRepository.save(FREE_BOARD); - // - // //then - // assertAll( - // () -> assertThat(saveBoard.getId()).isNotNull(), - // () -> assertThat(saveBoard.getDateCreated()).isNotNull(), - // () -> assertThat(saveBoard.getTitle()).isEqualTo(FREE_BOARD.getTitle()), - // () -> assertThat(saveBoard.getContent()).isEqualTo(FREE_BOARD.getContent()), - // () -> assertThat(saveBoard.getWriterId()).isEqualTo(saveMember.getId()) - // ); - // } - - // @DisplayName("id에 해당하는 게시글을 dto 로 반환한다.") - // @Test - // public void findDtoById() { - // //given - // boardRepository.save(FREE_BOARD); - // boardRepository.save(NOTICE_BOARD); - // - // //when - // BoardDto find = boardRepository.findDtoById(NOTICE_BOARD.getId()) - // .orElseThrow(EntityNotFoundException::new); - // - // //then - // assertAll( - // () -> assertThat(find.getId()).isEqualTo(NOTICE_BOARD.getId()), - // () -> assertThat(find.getTitle()).isEqualTo(NOTICE_BOARD.getTitle()), - // () -> assertThat(find.getContent()).isEqualTo(NOTICE_BOARD.getContent()), - // () -> assertThat(find.getMenuId()).isEqualTo(NOTICE_BOARD.getMenuId()), - // () -> assertThat(find.getWriterName()).isEqualTo(writer.getName()) - // ); - // } - - // @DisplayName("게시글을 수정한다.") - // @Test - // public void update() { - // //given - // Member saveMember = em.find(Member.class, basicMember1().getId()); - // boardRepository.save(FREE_BOARD); - // - // //when - // FREE_BOARD.modify("제목이 수정되었습니다.", "내용이 수정되었습니다.", saveMember.getId()); - // NormalBoard findBoard = boardRepository.findById(FREE_BOARD.getId()) - // .orElseThrow(EntityNotFoundException::new); - // - // //then - // assertThat(findBoard.getContent()).isEqualTo("내용이 수정되었습니다."); - // assertThat(findBoard.getTitle()).isEqualTo("제목이 수정되었습니다."); - // } - - // @DisplayName("id 로 게시글을 삭제한다.") - // @Test - // public void deleteById() { - // //given - // boardRepository.save(FREE_BOARD); - // - // //when - // boardRepository.deleteById(FREE_BOARD.getId()); - // - // //then - // assertTrue(boardRepository.findById(FREE_BOARD.getId()).isEmpty()); - // } - // - // @DisplayName("모든 게시글을 조회한다.") - // @Test - // public void findAll() { - // //given - // boardRepository.save(FREE_BOARD); - // boardRepository.save(NOTICE_BOARD); - - // //then - // assertThat(boards).contains(FREE_BOARD, NOTICE_BOARD); - // assertThat(boards.size()).isEqualTo(2); - // } - - // @DisplayName("메뉴 id 에 해당하는 게시글들을 갖고 온다.") - // @Test - // public void findAllByMenuId() { - // //given - // boardRepository.save(FREE_BOARD); - // boardRepository.save(NOTICE_BOARD); - // boardRepository.save(NOTICE_BOARD_2); - // MenuId freeBoardMenuId = FREE_BOARD.getMenuId(); - // MenuId noticeBoardMenuId = NOTICE_BOARD.getMenuId(); - // - // //when - // Page freeBoards = boardRepository.findAllByMenuId(freeBoardMenuId, - // Pageable.ofSize(5)); - // Page noticeBoards = boardRepository.findAllByMenuId(noticeBoardMenuId, - // Pageable.ofSize(5)); - // - // //then - // assertThat(freeBoards.getTotalElements()).isEqualTo(1); - // freeBoards.forEach( - // board->assertThat(board.getMenuId()).isEqualTo(freeBoardMenuId)); - // - // assertThat(noticeBoards.getTotalElements()).isEqualTo(2); - // noticeBoards.forEach( - // board->assertThat(board.getMenuId()).isEqualTo(noticeBoardMenuId)); - // } - - // @DisplayName("게시글 목록 페이지를 잘 불러온다.") - // @Test - // public void getBoardListPageTest() { - // //given - // ArrayList boards = new ArrayList<>(); - // for (int i = 0; i < 30; i++) { - // boards.add(new NormalBoard("이건 제목" + i, "이건 내용입니다.") - // .writtenBy(writer.getId()) - // .inMenu(freeBoardMenu.getId())); - // } - // boardRepository.saveAll(boards); - // - // //when - // Page page = - // boardRepository.findAllByMenuId( - // freeBoardMenu.getId(), - // PageRequest.of(2, 11, Direction.DESC, "created") - // ); - // - // //then - // assertThat(page.getTotalElements()).isEqualTo(30); - // assertThat(page.getTotalPages()).isEqualTo(3); - // assertThat(page.getNumber()).isEqualTo(2); - // assertThat(page.getNumberOfElements()).isEqualTo(8); - // } - -} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/board/usecase/BoardServiceTest.java b/resource-server/src/test/java/com/inhabas/api/domain/board/usecase/BoardServiceTest.java deleted file mode 100644 index 958072bb..00000000 --- a/resource-server/src/test/java/com/inhabas/api/domain/board/usecase/BoardServiceTest.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.inhabas.api.domain.board.usecase; - -import static org.mockito.BDDMockito.*; - -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import com.inhabas.api.domain.board.repository.NormalBoardRepository; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; - -@ExtendWith(MockitoExtension.class) -public class BoardServiceTest { - - @InjectMocks BoardServiceImpl boardService; - - @Mock NormalBoardRepository boardRepository; - - private MockMvc mockMvc; - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(boardService).build(); - } - - // @DisplayName("게시판을 성공적으로 생성한다.") - // @Test - // public void createBoard() { - // //given - // SaveBoardDto saveBoardDto = new SaveBoardDto("title", "content", new MenuId(1)); - // NormalBoard normalBoard = new NormalBoard("title", "content"); - // given(boardRepository.save(any())).willReturn(normalBoard); - // - // // when - // Integer returnedId = boardService.write(new StudentId("12201863"), saveBoardDto); - // - // // then - // then(boardRepository).should(times(1)).save(any()); - // } - // - // @DisplayName("게시판의 목록을 조회한다.") - // @Test - // public void getBoardList() { - // //given - // PageRequest pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "created"); - // - // BoardDto boardDto1 = - // new BoardDto(1, "title", "content", "mingyeom", - // new MenuId(1), LocalDateTime.now(), LocalDateTime.now()); - // BoardDto boardDto2 = - // new BoardDto(2, "title", "content", "minji", - // new MenuId(1), LocalDateTime.now(), LocalDateTime.now()); - // - // List results = new ArrayList<>(); - // results.add(boardDto1); - // results.add(boardDto2); - // Page expectedBoardDto = new PageImpl<>(results, pageable, results.size()); - // - // given(boardRepository.findAllByMenuId(any(), any())).willReturn(expectedBoardDto); - // - // //when - // Page returnedBoardList = boardService.getBoardList(new MenuId(1), pageable); - // - // //then - // then(boardRepository).should(times(1)).findAllByMenuId(any(), any()); - // assertThat(returnedBoardList).isEqualTo(expectedBoardDto); - // } - // - // @DisplayName("게시글 단일 조회에 성공한다.") - // @Test - // public void getDetailBoard() { - // //given - // BoardDto boardDto = - // new BoardDto(1, "title", "content", "김민겸", - // new MenuId(1), LocalDateTime.now(), null); - // given(boardRepository.findDtoById(any())).willReturn(Optional.of(boardDto)); - // - // // when - // boardService.getBoard(1); - // - // // then - // then(boardRepository).should(times(1)).findDtoById(any()); - // } - // - // @DisplayName("게시글을 성공적으로 삭제한다.") - // @Test - // public void deleteBoard() { - // //given - // StudentId writer = new StudentId("12201863"); - // NormalBoard board = new NormalBoard("Title", "Content").writtenBy(writer); - // given(boardRepository.findById(anyInt())).willReturn(Optional.of(board)); - // doNothing().when(boardRepository).deleteById(any()); - // - // // when - // boardService.delete(writer, 1); - // - // // then - // then(boardRepository).should(times(1)).deleteById(any()); - // } - // - // @DisplayName("게시글을 수정한다.") - // @Test - // public void updateBoard() { - // //given - // StudentId memberId = new StudentId("12201863"); - // NormalBoard savedNormalBoard = new NormalBoard("Origin Title", - // "Origin Content").writtenBy(memberId); - // NormalBoard updatedNormalBoard = new NormalBoard("Title", "Content").writtenBy( - // memberId); - // - // given(boardRepository.findById(anyInt())).willReturn(Optional.of(savedNormalBoard)); - // given(boardRepository.save(any())).willReturn(updatedNormalBoard); - // - // UpdateBoardDto updateBoardDto = new UpdateBoardDto(1, "수정된 제목", "수정된 내용"); - // - // // when - // Integer returnedId = boardService.update(memberId, updateBoardDto); - // - // // then - // then(boardRepository).should(times(1)).save(any()); - // } - // - // @DisplayName("작성자가 아니면 게시글 수정에 실패한다.") - // @Test - // public void failToUpdateBoard() { - // //given - // StudentId badUser = new StudentId("44444444"); - // StudentId originWriter = new StudentId("12201863"); - // NormalBoard board = new NormalBoard("Title", "Content").writtenBy(originWriter); - // given(boardRepository.findById(anyInt())).willReturn(Optional.of(board)); - // - // // when - // Assertions.assertThrows(BoardCannotModifiableException.class, - // () -> boardService.update(badUser, new UpdateBoardDto(1, "수정된 제목", "수정된 내용"))); - // } - // - // @DisplayName("작성자가 아니면 게시글 삭제에 실패한다.") - // @Test - // public void failToDeleteBoard() { - // //given - // StudentId badUser = new StudentId("44444444"); - // StudentId originWriter = new StudentId("12201863"); - // NormalBoard board = new NormalBoard("Title", "Content").writtenBy(originWriter); - // given(boardRepository.findById(anyInt())).willReturn(Optional.of(board)); - // - // // when - // Assertions.assertThrows(BoardCannotModifiableException.class, - // () -> boardService.delete(badUser, 1)); - // } -} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/comment/usecase/CommentServiceTest.java b/resource-server/src/test/java/com/inhabas/api/domain/comment/usecase/CommentServiceTest.java index 303a526f..be0b38d9 100644 --- a/resource-server/src/test/java/com/inhabas/api/domain/comment/usecase/CommentServiceTest.java +++ b/resource-server/src/test/java/com/inhabas/api/domain/comment/usecase/CommentServiceTest.java @@ -52,9 +52,9 @@ public class CommentServiceTest { public void setUpMocking() { proxyWriter = MemberTest.getTestBasicMember("12171652"); ReflectionTestUtils.setField(proxyWriter, "id", 1L); - proxyMenuGroup = MenuGroupExampleTest.getMenuGroup1(); + proxyMenuGroup = MenuGroupExampleTest.getIBASMenuGroup(); ReflectionTestUtils.setField(proxyMenuGroup, "id", 1); - proxyMenu = MenuExampleTest.getMenu1(proxyMenuGroup); + proxyMenu = MenuExampleTest.getAlbumMenu(proxyMenuGroup); ReflectionTestUtils.setField(proxyMenu, "id", 1); proxyBoard = AlbumExampleTest.getAlbumBoard1(proxyMenu); ReflectionTestUtils.setField(proxyBoard, "id", 1L); diff --git a/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/MenuExampleTest.java b/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/MenuExampleTest.java index 53a88b0e..17a3daec 100644 --- a/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/MenuExampleTest.java +++ b/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/MenuExampleTest.java @@ -4,10 +4,14 @@ public class MenuExampleTest { - public static Menu getMenu1(MenuGroup menuGroup) { + public static Menu getAlbumMenu(MenuGroup menuGroup) { return new Menu(menuGroup, 1, MenuType.ALBUM, "동아리 활동", "동아리 활동 설명"); } + public static Menu getNormalNoticeMenu(MenuGroup menuGroup) { + return new Menu(menuGroup, 1, MenuType.NORMAL_NOTICE, "공지 사항", "공지 사항 설명"); + } + public static Menu getMenu2(MenuGroup menuGroup) { return new Menu(menuGroup, 1, MenuType.STUDY, "강의", "강의 설명"); } diff --git a/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/valueObject/MenuGroupExampleTest.java b/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/valueObject/MenuGroupExampleTest.java index 9211aa0c..b427d696 100644 --- a/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/valueObject/MenuGroupExampleTest.java +++ b/resource-server/src/test/java/com/inhabas/api/domain/menu/domain/valueObject/MenuGroupExampleTest.java @@ -4,10 +4,14 @@ public class MenuGroupExampleTest { - public static MenuGroup getMenuGroup1() { + public static MenuGroup getIBASMenuGroup() { return new MenuGroup("IBAS"); } + public static MenuGroup getNormalMenuGroup() { + return new MenuGroup("게시판"); + } + public static MenuGroup getMenuGroup2() { return new MenuGroup("STUDY"); } diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardExampleTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardExampleTest.java new file mode 100644 index 00000000..b09248d8 --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardExampleTest.java @@ -0,0 +1,27 @@ +package com.inhabas.api.domain.normalBoard.domain; + +import org.springframework.test.util.ReflectionTestUtils; + +import com.inhabas.api.domain.menu.domain.Menu; + +public class NormalBoardExampleTest { + + public static NormalBoard getBoard1(Menu menu) { + return new NormalBoard("이건 공지1", menu, "이건 공지1입니다.", false, null); + } + + public static NormalBoard getBoard2(Menu menu) { + return new NormalBoard("이건 공지2", menu, "이건 공지2입니다.", false, null); + } + + public static NormalBoard getBoard3(Menu menu) { + return new NormalBoard("이건 공지3", menu, "이건 공지3입니다.", false, null); + } + + public static NormalBoard getTestBoard(Integer id, Menu menu) { + + NormalBoard board = new NormalBoard("이건 공지", menu, "이건 공지입니다.", false, null); + ReflectionTestUtils.setField(board, "id", id); + return board; + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardTest.java new file mode 100644 index 00000000..fd404d3a --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/domain/NormalBoardTest.java @@ -0,0 +1,69 @@ +package com.inhabas.api.domain.normalBoard.domain; + +import java.util.ArrayList; +import java.util.List; + +import com.inhabas.api.domain.file.domain.BoardFile; +import com.inhabas.api.domain.menu.domain.Menu; +import org.assertj.core.api.Assertions; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class NormalBoardTest { + + @Mock private Menu menu; + + private NormalBoard normalBoard; + + @BeforeEach + public void setUp() { + + MockitoAnnotations.openMocks(this); + + String title = "title1"; + String content = "content1"; + normalBoard = new NormalBoard(title, menu, content, false, null); + } + + @DisplayName("올바른 NormalBoard 를 생성한다.") + @Test + public void ConstructorTest() { + // then + Assertions.assertThat(normalBoard.getTitle()).isEqualTo("title1"); + Assertions.assertThat(normalBoard.getContent()).isEqualTo("content1"); + } + + @DisplayName("NormalBoard text 부분을 수정한다.") + @Test + public void updateTextTest() { + // given + String newTitle = "newTitle"; + String newContent = "newContent"; + + // when + normalBoard.updateText(newTitle, newContent); + + // then + Assertions.assertThat(normalBoard.getTitle()).isEqualTo(newTitle); + Assertions.assertThat(normalBoard.getContent()).isEqualTo(newContent); + } + + @DisplayName("NormalBoard file 부분을 수정한다.") + @Test + public void updateFilesTest() { + // given + List files = new ArrayList<>(); + BoardFile file = new BoardFile("fileName", "/hello", normalBoard); + files.add(file); + + // when + normalBoard.updateFiles(files); + + // then + Assertions.assertThat(normalBoard.getFiles().get(0)).isEqualTo(file); + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDetailDtoTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDetailDtoTest.java new file mode 100644 index 00000000..ed2219a2 --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDetailDtoTest.java @@ -0,0 +1,90 @@ +package com.inhabas.api.domain.normalBoard.dto; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import com.inhabas.api.domain.file.dto.FileDownloadDto; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NormalBoardDetailDtoTest { + + private static ValidatorFactory validatorFactory; + private static Validator validator; + private final List emptyList = new ArrayList<>(); + + @BeforeAll + public static void init() { + validatorFactory = Validation.buildDefaultValidatorFactory(); + validator = validatorFactory.getValidator(); + } + + @AfterAll + public static void close() { + validatorFactory.close(); + } + + @DisplayName("NormalBoardDetailDto 객체를 정상적으로 생성한다.") + @Test + public void NormalBoardDetailDto_is_OK() { + // given + NormalBoardDetailDto normalBoardDetailDto = + new NormalBoardDetailDto( + 1L, + "title", + "content", + 1L, + "writer", + LocalDateTime.now(), + LocalDateTime.now(), + LocalDateTime.now(), + emptyList, + emptyList, + false); + + // when + Set> violations = + validator.validate(normalBoardDetailDto); + + // then + assertThat(violations).isEmpty(); + } + + @DisplayName("normalBoardDetailDto title 필드가 null 이면 validation 실패") + @Test + public void Title_is_null() { + // given + NormalBoardDetailDto normalBoardDetailDto = + new NormalBoardDetailDto( + 1L, + null, + "content", + 1L, + "writer", + LocalDateTime.now(), + LocalDateTime.now(), + LocalDateTime.now(), + emptyList, + emptyList, + false); + + // when + Set> violations = + validator.validate(normalBoardDetailDto); + + // then + assertThat(violations.size()).isEqualTo(1); + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDtoTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDtoTest.java new file mode 100644 index 00000000..ccc68989 --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/NormalBoardDtoTest.java @@ -0,0 +1,77 @@ +package com.inhabas.api.domain.normalBoard.dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NormalBoardDtoTest { + + private static ValidatorFactory validatorFactory; + private static Validator validator; + + @BeforeAll + public static void init() { + validatorFactory = Validation.buildDefaultValidatorFactory(); + validator = validatorFactory.getValidator(); + } + + @AfterAll + public static void close() { + validatorFactory.close(); + } + + @DisplayName("NormalBoardDto 객체를 정상적으로 생성한다.") + @Test + public void NormalBoardDto_is_OK() { + // given + NormalBoardDto normalBoardDto = + new NormalBoardDto( + 1L, + "title", + 1L, + "writer", + LocalDateTime.now(), + LocalDateTime.now(), + LocalDateTime.now(), + false); + + // when + Set> violations = validator.validate(normalBoardDto); + + // then + assertThat(violations).isEmpty(); + } + + @DisplayName("NormalBoardDto title 필드가 null 이면 validation 실패") + @Test + public void Title_is_null() { + // given + NormalBoardDto normalBoardDto = + new NormalBoardDto( + 1L, + null, + 1L, + "writer", + LocalDateTime.now(), + LocalDateTime.now(), + LocalDateTime.now(), + false); + + // when + Set> violations = validator.validate(normalBoardDto); + + // then + assertThat(violations.size()).isEqualTo(1); + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/SaveNormalBoardDtoTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/SaveNormalBoardDtoTest.java new file mode 100644 index 00000000..cc8b9d4f --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/dto/SaveNormalBoardDtoTest.java @@ -0,0 +1,66 @@ +package com.inhabas.api.domain.normalBoard.dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import org.springframework.web.multipart.MultipartFile; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class SaveNormalBoardDtoTest { + + private static ValidatorFactory validatorFactory; + private static Validator validator; + private final List emptyList = new ArrayList<>(); + + @BeforeAll + public static void init() { + validatorFactory = Validation.buildDefaultValidatorFactory(); + validator = validatorFactory.getValidator(); + } + + @AfterAll + public static void close() { + validatorFactory.close(); + } + + @DisplayName("SaveNormalBoardDto 객체를 정상적으로 생성한다.") + @Test + public void SaveNormalBoardDto_is_OK() { + // given + SaveNormalBoardDto saveNormalBoardDto = + new SaveNormalBoardDto("title", "content", emptyList, 2); + + // when + Set> violations = + validator.validate(saveNormalBoardDto); + + // then + assertThat(violations).isEmpty(); + } + + @DisplayName("SaveNormalBoardDto content 필드가 null 이면 validation 실패") + @Test + public void Content_is_null() { + // given + SaveNormalBoardDto saveNormalBoardDto = new SaveNormalBoardDto("title", null, emptyList, 2); + + // when + Set> violations = + validator.validate(saveNormalBoardDto); + + // then + assertThat(violations.size()).isEqualTo(1); + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryTest.java new file mode 100644 index 00000000..6001b2b5 --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/repository/NormalBoardRepositoryTest.java @@ -0,0 +1,141 @@ +package com.inhabas.api.domain.normalBoard.repository; + +import static com.inhabas.api.domain.member.domain.entity.MemberTest.basicMember1; +import static com.inhabas.api.domain.normalBoard.domain.NormalBoardType.NOTICE; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.util.ReflectionTestUtils; + +import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; +import com.inhabas.api.domain.menu.domain.Menu; +import com.inhabas.api.domain.menu.domain.MenuGroup; +import com.inhabas.api.domain.menu.domain.valueObject.MenuType; +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; +import com.inhabas.api.domain.normalBoard.domain.NormalBoardExampleTest; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.inhabas.testAnnotataion.DefaultDataJpaTest; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DefaultDataJpaTest +public class NormalBoardRepositoryTest { + + @Autowired NormalBoardRepository normalBoardRepository; + @Autowired TestEntityManager em; + + NormalBoard NOTICE_BOARD; + NormalBoard NOTICE_BOARD_2; + Member writer; + + @BeforeEach + public void setUp() { + em.getEntityManager() + .createNativeQuery("ALTER TABLE MENU ALTER COLUMN `id` RESTART WITH 4") + .executeUpdate(); + writer = em.persist(basicMember1()); + MenuGroup boardMenuGroup = em.persist(new MenuGroup("게시판")); + Menu noticeBoardMenu = + em.persist( + Menu.builder() + .menuGroup(boardMenuGroup) + .priority(1) + .type(MenuType.NORMAL_NOTICE) + .name("공지사항") + .description("부원이 알아야 할 내용을 게시합니다.") + .build()); + ReflectionTestUtils.setField(noticeBoardMenu, "id", 4); + NOTICE_BOARD = + NormalBoardExampleTest.getBoard1(noticeBoardMenu).writtenBy(writer, NormalBoard.class); + NOTICE_BOARD_2 = + NormalBoardExampleTest.getBoard2(noticeBoardMenu).writtenBy(writer, NormalBoard.class); + } + + @AfterEach + public void deleteAll() { + + this.normalBoardRepository.deleteAll(); + this.em.clear(); + } + + @DisplayName("저장 후 반환값이 처음과 같다.") + @Test + public void save() { + // when + NormalBoard saveBoard = normalBoardRepository.save(NOTICE_BOARD); + + // then + assertThat(saveBoard.getWriter()).isEqualTo(writer); + } + + @DisplayName("memberId, type, search 로 게시글 목록 조회한다.") + @Test + public void findAllByMemberIdAndTypeAndSearch() { + // given + NormalBoard saveBoard = normalBoardRepository.save(NOTICE_BOARD); + NormalBoard saveBoard2 = normalBoardRepository.save(NOTICE_BOARD_2); + Long writerId = writer.getId(); + + // when + List dtoList = + normalBoardRepository.findAllByMemberIdAndTypeAndSearch(writerId, NOTICE, ""); + + // then + assertThat(dtoList).hasSize(2); + assertThat(dtoList.get(0).getTitle()).isEqualTo(saveBoard.getTitle()); + } + + @DisplayName("type, search 로 게시글 목록 조회한다.") + @Test + public void findAllByTypeAndSearch() { + // given + NormalBoard saveBoard = normalBoardRepository.save(NOTICE_BOARD); + NormalBoard saveBoard2 = normalBoardRepository.save(NOTICE_BOARD_2); + + // when + List dtoList = normalBoardRepository.findAllByTypeAndSearch(NOTICE, ""); + + // then + assertThat(dtoList).hasSize(2); + assertThat(dtoList.get(0).getTitle()).isEqualTo(saveBoard.getTitle()); + } + + @DisplayName("memberId, type, id 로 게시글 상세 조회한다.") + @Test + public void findByMemberIdAndTypeAndId() { + // given + NormalBoard saveBoard = normalBoardRepository.save(NOTICE_BOARD); + Long writerId = writer.getId(); + + // when + NormalBoard normalBoard = + normalBoardRepository + .findByMemberIdAndTypeAndId(writerId, NOTICE, saveBoard.getId()) + .orElse(null); + + // then + assertThat(normalBoard).isNotNull(); + assertThat(normalBoard.getTitle()).isEqualTo(saveBoard.getTitle()); + } + + @DisplayName("type, id 로 게시글 상세 조회한다.") + @Test + public void findByTypeAndId() { + // given + NormalBoard saveBoard = normalBoardRepository.save(NOTICE_BOARD); + + // when + NormalBoard normalBoard = + normalBoardRepository.findByTypeAndId(NOTICE, saveBoard.getId()).orElse(null); + + // then + assertThat(normalBoard).isNotNull(); + assertThat(normalBoard.getTitle()).isEqualTo(saveBoard.getTitle()); + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardServiceImplTest.java b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardServiceImplTest.java new file mode 100644 index 00000000..01fd5d7c --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/domain/normalBoard/usecase/NormalBoardServiceImplTest.java @@ -0,0 +1,148 @@ +package com.inhabas.api.domain.normalBoard.usecase; + +import static com.inhabas.api.domain.menu.domain.MenuExampleTest.getNormalNoticeMenu; +import static com.inhabas.api.domain.menu.domain.valueObject.MenuGroupExampleTest.getNormalMenuGroup; +import static com.inhabas.api.domain.normalBoard.domain.NormalBoardType.NOTICE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import org.springframework.test.util.ReflectionTestUtils; + +import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; +import com.inhabas.api.auth.domain.oauth2.member.repository.MemberRepository; +import com.inhabas.api.domain.file.usecase.S3Service; +import com.inhabas.api.domain.member.domain.entity.MemberTest; +import com.inhabas.api.domain.menu.domain.Menu; +import com.inhabas.api.domain.menu.repository.MenuRepository; +import com.inhabas.api.domain.normalBoard.domain.NormalBoard; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDetailDto; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.inhabas.api.domain.normalBoard.dto.SaveNormalBoardDto; +import com.inhabas.api.domain.normalBoard.repository.NormalBoardRepository; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(MockitoExtension.class) +public class NormalBoardServiceImplTest { + + @InjectMocks NormalBoardServiceImpl normalBoardService; + + @Mock MemberRepository memberRepository; + @Mock NormalBoardRepository normalBoardRepository; + @Mock MenuRepository menuRepository; + @Mock S3Service s3Service; + + @DisplayName("normal board 게시글 목록을 조회한다.") + @Test + void getPosts() { + // given + NormalBoardDto dto = + new NormalBoardDto( + 1L, + "title", + 1L, + "writer", + LocalDateTime.now(), + LocalDateTime.now(), + LocalDateTime.now(), + false); + + given(normalBoardRepository.findAllByTypeAndSearch(any(), any())).willReturn(List.of(dto)); + + // when + List clubActivityDtoList = normalBoardService.getPosts(NOTICE, ""); + + // then + assertThat(clubActivityDtoList).hasSize(1); + } + + @DisplayName("normal board 게시글 단일 조회한다.") + @Test + void getPost() { + // given + Member member = MemberTest.chiefMember(); + Menu menu = getNormalNoticeMenu(getNormalMenuGroup()); + NormalBoard normalBoard = + new NormalBoard("title", menu, "content", false, LocalDateTime.now()) + .writtenBy(member, NormalBoard.class); + + given(normalBoardRepository.findByTypeAndId(any(), any())).willReturn(Optional.of(normalBoard)); + + // when + NormalBoardDetailDto dto = normalBoardService.getPost(1L, NOTICE, 1L); + + // then + assertThat(dto.getTitle()).isEqualTo(normalBoard.getTitle()); + } + + @DisplayName("normal board 게시글을 작성한다.") + @Test + void write() { + // given + Member member = MemberTest.chiefMember(); + SaveNormalBoardDto saveNormalBoardDto = new SaveNormalBoardDto("title", "content", null, 2); + Menu menu = getNormalNoticeMenu(getNormalMenuGroup()); + NormalBoard normalBoard = + new NormalBoard("title", menu, "content", false, LocalDateTime.now()) + .writtenBy(member, NormalBoard.class); + + given(memberRepository.findById(any())).willReturn(Optional.of(member)); + given(normalBoardRepository.save(any())).willReturn(normalBoard); + given(menuRepository.findById(anyInt())).willReturn(Optional.of(menu)); + + // when + normalBoardService.write(1L, NOTICE, saveNormalBoardDto); + + // then + then(menuRepository).should(times(1)).findById(anyInt()); + then(normalBoardRepository).should(times(1)).save(any()); + } + + @DisplayName("normal board 게시글을 수정한다.") + @Test + void update() { + // given + SaveNormalBoardDto saveNormalBoardDto = new SaveNormalBoardDto("title", "content", null, 2); + Menu menu = getNormalNoticeMenu(getNormalMenuGroup()); + NormalBoard normalBoard = new NormalBoard("title", menu, "content", false, LocalDateTime.now()); + ReflectionTestUtils.setField(normalBoard, "id", 1L); + + given(normalBoardRepository.findById(any())).willReturn(Optional.of(normalBoard)); + given(normalBoardRepository.save(any())).willReturn(normalBoard); + + // when + normalBoardService.update(normalBoard.getId(), NOTICE, saveNormalBoardDto); + + // then + then(normalBoardRepository).should(times(1)).findById(any()); + then(normalBoardRepository).should(times(1)).save(any()); + } + + @DisplayName("normal board 게시글을 삭제한다.") + @Test + void delete() { + // given + Long boardId = 1L; + doNothing().when(normalBoardRepository).deleteById(boardId); + + // when + normalBoardService.delete(boardId); + + // then + then(normalBoardRepository).should(times(1)).deleteById(boardId); + } +} diff --git a/resource-server/src/test/java/com/inhabas/api/securityConfig/RoleHierarchyTest.java b/resource-server/src/test/java/com/inhabas/api/securityConfig/RoleHierarchyTest.java index 2a17e3c2..45bc9496 100644 --- a/resource-server/src/test/java/com/inhabas/api/securityConfig/RoleHierarchyTest.java +++ b/resource-server/src/test/java/com/inhabas/api/securityConfig/RoleHierarchyTest.java @@ -1,19 +1,19 @@ package com.inhabas.api.securityConfig; -import com.inhabas.api.web.BoardController; +import com.inhabas.api.web.NormalBoardController; import com.inhabas.testAnnotataion.DefaultWebMvcTest; -@DefaultWebMvcTest(BoardController.class) +@DefaultWebMvcTest(NormalBoardController.class) public class RoleHierarchyTest { // @Autowired // private MockMvc mockMvc; // // @Autowired - // BoardController boardController; + // NormalBoardController boardController; // // @MockBean - // BoardService boardService; + // NormalBoardService boardService; // // @MockBean // MemberManageService memberService; diff --git a/resource-server/src/test/java/com/inhabas/api/web/BoardControllerTest.java b/resource-server/src/test/java/com/inhabas/api/web/BoardControllerTest.java deleted file mode 100644 index f983f390..00000000 --- a/resource-server/src/test/java/com/inhabas/api/web/BoardControllerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.inhabas.api.web; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.inhabas.api.auth.domain.oauth2.member.service.MemberService; -import com.inhabas.api.domain.board.domain.NormalBoard; -import com.inhabas.api.domain.board.usecase.BoardService; -import com.inhabas.testAnnotataion.NoSecureWebMvcTest; - -@NoSecureWebMvcTest(BoardController.class) -public class BoardControllerTest { - - @Autowired private MockMvc mvc; - - @Autowired private ObjectMapper objectMapper; - - @Autowired BoardController boardController; - - @MockBean BoardService boardService; - - @MockBean MemberService memberService; - - @MockBean NormalBoard normalBoard; - - // @DisplayName("게시글 저장을 요청한다.") - // @Test - // @WithMockJwtAuthenticationToken - // public void addNewBoard() throws Exception { - // //given - // SaveBoardDto saveBoardDto = new SaveBoardDto("This is title", "This is content", new - // MenuId(1)); - // given(boardService.write(any(), any(SaveBoardDto.class))).willReturn(1); - // - // // when - // mvc.perform(post("/board") - // .contentType(MediaType.APPLICATION_JSON) - // .content(objectMapper.writeValueAsString(saveBoardDto))) - // .andExpect(status().isCreated()) - // .andExpect(content().string("1")); - // } - - // @DisplayName("게시글 수정을 요청한다.") - // @Test - // @WithMockJwtAuthenticationToken - // public void updateBoard() throws Exception{ - // //given - // UpdateBoardDto updateBoardDto = new UpdateBoardDto(1, "제목을 수정하였습니다.", "내용을 수정하였습니다."); - // given(boardService.update(any(), any(UpdateBoardDto.class))).willReturn(1); - // - // // when - // mvc.perform(put("/board") - // .contentType(MediaType.APPLICATION_JSON) - // .content(objectMapper.writeValueAsString(updateBoardDto))) - // .andExpect(status().isOk()) - // .andExpect(content().string("1")); - // } - - // @DisplayName("게시글 삭제를 요청한다.") - // @Test - // public void deleteBoard() throws Exception{ - // //given - // doNothing().when(boardService).delete(any(), anyInt()); - // - // // when - // mvc.perform(delete("/board/1")) - // .andExpect(status().isNoContent()); - // } - - // @DisplayName("게시글 목록 조회를 요청한다.") - // @Test - // public void getBoardList() throws Exception { - // PageRequest pageable = PageRequest.of(2,1, Sort.Direction.ASC, "id"); - // - // List results = new ArrayList<>(); - // results.add(new BoardDto(1, "Shown Title1", null, "Mingyeom", - // new MenuId(2), LocalDateTime.now(), null)); - // results.add(new BoardDto(2, "Shown Title2", null, "Mingyeom", - // new MenuId(2), LocalDateTime.now(), null)); - // results.add(new BoardDto(3, "Shown Title3", null, "Mingyeom", - // new MenuId(2), LocalDateTime.now(), null)); - // - // Page expectedBoardDto = new PageImpl<>(results, pageable, results.size()); - // - // given(boardService.getBoardList(any(), any())).willReturn(expectedBoardDto); - // - // // when - // String responseBody = mvc.perform(get("/boards?menu_id=6") - // .contentType(MediaType.APPLICATION_JSON) - // .param("page", "2") - // .param("size", "1") - // .param("sort", "ASC") - // .param("properties", "id")) - // .andExpect(status().isOk()) - // .andDo(print()) - // .andReturn() - // .getResponse().getContentAsString(); - // - // // then - // assertThat(responseBody).isEqualTo(objectMapper.writeValueAsString(expectedBoardDto)); - // } - - // @DisplayName("게시글 단일 조회를 요청한다.") - // @Test - // public void getBoardDetail() throws Exception{ - // //given - // BoardDto boardDto = - // new BoardDto(1, "Shown Title", "Shown Content", "Mingyeom", - // new MenuId(1), LocalDateTime.now(), null); - // given(boardService.getBoard(anyLong())).willReturn(boardDto); - // - // // when - // String responseBody = mvc.perform(get("/board/1")) - // .andExpect(status().isOk()) - // .andReturn() - // .getResponse().getContentAsString(); - // - // // then - // assertThat(responseBody).isEqualTo(objectMapper.writeValueAsString(boardDto)); - // - // } - - // @DisplayName("게시글 작성 시 Title의 길이가 범위를 초과해 오류 발생") - // @Test - // @WithMockJwtAuthenticationToken - // public void TitleIsTooLongError() throws Exception { - // //given - // SaveBoardDto saveBoardDto = new SaveBoardDto("title".repeat(20) + ".", "content", new - // MenuId(1)); - // - // // when - // String errorMessage = Objects.requireNonNull( - // mvc.perform(post("/board") - // .contentType(MediaType.APPLICATION_JSON) - // .content(objectMapper.writeValueAsString(saveBoardDto))) - // .andExpect(status().isBadRequest()) - // .andReturn() - // .getResolvedException()) - // .getMessage(); - // - // // then - // assertThat(errorMessage).isNotBlank(); - // assertThat(errorMessage).contains("제목은 최대 100자입니다."); - // } - - // @DisplayName("게시글 작성 시 Contents가 null인 경우 오류 발생") - // @Test - // @WithMockJwtAuthenticationToken - // public void ContentIsNullError() throws Exception { - // //given - // SaveBoardDto saveBoardDto = new SaveBoardDto("title", " ", new MenuId(1)); - // - // // when - // String errorMessage = Objects.requireNonNull( - // mvc.perform(post("/board") - // .contentType(MediaType.APPLICATION_JSON) - // .content(objectMapper.writeValueAsString(saveBoardDto))) - // .andExpect(status().isBadRequest()) - // .andReturn() - // .getResolvedException()) - // .getMessage(); - // - // // then - // assertThat(errorMessage).isNotBlank(); - // assertThat(errorMessage).contains("본문을 입력하세요."); - // } -} diff --git a/resource-server/src/test/java/com/inhabas/api/web/NormalBoardControllerTest.java b/resource-server/src/test/java/com/inhabas/api/web/NormalBoardControllerTest.java new file mode 100644 index 00000000..3d84d67a --- /dev/null +++ b/resource-server/src/test/java/com/inhabas/api/web/NormalBoardControllerTest.java @@ -0,0 +1,347 @@ +package com.inhabas.api.web; + +import static com.inhabas.api.auth.domain.error.ErrorCode.INVALID_INPUT_VALUE; +import static com.inhabas.api.auth.domain.error.ErrorCode.NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.inhabas.api.auth.domain.error.businessException.InvalidInputException; +import com.inhabas.api.auth.domain.error.businessException.NotFoundException; +import com.inhabas.api.auth.domain.oauth2.member.domain.entity.Member; +import com.inhabas.api.domain.board.dto.BoardCountDto; +import com.inhabas.api.domain.board.repository.BaseBoardRepository; +import com.inhabas.api.domain.member.domain.entity.MemberTest; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDetailDto; +import com.inhabas.api.domain.normalBoard.dto.NormalBoardDto; +import com.inhabas.api.domain.normalBoard.usecase.NormalBoardService; +import com.inhabas.testAnnotataion.NoSecureWebMvcTest; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@NoSecureWebMvcTest(NormalBoardController.class) +public class NormalBoardControllerTest { + + @Autowired private MockMvc mvc; + + @Autowired private ObjectMapper objectMapper; + + @MockBean private NormalBoardService normalBoardService; + @MockBean private BaseBoardRepository baseBoardRepository; + + private String jsonOf(Object response) throws JsonProcessingException { + return objectMapper.writeValueAsString(response); + } + + @DisplayName("게시판 종류 당 글 개수 조회 성공 200") + @Test + void getBoardCount_Success() throws Exception { + // given + BoardCountDto boardCountDto = new BoardCountDto("공지사항", 10); + List dtoList = List.of(boardCountDto); + given(baseBoardRepository.countRowsGroupByMenuName(any())).willReturn(dtoList); + + // when + String response = + mvc.perform(get("/board/count")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(jsonOf(dtoList)); + } + + @DisplayName("게시글 목록 조회 성공 200") + @Test + void getBoardList_Success() throws Exception { + // given + Member writer = MemberTest.chiefMember(); + NormalBoardDto normalBoardDto = + NormalBoardDto.builder() + .id(1L) + .title("title") + .writerName(writer.getName()) + .dateCreated(LocalDateTime.now()) + .dateUpdated(LocalDateTime.now()) + .isPinned(false) + .build(); + List dtoList = List.of(normalBoardDto); + given(normalBoardService.getPosts(any(), any())).willReturn(dtoList); + + // when + String response = + mvc.perform(get("/board/notice")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(jsonOf(dtoList)); + } + + @DisplayName("게시글 목록 조회 데이터가 올바르지 않다면 400") + @Test + void getBoardList_Invalid_Input() throws Exception { + // when + String response = + mvc.perform(get("/board/invalid")) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(INVALID_INPUT_VALUE.getMessage()); + } + + @DisplayName("게시글 단일 조회 성공 200") + @Test + void getBoard() throws Exception { + // given + Member writer = MemberTest.chiefMember(); + NormalBoardDetailDto normalBoardDetailDto = + NormalBoardDetailDto.builder() + .id(1L) + .title("title") + .content("content") + .writerName(writer.getName()) + .images(null) + .otherFiles(null) + .dateCreated(LocalDateTime.now()) + .dateUpdated(LocalDateTime.now()) + .isPinned(false) + .build(); + given(normalBoardService.getPost(any(), any(), any())).willReturn(normalBoardDetailDto); + + // when + String response = + mvc.perform(get("/board/notice/1")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(jsonOf(normalBoardDetailDto)); + } + + @DisplayName("게시글 단일 조회 데이터가 올바르지 않다면 400") + @Test + void getBoard_Invalid_Input() throws Exception { + // when + String response = + mvc.perform(get("/board/notice/invalid")) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(INVALID_INPUT_VALUE.getMessage()); + } + + @DisplayName("게시글 단일 조회 데이터가 올바르지 않다면 404") + @Test + void getBoard_Not_Found() throws Exception { + // given + doThrow(NotFoundException.class).when(normalBoardService).getPost(any(), any(), any()); + + // when + String response = + mvc.perform(get("/board/notice/1")) + .andExpect(status().isNotFound()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(NOT_FOUND.getMessage()); + } + + @DisplayName("게시글 추가 성공 201") + @Test + void addBoard() throws Exception { + // given + given(normalBoardService.write(any(), any(), any())).willReturn(1L); + MockMultipartFile titlePart = + new MockMultipartFile("title", "good title".getBytes(StandardCharsets.UTF_8)); + MockMultipartFile contentPart = + new MockMultipartFile("content", "good content".getBytes(StandardCharsets.UTF_8)); + + // when + String header = + mvc.perform( + multipart("/board/notice?pinOption=2") + .file(titlePart) + .file(contentPart) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isCreated()) + .andReturn() + .getResponse() + .getHeader("Location"); + + // then + assertThat(header).contains("/board/notice/1"); + } + + @DisplayName("게시글 추가 데이터가 올바르지 않다면 400") + @Test + void addBoard_Invalid_Input() throws Exception { + // given + MockMultipartFile titlePart = + new MockMultipartFile("title", "".getBytes(StandardCharsets.UTF_8)); + MockMultipartFile contentPart = + new MockMultipartFile("content", "good content".getBytes(StandardCharsets.UTF_8)); + // when + String response = + mvc.perform( + multipart("/board/notice?pinOption=2") + .file(titlePart) + .file(contentPart) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(INVALID_INPUT_VALUE.getMessage()); + } + + @DisplayName("게시글 수정 성공 204") + @Test + void updateBoard() throws Exception { + // given + MockMultipartFile titlePart = + new MockMultipartFile("title", "good title".getBytes(StandardCharsets.UTF_8)); + MockMultipartFile contentPart = + new MockMultipartFile("content", "good content".getBytes(StandardCharsets.UTF_8)); + doNothing().when(normalBoardService).update(any(), any(), any()); + + // when then + mvc.perform( + multipart("/board/notice/1?pinOption=2") + .file(titlePart) + .file(contentPart) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isNoContent()); + } + + @DisplayName("게시글 수정 데이터가 올바르지 않다면 400") + @Test + void updateBoard_Invalid_Input() throws Exception { + // given + MockMultipartFile titlePart = + new MockMultipartFile("title", "".getBytes(StandardCharsets.UTF_8)); + MockMultipartFile contentPart = + new MockMultipartFile("content", "good content".getBytes(StandardCharsets.UTF_8)); + doThrow(InvalidInputException.class).when(normalBoardService).update(any(), any(), any()); + + // when + String response = + mvc.perform( + multipart("/board/notice?pinOption=2") + .file(titlePart) + .file(contentPart) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(INVALID_INPUT_VALUE.getMessage()); + } + + @DisplayName("게시글 수정 데이터가 올바르지 않다면 404") + @Test + void updateBoard_Not_Found() throws Exception { + // given + MockMultipartFile titlePart = + new MockMultipartFile("title", "good title".getBytes(StandardCharsets.UTF_8)); + MockMultipartFile contentPart = + new MockMultipartFile("content", "good content".getBytes(StandardCharsets.UTF_8)); + doThrow(NotFoundException.class).when(normalBoardService).update(any(), any(), any()); + + // when + String response = + mvc.perform( + multipart("/board/notice/1?pinOption=2") + .file(titlePart) + .file(contentPart) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isNotFound()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(NOT_FOUND.getMessage()); + } + + @DisplayName("게시글 삭제 성공 204") + @Test + void deleteBoard() throws Exception { + // given + doNothing().when(normalBoardService).delete(any()); + + // when then + mvc.perform(delete("/board/notice/1")).andExpect(status().isNoContent()); + } + + @DisplayName("게시글 삭제 데이터가 올바르지 않다면 400") + @Test + void deleteBoard_Invalid_Input() throws Exception { + // given + doNothing().when(normalBoardService).delete(any()); + + // when + String response = + mvc.perform(delete("/board/invalid/invalid")) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(INVALID_INPUT_VALUE.getMessage()); + } + + @DisplayName("게시글 삭제 데이터가 올바르지 않다면 404") + @Test + void deleteBoard_Not_Found() throws Exception { + // given + doThrow(NotFoundException.class).when(normalBoardService).delete(any()); + + // when + String response = + mvc.perform(delete("/board/notice/1")) + .andExpect(status().isNotFound()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); + + // then + assertThat(response).contains(NOT_FOUND.getMessage()); + } +}