Merge branch 'dev'
Deploy / deploy (push) Failing after 15m22s

This commit is contained in:
ByungCheol
2026-06-28 13:34:21 +09:00
7 changed files with 98 additions and 15 deletions
@@ -125,10 +125,10 @@ public class BoardController {
return boardService.allTags();
}
/** 글 작성 시 선택 가능한 태그 (게시판 설정 카테고리로 제한) */
/** 글 작성 시 선택 가능한 태그 (해당 게시판에 매핑된 그룹만) */
@GetMapping("/tag-groups")
public List<TagCategoryResponse> tagGroups() {
return tagService.listWritableGroups();
public List<TagCategoryResponse> tagGroups(@RequestParam(required = false) String category) {
return tagService.listWritableGroups(category);
}
@PostMapping("/posts/{id}/comments")
@@ -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<String> boards;
}
@@ -17,13 +17,15 @@ public class TagCategoryResponse {
private String name;
private Integer sortOrder;
private List<TagResponse> tags;
private List<String> boards; // 이 그룹을 사용할 게시판 (비어있으면 전체)
public static TagCategoryResponse of(TagCategory c, List<TagResponse> tags) {
public static TagCategoryResponse of(TagCategory c, List<TagResponse> tags, List<String> boards) {
return TagCategoryResponse.builder()
.id(c.getId())
.name(c.getName())
.sortOrder(c.getSortOrder())
.tags(tags)
.boards(boards)
.build();
}
}
@@ -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<String> findBoards(@Param("categoryId") Long categoryId);
void deleteByCategory(@Param("categoryId") Long categoryId);
void insert(@Param("categoryId") Long categoryId, @Param("board") String board);
}
@@ -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<String> VALID_BOARDS = Set.of("community", "saving", "tips");
/** 카테고리별로 묶은 전체 태그 (관리 화면용) */
public List<TagCategoryResponse> listGrouped() {
@@ -39,20 +45,39 @@ public class TagService {
return result;
}
/** 글 작성 시 선택 가능한 태그 그룹 — 게시판 설정 카테고리로 제한 (없으면 전체) */
public List<TagCategoryResponse> listWritableGroups() {
Long catId = boardSettingMapper.findTagCategoryId();
if (catId == null) {
return listGrouped();
/**
* 글 작성 시 선택 가능한 태그 그룹 — 해당 게시판에 매핑된 그룹만.
* 매핑이 하나도 없는 그룹은 전체 게시판에서 노출(하위호환).
*/
public List<TagCategoryResponse> listWritableGroups(String boardCategory) {
List<TagCategoryResponse> result = new ArrayList<>();
for (TagCategory c : categoryMapper.findAll()) {
TagCategoryResponse g = toGroup(c);
List<String> 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<TagResponse> tags = tagMapper.findByCategoryId(c.getId())
.stream().map(TagResponse::from).toList();
return TagCategoryResponse.of(c, tags);
List<String> boards = categoryBoardMapper.findBoards(c.getId());
return TagCategoryResponse.of(c, tags, boards);
}
/** 카테고리의 게시판 매핑을 교체 저장 (유효 게시판 값만, 중복 제거) */
private void saveBoards(Long categoryId, List<String> 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<TagResponse> 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);
}
+9
View File
@@ -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,
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sb.web.board.mapper.TagCategoryBoardMapper">
<select id="findBoards" parameterType="long" resultType="string">
SELECT board_category FROM tag_category_board WHERE tag_category_id = #{categoryId}
</select>
<delete id="deleteByCategory" parameterType="long">
DELETE FROM tag_category_board WHERE tag_category_id = #{categoryId}
</delete>
<insert id="insert">
INSERT INTO tag_category_board (tag_category_id, board_category)
VALUES (#{categoryId}, #{board})
</insert>
</mapper>