diff --git a/src/main/java/com/sb/web/board/controller/BoardController.java b/src/main/java/com/sb/web/board/controller/BoardController.java index 22d2419..fcabebb 100644 --- a/src/main/java/com/sb/web/board/controller/BoardController.java +++ b/src/main/java/com/sb/web/board/controller/BoardController.java @@ -125,10 +125,10 @@ public class BoardController { return boardService.allTags(); } - /** 글 작성 시 선택 가능한 태그 (게시판 설정 카테고리로 제한) */ + /** 글 작성 시 선택 가능한 태그 (해당 게시판에 매핑된 그룹만) */ @GetMapping("/tag-groups") - public List tagGroups() { - return tagService.listWritableGroups(); + public List tagGroups(@RequestParam(required = false) String category) { + return tagService.listWritableGroups(category); } @PostMapping("/posts/{id}/comments") diff --git a/src/main/java/com/sb/web/board/dto/TagCategoryRequest.java b/src/main/java/com/sb/web/board/dto/TagCategoryRequest.java index 08d7330..f10bdde 100644 --- a/src/main/java/com/sb/web/board/dto/TagCategoryRequest.java +++ b/src/main/java/com/sb/web/board/dto/TagCategoryRequest.java @@ -4,6 +4,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; +import java.util.List; + /** * 태그 카테고리 생성/수정 요청. */ @@ -15,4 +17,7 @@ public class TagCategoryRequest { private String name; private Integer sortOrder; + + /** 이 그룹을 사용할 게시판 (community/saving/tips). 비어있으면 전체 게시판. */ + private List boards; } diff --git a/src/main/java/com/sb/web/board/dto/TagCategoryResponse.java b/src/main/java/com/sb/web/board/dto/TagCategoryResponse.java index 6768780..4df0b78 100644 --- a/src/main/java/com/sb/web/board/dto/TagCategoryResponse.java +++ b/src/main/java/com/sb/web/board/dto/TagCategoryResponse.java @@ -17,13 +17,15 @@ public class TagCategoryResponse { private String name; private Integer sortOrder; private List tags; + private List boards; // 이 그룹을 사용할 게시판 (비어있으면 전체) - public static TagCategoryResponse of(TagCategory c, List tags) { + public static TagCategoryResponse of(TagCategory c, List tags, List boards) { return TagCategoryResponse.builder() .id(c.getId()) .name(c.getName()) .sortOrder(c.getSortOrder()) .tags(tags) + .boards(boards) .build(); } } diff --git a/src/main/java/com/sb/web/board/mapper/TagCategoryBoardMapper.java b/src/main/java/com/sb/web/board/mapper/TagCategoryBoardMapper.java new file mode 100644 index 0000000..917dd25 --- /dev/null +++ b/src/main/java/com/sb/web/board/mapper/TagCategoryBoardMapper.java @@ -0,0 +1,20 @@ +package com.sb.web.board.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 태그 카테고리(그룹) ↔ 게시판 매핑 (tag_category_board). + */ +@Mapper +public interface TagCategoryBoardMapper { + + /** 카테고리가 노출될 게시판 목록 */ + List findBoards(@Param("categoryId") Long categoryId); + + void deleteByCategory(@Param("categoryId") Long categoryId); + + void insert(@Param("categoryId") Long categoryId, @Param("board") String board); +} diff --git a/src/main/java/com/sb/web/board/service/TagService.java b/src/main/java/com/sb/web/board/service/TagService.java index de48043..9f9cf87 100644 --- a/src/main/java/com/sb/web/board/service/TagService.java +++ b/src/main/java/com/sb/web/board/service/TagService.java @@ -8,6 +8,7 @@ import com.sb.web.board.dto.BoardSettingResponse; import com.sb.web.board.dto.TagRequest; import com.sb.web.board.dto.TagResponse; import com.sb.web.board.mapper.BoardSettingMapper; +import com.sb.web.board.mapper.TagCategoryBoardMapper; import com.sb.web.board.mapper.TagCategoryMapper; import com.sb.web.board.mapper.TagMapper; import com.sb.web.common.exception.ApiException; @@ -18,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * 태그 카테고리 + 태그 관리 서비스 (관리자 기능 + 그룹 조회). @@ -29,6 +31,10 @@ public class TagService { private final TagCategoryMapper categoryMapper; private final TagMapper tagMapper; private final BoardSettingMapper boardSettingMapper; + private final TagCategoryBoardMapper categoryBoardMapper; + + /** 게시판 매핑에 허용되는 값 */ + private static final Set VALID_BOARDS = Set.of("community", "saving", "tips"); /** 카테고리별로 묶은 전체 태그 (관리 화면용) */ public List listGrouped() { @@ -39,20 +45,39 @@ public class TagService { return result; } - /** 글 작성 시 선택 가능한 태그 그룹 — 게시판 설정 카테고리로 제한 (없으면 전체) */ - public List listWritableGroups() { - Long catId = boardSettingMapper.findTagCategoryId(); - if (catId == null) { - return listGrouped(); + /** + * 글 작성 시 선택 가능한 태그 그룹 — 해당 게시판에 매핑된 그룹만. + * 매핑이 하나도 없는 그룹은 전체 게시판에서 노출(하위호환). + */ + public List listWritableGroups(String boardCategory) { + List result = new ArrayList<>(); + for (TagCategory c : categoryMapper.findAll()) { + TagCategoryResponse g = toGroup(c); + List boards = g.getBoards(); + if (boards == null || boards.isEmpty() || boards.contains(boardCategory)) { + result.add(g); + } } - TagCategory c = categoryMapper.findById(catId); - return c == null ? listGrouped() : List.of(toGroup(c)); + return result; } private TagCategoryResponse toGroup(TagCategory c) { List tags = tagMapper.findByCategoryId(c.getId()) .stream().map(TagResponse::from).toList(); - return TagCategoryResponse.of(c, tags); + List boards = categoryBoardMapper.findBoards(c.getId()); + return TagCategoryResponse.of(c, tags, boards); + } + + /** 카테고리의 게시판 매핑을 교체 저장 (유효 게시판 값만, 중복 제거) */ + private void saveBoards(Long categoryId, List boards) { + categoryBoardMapper.deleteByCategory(categoryId); + if (boards == null) { + return; + } + boards.stream() + .filter(b -> b != null && VALID_BOARDS.contains(b)) + .distinct() + .forEach(b -> categoryBoardMapper.insert(categoryId, b)); } /* ===================== 게시판 설정 ===================== */ @@ -85,7 +110,8 @@ public class TagService { .sortOrder(req.getSortOrder() != null ? req.getSortOrder() : 0) .build(); categoryMapper.insert(c); - return TagCategoryResponse.of(c, List.of()); + saveBoards(c.getId(), req.getBoards()); + return TagCategoryResponse.of(c, List.of(), categoryBoardMapper.findBoards(c.getId())); } @Transactional @@ -100,11 +126,12 @@ public class TagService { c.setSortOrder(req.getSortOrder()); } categoryMapper.update(c); + saveBoards(id, req.getBoards()); List tags = tagMapper.findByCategoryId(id).stream().map(TagResponse::from).toList(); - return TagCategoryResponse.of(c, tags); + return TagCategoryResponse.of(c, tags, categoryBoardMapper.findBoards(id)); } - /** 카테고리 삭제 — 소속 태그와 그 연결(post_tag)까지 함께 삭제 */ + /** 카테고리 삭제 — 소속 태그와 그 연결(post_tag)·게시판 매핑까지 함께 삭제 */ @Transactional public void deleteCategory(Long id) { mustFindCategory(id); @@ -112,6 +139,7 @@ public class TagService { tagMapper.deletePostTagsByTagId(t.getId()); tagMapper.delete(t.getId()); } + categoryBoardMapper.deleteByCategory(id); categoryMapper.delete(id); } diff --git a/src/main/resources/db/board.sql b/src/main/resources/db/board.sql index a2053b5..8a1deee 100644 --- a/src/main/resources/db/board.sql +++ b/src/main/resources/db/board.sql @@ -54,6 +54,15 @@ CREATE TABLE IF NOT EXISTS tag ( -- 기존 tag 테이블에 category_id 가 없으면 추가 (멱등) ALTER TABLE tag ADD COLUMN IF NOT EXISTS category_id BIGINT NULL; +-- 태그 카테고리(그룹) ↔ 게시판 매핑. 어느 게시판(community/saving/tips)에서 이 그룹을 쓸지 지정. +-- 매핑이 하나도 없는 카테고리는 모든 게시판에서 사용(하위호환). +CREATE TABLE IF NOT EXISTS tag_category_board ( + tag_category_id BIGINT NOT NULL, + board_category VARCHAR(30) NOT NULL COMMENT 'community / saving / tips', + PRIMARY KEY (tag_category_id, board_category), + KEY idx_tcb_board (board_category) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + -- 게시글-태그 (다대다) CREATE TABLE IF NOT EXISTS post_tag ( post_id BIGINT NOT NULL, diff --git a/src/main/resources/mapper/TagCategoryBoardMapper.xml b/src/main/resources/mapper/TagCategoryBoardMapper.xml new file mode 100644 index 0000000..1148ab7 --- /dev/null +++ b/src/main/resources/mapper/TagCategoryBoardMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + DELETE FROM tag_category_board WHERE tag_category_id = #{categoryId} + + + + INSERT INTO tag_category_board (tag_category_id, board_category) + VALUES (#{categoryId}, #{board}) + + +