Merge branch 'dev'
CI / build (push) Failing after 12m28s
Deploy / deploy (push) Failing after 15m38s

This commit is contained in:
ByungCheol
2026-06-27 22:27:13 +09:00
9 changed files with 50 additions and 5 deletions
@@ -99,6 +99,24 @@ public class BoardController {
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
} }
/** 공지 등록 (관리자) — 목록 최상단 고정 */
@PostMapping("/posts/{id}/notice")
public ResponseEntity<Void> 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<Void> unnotice(
@PathVariable Long id,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
boardService.setNotice(id, false, current);
return ResponseEntity.noContent().build();
}
@GetMapping("/tags") @GetMapping("/tags")
public List<String> tags() { public List<String> tags() {
return boardService.allTags(); return boardService.allTags();
@@ -25,6 +25,7 @@ public class Post implements Serializable {
private Integer viewCount; private Integer viewCount;
private Boolean blocked; private Boolean blocked;
private String blockReason; private String blockReason;
private Boolean notice; // 공지(상단 고정) — 관리자만 설정
private String category; // 게시판 구분: community/saving/tips private String category; // 게시판 구분: community/saving/tips
private LocalDateTime createdAt; private LocalDateTime createdAt;
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@@ -22,6 +22,7 @@ public class PostDetail {
private Integer viewCount; private Integer viewCount;
private Boolean blocked; private Boolean blocked;
private String blockReason; private String blockReason;
private Boolean notice;
private String category; private String category;
private LocalDateTime createdAt; private LocalDateTime createdAt;
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@@ -38,6 +39,7 @@ public class PostDetail {
.viewCount(p.getViewCount()) .viewCount(p.getViewCount())
.blocked(Boolean.TRUE.equals(p.getBlocked())) .blocked(Boolean.TRUE.equals(p.getBlocked()))
.blockReason(p.getBlockReason()) .blockReason(p.getBlockReason())
.notice(Boolean.TRUE.equals(p.getNotice()))
.category(p.getCategory()) .category(p.getCategory())
.createdAt(p.getCreatedAt()) .createdAt(p.getCreatedAt())
.updatedAt(p.getUpdatedAt()) .updatedAt(p.getUpdatedAt())
@@ -54,6 +56,7 @@ public class PostDetail {
.authorName(p.getAuthorName()) .authorName(p.getAuthorName())
.viewCount(p.getViewCount()) .viewCount(p.getViewCount())
.blocked(true) .blocked(true)
.notice(Boolean.TRUE.equals(p.getNotice()))
.category(p.getCategory()) .category(p.getCategory())
.createdAt(p.getCreatedAt()) .createdAt(p.getCreatedAt())
.tags(List.of()) .tags(List.of())
@@ -22,6 +22,9 @@ public class PostRequest {
/** 게시판 구분: community/saving/tips. 없으면 community */ /** 게시판 구분: community/saving/tips. 없으면 community */
private String category; private String category;
/** 공지 등록 여부 (관리자만 반영). 작성 시 선택 */
private Boolean notice;
/** 선택한 태그 ID 목록 (DB에 등록된 태그만, 선택) */ /** 선택한 태그 ID 목록 (DB에 등록된 태그만, 선택) */
private List<Long> tagIds; private List<Long> tagIds;
} }
@@ -17,6 +17,7 @@ public class PostSummary {
private Integer viewCount; private Integer viewCount;
private Integer commentCount; private Integer commentCount;
private Boolean blocked; private Boolean blocked;
private Boolean notice;
private LocalDateTime createdAt; private LocalDateTime createdAt;
private List<String> tags; private List<String> tags;
} }
@@ -35,4 +35,7 @@ public interface PostMapper {
int updateBlocked(@Param("id") Long id, int updateBlocked(@Param("id") Long id,
@Param("blocked") boolean blocked, @Param("blocked") boolean blocked,
@Param("blockReason") String blockReason); @Param("blockReason") String blockReason);
int updateNotice(@Param("id") Long id,
@Param("notice") boolean notice);
} }
@@ -91,6 +91,14 @@ public class BoardService {
postMapper.updateBlocked(id, blocked, blocked ? reason : null); 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 로 (사용자, 게시글) 단위 첫 열람 여부 판단 → 중복 클릭 시 조회수 증가 방지 */ /** Redis 로 (사용자, 게시글) 단위 첫 열람 여부 판단 → 중복 클릭 시 조회수 증가 방지 */
private boolean shouldCountView(Long postId, SessionUser viewer) { private boolean shouldCountView(Long postId, SessionUser viewer) {
if (viewer == null) { if (viewer == null) {
@@ -108,6 +116,8 @@ public class BoardService {
.content(req.getContent()) .content(req.getContent())
.authorId(author.getId()) .authorId(author.getId())
.authorName(author.getName()) .authorName(author.getName())
// 공지는 관리자만 설정 가능
.notice(isAdmin(author) && Boolean.TRUE.equals(req.getNotice()))
.category(toCategory(req.getCategory())) .category(toCategory(req.getCategory()))
.build(); .build();
postMapper.insert(post); postMapper.insert(post);
+2
View File
@@ -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'; ALTER TABLE post ADD COLUMN IF NOT EXISTS category VARCHAR(30) NOT NULL DEFAULT 'community';
-- 큰 이미지(base64 인라인) 수용을 위해 content 를 LONGTEXT 로 확대 (멱등) -- 큰 이미지(base64 인라인) 수용을 위해 content 를 LONGTEXT 로 확대 (멱등)
ALTER TABLE post MODIFY content LONGTEXT NOT NULL; 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 ... -- 태그 카테고리(그룹) — 예: 게시판, 게시판2 ...
CREATE TABLE IF NOT EXISTS tag_category ( CREATE TABLE IF NOT EXISTS tag_category (
+9 -5
View File
@@ -35,11 +35,11 @@
<select id="findPage" resultType="com.sb.web.board.dto.PostSummary"> <select id="findPage" resultType="com.sb.web.board.dto.PostSummary">
SELECT SELECT
p.id, p.title, p.author_name, p.view_count, p.blocked, p.created_at, p.id, p.title, p.author_name, p.view_count, p.blocked, p.notice, p.created_at,
(SELECT COUNT(*) FROM comment c WHERE c.post_id = p.id) AS comment_count (SELECT COUNT(*) FROM comment c WHERE c.post_id = p.id) AS comment_count
FROM post p FROM post p
<include refid="filter"/> <include refid="filter"/>
ORDER BY p.id DESC ORDER BY p.notice DESC, p.id DESC
LIMIT #{size} OFFSET #{offset} LIMIT #{size} OFFSET #{offset}
</select> </select>
@@ -51,15 +51,15 @@
<select id="findById" parameterType="long" resultType="com.sb.web.board.domain.Post"> <select id="findById" parameterType="long" resultType="com.sb.web.board.domain.Post">
SELECT id, title, content, author_id, author_name, view_count, SELECT id, title, content, author_id, author_name, view_count,
blocked, block_reason, category, created_at, updated_at blocked, block_reason, notice, category, created_at, updated_at
FROM post FROM post
WHERE id = #{id} WHERE id = #{id}
</select> </select>
<insert id="insert" parameterType="com.sb.web.board.domain.Post" <insert id="insert" parameterType="com.sb.web.board.domain.Post"
useGeneratedKeys="true" keyProperty="id"> useGeneratedKeys="true" keyProperty="id">
INSERT INTO post (title, content, author_id, author_name, view_count, category, created_at, updated_at) INSERT INTO post (title, content, author_id, author_name, view_count, notice, category, created_at, updated_at)
VALUES (#{title}, #{content}, #{authorId}, #{authorName}, 0, #{category}, NOW(), NOW()) VALUES (#{title}, #{content}, #{authorId}, #{authorName}, 0, #{notice}, #{category}, NOW(), NOW())
</insert> </insert>
<update id="update" parameterType="com.sb.web.board.domain.Post"> <update id="update" parameterType="com.sb.web.board.domain.Post">
@@ -80,4 +80,8 @@
UPDATE post SET blocked = #{blocked}, block_reason = #{blockReason} WHERE id = #{id} UPDATE post SET blocked = #{blocked}, block_reason = #{blockReason} WHERE id = #{id}
</update> </update>
<update id="updateNotice">
UPDATE post SET notice = #{notice} WHERE id = #{id}
</update>
</mapper> </mapper>