[feat] 백오피스에서 관리자(허용 이메일) DB 관리 - #33
Conversation
- admin_accounts 테이블 + 허용 판정을 ENV 부트스트랩 ∪ DB 합집합으로 변경(재배포 없이 즉시 반영) - /api/admin/accounts 목록·추가·삭제(ROLE_ADMIN), 자기삭제·중복·형식 검증 - ENV 부트스트랩은 UI 삭제 불가(자기잠금 방지 break-glass) - 백오피스 '관리자 관리' 페이지(추가/삭제 UI)
Test Coverage
|
WalkthroughENV 부트스트랩 관리자와 DB 관리자를 통합 관리하는 백엔드 모델·서비스·API를 추가하고, 관리자 인증이 DB 허용 여부를 반영하도록 변경했다. 백오피스에는 관리자 조회·추가·삭제 화면과 라우팅·내비게이션을 연결했다. Changes관리자 계정 관리
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
actor Admin
participant AdminAccountList
participant AdminAccountController
participant AdminAccountService
participant AdminAccountRepository
Admin->>AdminAccountList: 관리자 관리 페이지 접근
AdminAccountList->>AdminAccountController: GET /api/admin/accounts
AdminAccountController->>AdminAccountService: list(principal.email)
AdminAccountService->>AdminAccountRepository: DB 관리자 목록 조회
AdminAccountService-->>AdminAccountController: ENV 및 DB 관리자 목록
AdminAccountController-->>AdminAccountList: 관리자 응답 반환
Admin->>AdminAccountList: 이메일 추가 또는 삭제
AdminAccountList->>AdminAccountController: POST 또는 DELETE 요청
AdminAccountController->>AdminAccountService: add 또는 remove 호출
AdminAccountService->>AdminAccountRepository: 계정 저장 또는 삭제
AdminAccountController-->>AdminAccountList: 작업 결과 반환
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@admin-web/src/pages/admin-accounts/list.tsx`:
- Around line 175-180: Update the icon-only delete Button within the
account.removable condition to include an accessible aria-label containing the
account’s email, so assistive technology identifies which account will be
deleted.
- Around line 25-38: 관리자 계정 요청의 onError가 e.message를 오류 코드로 직접 전달하지 않도록 수정하세요.
apiFetch의 실패 응답에서 표준 error.code를 읽을 수 있는 타입으로 오류를 파싱하고, mapError가 해당 코드만 받아 사용자
메시지로 변환하게 하세요. 전역 오류 처리 계약을 사용하는 경우에도 message 대신 표준 오류 코드 필드를 전달하도록 일관되게 맞추세요.
In
`@src/main/kotlin/com/nexters/gamss/admin/controller/dto/AdminAccountResponse.kt`:
- Around line 23-30: Update AdminAccountResponse.from so removable is true only
when the account source is DB and entry.email differs from the current
principal’s email; pass the current email into this mapping flow and preserve
the existing false behavior for non-DB accounts.
In `@src/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.kt`:
- Around line 33-40: Update AdminAccountService so bootstrapEmails() takes
precedence over database entries: build the ENV email set, exclude matching DB
entries from the displayed list, and always expose ENV entries as
bootstrap/read-only entries. Also update the delete flow to reject any email
present in the ENV set, preserving isAllowed() behavior and preventing
ENV-backed accounts from being deleted.
In `@src/test/kotlin/com/nexters/gamss/admin/service/AdminAccountServiceTest.kt`:
- Around line 58-65: Update AdminAccountService.list and the corresponding test
so duplicate emails present in both ENV bootstrap accounts and the database
retain the ENV-sourced entry, with the test expecting AdminAccountSource.ENV.
Ensure the DB duplicate is omitted from the returned list so ENV accounts remain
read-only and cannot appear deletable.
In `@src/test/kotlin/com/nexters/gamss/admin/service/AdminAuthServiceTest.kt`:
- Around line 21-26: Update the AdminAuthServiceTest service helper to configure
accountRepository.existsByEmail for DB-approved emails, while preserving the
existing email configuration. Add a test covering an email where existsByEmail
returns true and verify that AdminAuthService issues an administrator token,
optionally mocking AdminAccountService directly to keep the
authentication-service boundary explicit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f43f1acd-d24c-4d0b-a47d-5eb0495a79be
📒 Files selected for processing (18)
admin-web/src/App.tsxadmin-web/src/components/layout/AdminLayout.tsxadmin-web/src/pages/admin-accounts/list.tsxadmin-web/src/types/adminAccount.tssrc/main/kotlin/com/nexters/gamss/admin/config/AdminProperties.ktsrc/main/kotlin/com/nexters/gamss/admin/controller/AdminAccountController.ktsrc/main/kotlin/com/nexters/gamss/admin/controller/dto/AddAdminAccountRequest.ktsrc/main/kotlin/com/nexters/gamss/admin/controller/dto/AdminAccountResponse.ktsrc/main/kotlin/com/nexters/gamss/admin/domain/AdminAccount.ktsrc/main/kotlin/com/nexters/gamss/admin/repository/AdminAccountRepository.ktsrc/main/kotlin/com/nexters/gamss/admin/service/AdminAccountEntry.ktsrc/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.ktsrc/main/kotlin/com/nexters/gamss/admin/service/AdminAccountSource.ktsrc/main/kotlin/com/nexters/gamss/admin/service/AdminAuthService.ktsrc/main/kotlin/com/nexters/gamss/global/exception/ErrorCode.ktsrc/main/resources/db/migration/V5__admin_accounts.sqlsrc/test/kotlin/com/nexters/gamss/admin/service/AdminAccountServiceTest.ktsrc/test/kotlin/com/nexters/gamss/admin/service/AdminAuthServiceTest.kt
- 목록에서 ENV를 DB보다 우선(ENV와 중복된 DB 항목 숨김) → '삭제=즉시 차단' 안내 일관성 유지 - 본인 계정은 removable=false로 표시해 자기삭제 방지와 UI 일치 - 삭제 버튼에 이메일 포함 aria-label 추가 - DB 허용 로그인 경로 테스트 추가
- 정규화(공백 제거·소문자)되지 않은 이메일이면 init require로 저장 전 차단 - unique 제약·isAllowed 조회가 정규화를 전제하므로 도메인이 fail-fast로 가드
- ErrorCode: 카드·관리자 에러코드 둘 다 유지 - App.tsx·AdminLayout: LLM설정·관리자관리 라우트·nav 둘 다 유지 - 마이그레이션 V5→V7 (dev의 V5 llm_settings·V6 cards 뒤로)
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
admin-web/src/pages/admin-accounts/list.tsx (2)
66-67: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win표준 오류 코드 필드를 사용하도록 오류 계약을 고정하세요.
두 호출부가
e?.message를 직접mapError에 전달합니다. 현재 data provider가 우연히message에 코드 값을 넣는 구현이라면 동작하지만, 표준 응답의error.code가 별도 필드로 전달될 때 모든 오류가 기본 문구로 뭉개집니다. 타입화된 오류 객체에서code를 읽어 전달하도록 통일하세요.Also applies to: 77-78
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@admin-web/src/pages/admin-accounts/list.tsx` around lines 66 - 67, Update the onError handlers in the affected account operations to read the standard error code field from the typed error object and pass that value to mapError instead of e?.message. Apply the same change to both call sites while preserving the existing setError behavior.
45-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win삭제 요청 중 중복 클릭을 막아 주세요.
삭제 뮤테이션의 진행 상태를 사용하지 않아 확인 버튼을 빠르게 여러 번 누르면 같은 ID에 DELETE가 중복 전송될 수 있습니다. 첫 요청이 성공한 뒤 두 번째 요청이 404가 되어 성공 작업 뒤 오류가 표시될 수 있으므로, 삭제 진행 중 확인 버튼을 비활성화하세요.
권장 수정 방향
- const { mutate: removeMutate } = useCustomMutation() + const { mutate: removeMutate, isLoading: removing } = useCustomMutation() - <AlertDialogAction onClick={() => remove(account.id as number)}>삭제</AlertDialogAction> + <AlertDialogAction disabled={removing} onClick={() => remove(account.id as number)}> + 삭제 + </AlertDialogAction>Also applies to: 71-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@admin-web/src/pages/admin-accounts/list.tsx` around lines 45 - 46, Update the removeMutate setup to capture its loading state (for example, removing) and use that state in the delete confirmation UI around the affected lines to disable the confirm button while deletion is in progress. Preserve the existing add mutation state and re-enable the button after the deletion completes.src/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.kt (2)
61-65: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win무결성 오류 전체를 중복 오류로 변환하지 마세요.
DataIntegrityViolationException은 이메일 UNIQUE 위반 외에도 길이 초과나 NOT NULL 위반을 포함할 수 있습니다. 현재는 이런 오류도ADMIN_ACCOUNT_ALREADY_EXISTS로 바뀌어 잘못된 409 응답을 반환합니다.uk_admin_accounts_email위반만 식별해 변환하고, 나머지는 원래 예외로 처리하세요.권장 수정 방향
} catch (e: DataIntegrityViolationException) { - throw BusinessException(ErrorCode.ADMIN_ACCOUNT_ALREADY_EXISTS, e.message) + if (isEmailUniqueViolation(e)) { + throw BusinessException(ErrorCode.ADMIN_ACCOUNT_ALREADY_EXISTS) + } + throw e }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.kt` around lines 61 - 65, Update the exception handling around AdminAccountService’s adminAccountRepository.saveAndFlush call to convert only violations of the uk_admin_accounts_email unique constraint into ADMIN_ACCOUNT_ALREADY_EXISTS. Re-throw other DataIntegrityViolationException instances unchanged so length, NOT NULL, and unrelated integrity failures retain their original handling.
74-81: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winENV와 중복된 DB 행도 삭제 경로에서 차단해야 합니다.
list()에서는 ENV 이메일과 동일한 DB 행을 숨기지만,remove()는 본인 여부만 검사합니다. 따라서 ENV에 나중에 추가된 이메일의 기존 DB 행은 다른 관리자가 ID를 직접 호출해 삭제할 수 있습니다. ENV 이메일도 삭제 불가 조건에 포함하거나, 숨겨진 행을NOT_FOUND로 처리해 부트스트랩 계정을 일관되게 읽기 전용으로 유지하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.kt` around lines 74 - 81, Update the remove() deletion guard to also reject accounts whose email matches any normalized ENV-configured admin email, not only currentEmail. Preserve the existing CANNOT_REMOVE_SELF behavior and ensure such duplicate bootstrap-account rows cannot be deleted through direct IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@admin-web/src/pages/admin-accounts/list.tsx`:
- Around line 66-67: Update the onError handlers in the affected account
operations to read the standard error code field from the typed error object and
pass that value to mapError instead of e?.message. Apply the same change to both
call sites while preserving the existing setError behavior.
- Around line 45-46: Update the removeMutate setup to capture its loading state
(for example, removing) and use that state in the delete confirmation UI around
the affected lines to disable the confirm button while deletion is in progress.
Preserve the existing add mutation state and re-enable the button after the
deletion completes.
In `@src/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.kt`:
- Around line 61-65: Update the exception handling around AdminAccountService’s
adminAccountRepository.saveAndFlush call to convert only violations of the
uk_admin_accounts_email unique constraint into ADMIN_ACCOUNT_ALREADY_EXISTS.
Re-throw other DataIntegrityViolationException instances unchanged so length,
NOT NULL, and unrelated integrity failures retain their original handling.
- Around line 74-81: Update the remove() deletion guard to also reject accounts
whose email matches any normalized ENV-configured admin email, not only
currentEmail. Preserve the existing CANNOT_REMOVE_SELF behavior and ensure such
duplicate bootstrap-account rows cannot be deleted through direct IDs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5bfb6510-7aea-4be1-83e4-6028ae2ec3a0
📒 Files selected for processing (13)
admin-web/src/App.tsxadmin-web/src/components/layout/AdminLayout.tsxadmin-web/src/pages/admin-accounts/list.tsxsrc/main/kotlin/com/nexters/gamss/admin/controller/AdminAccountController.ktsrc/main/kotlin/com/nexters/gamss/admin/controller/dto/AdminAccountResponse.ktsrc/main/kotlin/com/nexters/gamss/admin/domain/AdminAccount.ktsrc/main/kotlin/com/nexters/gamss/admin/service/AdminAccountEntry.ktsrc/main/kotlin/com/nexters/gamss/admin/service/AdminAccountService.ktsrc/main/kotlin/com/nexters/gamss/global/exception/ErrorCode.ktsrc/main/resources/db/migration/V7__admin_accounts.sqlsrc/test/kotlin/com/nexters/gamss/admin/domain/AdminAccountTest.ktsrc/test/kotlin/com/nexters/gamss/admin/service/AdminAccountServiceTest.ktsrc/test/kotlin/com/nexters/gamss/admin/service/AdminAuthServiceTest.kt
kite707
left a comment
There was a problem hiding this comment.
백오피스에서 관리자를 추가할 수 있게 개발해주셨네요! 고생하셨습니다~
🔗 연관 이슈
📌 개요
백오피스 접근 허용 관리자를 지금은
ADMIN_EMAILS환경 변수로만 관리해 추가/삭제에 GitHub 접근 + 재배포가 필요하다. 이를 DB로 관리 + 백오피스 UI에서 추가/삭제해 재배포 없이 즉시 반영되게 한다.🔧 주요 변경사항
admin_accounts테이블 +AdminAccount엔티티(email UNIQUE·정규화 저장·불변식 가드, 추가한 관리자·시각 기록)ADMIN_EMAILS부트스트랩 ∪ DB 합집합 —AdminAuthService가 DB까지 조회AdminAccountService: 허용 판정 / 목록(ENV+DB 병합) / 추가(중복·형식 검증, 동시 추가 유니크 처리) / 삭제(자기 자신 삭제 방지)🌐 API · DB 영향
GET /api/admin/accounts— 허용 관리자 목록(ENV+DB 병합)POST /api/admin/accounts{email}— 추가(다음 로그인부터 즉시 허용)DELETE /api/admin/accounts/{id}— 삭제(자기 자신 불가)ADMIN_ACCOUNT_ALREADY_EXISTS(409) ·ADMIN_ACCOUNT_NOT_FOUND(404) ·CANNOT_REMOVE_SELF(409)V5__admin_accounts.sql(email UNIQUE)ADMIN_EMAILS그대로 동작(부트스트랩으로 흡수), 로그인 계약 변경 없음💬 리뷰 포인트
V5를 사용합니다. 머지 순서에 따라 나중 것들이 재넘버링 필요 — [feat] 백오피스에서 LLM 모델·프롬프트 설정 #29·[feat] 대화 종료 시 감정 카드 생성 및 캘린더 조회 #31 정리 후 이 PR을 뒤 번호로 맞추는 걸 권장합니다.AdminProperties.isAllowed에서AdminAccountService.isAllowed(ENV ∪ DB)로 옮겼습니다. 부트스트랩 계정은 UI에서 삭제 대상이 아니게 설계(자기잠금 방지).saveAndFlush유니크 위반 →ADMIN_ACCOUNT_ALREADY_EXISTS변환으로 원자 처리.Summary by CodeRabbit
새로운 기능
개선 사항