From 8d8d26ec3a04d0b742091dce94b4c7151e0e09d9 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Sat, 27 Jun 2026 22:06:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=ED=8C=90=20=EA=B3=B5?= =?UTF-8?q?=EC=A7=80=20=EA=B8=B0=EB=8A=A5=20(=EA=B4=80=EB=A6=AC=EC=9E=90)?= =?UTF-8?q?=20=E2=80=94=20=EB=AA=A9=EB=A1=9D=20=EC=B5=9C=EC=83=81=EB=8B=A8?= =?UTF-8?q?=20=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - post.notice 컬럼 추가(멱등 ALTER) - 목록 정렬 ORDER BY notice DESC, id DESC (공지 최상단) - 작성 시 공지 설정(관리자만 반영) + /posts/{id}/notice·unnotice 토글 - Post/PostSummary/PostDetail/PostRequest 에 notice 반영 Co-Authored-By: Claude Opus 4.8 --- .../web/board/controller/BoardController.java | 18 ++++++++++++++++++ .../java/com/sb/web/board/domain/Post.java | 1 + .../java/com/sb/web/board/dto/PostDetail.java | 3 +++ .../java/com/sb/web/board/dto/PostRequest.java | 3 +++ .../java/com/sb/web/board/dto/PostSummary.java | 1 + .../com/sb/web/board/mapper/PostMapper.java | 3 +++ .../com/sb/web/board/service/BoardService.java | 10 ++++++++++ src/main/resources/db/board.sql | 2 ++ src/main/resources/mapper/PostMapper.xml | 14 +++++++++----- 9 files changed, 50 insertions(+), 5 deletions(-) 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 858420e..00fa962 100644 --- a/src/main/java/com/sb/web/board/controller/BoardController.java +++ b/src/main/java/com/sb/web/board/controller/BoardController.java @@ -99,6 +99,24 @@ public class BoardController { return ResponseEntity.noContent().build(); } + /** 공지 등록 (관리자) — 목록 최상단 고정 */ + @PostMapping("/posts/{id}/notice") + public ResponseEntity notice( + @PathVariable Long id, + @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { + boardService.setNotice(id, true, current); + return ResponseEntity.noContent().build(); + } + + /** 공지 해제 (관리자) */ + @PostMapping("/posts/{id}/unnotice") + public ResponseEntity unnotice( + @PathVariable Long id, + @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { + boardService.setNotice(id, false, current); + return ResponseEntity.noContent().build(); + } + @GetMapping("/tags") public List tags() { return boardService.allTags(); diff --git a/src/main/java/com/sb/web/board/domain/Post.java b/src/main/java/com/sb/web/board/domain/Post.java index a05a475..f05bfa5 100644 --- a/src/main/java/com/sb/web/board/domain/Post.java +++ b/src/main/java/com/sb/web/board/domain/Post.java @@ -25,6 +25,7 @@ public class Post implements Serializable { private Integer viewCount; private Boolean blocked; private String blockReason; + private Boolean notice; // 공지(상단 고정) — 관리자만 설정 private String category; // 게시판 구분: community/saving/tips private LocalDateTime createdAt; private LocalDateTime updatedAt; diff --git a/src/main/java/com/sb/web/board/dto/PostDetail.java b/src/main/java/com/sb/web/board/dto/PostDetail.java index a4df516..21377de 100644 --- a/src/main/java/com/sb/web/board/dto/PostDetail.java +++ b/src/main/java/com/sb/web/board/dto/PostDetail.java @@ -22,6 +22,7 @@ public class PostDetail { private Integer viewCount; private Boolean blocked; private String blockReason; + private Boolean notice; private String category; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -38,6 +39,7 @@ public class PostDetail { .viewCount(p.getViewCount()) .blocked(Boolean.TRUE.equals(p.getBlocked())) .blockReason(p.getBlockReason()) + .notice(Boolean.TRUE.equals(p.getNotice())) .category(p.getCategory()) .createdAt(p.getCreatedAt()) .updatedAt(p.getUpdatedAt()) @@ -54,6 +56,7 @@ public class PostDetail { .authorName(p.getAuthorName()) .viewCount(p.getViewCount()) .blocked(true) + .notice(Boolean.TRUE.equals(p.getNotice())) .category(p.getCategory()) .createdAt(p.getCreatedAt()) .tags(List.of()) diff --git a/src/main/java/com/sb/web/board/dto/PostRequest.java b/src/main/java/com/sb/web/board/dto/PostRequest.java index e837891..e59b029 100644 --- a/src/main/java/com/sb/web/board/dto/PostRequest.java +++ b/src/main/java/com/sb/web/board/dto/PostRequest.java @@ -22,6 +22,9 @@ public class PostRequest { /** 게시판 구분: community/saving/tips. 없으면 community */ private String category; + /** 공지 등록 여부 (관리자만 반영). 작성 시 선택 */ + private Boolean notice; + /** 선택한 태그 ID 목록 (DB에 등록된 태그만, 선택) */ private List tagIds; } diff --git a/src/main/java/com/sb/web/board/dto/PostSummary.java b/src/main/java/com/sb/web/board/dto/PostSummary.java index 6137eed..28a9069 100644 --- a/src/main/java/com/sb/web/board/dto/PostSummary.java +++ b/src/main/java/com/sb/web/board/dto/PostSummary.java @@ -17,6 +17,7 @@ public class PostSummary { private Integer viewCount; private Integer commentCount; private Boolean blocked; + private Boolean notice; private LocalDateTime createdAt; private List tags; } diff --git a/src/main/java/com/sb/web/board/mapper/PostMapper.java b/src/main/java/com/sb/web/board/mapper/PostMapper.java index 3ddaa4b..f622ee2 100644 --- a/src/main/java/com/sb/web/board/mapper/PostMapper.java +++ b/src/main/java/com/sb/web/board/mapper/PostMapper.java @@ -35,4 +35,7 @@ public interface PostMapper { int updateBlocked(@Param("id") Long id, @Param("blocked") boolean blocked, @Param("blockReason") String blockReason); + + int updateNotice(@Param("id") Long id, + @Param("notice") boolean notice); } diff --git a/src/main/java/com/sb/web/board/service/BoardService.java b/src/main/java/com/sb/web/board/service/BoardService.java index e89bec9..c8b9105 100644 --- a/src/main/java/com/sb/web/board/service/BoardService.java +++ b/src/main/java/com/sb/web/board/service/BoardService.java @@ -91,6 +91,14 @@ public class BoardService { postMapper.updateBlocked(id, blocked, blocked ? reason : null); } + /** 게시글 공지 설정/해제 (관리자 전용) — 목록 최상단 고정 */ + @Transactional + public void setNotice(Long id, boolean notice, SessionUser user) { + assertAdmin(user); + mustFindPost(id); + postMapper.updateNotice(id, notice); + } + /** Redis 로 (사용자, 게시글) 단위 첫 열람 여부 판단 → 중복 클릭 시 조회수 증가 방지 */ private boolean shouldCountView(Long postId, SessionUser viewer) { if (viewer == null) { @@ -108,6 +116,8 @@ public class BoardService { .content(req.getContent()) .authorId(author.getId()) .authorName(author.getName()) + // 공지는 관리자만 설정 가능 + .notice(isAdmin(author) && Boolean.TRUE.equals(req.getNotice())) .category(toCategory(req.getCategory())) .build(); postMapper.insert(post); diff --git a/src/main/resources/db/board.sql b/src/main/resources/db/board.sql index 022de7a..00fe8b9 100644 --- a/src/main/resources/db/board.sql +++ b/src/main/resources/db/board.sql @@ -28,6 +28,8 @@ ALTER TABLE post ADD COLUMN IF NOT EXISTS block_reason VARCHAR(255) NULL; ALTER TABLE post ADD COLUMN IF NOT EXISTS category VARCHAR(30) NOT NULL DEFAULT 'community'; -- 큰 이미지(base64 인라인) 수용을 위해 content 를 LONGTEXT 로 확대 (멱등) ALTER TABLE post MODIFY content LONGTEXT NOT NULL; +-- 공지(상단 고정) — 관리자만 설정. 목록에서 최상단 노출 (멱등) +ALTER TABLE post ADD COLUMN IF NOT EXISTS notice TINYINT(1) NOT NULL DEFAULT 0 COMMENT '공지(상단 고정)'; -- 태그 카테고리(그룹) — 예: 게시판, 게시판2 ... CREATE TABLE IF NOT EXISTS tag_category ( diff --git a/src/main/resources/mapper/PostMapper.xml b/src/main/resources/mapper/PostMapper.xml index 0d8e212..edea528 100644 --- a/src/main/resources/mapper/PostMapper.xml +++ b/src/main/resources/mapper/PostMapper.xml @@ -35,11 +35,11 @@ @@ -51,15 +51,15 @@ - INSERT INTO post (title, content, author_id, author_name, view_count, category, created_at, updated_at) - VALUES (#{title}, #{content}, #{authorId}, #{authorName}, 0, #{category}, NOW(), NOW()) + INSERT INTO post (title, content, author_id, author_name, view_count, notice, category, created_at, updated_at) + VALUES (#{title}, #{content}, #{authorId}, #{authorName}, 0, #{notice}, #{category}, NOW(), NOW()) @@ -80,4 +80,8 @@ UPDATE post SET blocked = #{blocked}, block_reason = #{blockReason} WHERE id = #{id} + + UPDATE post SET notice = #{notice} WHERE id = #{id} + +