Skip to content

[Refactor] WTH-401: 어드민 출석 캐시 최적화#126

Open
dalzzy wants to merge 6 commits into
developfrom
refactor/WTH-401-어드민-출석-정보설정-캐시-최적화

Hidden character warning

The head ref may contain hidden characters: "refactor/WTH-401-\uc5b4\ub4dc\ubbfc-\ucd9c\uc11d-\uc815\ubcf4\uc124\uc815-\uce90\uc2dc-\ucd5c\uc801\ud654"
Open

[Refactor] WTH-401: 어드민 출석 캐시 최적화#126
dalzzy wants to merge 6 commits into
developfrom
refactor/WTH-401-어드민-출석-정보설정-캐시-최적화

Conversation

@dalzzy

@dalzzy dalzzy commented Jun 3, 2026

Copy link
Copy Markdown
Member

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호

  • Closed #401

✅ Key Changes

1. 어드민 QueryKey Factory 통합 (adminQueryKeys.ts 신규)

  • 멤버/게시판/일정/출석/클럽 전 도메인의 QueryKey를 adminQueryKeys.ts 단일 factory로 통합
  • 기존 훅 내 인라인 하드코딩(['admin', 'members', clubId] 등) 전체 제거, 게시판만 갖던 boardQueryKeys.ts 흡수·삭제

2. 세션 QueryKey 중복 제거

  • 출석(useAdminSessions)과 일정(useAdminSessionList)이 같은 API를 호출하면서 서로 다른 키(['admin', 'sessions'] vs ['admin', 'sessionList'])를 사용하던 구조 통일
  • 세션 뮤테이션 4개에서 두 키를 모두 invalidate하던 중복 처리 제거

3. staleTime / gcTime 명시적 설정

데이터 staleTime gcTime 비고
세션 목록 (출석/일정 공용) 5분 10분 기존 누락
월간 일정 5분 10분 기존 누락
출석 상세 0 5분 항상 최신화 필요

4. Board Mutation 파일 이동

  • use*BoardMutation.ts 5개 파일을 hooks/queries/admin/hooks/mutations/admin/으로 이동해 쿼리/뮤테이션 레이어 경계 정비

5. 공개 클럽 페이지 캐시 전략 개선

  • 클럽 상세 페이지(/club/[clubId]) cache: 'no-store'next: { revalidate: 300, tags: ['public-club-${clubId}'] } (5분 ISR)
  • 어드민에서 클럽 정보/이미지 수정 시 revalidatePublicClub Server Action으로 즉시 캐시 무효화 연동

6. 동아리 생성 폼 상태 유지

  • useCreateClubDraftStore에 Zustand persist 미들웨어 추가 → 동아리 생성 도중 페이지 이탈 후 복귀 시 입력값 유지

7. universityServerApi 타입 안전 API 함수 적용

  • apiServer.get<{ data: School[] }>('/university/schools') 직접 호출 → universityServerApi.getSchools() 교체 (2곳)
  • 중복된 학교명 dedup 로직을 utils/shared/school.tsdeduplicateSchoolNames() 유틸로 통합

📸 스크린샷 or 실행영상


🎸 기타 사항 or 추가 코멘트

  • pnpm typecheck: @tiptap/extension-* 모듈 누락 에러 발생 — 이번 브랜치와 무관한 기존 이슈
  • pnpm build: 동일 원인으로 빌드 실패 — 이번 브랜치 변경 파일에서의 에러 없음 확인
  • pnpm format:check: 이번 브랜치 변경 파일은 모두 통과, 나머지 55개는 기존 미포맷 파일

Summary by CodeRabbit

릴리스 노트

  • New Features
    • 동아리 생성 중 입력(학교, 이름, 설명 등)이 자동 저장되어 새로고침 후에도 유지됩니다.
  • UI 개선
    • 학교 목록에서 같은 이름은 자동 병합되고, 중복된 경우 지역명이 괄호로 표시되어 구분됩니다.
  • Performance
    • 공개 동아리 페이지 캐시 정책이 개선되어(주기적 재검증·태그 기반 갱신) 관리자 변경이 더 빠르게 반영됩니다.

@dalzzy dalzzy requested review from JIN921, nabbang6 and woneeeee June 3, 2026 08:43
@dalzzy dalzzy self-assigned this Jun 3, 2026
@dalzzy dalzzy added the 🔨 Refactor 코드 리팩토링 label Jun 3, 2026
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JIN921, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 26 minutes and 34 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f480352-3381-45d5-a917-6b75cd28808e

📥 Commits

Reviewing files that changed from the base of the PR and between a0d7226 and 7762b10.

📒 Files selected for processing (2)
  • .prettierignore
  • docs/.obsidian/workspace.json
📝 Walkthrough

Walkthrough

학교 목록 호출을 전용 클라이언트로 통합하고, 공개 동아리 페이지에 Next.js 태그 기반 캐시 재검증을 추가했습니다. admin 쿼리 키 네임스페이스를 adminQueryKeys로 통일하고 관련 훅/뮤테이션의 invalidate 대상을 갱신했으며, 동아리 초안 일부 상태를 persist로 영속화했습니다.


Changes

Club 기능 최적화: API 통합, 캐시 전략, 쿼리 키 정리

Layer / File(s) Summary
학교 목록 API 클라이언트 통합
src/app/(private)/(auth)/club/create/page.tsx, src/app/(private)/[clubId]/admin/club-info/page.tsx, src/utils/shared/school.ts, src/utils/shared/index.ts
Create/ClubInfo 페이지에서 apiServer.get('/university/schools') 호출을 universityServerApi.getSchools()로 교체하고, 중복 schoolName 처리 로직을 deduplicateSchoolNames 유틸로 이동·재사용합니다.
공개 동아리 페이지 캐시 및 무효화 시스템
src/app/(public)/club/[clubId]/page.tsx, src/lib/actions/club.ts, src/hooks/mutations/admin/useAdminClubMutations.ts
공개 페이지 fetch에 next: { revalidate: 300, tags: ['public-club-${clubId}'] }를 추가하고, 동아리 수정/이미지 삭제 뮤테이션의 onSuccess에서 revalidatePublicClub(clubId)를 호출해 태그 기반 무효화를 트리거합니다.
관리자 쿼리 키 네임스페이스 도입
src/hooks/queries/admin/adminQueryKeys.ts, src/hooks/queries/admin/index.ts, 관련 훅/뮤테이션 파일들
adminQueryKeys를 추가하고, 기존 하드코드/adminBoardQueryKeys 참조를 adminQueryKeys.*로 대체하여 쿼리 key 생성과 invalidate 대상을 일관화합니다. 보드·스케줄·세션·출석 관련 훅과 뮤테이션이 갱신되었습니다.
게시판 컴포넌트·드래그 정렬 갱신
src/components/admin/board/BoardPageContent.tsx, src/hooks/admin/useBoardDragReorder.ts
보드 관련 mutation 훅을 @/hooks/mutations/admin로 통합 임포트하고, optimistic 업데이트 및 캐시 조회에서 사용하는 cacheKey를 adminQueryKeys.boards(clubId)로 변경했습니다.
멤버/뮤테이션의 쿼리키 교체
src/hooks/mutations/admin/useAdminMemberMutations.ts
멤버 관련 mutation 훅들의 queryKey를 하드코드 배열에서 adminQueryKeys.members(clubId)로 변경했습니다.
동아리 초안 상태 로컬 영속화
src/stores/useCreateClubDraftStore.ts
zustandpersist 미들웨어를 적용하고 partialize로 저장할 필드를 제한해 일부 초안 상태를 브라우저에 영속화합니다.
문서: Obsidian 워크스페이스
docs/.obsidian/workspace.json
Obsidian 워크스페이스 상태 파일을 추가했습니다 (편집기 레이아웃/최근 파일 등).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs


Suggested reviewers

  • woneeeee
  • JIN921

Poem

🐰 학교 목록은 한곳으로 모여,
캐시 태그로 퍼블릭 페이지 깨어나고,
쿼리 키는 가지런히 정리했네,
초안은 조용히 저장소에 머물고,
코드는 또 한걸음 정돈되었어요 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.50% 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
Title check ✅ Passed PR 제목이 주요 변경사항인 어드민 출석 캐시 최적화를 반영하고 있으나, 실제 PR은 학교 목록 API 변경, 동아리 페이지 ISR 적용, draft 상태 유지, QueryKey 통합 등 7가지 주요 개선사항을 포함하고 있어 제목이 전체 범위를 대표하지 못함.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(PR 유형, 관련 이슈번호, Key Changes)을 포함하고 있으며, 7가지 주요 변경사항을 표와 함께 상세히 설명하고 있고, 테스트 결과도 기타 사항에서 명시하고 있음.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/WTH-401-어드민-출석-정보설정-캐시-최적화

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-cfste4tp7-weethsite-4975s-projects.vercel.app

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/hooks/mutations/admin/useAdminClubMutations.ts (1)

17-19: ⚡ Quick win

revalidatePublicClub 호출 결과를 await 하세요.

revalidatePublicClub는 async 서버 액션인데 await/return 없이 호출되어, 실패 시 처리되지 않은 Promise 거부(unhandled rejection)가 발생할 수 있고 캐시 무효화 완료 시점이 보장되지 않습니다. onSuccess를 async로 만들거나 Promise를 반환하면 React Query가 완료를 기다립니다. 동일한 패턴이 useDeleteClubProfileImage(Line 35-37), useDeleteClubBackgroundImage(Line 53-55)에도 적용됩니다.

♻️ 제안 수정
-    onSuccess: () => {
-      if (clubId) revalidatePublicClub(clubId);
-    },
+    onSuccess: async () => {
+      if (clubId) await revalidatePublicClub(clubId);
+    },
🤖 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/hooks/mutations/admin/useAdminClubMutations.ts` around lines 17 - 19, The
onSuccess handlers call the async server action revalidatePublicClub without
awaiting or returning it, risking unhandled rejections and not guaranteeing
cache invalidation completion; update each onSuccess in useAdminClubMutations
(the mutation that currently calls revalidatePublicClub(clubId)) and the
handlers inside useDeleteClubProfileImage and useDeleteClubBackgroundImage so
they either are declared async and await revalidatePublicClub(clubId) or return
the Promise (return revalidatePublicClub(clubId)); ensure you reference those
exact handlers (onSuccess in the mutation, useDeleteClubProfileImage,
useDeleteClubBackgroundImage) and await/return the call to revalidatePublicClub
to let React Query wait for completion.
🤖 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 `@src/stores/useCreateClubDraftStore.ts`:
- Around line 19-36: The persist usage around combine(initialState, ...)
(symbols: persist, combine, initialState, setDraft, reset, partialize, name:
'createClubDraft') causes client-side localStorage to hydrate on first render
and can produce SSR hydration mismatches; change the persist options to use
skipHydration: true and add an onRehydrateStorage handler so stored state is
applied only after mount (or ensure components use a mount guard) — i.e., update
the persist options to include skipHydration: true and implement
onRehydrateStorage to rehydrate/update the store after client mount (and keep
the existing partialize/name settings).

---

Nitpick comments:
In `@src/hooks/mutations/admin/useAdminClubMutations.ts`:
- Around line 17-19: The onSuccess handlers call the async server action
revalidatePublicClub without awaiting or returning it, risking unhandled
rejections and not guaranteeing cache invalidation completion; update each
onSuccess in useAdminClubMutations (the mutation that currently calls
revalidatePublicClub(clubId)) and the handlers inside useDeleteClubProfileImage
and useDeleteClubBackgroundImage so they either are declared async and await
revalidatePublicClub(clubId) or return the Promise (return
revalidatePublicClub(clubId)); ensure you reference those exact handlers
(onSuccess in the mutation, useDeleteClubProfileImage,
useDeleteClubBackgroundImage) and await/return the call to revalidatePublicClub
to let React Query wait for completion.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce9d4b63-eeca-40ff-a1c3-b493fe13a3a7

📥 Commits

Reviewing files that changed from the base of the PR and between 80341b9 and 1874f78.

📒 Files selected for processing (7)
  • src/app/(private)/(auth)/club/create/page.tsx
  • src/app/(private)/[clubId]/admin/club-info/page.tsx
  • src/app/(public)/club/[clubId]/page.tsx
  • src/hooks/mutations/admin/useAdminClubMutations.ts
  • src/hooks/queries/admin/useAdminAttendanceQueries.ts
  • src/lib/actions/club.ts
  • src/stores/useCreateClubDraftStore.ts

Comment on lines +19 to +36
persist(
combine(initialState, (set) => ({
setDraft: (draft: Partial<CreateClubDraftState>) => set(draft, false, 'setDraft'),
reset: () => set(initialState, false, 'reset'),
})),
{
name: 'createClubDraft',
partialize: (state) => ({
school: state.school,
name: state.name,
description: state.description,
generation: state.generation,
phone: state.phone,
email: state.email,
contactType: state.contactType,
}),
},
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 스토어 소비처와 'use client' 지시문, RSC 경로 여부 확인
rg -nP "useCreateClubDraft(Values|Actions|Store)" -g '!**/useCreateClubDraftStore.ts'
echo "----- consumer files top lines (check 'use client') -----"
rg -lP "useCreateClubDraft(Values|Actions|Store)" -g '!**/useCreateClubDraftStore.ts' \
  | while read -r f; do echo "== $f =="; head -n 3 "$f"; done

Repository: Team-Weeth/weeth-client

Length of output: 1456


Zustand persist 기본 localStorage 사용으로 SSR-하이드레이션 불일치 위험(클라이언트 컴포넌트에서 DOM이 달라질 수 있음)

src/components/auth/hub/CreateClubForm.tsxsrc/components/auth/hub/ClubCreatingPage.tsx는 모두 'use client'라 서버에서 HTML을 만든 뒤 클라이언트에서 하이드레이션합니다. 그런데 src/stores/useCreateClubDraftStore.tspersiststorage/skipHydration 설정이 보이지 않아 기본 localStorage로 동기 복원된 값이 클라이언트 첫 렌더에 반영되며, 서버의 initialState와 DOM이 달라 React 하이드레이션 경고/깜빡임이 날 수 있습니다(특히 CreateClubForm의 RHF defaultValues가 초안 값에 의존하는 경우).

🛠️ 방어 예시: `skipHydration` 사용
       {
         name: 'createClubDraft',
+        skipHydration: true,
         partialize: (state) => ({
           school: state.school,
           name: state.name,
           description: state.description,
           generation: state.generation,
           phone: state.phone,
           email: state.email,
           contactType: state.contactType,
         }),
       },

서버/클라이언트 첫 렌더가 동일한 값을 쓰도록 skipHydration + 마운트 후 rehydrate(또는 마운트 가드) 방식으로 타이밍을 제어해 주세요.

🤖 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/stores/useCreateClubDraftStore.ts` around lines 19 - 36, The persist
usage around combine(initialState, ...) (symbols: persist, combine,
initialState, setDraft, reset, partialize, name: 'createClubDraft') causes
client-side localStorage to hydrate on first render and can produce SSR
hydration mismatches; change the persist options to use skipHydration: true and
add an onRehydrateStorage handler so stored state is applied only after mount
(or ensure components use a mount guard) — i.e., update the persist options to
include skipHydration: true and implement onRehydrateStorage to rehydrate/update
the store after client mount (and keep the existing partialize/name settings).

@JIN921 JIN921 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다 수고하셧어요!!

@nabbang6 nabbang6 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인이 늦었습니다.........ㅠ.ㅠ 고생하셨어요!!!! 👍

JIN921 and others added 2 commits June 8, 2026 18:04
- adminQueryKeys.ts 생성: 어드민 전 도메인(멤버/게시판/일정/출석/클럽) QueryKey를 단일 factory로 통합, 기존 인라인 하드코딩 제거
- 세션 QueryKey 통일: useAdminSessionList의 ['admin', 'sessionList'] → ['admin', 'sessions']로 통일해 출석/일정 간 캐시 공유 및 중복 invalidation 제거
- staleTime/gcTime 명시적 설정: 월간 일정·세션 목록(5분/10분), 출석 상세(0/5분) 추가
- Board mutation 파일 5개를 hooks/queries/admin/ → hooks/mutations/admin/으로 이동해 아키텍처 일관성 확보
- deduplicateSchoolNames 유틸 추출(utils/shared/school.ts): create/page와 club-info/page의 학교명 중복 처리 로직 통합

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@JIN921 JIN921 changed the title [Refactor] WTH-401: 로그인,동아리 생성 및 어드민 출석 정보설정 캐시 최적화 [Refactor] WTH-401: 어드민 출석 캐시 최적화 Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 실패
Build: 통과

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-ituoz25mz-weethsite-4975s-projects.vercel.app

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 실패
Build: 통과

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-l7esqfi89-weethsite-4975s-projects.vercel.app

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-ozbmjc7cy-weethsite-4975s-projects.vercel.app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants