feat(admin): 관리자 기능 — 신고 처리, 사용자 관리, 장소 데이터, 통계 - #83
Merged
Conversation
신고 처리, 사용자 관리, 장소 데이터 관리, 통계다. 모두 /api/v1/admin 아래에 있고
SecurityConfig 가 hasRole('ADMIN') 으로 막는다. 사용자 API 인가는 아직 켜지 않았다.
정지가 아무것도 막지 않고 있었다
V9 에서 suspend() 와 isWriteBlockedAt() 을 만들어 두었지만 확인하는 곳이 없었다.
관리자가 정지시켜도 그 사용자는 계속 글을 썼다. SuspendedUserWriteFilter 로 막는다.
쓰기 요청에서만 사용자를 조회한다. 액세스 토큰에 정지 여부가 없어 매 요청 확인하면
모든 조회에 질의가 하나씩 늘어난다. 읽기를 막지 않는 것은 의도이기도 하다. 정지된
사용자가 자기 상태를 확인할 수는 있어야 한다. /api/v1/auth 도 열어 둔다.
토큰 폐기 실패는 정지를 되돌리지 않는다. Redis 가 죽었다고 악성 사용자를 정지시키지
못하면 곤란하다. 게다가 Redis 가 없으면 갱신 자체가 실패하므로 폐기하지 못한 토큰으로
세션을 이어갈 수도 없다.
신고 처리
상세에 대상 원본을 함께 준다. 신고 사유만 보고는 조치할 수 없다. 이미 지워진 대상도
삭제 표시와 함께 보여준다. 감추면 관리자가 사라진 대상을 계속 찾는다.
오래된 신고부터 반환한다. 최신순이면 방치된 신고가 계속 뒤로 밀린다.
종결한 신고를 되돌릴 수 있다. 못 되돌리면 잘못 종결한 건을 다루려고 같은 내용의 새
신고를 기다려야 한다.
ReportStatus 에 REVIEWING 과 REJECTED 를 더한다. 확인만 하고 조치하지 않은 것과 아직
아무도 보지 않은 것을 구분하지 못하면 대기 목록이 같은 항목으로 계속 채워진다.
관리자 삭제도 소프트 삭제다. 작성자 확인만 건너뛰고 해시태그 정리와 댓글 수 감소는
본인 삭제와 같다. 다르게 처리하면 관리자가 지운 것만 다른 상태로 남아 복구·정리
경로가 갈라진다.
사용자 관리
관리자는 정지할 수 없다. 서로 정지시키면 아무도 풀 수 없는 상태가 되고, 그때 남는
수단은 DB 를 직접 고치는 것뿐이다.
받은 신고 수는 사용자 신고뿐 아니라 그가 쓴 게시물·댓글 신고도 함께 센다. 사용자
신고만 세면 문제 글을 반복해 올리는 계정이 깨끗해 보인다.
장소 데이터 관리
지우지 않고 가린다. 행을 지우면 TourAPI 증분 동기화가 다음 실행에서 같은 장소를 다시
만든다. 검색과 상세 조회 양쪽에서 뺀다. 검색에서만 빼고 상세를 열어 두면 예전 링크로
그대로 들어온다.
예산이 없으면 수동 적재를 시작하지 않는다. 시작해 봐야 예약 단계에서 막혀 아무것도
하지 못하고, 관리자는 왜 안 되는지 알 수 없다.
적재 트리거에는 트랜잭션을 걸지 않는다. TourAPI 호출이 길게 이어진다.
통계
실시간 집계다. 집계 테이블을 두면 그것을 채우는 배치와 어긋남을 관리해야 한다.
지표 이름을 SQL 에 그대로 넣지 않는다. StatsMetric 이 허용된 테이블만 담는다.
값이 0 인 날도 채운다. DB 는 행이 없는 날을 돌려주지 않아, 그대로 두면 화면이 빈 날을
건너뛰어 그래프가 실제보다 완만해 보인다.
인기 장소는 게시물 태그 횟수로 센다. 일정에 담긴 횟수를 쓰면 Planner 가 고른 것이
섞여 사용자가 고른 것과 구분되지 않는다.
인기 해시태그는 hashtags.post_count 대신 실제 연결을 센다. 집계 컬럼은 어긋날 수 있고
삭제된 게시물의 몫이 남아 있을 수 있다.
정리 스케줄러를 배포에서 켠다
COMMUNITY_POST_PURGE_ENABLED 가 기본 false 인데 배포 워크플로에 없어, 지운 게시물이
영원히 쌓이고 본문과 사진 URL 이 DB 에 남는 상태였다.
스키마
V12 는 reports 에 handled_by 와 handled_at 을 더한다.
V13 은 places 에 hidden_at 과 hidden_reason 을 더한다. 일정 후보에서 빼는 것은 FastAPI
가 places 를 직접 읽으므로 data 저장소에서 함께 처리한다(SMBDHTeam/data#38).
전체 267건 통과. Testcontainers 로 실제 PostgreSQL 에 V12·V13 적용을 확인했다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🤖 Gemini PR Review
1. [HIGH] 인증 누락 시 부적절한 예외 처리 및 보안 흐름 취약점
// AdminReportController.java
@PatchMapping("/reports/{reportId}")
public AdminReportResponse updateStatus(
@Parameter(example = "12") @PathVariable Long reportId,
@Valid @RequestBody ReportStatusUpdateRequest request
) {
Long adminId = CurrentUser.idOrNull();
if (adminId == null) {
throw new BusinessException(ErrorCode.UNAUTHORIZED); // 또는 적절한 인증 예외
}
return adminReportService.updateStatus(reportId, request.status(), adminId);
}2. [MEDIUM] JDBC 드라이버 호환성에 따른 런타임 오류 가능성
// AdminStatsService.java
Map<LocalDate, Long> byDay = new HashMap<>();
jdbcTemplate.query(sql, rs -> {
java.sql.Date sqlDate = rs.getDate("stat_date");
if (sqlDate != null) {
byDay.put(sqlDate.toLocalDate(), rs.getLong("stat_count"));
}
}, from.atStartOfDay());Model: `gemini-3.5-flash` · API key: `PRIMARY` · Commit: `68e029c` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
관리자 기능 네 가지다. 모두
/api/v1/admin아래이며hasRole('ADMIN')으로 막힌다.사용자 API 인가는 아직 켜지 않았다.
엔드포인트
GET/PATCH /admin/reports,GET /admin/reports/{id},DELETE /admin/posts|comments/{id}GET /admin/users,GET /admin/users/{id},PATCH /admin/users/{id}/statusGET/POST /admin/places/ingestion,GET /admin/places/hidden,PATCH /admin/places/{id}/hiddenGET /admin/stats/summary|trend|popular정지가 아무것도 막지 않고 있었다
V9 에서
suspend()와isWriteBlockedAt()을 만들어 두었는데 확인하는 곳이 한 군데도없었다. 관리자가 정지시켜도 그 사용자는 계속 글을 쓴다.
SuspendedUserWriteFilter로 막는다.쓰기 요청에서만 사용자를 조회한다. 액세스 토큰에 정지 여부가 없어 매 요청 확인하면
모든 조회에 질의가 하나씩 늘어난다. 읽기를 막지 않는 것은 의도이기도 하다. 정지된
사용자가 자기 상태를 확인할 수는 있어야 한다.
/api/v1/auth도 열어 둔다.토큰 폐기 실패는 정지를 되돌리지 않는다. Redis 가 죽었다고 악성 사용자를 정지시키지
못하면 곤란하다. 게다가 Redis 가 없으면 갱신 자체가 실패하므로 폐기하지 못한 토큰으로
세션을 이어갈 수도 없다.
판단한 것
신고 상세에 대상 원본을 함께 준다. 신고 사유만 보고는 조치할 수 없다. 이미 지워진
대상도 삭제 표시와 함께 보여준다. 감추면 관리자가 사라진 대상을 계속 찾는다.
오래된 신고부터 반환한다. 최신순이면 방치된 신고가 계속 뒤로 밀린다.
관리자 삭제도 소프트 삭제다. 작성자 확인만 건너뛰고 해시태그 정리·댓글 수 감소는
본인 삭제와 같다. 다르게 처리하면 관리자가 지운 것만 다른 상태로 남아 복구·정리 경로가
갈라진다.
관리자는 정지할 수 없다. 서로 정지시키면 아무도 풀 수 없는 상태가 되고, 그때 남는
수단은 DB 를 직접 고치는 것뿐이다.
장소는 지우지 않고 가린다. 행을 지우면 TourAPI 증분 동기화가 다음 실행에서 같은 장소를
다시 만든다. 검색과 상세 양쪽에서 뺀다. 검색에서만 빼고 상세를 열어 두면 예전 링크로
그대로 들어온다.
예산이 없으면 수동 적재를 시작하지 않는다. 시작해 봐야 예약 단계에서 막혀 아무것도
하지 못하고, 관리자는 왜 안 되는지 알 수 없다.
통계는 값이 0 인 날도 채운다. DB 는 행이 없는 날을 돌려주지 않아, 그대로 두면 화면이
빈 날을 건너뛰어 그래프가 실제보다 완만해 보인다.
인기 해시태그는
hashtags.post_count대신 실제 연결을 센다. 집계 컬럼은 어긋날 수 있고삭제된 게시물의 몫이 남아 있을 수 있다. (#78 리뷰에서 지적한 그 문제다)
정리 스케줄러를 배포에서 켠다
COMMUNITY_POST_PURGE_ENABLED가 기본false인데 배포 워크플로에 없어, #78 로 만든정리 스케줄러가 배포돼도 한 번도 돌지 않는 상태였다. 지운 게시물이 영원히 쌓이고
사용자가 지웠다고 믿는 본문과 사진 URL 이 DB 에 남는다.
스키마
reports.handled_by,handled_at+idx_reports_status_created_atplaces.hidden_at,hidden_reason+ 부분 인덱스data 저장소와 함께 봐야 한다
가린 장소를 일정 후보에서 빼는 것은 data#38 이다.
일정 후보는 FastAPI 가
places를 직접 읽어 뽑으므로 Spring 만으로는 완결되지 않는다.이 PR 을 먼저 배포해야 한다. V13 이
hidden_at을 만든다. 순서가 바뀌면 없는 컬럼을참조해 후보 조회가 전부 실패한다.
검증
./gradlew test— 267건 통과 (기존 232 + 신규 35)/auth통과, 해제 후 복구Testcontainers 로 실제 PostgreSQL 에 V12·V13 적용과 JPA 스키마 검증을 확인했다.
영향
/api/v1/admin/**)🤖 Generated with Claude Code