feat: 커뮤니티 UGC 안전장치 추가 (App Store 가이드라인 1.2 대응)
- 사용자 차단(user_block) 테이블·매퍼·API 추가 - 게시글/댓글 목록·상세 쿼리에 차단 사용자 필터링(즉시 숨김) - 부적절 표현(욕설·비방) 콘텐츠 필터 추가(작성·수정·댓글 차단) - 관리자용 사용자 차단 통지 조회 API Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b6976f5612
commit
0b027b3704
@@ -50,8 +50,9 @@ public class BoardController {
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String tag,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String searchType) {
|
||||
return boardService.list(page, size, category, tag, keyword, searchType);
|
||||
@RequestParam(required = false) String searchType,
|
||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||
return boardService.list(page, size, category, tag, keyword, searchType, current);
|
||||
}
|
||||
|
||||
/** 내 글 모아보기 */
|
||||
@@ -240,4 +241,39 @@ public class BoardController {
|
||||
boardService.dismissReports(body.get("targetType"), Long.valueOf(body.get("targetId")), current);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/* ===================== 사용자 차단 (UGC 1.2) ===================== */
|
||||
|
||||
/** 사용자 차단 — 차단 즉시 상대 콘텐츠가 내 화면에서 숨겨진다 */
|
||||
@PostMapping("/users/{id}/block")
|
||||
public ResponseEntity<Void> blockUser(
|
||||
@PathVariable Long id,
|
||||
@RequestBody(required = false) Map<String, String> body,
|
||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||
boardService.blockUser(id, body != null ? body.get("reason") : null, current);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/** 사용자 차단 해제 */
|
||||
@PostMapping("/users/{id}/unblock")
|
||||
public ResponseEntity<Void> unblockUser(
|
||||
@PathVariable Long id,
|
||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||
boardService.unblockUser(id, current);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/** 내가 차단한 사용자 목록 (관리 화면) */
|
||||
@GetMapping("/blocks")
|
||||
public List<com.sb.web.board.dto.BlockedUser> myBlocks(
|
||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||
return boardService.myBlockedUsers(current);
|
||||
}
|
||||
|
||||
/** 최근 사용자 차단 통지 (관리자) — 운영자 검토용 */
|
||||
@GetMapping("/user-blocks")
|
||||
public List<com.sb.web.board.dto.UserBlockNotice> userBlocks(
|
||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||
return boardService.listUserBlocks(current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.sb.web.board.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 내가 차단한 사용자 항목 (차단 목록 관리 화면용).
|
||||
*/
|
||||
@Data
|
||||
public class BlockedUser {
|
||||
private Long blockedId;
|
||||
private String name;
|
||||
private String googlePicture;
|
||||
private String profileImage;
|
||||
private String reason;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sb.web.board.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 사용자 차단 통지 항목 (관리자 화면용).
|
||||
* 어떤 회원이 어떤 회원을 왜 차단했는지 — 운영자가 24시간 내 검토·조치할 수 있도록 노출.
|
||||
*/
|
||||
@Data
|
||||
public class UserBlockNotice {
|
||||
private Long blockerId;
|
||||
private String blockerName;
|
||||
private Long blockedId;
|
||||
private String blockedName;
|
||||
private String reason;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface CommentMapper {
|
||||
|
||||
List<Comment> findByPostId(@Param("postId") Long postId);
|
||||
List<Comment> findByPostId(@Param("postId") Long postId, @Param("viewerId") Long viewerId);
|
||||
|
||||
Comment findById(@Param("id") Long id);
|
||||
|
||||
|
||||
@@ -15,18 +15,21 @@ public interface PostMapper {
|
||||
@Param("category") String category,
|
||||
@Param("tag") String tag,
|
||||
@Param("keyword") String keyword,
|
||||
@Param("searchType") String searchType);
|
||||
@Param("searchType") String searchType,
|
||||
@Param("viewerId") Long viewerId);
|
||||
|
||||
long countPage(@Param("category") String category,
|
||||
@Param("tag") String tag,
|
||||
@Param("keyword") String keyword,
|
||||
@Param("searchType") String searchType);
|
||||
@Param("searchType") String searchType,
|
||||
@Param("viewerId") Long viewerId);
|
||||
|
||||
/** 인기 글(추천 minUp 이상 & hours 시간 이내) 상단 노출용 — 추천 많은 순 limit 건 */
|
||||
List<PostSummary> findHotPosts(@Param("category") String category,
|
||||
@Param("hours") int hours,
|
||||
@Param("minUp") int minUp,
|
||||
@Param("limit") int limit);
|
||||
@Param("limit") int limit,
|
||||
@Param("viewerId") Long viewerId);
|
||||
|
||||
/** 내 글 모아보기 */
|
||||
List<PostSummary> findMyPage(@Param("memberId") Long memberId,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.sb.web.board.mapper;
|
||||
|
||||
import com.sb.web.board.dto.BlockedUser;
|
||||
import com.sb.web.board.dto.UserBlockNotice;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 사용자 차단(user_block) 매퍼 — 회원 간 차단 관계.
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserBlockMapper {
|
||||
|
||||
/** 차단 추가 (중복은 무시, 재차단 시 사유 갱신) */
|
||||
int upsert(@Param("blockerId") Long blockerId,
|
||||
@Param("blockedId") Long blockedId,
|
||||
@Param("reason") String reason);
|
||||
|
||||
/** 차단 해제 */
|
||||
int delete(@Param("blockerId") Long blockerId,
|
||||
@Param("blockedId") Long blockedId);
|
||||
|
||||
/** 내가 차단한 회원 id 목록 (피드 필터용) */
|
||||
List<Long> findBlockedIds(@Param("blockerId") Long blockerId);
|
||||
|
||||
/** 내가 차단한 회원 목록 (관리 화면용 — 이름/아바타 포함) */
|
||||
List<BlockedUser> findBlockedUsers(@Param("blockerId") Long blockerId);
|
||||
|
||||
/** 최근 차단 통지 목록 (관리자 — 운영자 검토용) */
|
||||
List<UserBlockNotice> listRecent(@Param("limit") int limit);
|
||||
|
||||
/** 회원 탈퇴 시 관련 차단 관계 정리 */
|
||||
int deleteByMember(@Param("memberId") Long memberId);
|
||||
}
|
||||
@@ -13,10 +13,13 @@ import com.sb.web.board.dto.PostSummary;
|
||||
import com.sb.web.board.dto.Recommender;
|
||||
import com.sb.web.board.dto.ReportedItem;
|
||||
import com.sb.web.board.dto.VoteResponse;
|
||||
import com.sb.web.board.dto.BlockedUser;
|
||||
import com.sb.web.board.dto.UserBlockNotice;
|
||||
import com.sb.web.board.mapper.CommentMapper;
|
||||
import com.sb.web.board.mapper.PostMapper;
|
||||
import com.sb.web.board.mapper.ReportMapper;
|
||||
import com.sb.web.board.mapper.TagMapper;
|
||||
import com.sb.web.board.mapper.UserBlockMapper;
|
||||
import com.sb.web.board.mapper.VoteMapper;
|
||||
import com.sb.web.common.exception.ApiException;
|
||||
import com.sb.web.point.PointService;
|
||||
@@ -44,7 +47,9 @@ public class BoardService {
|
||||
private final CommentMapper commentMapper;
|
||||
private final VoteMapper voteMapper;
|
||||
private final ReportMapper reportMapper;
|
||||
private final UserBlockMapper userBlockMapper;
|
||||
private final PointService pointService;
|
||||
private final ContentFilter contentFilter;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/** 같은 사용자가 이 시간 내 재열람 시 조회수를 다시 올리지 않음 */
|
||||
@@ -63,20 +68,21 @@ public class BoardService {
|
||||
|
||||
/* ===================== 게시글 ===================== */
|
||||
|
||||
public PageResponse<PostSummary> list(int page, int size, String category, String tag, String keyword, String searchType) {
|
||||
public PageResponse<PostSummary> list(int page, int size, String category, String tag, String keyword, String searchType, SessionUser viewer) {
|
||||
int p = Math.max(page, 1);
|
||||
int s = Math.min(Math.max(size, 1), 100);
|
||||
int offset = (p - 1) * s;
|
||||
String st = normalizeSearchType(searchType);
|
||||
String cat = normalizeCategory(category);
|
||||
Long viewerId = viewer == null ? null : viewer.getId();
|
||||
|
||||
// 목록에는 태그를 표시하지 않으므로 게시글별 태그 조회를 생략한다.
|
||||
List<PostSummary> content = postMapper.findPage(offset, s, cat, blankToNull(tag), blankToNull(keyword), st);
|
||||
long total = postMapper.countPage(cat, blankToNull(tag), blankToNull(keyword), st);
|
||||
List<PostSummary> content = postMapper.findPage(offset, s, cat, blankToNull(tag), blankToNull(keyword), st, viewerId);
|
||||
long total = postMapper.countPage(cat, blankToNull(tag), blankToNull(keyword), st, viewerId);
|
||||
|
||||
// 1페이지·검색 없을 때만 인기 글을 최상단에 노출 (중복 제거)
|
||||
if (p == 1 && blankToNull(tag) == null && blankToNull(keyword) == null) {
|
||||
List<PostSummary> hot = postMapper.findHotPosts(cat, HOT_WITHIN_HOURS, HOT_MIN_UP, HOT_MAX);
|
||||
List<PostSummary> hot = postMapper.findHotPosts(cat, HOT_WITHIN_HOURS, HOT_MIN_UP, HOT_MAX, viewerId);
|
||||
if (!hot.isEmpty()) {
|
||||
java.util.Set<Long> hotIds = new java.util.HashSet<>();
|
||||
hot.forEach(h -> { h.setHot(true); hotIds.add(h.getId()); });
|
||||
@@ -180,6 +186,9 @@ public class BoardService {
|
||||
|
||||
@Transactional
|
||||
public PostDetail create(PostRequest req, SessionUser author) {
|
||||
// 무관용 콘텐츠 필터 — 욕설·비방 등 부적절 표현 포함 시 등록 차단
|
||||
contentFilter.check(req.getTitle());
|
||||
contentFilter.check(req.getContent());
|
||||
Post post = Post.builder()
|
||||
.title(req.getTitle())
|
||||
.content(req.getContent())
|
||||
@@ -207,6 +216,10 @@ public class BoardService {
|
||||
}
|
||||
assertCanModify(post.getAuthorId(), user);
|
||||
|
||||
// 무관용 콘텐츠 필터 — 수정 시에도 부적절 표현 차단
|
||||
contentFilter.check(req.getTitle());
|
||||
contentFilter.check(req.getContent());
|
||||
|
||||
post.setTitle(req.getTitle());
|
||||
post.setContent(req.getContent());
|
||||
postMapper.update(post);
|
||||
@@ -242,6 +255,8 @@ public class BoardService {
|
||||
if (Boolean.TRUE.equals(post.getBlocked())) {
|
||||
throw new ApiException(HttpStatus.FORBIDDEN, "제한된 게시글에는 댓글을 작성할 수 없습니다.");
|
||||
}
|
||||
// 무관용 콘텐츠 필터 — 부적절 표현 포함 댓글 등록 차단
|
||||
contentFilter.check(req.getContent());
|
||||
Comment comment = Comment.builder()
|
||||
.postId(postId)
|
||||
.authorId(author.getId())
|
||||
@@ -319,6 +334,39 @@ public class BoardService {
|
||||
reportMapper.deleteByTarget(targetType, targetId);
|
||||
}
|
||||
|
||||
/* ===================== 사용자 차단 ===================== */
|
||||
|
||||
/**
|
||||
* 사용자 차단 (App Store 1.2 UGC). 차단 즉시 상대 콘텐츠가 내 목록·상세·댓글에서 숨겨진다.
|
||||
* reason 은 운영자 통지(관리자 차단 목록)에 사용된다.
|
||||
*/
|
||||
@Transactional
|
||||
public void blockUser(Long blockedId, String reason, SessionUser user) {
|
||||
if (blockedId == null || blockedId.equals(user.getId())) {
|
||||
throw new ApiException(HttpStatus.BAD_REQUEST, "자기 자신은 차단할 수 없습니다.");
|
||||
}
|
||||
userBlockMapper.upsert(user.getId(), blockedId, blankToNull(reason));
|
||||
}
|
||||
|
||||
/** 차단 해제 */
|
||||
@Transactional
|
||||
public void unblockUser(Long blockedId, SessionUser user) {
|
||||
userBlockMapper.delete(user.getId(), blockedId);
|
||||
}
|
||||
|
||||
/** 내가 차단한 사용자 목록 (관리 화면용) */
|
||||
@Transactional(readOnly = true)
|
||||
public List<BlockedUser> myBlockedUsers(SessionUser user) {
|
||||
return userBlockMapper.findBlockedUsers(user.getId());
|
||||
}
|
||||
|
||||
/** 최근 사용자 차단 통지 (관리자 전용) — 운영자 24시간 내 검토용 */
|
||||
@Transactional(readOnly = true)
|
||||
public List<UserBlockNotice> listUserBlocks(SessionUser user) {
|
||||
assertAdmin(user);
|
||||
return userBlockMapper.listRecent(200);
|
||||
}
|
||||
|
||||
/* ===================== 추천/비추천 ===================== */
|
||||
|
||||
/** 게시글 추천/비추천 토글. 같은 표 재요청 시 취소, 반대면 전환. */
|
||||
@@ -392,7 +440,7 @@ public class BoardService {
|
||||
for (CommentVoteSummary s : voteMapper.findCommentVoteSummary(postId, viewerId)) {
|
||||
voteByComment.put(s.getCommentId(), s);
|
||||
}
|
||||
List<CommentResponse> comments = new java.util.ArrayList<>(commentMapper.findByPostId(postId).stream().map(c -> {
|
||||
List<CommentResponse> comments = new java.util.ArrayList<>(commentMapper.findByPostId(postId, viewerId).stream().map(c -> {
|
||||
CommentResponse cr = CommentResponse.from(c);
|
||||
CommentVoteSummary s = voteByComment.get(c.getId());
|
||||
if (s != null) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.sb.web.board.service;
|
||||
|
||||
import com.sb.web.common.exception.ApiException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 커뮤니티 콘텐츠 필터 (App Store 가이드라인 1.2 UGC — "부적절한 콘텐츠 필터링" 요건).
|
||||
*
|
||||
* 게시글·댓글 작성 시 욕설/혐오/차별 등 불쾌한 표현이 포함되면 등록을 차단한다.
|
||||
* (스팸/광고 도배는 별도로 PointService.isSpam 에서 블라인드 처리)
|
||||
*
|
||||
* 비교 전 정규화: 소문자화 + 공백/특수문자 제거로 "시 발", "시_발" 등 우회를 일부 차단.
|
||||
*/
|
||||
@Component
|
||||
public class ContentFilter {
|
||||
|
||||
// 무관용 정책 대상 금지어(대표 예시 — 필요 시 확장). 소문자·정규화 후 부분일치로 검사.
|
||||
private static final List<String> BANNED_WORDS = List.of(
|
||||
// 한글 욕설/비속어
|
||||
"시발", "씨발", "씨빨", "시팔", "씨팔", "ㅅㅂ", "ㅆㅂ",
|
||||
"병신", "ㅄ", "ㅂㅅ", "지랄", "ㅈㄹ", "좆", "좃", "존나", "졸라",
|
||||
"개새끼", "개색끼", "새끼", "쌍놈", "쌍년", "니미", "니애미", "느금마", "느그",
|
||||
"닥쳐", "꺼져", "엿먹", "뒤져", "뒈져", "죽어버려", "자살해",
|
||||
"창녀", "걸레년", "보지", "자지", "섹스", "야동",
|
||||
// 영문 욕설
|
||||
"fuck", "fucking", "motherfucker", "shit", "bitch", "asshole", "bastard", "dick", "cunt", "slut"
|
||||
);
|
||||
|
||||
/** 부적절 표현 포함 여부. 정규화(소문자 + 공백·비문자 제거) 후 부분일치 검사. */
|
||||
public boolean isObjectionable(String content) {
|
||||
if (content == null || content.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
String normalized = normalize(content);
|
||||
return BANNED_WORDS.stream().anyMatch(w -> normalized.contains(normalize(w)));
|
||||
}
|
||||
|
||||
/** 부적절 표현이 있으면 400 예외를 던져 등록을 차단한다. */
|
||||
public void check(String content) {
|
||||
if (isObjectionable(content)) {
|
||||
throw new ApiException(HttpStatus.BAD_REQUEST,
|
||||
"부적절한 표현(욕설·비방 등)이 포함되어 있어 등록할 수 없습니다. 커뮤니티 이용수칙을 확인해 주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
/** 소문자화 + 유니코드 정규화 + 문자/숫자 외(공백·기호·자모 분리 우회 등) 제거 */
|
||||
private String normalize(String s) {
|
||||
String lower = Normalizer.normalize(s.toLowerCase(), Normalizer.Form.NFKC);
|
||||
// 한글/영문/숫자만 남기고 제거 → "시 발", "s.h.i.t" 같은 우회 완화
|
||||
return lower.replaceAll("[^0-9a-z\\uac00-\\ud7a3\\u3131-\\u318e]", "");
|
||||
}
|
||||
}
|
||||
@@ -130,3 +130,16 @@ CREATE TABLE IF NOT EXISTS comment_vote (
|
||||
PRIMARY KEY (comment_id, member_id),
|
||||
KEY idx_comment_vote_comment (comment_id, vote_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- 사용자 차단 (App Store 가이드라인 1.2 UGC — "악성 사용자 차단" 요건).
|
||||
-- blocker_id 가 blocked_id 를 차단하면, 목록·상세·댓글에서 blocked_id 의 콘텐츠가
|
||||
-- blocker 화면에서 즉시 숨겨진다. reason 은 운영자 통지(신고 기록)에 사용.
|
||||
CREATE TABLE IF NOT EXISTS user_block (
|
||||
blocker_id BIGINT NOT NULL COMMENT '차단한 회원',
|
||||
blocked_id BIGINT NOT NULL COMMENT '차단당한 회원',
|
||||
reason VARCHAR(255) NULL COMMENT '차단 사유(운영자 통지용)',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (blocker_id, blocked_id),
|
||||
KEY idx_user_block_blocker (blocker_id),
|
||||
KEY idx_user_block_blocked (blocked_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@@ -15,12 +15,19 @@
|
||||
<result property="createdAt" column="created_at"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="findByPostId" parameterType="long" resultMap="commentResultMap">
|
||||
<select id="findByPostId" resultMap="commentResultMap">
|
||||
SELECT c.id, c.post_id, c.author_id, c.author_name, c.content, c.blocked, c.created_at,
|
||||
m.google_picture AS author_google_picture, m.profile_image AS author_profile_image
|
||||
FROM comment c
|
||||
LEFT JOIN member m ON m.id = c.author_id
|
||||
WHERE c.post_id = #{postId}
|
||||
<!-- 차단한 사용자의 댓글 숨김 -->
|
||||
<if test="viewerId != null">
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_block ub
|
||||
WHERE ub.blocker_id = #{viewerId} AND ub.blocked_id = c.author_id
|
||||
)
|
||||
</if>
|
||||
ORDER BY c.id ASC
|
||||
</select>
|
||||
|
||||
|
||||
@@ -3,12 +3,23 @@
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.sb.web.board.mapper.PostMapper">
|
||||
|
||||
<!-- 차단한 사용자의 글 숨김 (viewerId 가 차단한 author 제외) -->
|
||||
<sql id="blockFilter">
|
||||
<if test="viewerId != null">
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_block ub
|
||||
WHERE ub.blocker_id = #{viewerId} AND ub.blocked_id = p.author_id
|
||||
)
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<sql id="filter">
|
||||
<if test="tag != null and tag != ''">
|
||||
JOIN post_tag pt ON pt.post_id = p.id
|
||||
JOIN tag t ON t.id = pt.tag_id
|
||||
</if>
|
||||
<where>
|
||||
<include refid="blockFilter"/>
|
||||
<if test="category != null and category != ''">
|
||||
AND p.category = #{category}
|
||||
</if>
|
||||
@@ -61,6 +72,7 @@
|
||||
AND (#{category} IS NULL OR p.category = #{category})
|
||||
AND p.created_at >= NOW() - INTERVAL #{hours} HOUR
|
||||
AND (SELECT COUNT(*) FROM post_vote v WHERE v.post_id = p.id AND v.vote_type = 'UP') >= #{minUp}
|
||||
<include refid="blockFilter"/>
|
||||
ORDER BY (SELECT COUNT(*) FROM post_vote v WHERE v.post_id = p.id AND v.vote_type = 'UP') DESC, p.id DESC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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.UserBlockMapper">
|
||||
|
||||
<!-- 차단 추가 (중복 시 사유만 갱신) -->
|
||||
<insert id="upsert">
|
||||
INSERT INTO user_block (blocker_id, blocked_id, reason, created_at)
|
||||
VALUES (#{blockerId}, #{blockedId}, #{reason}, NOW())
|
||||
ON DUPLICATE KEY UPDATE reason = VALUES(reason)
|
||||
</insert>
|
||||
|
||||
<delete id="delete">
|
||||
DELETE FROM user_block WHERE blocker_id = #{blockerId} AND blocked_id = #{blockedId}
|
||||
</delete>
|
||||
|
||||
<select id="findBlockedIds" resultType="long">
|
||||
SELECT blocked_id FROM user_block WHERE blocker_id = #{blockerId}
|
||||
</select>
|
||||
|
||||
<select id="findBlockedUsers" resultType="com.sb.web.board.dto.BlockedUser">
|
||||
SELECT ub.blocked_id AS blockedId,
|
||||
m.name AS name,
|
||||
m.google_picture AS googlePicture,
|
||||
m.profile_image AS profileImage,
|
||||
ub.reason AS reason,
|
||||
ub.created_at AS createdAt
|
||||
FROM user_block ub
|
||||
LEFT JOIN member m ON m.id = ub.blocked_id
|
||||
WHERE ub.blocker_id = #{blockerId}
|
||||
ORDER BY ub.created_at DESC
|
||||
</select>
|
||||
|
||||
<select id="listRecent" resultType="com.sb.web.board.dto.UserBlockNotice">
|
||||
SELECT ub.blocker_id AS blockerId,
|
||||
bm.name AS blockerName,
|
||||
ub.blocked_id AS blockedId,
|
||||
tm.name AS blockedName,
|
||||
ub.reason AS reason,
|
||||
ub.created_at AS createdAt
|
||||
FROM user_block ub
|
||||
LEFT JOIN member bm ON bm.id = ub.blocker_id
|
||||
LEFT JOIN member tm ON tm.id = ub.blocked_id
|
||||
ORDER BY ub.created_at DESC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
||||
<delete id="deleteByMember">
|
||||
DELETE FROM user_block WHERE blocker_id = #{memberId} OR blocked_id = #{memberId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user