Skip to content

feat: 어드민 대학 이미지 업로드 연동 - #568

Merged
whqtker merged 4 commits into
mainfrom
feat/admin-university-image-upload
Jun 22, 2026
Merged

feat: 어드민 대학 이미지 업로드 연동#568
whqtker merged 4 commits into
mainfrom
feat/admin-university-image-upload

Conversation

@whqtker

@whqtker whqtker commented Jun 22, 2026

Copy link
Copy Markdown
Member

작업 내용

  • 어드민 호스트 대학 관리 화면에서 로고와 배경 이미지 파일 업로드 API를 연동했습니다.
  • 서버 계약을 유지해 multipart의 englishName 키에 호스트 대학의 formatName 값을 전달합니다.
  • 업로드 진행 상태와 이미지 미리보기를 제공하고 중복 제출을 방지합니다.
  • 상대 이미지 경로는 미리보기에서만 업로드 CDN URL로 정규화하며 저장용 경로 값은 유지합니다.
  • 업로드 성공·실패 및 CDN 미리보기 경로 회귀 테스트를 추가했습니다.

배경 및 원인

기존 어드민 화면은 대학 이미지 URL을 직접 입력해야 했습니다. 업로드 기능 추가 후 상대 경로를 img src에 그대로 사용하면 Vite RSC 개발 서버가 이미지 경로를 애플리케이션 요청으로 처리해 런타임 오류가 발생하므로, 기존 CDN 정규화 유틸을 미리보기에 적용했습니다.

검증

  • pnpm --filter admin test — 18개 테스트 통과
  • pnpm --filter admin lint:check
  • pnpm --filter admin typecheck
  • pnpm --filter admin build
  • commit/push 훅 CI parity 검사 통과

로컬 검증 환경은 Node.js 24.14.0이며 프로젝트 권장 버전은 Node.js 22.x입니다.

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connect-university-web Error Error Jun 22, 2026 4:23am
solid-connect-web-admin Ready Ready Preview, Comment Jun 22, 2026 4:23am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connection-web Skipped Skipped Jun 22, 2026 4:23am

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

이번 PR은 관리자 페이지의 HostUniversityTab에 대학 로고 및 배경 이미지를 업로드하는 기능을 추가합니다.

  1. API 계층 확장

    • AdminUniversityImageUploadResponse 인터페이스를 신규 export로 추가했습니다.
    • adminApiuploadAdminUniversityLogo, uploadAdminUniversityBackground 메서드를 추가하여 multipart/form-data 방식으로 각 엔드포인트에 POST 요청을 수행합니다.
  2. 컴포넌트 업로드 기능 구현

    • 두 개의 React Query mutation(logoUploadMutation, backgroundUploadMutation)을 추가하고, 업로드 성공 시 폼 필드에 fileUrl을 반영하며 실패 시 토스트를 표시합니다.
    • uploadImage 헬퍼를 통해 formatName 입력 여부를 검증한 뒤 mutation을 실행합니다.
    • 모달 폼의 이미지 URL 입력 필드를 업로드 버튼과 미리보기 UI로 교체하고, isUploading 상태를 제출 버튼 disabled 조건에 포함시켰습니다.
  3. 테스트 추가

    • admin.test.ts에서 axiosInstance.post를 모킹하여 업로드 메서드의 엔드포인트·헤더·FormData 값·반환값을 검증합니다.
    • HostUniversityTab.test.tsx에서 로고 업로드 성공, formatName 누락 시 오류, 배경 업로드 실패 시 URL 유지의 세 가지 시나리오를 검증합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wibaek
  • manNomi
  • enunsnv
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description check ✅ Passed PR 설명은 작업 내용, 배경 및 원인, 검증 과정을 포함하며 저장소 템플릿의 필수 섹션들을 충실히 다루고 있습니다.
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.
Title check ✅ Passed PR 제목이 변경사항의 핵심을 명확하게 요약하고 있으며, 대학 이미지 업로드 연동이라는 주요 기능을 간결하게 표현하고 있습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/admin-university-image-upload

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 added the admin label Jun 22, 2026
@whqtker whqtker changed the title [codex] 어드민 대학 이미지 업로드 연동 어드민 대학 이미지 업로드 연동 Jun 22, 2026
@whqtker
whqtker marked this pull request as ready for review June 22, 2026 04:23
@whqtker whqtker self-assigned this Jun 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2fb02da67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@whqtker whqtker changed the title 어드민 대학 이미지 업로드 연동 feat: 어드민 대학 이미지 업로드 연동 Jun 22, 2026

@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 (5)
apps/admin/src/lib/api/admin.ts (1)

351-372: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

두 업로드 메서드의 중복 코드를 헬퍼 함수로 추출하시는 것을 권장드립니다.

현재 uploadAdminUniversityLogouploadAdminUniversityBackground 메서드는 엔드포인트만 다르고 나머지 로직이 동일합니다. 공통 헬퍼를 추출하면 유지보수가 더 쉬워집니다.

♻️ 제안하는 리팩토링
+	uploadUniversityImage: (endpoint: string, file: File, englishName: string) => {
+		const formData = new FormData();
+		formData.append("file", file);
+		formData.append("englishName", englishName);
+		return axiosInstance
+			.post<AdminUniversityImageUploadResponse>(endpoint, formData, {
+				headers: { "Content-Type": "multipart/form-data" },
+			})
+			.then((res) => res.data);
+	},
+
 	uploadAdminUniversityLogo: (file: File, englishName: string) => {
-		const formData = new FormData();
-		formData.append("file", file);
-		formData.append("englishName", englishName);
-		return axiosInstance
-			.post<AdminUniversityImageUploadResponse>("/file/admin/university/logo", formData, {
-				headers: { "Content-Type": "multipart/form-data" },
-			})
-			.then((res) => res.data);
+		return adminApi.uploadUniversityImage("/file/admin/university/logo", file, englishName);
 	},

 	uploadAdminUniversityBackground: (file: File, englishName: string) => {
-		const formData = new FormData();
-		formData.append("file", file);
-		formData.append("englishName", englishName);
-		return axiosInstance
-			.post<AdminUniversityImageUploadResponse>("/file/admin/university/background", formData, {
-				headers: { "Content-Type": "multipart/form-data" },
-			})
-			.then((res) => res.data);
+		return adminApi.uploadUniversityImage("/file/admin/university/background", file, englishName);
 	},
🤖 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 `@apps/admin/src/lib/api/admin.ts` around lines 351 - 372, The
uploadAdminUniversityLogo and uploadAdminUniversityBackground methods contain
duplicate code that differs only in the endpoint URL. Create a private helper
function that accepts the endpoint URL as a parameter and handles the common
logic of creating FormData, appending the file and englishName, and making the
POST request with the multipart header. Then refactor both
uploadAdminUniversityLogo and uploadAdminUniversityBackground to call this
helper function with their respective endpoint paths.
apps/admin/src/lib/api/admin.test.ts (1)

17-42: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

성공 시나리오 외에 실패 케이스 테스트도 추가하시는 것을 권장드립니다.

현재 테스트는 업로드 성공 시나리오만 검증합니다. 아래 케이스들을 추가하면 더 견고한 테스트 커버리지를 확보하실 수 있습니다:

  1. 네트워크 오류 시 에러 전파 확인
  2. 서버 응답 오류(4xx/5xx) 처리 확인
  3. 잘못된 응답 형식 처리
💚 제안하는 테스트 추가 예시
it("rejects when the server returns an error", async () => {
	post.mockRejectedValue(new Error("Upload failed"));
	const file = new File(["image"], "logo.png", { type: "image/png" });

	await expect(
		adminApi.uploadAdminUniversityLogo(file, "university_of_test")
	).rejects.toThrow("Upload failed");
});
🤖 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 `@apps/admin/src/lib/api/admin.test.ts` around lines 17 - 42, The current test
suite for "admin university image uploads" only covers the success scenario. Add
additional test cases to validate error handling by creating tests that cover
network errors (using post.mockRejectedValue to simulate failures), server error
responses (4xx/5xx status codes), and invalid response formats. Use the same
parametrization pattern with it.each for the uploadAdminUniversityLogo and
uploadAdminUniversityBackground functions to ensure both endpoints properly
handle and propagate errors when the post mock is configured to reject or return
malformed data.
apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx (2)

213-227: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

파일 크기 검증 추가를 고려하세요.

현재 업로드 전 파일 크기 검증이 없어 사용자가 매우 큰 이미지를 업로드할 수 있습니다. 클라이언트 측에서 파일 크기를 미리 검증하면 불필요한 서버 요청과 사용자 대기 시간을 줄일 수 있습니다.

📏 파일 크기 검증 예시
 const uploadImage = (kind: "logo" | "background", file: File | undefined) => {
   if (!file) return;
   if (!form.formatName.trim()) {
     toast.error("표시명을 먼저 입력해 주세요.");
     return;
   }
+  const maxSize = 5 * 1024 * 1024; // 5MB
+  if (file.size > maxSize) {
+    toast.error("이미지 크기는 5MB 이하여야 합니다.");
+    return;
+  }

   const variables = { file, englishName: form.formatName };
🤖 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
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx`
around lines 213 - 227, The uploadImage function lacks file size validation,
allowing users to upload potentially large image files unnecessarily consuming
server resources and user bandwidth. Add a file size check in the uploadImage
function after verifying the file exists (the !file check) but before the
formatName validation, and display a toast error message if the file exceeds an
acceptable size limit. This prevents oversized uploads from reaching the
mutation methods logoUploadMutation.mutate and backgroundUploadMutation.mutate.

442-533: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

미리보기 이미지 오류 처리 추가를 고려하세요.

Lines 446, 492의 <img> 요소에 onError 핸들러가 없어, normalizeImageUrlToUploadCdn 결과가 유효하지 않은 URL일 경우 깨진 이미지 아이콘이 표시됩니다. 오류 시 폴백 UI로 전환하면 사용자 경험을 개선할 수 있습니다.

🖼️ 이미지 오류 처리 예시
+const [logoError, setLogoError] = useState(false);
+const [backgroundError, setBackgroundError] = useState(false);

 {field === "logoImageUrl" && (
   <div className="flex items-center gap-3 rounded-lg border border-k-100 bg-k-50 p-3">
-    {form.logoImageUrl ? (
+    {form.logoImageUrl && !logoError ? (
       <img
         src={normalizeImageUrlToUploadCdn(form.logoImageUrl)}
         alt="로고 미리보기"
         className="h-14 w-14 shrink-0 rounded-md border border-k-100 bg-white object-contain p-1"
+        onError={() => setLogoError(true)}
       />
     ) : (
🤖 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
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx`
around lines 442 - 533, Add onError event handlers to both the logo and
background image preview elements to handle invalid URLs gracefully. For the img
element when field equals "logoImageUrl" and the one when field equals
"backgroundImageUrl", add an onError handler that switches the display to show
the empty state UI (the div with ImageIcon) instead of showing a broken image
icon. This can be achieved by managing state for failed image loads or using a
conditional check based on the image load success.
apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx (1)

52-68: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

CDN URL 하드코딩을 상수/설정으로 대체하세요.

Line 66에서 CDN 도메인 "https://cdn.upload.solid-connection.com"을 하드코딩하고 있습니다. 이 값은 normalizeImageUrlToUploadCdn이 사용하는 설정과 일치해야 하는데, 별도로 관리하면 CDN URL 변경 시 테스트가 깨질 수 있습니다.

🔧 CDN URL 상수화 예시

cdnUrl.ts에서 업로드 origin 상수를 export하고 테스트에서 import:

+import { UPLOAD_ORIGIN } from "`@/lib/utils/cdnUrl`";

 expect(screen.getByRole("img", { name: "로고 미리보기" }).getAttribute("src")).toBe(
-  "https://cdn.upload.solid-connection.com/admin/logo/test.webp",
+  `${UPLOAD_ORIGIN}/admin/logo/test.webp`,
 );

또는 normalizeImageUrlToUploadCdn 결과를 직접 사용:

+import { normalizeImageUrlToUploadCdn } from "`@/lib/utils/cdnUrl`";

 expect(screen.getByRole("img", { name: "로고 미리보기" }).getAttribute("src")).toBe(
-  "https://cdn.upload.solid-connection.com/admin/logo/test.webp",
+  normalizeImageUrlToUploadCdn("admin/logo/test.webp"),
 );
🤖 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
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx`
around lines 52 - 68, The test hardcodes the CDN domain URL
"https://cdn.upload.solid-connection.com" in the image source assertion, which
will break if the CDN configuration changes. Instead of hardcoding the full URL,
either extract the CDN domain as a constant and import it from a centralized
configuration file, or use the normalizeImageUrlToUploadCdn function directly to
construct the expected image source URL using the fileUrl returned from the
uploadAdminUniversityLogo mock. This ensures the test remains synchronized with
actual CDN configuration.
🤖 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
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx`:
- Around line 52-68: The test for the "uploads a selected logo with formatName
and writes the returned URL" case is missing validation for the success toast
notification that is called on successful upload. Add a mock or spy on the
toast.success function and include an expectation to verify that toast.success
is called with the appropriate message after the upload completes. This ensures
the complete user feedback flow, including both the field updates and the toast
notification, is properly tested.

---

Nitpick comments:
In
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx`:
- Around line 52-68: The test hardcodes the CDN domain URL
"https://cdn.upload.solid-connection.com" in the image source assertion, which
will break if the CDN configuration changes. Instead of hardcoding the full URL,
either extract the CDN domain as a constant and import it from a centralized
configuration file, or use the normalizeImageUrlToUploadCdn function directly to
construct the expected image source URL using the fileUrl returned from the
uploadAdminUniversityLogo mock. This ensures the test remains synchronized with
actual CDN configuration.

In
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx`:
- Around line 213-227: The uploadImage function lacks file size validation,
allowing users to upload potentially large image files unnecessarily consuming
server resources and user bandwidth. Add a file size check in the uploadImage
function after verifying the file exists (the !file check) but before the
formatName validation, and display a toast error message if the file exceeds an
acceptable size limit. This prevents oversized uploads from reaching the
mutation methods logoUploadMutation.mutate and backgroundUploadMutation.mutate.
- Around line 442-533: Add onError event handlers to both the logo and
background image preview elements to handle invalid URLs gracefully. For the img
element when field equals "logoImageUrl" and the one when field equals
"backgroundImageUrl", add an onError handler that switches the display to show
the empty state UI (the div with ImageIcon) instead of showing a broken image
icon. This can be achieved by managing state for failed image loads or using a
conditional check based on the image load success.

In `@apps/admin/src/lib/api/admin.test.ts`:
- Around line 17-42: The current test suite for "admin university image uploads"
only covers the success scenario. Add additional test cases to validate error
handling by creating tests that cover network errors (using
post.mockRejectedValue to simulate failures), server error responses (4xx/5xx
status codes), and invalid response formats. Use the same parametrization
pattern with it.each for the uploadAdminUniversityLogo and
uploadAdminUniversityBackground functions to ensure both endpoints properly
handle and propagate errors when the post mock is configured to reject or return
malformed data.

In `@apps/admin/src/lib/api/admin.ts`:
- Around line 351-372: The uploadAdminUniversityLogo and
uploadAdminUniversityBackground methods contain duplicate code that differs only
in the endpoint URL. Create a private helper function that accepts the endpoint
URL as a parameter and handles the common logic of creating FormData, appending
the file and englishName, and making the POST request with the multipart header.
Then refactor both uploadAdminUniversityLogo and uploadAdminUniversityBackground
to call this helper function with their respective endpoint paths.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c943c1ad-c746-4dd0-a1fb-62be85c660a8

📥 Commits

Reviewing files that changed from the base of the PR and between 4dea9b1 and b2fb02d.

📒 Files selected for processing (4)
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx
  • apps/admin/src/lib/api/admin.test.ts
  • apps/admin/src/lib/api/admin.ts

@whqtker
whqtker merged commit 4f9b16c into main Jun 22, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant