[feature] 개발자 페이지 만들어서 관리 쉽게 ㄱㄱ #1164
Conversation
- .gitignore: /.cursor 추가 - dev 포털: 스티키 헤더·섹션 네비, 동아리 목록 페이지네이션·ID 복사, 토스트·저장 피드백, 스타일·접근성 정리 Co-authored-by: Cursor <cursoragent@cursor.com>
- edit.html: 동아리 수정 전용 페이지 추가 (약력/모집정보 폼)\n- index.html: 수정 UI 제거, 목록 클릭 시 openEditWindow로 수정 창 오픈 Co-authored-by: Cursor <cursoragent@cursor.com>
- dict-edit.html: 단어사전 수정 전용 페이지 추가\n- index.html: 단어사전 목록·페이지네이션, 단일/CSV 추가, 수정 창 연동, 삭제, 캐시 새로고침 Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
User & Authentication Management backend/src/main/java/moadong/user/entity/User.java, backend/src/main/java/moadong/user/entity/enums/UserRole.java, backend/src/main/java/moadong/user/controller/DevAuthController.java, backend/src/main/java/moadong/user/payload/request/DevRegisterRequest.java, backend/src/main/java/moadong/user/payload/request/UserRegisterRequest.java, backend/src/main/java/moadong/user/service/UserCommandService.java |
사용자 역할 시스템 추가(CLUB_ADMIN, DEVELOPER), 개발자 등록 엔드포인트 추가, 역할별 사용자 생성 로직 구현 |
Club Admin API backend/src/main/java/moadong/club/controller/ClubAdminController.java, backend/src/main/java/moadong/club/payload/response/ClubListResponse.java, backend/src/main/java/moadong/club/service/ClubProfileService.java |
클럽 목록 조회, 클럽 정보 및 모집정보 수정 엔드포인트 추가 |
Security & Portal Setup backend/src/main/java/moadong/global/config/SecurityConfig.java, backend/src/main/java/moadong/global/controller/DevPortalController.java |
/api/admin 경로에 DEVELOPER 역할 요구 권한 설정, 개발자 포털 리디렉션 컨트롤러 추가 |
Developer Portal UI backend/src/main/resources/static/dev/index.html, backend/src/main/resources/static/dev/edit.html, backend/src/main/resources/static/dev/dict-edit.html |
토큰 기반 인증, 클럽 관리 및 사전 편집 기능을 포함한 개발자 포털 웹 UI |
Configuration backend/.gitignore |
/.cursor 무시 규칙 추가 |
Sequence Diagram(s)
sequenceDiagram
participant Browser as Developer Browser
participant Portal as Dev Portal UI
participant Auth as Auth API
participant Server as Admin API
participant DB as Database
rect rgba(200, 150, 255, 0.5)
Note over Browser,DB: 개발자 등록 & 로그인 흐름
Browser->>Portal: /dev 접속
Portal->>Auth: POST /auth/dev/register (userId, password, secret)
Auth->>DB: 개발자 사용자 생성 (role=DEVELOPER)
Auth-->>Portal: 등록 성공
Browser->>Portal: /auth/user/login 제출 (userId, password)
Auth->>DB: 자격증명 검증
Auth-->>Portal: 토큰 + userId 반환
Portal->>Portal: sessionStorage에 토큰 저장
end
rect rgba(100, 200, 255, 0.5)
Note over Browser,DB: 클럽 관리 흐름
Portal->>Server: GET /api/admin/clubs (Authorization: Bearer token)
Server->>DB: 모든 클럽 조회
DB-->>Server: ClubListResponse
Server-->>Portal: 클럽 목록 표시
Browser->>Portal: 클럽 편집 클릭
Portal->>Server: GET /api/club/{clubId}
Server->>DB: 클럽 상세 조회
DB-->>Portal: 클럽 데이터
Portal->>Portal: 편집 폼 표시
Browser->>Portal: 정보 수정 후 저장
Portal->>Server: PUT /api/admin/club/{clubId}/info (ClubInfoRequest)
Server->>DB: 클럽 정보 업데이트
Server-->>Portal: 성공 응답
end
rect rgba(100, 255, 200, 0.5)
Note over Browser,DB: 사전 관리 흐름
Portal->>Server: GET /api/admin/word-dictionary
Server->>DB: 사전 목록 조회
DB-->>Portal: 사전 데이터
Portal->>Portal: 사전 테이블 표시
Browser->>Portal: 사전 항목 편집
Portal->>Portal: 편집 페이지 (dict-edit.html) 오픈
Browser->>Portal: 단어 저장
Portal->>Server: PUT /api/admin/dictionary/{id} (inputWords)
Server->>DB: 사전 항목 업데이트
Server-->>Portal: 성공 응답
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
- [feature] 관리자페이지 기본정보 및 소개정보 수정 api 연동 #971: 프론트엔드 관리자 UI 및 클럽 정보/설명 편집 API 통합이 이 PR의 새로운 관리자 엔드포인트 및 서비스 메서드와 직접 연계됩니다.
- [feature] 액세스 토큰 및 리프레시 토큰의 만료 시간을 수정하고, 로그인, 관리자 계정 관련의 동시성 문제를 해결한다 #713: 두 PR 모두 ClubProfileService의 거래 처리 및 버전 관리 지원을 추가하여 클럽 업데이트 흐름을 수정합니다.
- [feature] 상시 모집 상태 추가 및 모집 정보 변경 시 반영되도록 변경 #485: 두 PR 모두 ClubProfileService의 클럽 모집 상태 업데이트 로직을 수정합니다(이번 PR은 updateClubRecruitmentInfoByClubId에서 모집 상태 재계산).
Suggested labels
✨ Feature, 📬 API, 💾 BE
Suggested reviewers
- lepitaaar
- seongwon030
- oesnuj
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Linked Issues check | ✅ Passed | PR의 모든 코드 변경사항은 MOA-620 관련 이슈(개발자 페이지 생성)의 목표를 충족합니다. |
| Out of Scope Changes check | ✅ Passed | 모든 변경사항은 개발자/관리 페이지 기능 구현 범위 내에 있으며 범위를 벗어난 변경은 없습니다. |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 제목이 개발자 페이지 생성과 관리 개선이라는 주요 변경사항을 명확히 설명하고 있으며, 풀 리퀘스트의 핵심 목표와 일치합니다. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/#1161-add-admin-page-MOA-620
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 @coderabbitai help to get the list of available commands and usage tips.
Test Results77 tests 77 ✅ 16s ⏱️ Results for commit 75b5ad7. |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@backend/src/main/java/moadong/club/controller/ClubAdminController.java`:
- Around line 53-58: The updateClubDescription method is missing request-body
validation: add the `@Valid` annotation to the ClubRecruitmentInfoUpdateRequest
parameter in updateClubDescription (matching the existing use on
ClubInfoRequest) so Spring will perform bean validation before calling
clubProfileService.updateClubRecruitmentInfoByClubId; import the appropriate
`@Valid` (javax.validation or jakarta.validation) if not already present and keep
the parameter order/annotations consistent with other controller methods.
In `@backend/src/main/java/moadong/user/payload/request/DevRegisterRequest.java`:
- Around line 9-24: DevRegisterRequest is missing the compact-constructor
validation that prevents userId and password being identical (as present in
UserRegisterRequest); add a compact constructor in the DevRegisterRequest record
that checks if userId.equals(password) and throws an IllegalArgumentException
(or appropriate validation exception) with a clear message when they match,
mirroring the validation logic used in UserRegisterRequest so the same security
policy applies during developer registration.
In `@backend/src/main/java/moadong/user/service/UserCommandService.java`:
- Around line 67-88: In registerDeveloper in UserCommandService: remove the
unnecessary club creation for DEVELOPER by not generating clubId and deleting
the createClub(clubId, userId) call (generate only userId and pass null/empty
for clubId when calling createUserWithRole or use the overload for roles that
don't need a club); and replace the plain
devRegistrationSecret.equals(request.secret()) check with a constant-time
comparison using MessageDigest.isEqual on the UTF-8 byte arrays (ensure you
handle nulls by failing when either secret is null before comparing).
In `@backend/src/main/resources/static/dev/dict-edit.html`:
- Around line 87-95: The fetch response is being parsed with res.json() before
checking res.ok, which throws on non-JSON error pages; change the load fetch
(the fetch to API_BASE + '/api/admin/word-dictionary/' +
encodeURIComponent(dictId)) to first check res.ok and, if not ok, read the body
with res.text() and attempt to JSON-parse that text to extract a message
(falling back to the raw text or 'HTTP ' + res.status) for display; only call
res.json() for successful responses (and apply the same pattern to the save
request around Line 127 where res.json() is invoked before res.ok).
In `@backend/src/main/resources/static/dev/edit.html`:
- Around line 107-115: The code calls res.json() before checking res.ok which
can throw on non-JSON error responses; update the fetch handling in the blocks
that call fetch(API_BASE + '/api/club/' + clubId, ...) (and the similar blocks
at the other occurrences around lines referenced) to first check res.ok and only
call res.json() when appropriate, or use a safe JSON parse helper (e.g.,
try/catch around res.text() -> JSON.parse) so that HTML/error pages do not cause
uncaught parsing errors; ensure you still extract and show data.message when
available and show a fallback message like 'HTTP ' + res.status when parsing
fails.
- Around line 96-108: The code uses the raw clubId URL param directly in the
fetch URL (see clubId and the fetch(API_BASE + '/api/club/' + clubId, { headers:
headers() }) call); add validation right after obtaining clubId (before setting
pageTitle or entering the async IIFE) to reject malformed values — for example
check against an allowed pattern such as a MongoDB ObjectId regex
(/^[a-fA-F0-9]{24}$/) or other app-specific whitelist, show the existing
loadError message and return/abort if invalid, and if valid use the sanitized
value (or at minimum encodeURIComponent(clubId)) when building the fetch URL to
prevent path traversal or injection.
In `@backend/src/main/resources/static/dev/index.html`:
- Around line 414-418: The code inserts e.message directly into tbody.innerHTML
causing XSS risk; change the error rendering in the catch blocks that use
tbody.innerHTML and banner.textContent to instead set text via safe DOM APIs:
clear tbody, create a tr/td element, set its textContent (or use createTextNode)
with e.message || '오류', append it to tbody, and ensure banner.textContent (not
innerHTML) is used for the banner; update both occurrences that reference
e.message and tbody.innerHTML to use these safe DOM operations (refer to the
catch blocks that access banner, tbody, and e.message).
🧹 Nitpick comments (10)
backend/.gitignore (1)
47-47: Cursor IDE 디렉토리 무시 규칙 추가를 확인했습니다.
.cursor디렉토리를 무시하는 것은 적절한 변경사항입니다. 다만 다른 IDE 패턴들(.idea,.vscode/)과의 일관성을 위해 선행 슬래시를 제거하는 것을 고려해볼 수 있습니다.♻️ 다른 IDE 패턴과 일관성을 맞추기 위한 제안
-/.cursor +.cursor/이렇게 하면
.cursor디렉토리가 backend 디렉토리의 어디에 생성되더라도 무시됩니다. 다만 현재 패턴도 실무에서는 문제없이 동작할 것입니다.backend/src/main/resources/static/dev/edit.html (2)
116-129: API 응답 구조에 대한 다중 폴백 패턴이 취약합니다.
data.data?.club || data?.club || {}와club.name || club.club?.name등 여러 레벨의 폴백이 혼재되어 있어, API 응답 스키마와 맞지 않을 경우 조용히 빈 값으로 채워질 수 있습니다. API 응답 스키마를 하나로 확정하고, 예상 외 구조일 때 명시적으로 에러를 표시하는 것이 유지보수에 유리합니다.
140-171: 저장 전 필수 필드 유효성 검사가 없습니다.
name등 필수값이 비어있어도 그대로 PUT 요청이 전송됩니다. 서버 측 검증에만 의존하면 사용자에게 불친절한 에러가 반환될 수 있으므로, 클라이언트 측에서 최소한의 검증을 추가하는 것을 권장합니다.✅ 간단한 클라이언트 검증 예시
document.getElementById('btnUpdateInfo').onclick = async function() { if (!clubId) return; + const name = document.getElementById('editName').value.trim(); + if (!name) { + showResult('infoResult', false, '이름은 필수 입력 항목입니다.'); + return; + } const btn = this;backend/src/main/java/moadong/global/controller/DevPortalController.java (1)
6-12:/dev경로에 대한 접근 제어가 없습니다.이 컨트롤러는 인증 없이
/dev포털 UI에 접근할 수 있도록 합니다. API 엔드포인트(/api/admin/**)는 별도로 보호되고, 정적 페이지 자체는sessionStorage토큰으로 클라이언트 측 인증을 처리하지만, 개발자 포털 페이지 자체의 존재를 노출시키는 것은 보안 표면을 넓힐 수 있습니다.프로덕션 환경에서
/dev/**경로를DEVELOPER역할로 제한하거나, 프로파일 기반으로 이 컨트롤러를 활성화/비활성화하는 것을 고려해 보세요 (예:@Profile("dev")).backend/src/main/java/moadong/user/entity/User.java (1)
54-55:role필드에@NotNull어노테이션이 누락되었습니다.다른 필드들(
emailVerified,clubId,status)은 모두@NotNull로 선언되어 있지만role은 빠져 있습니다.getAuthorities()에서 null 방어 처리를 하고 있지만, 새로 생성되는 문서에 대해서는@NotNull을 추가하여 일관성을 유지하는 것이 좋습니다.제안 diff
`@Builder.Default` + `@NotNull` private UserRole role = UserRole.CLUB_ADMIN;backend/src/main/java/moadong/user/service/UserCommandService.java (1)
49-50:@Value필드 주입 vs 생성자 주입 일관성다른 모든 의존성은
@RequiredArgsConstructor를 통한 생성자 주입을 사용하지만,devRegistrationSecret는 non-final 필드 주입입니다.final로 선언하면 생성자 주입으로 통합되어 일관성이 높아지고, 테스트 시에도 더 편리합니다.제안 diff
- `@Value`("${app.dev-registration-secret:}") - private String devRegistrationSecret; // set by Spring after construction (field injection) + `@Value`("${app.dev-registration-secret:}") + private final String devRegistrationSecret;backend/src/main/java/moadong/user/controller/DevAuthController.java (1)
24-29: 개발자 등록 엔드포인트에 대한 Rate Limiting 부재
/auth/dev/register는SecurityConfig에서permitAll()로 공개되어 있습니다. 시크릿 키 검증이 서비스 레이어에서 이루어지더라도, 무차별 대입 공격(brute-force)으로 시크릿 키를 추측하려는 시도를 방지하기 위해 rate limiting을 적용하는 것을 권장합니다.backend/src/main/java/moadong/club/service/ClubProfileService.java (2)
90-115: 기존 메서드와 중복 코드가 많습니다
updateClubInfoByClubId는updateClubInfo(Line 40-47)와,updateClubRecruitmentInfoByClubId는updateClubRecruitmentInfo(Line 49-62)와 클럽 조회 방식만 다르고 나머지 로직이 동일합니다. 클럽 조회 부분을 헬퍼로 추출하면 중복을 줄일 수 있습니다.♻️ 헬퍼 메서드 추출 예시
+ private Club findClubByUserId(String userId) { + return clubRepository.findClubByUserId(userId) + .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); + } + + private Club findClubById(String clubId) { + ObjectId objectId = ObjectIdConverter.convertString(clubId); + return clubRepository.findClubById(objectId) + .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); + }이후 각 업데이트 메서드에서 조회 로직을 헬퍼 호출로 대체할 수 있습니다.
64-74:getAllClubsForAdmin의 페이지네이션 부재현재 모든 클럽을 한 번에 메모리에 로드합니다. 클럽 수가 증가하면 성능 문제가 될 수 있으므로, 향후 서버 사이드 페이지네이션 도입을 고려해 주세요.
backend/src/main/resources/static/dev/index.html (1)
302-310:navigator.clipboard.writeText에러 미처리
navigator.clipboard.writeText는 Promise를 반환하며, HTTPS가 아닌 환경이나 권한이 없을 때 실패할 수 있습니다. Line 355, 448에서도 동일한 패턴이 사용됩니다..catch()로 실패 시 폴백을 추가하는 것을 권장합니다.🔧 수정 예시
- navigator.clipboard.writeText(t); - const fb = document.getElementById('copyFeedback'); - fb.classList.remove('hidden'); - setTimeout(() => fb.classList.add('hidden'), 2000); + navigator.clipboard.writeText(t).then(() => { + const fb = document.getElementById('copyFeedback'); + fb.classList.remove('hidden'); + setTimeout(() => fb.classList.add('hidden'), 2000); + }).catch(() => { + showToast('복사 실패', 'error'); + });
#️⃣연관된 이슈
📝작업 내용
개발자 인증 체계 + 개발자 포털 만들었습니다.
제공 기능
링크
http://localhost:8080/dev.html
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
보안