From 20b901d4a1bcdd84cae8312e6731e39c8e1b0599 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 15 May 2026 10:11:02 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/admin/AdminMemberForm.vue | 23 ++- docs/changelog.md | 6 + docs/history.md | 6 + docs/map.md | 7 +- docs/spec.md | 6 +- docs/update.md | 9 + package-lock.json | 4 +- package.json | 2 +- pages/tags/index.vue | 2 +- server/api/auth/avatar.post.js | 152 +---------------- server/repositories/member-repository.js | 19 +++ server/routes/admin/api/member-avatar.post.js | 19 +++ server/routes/admin/api/members/[id].put.js | 5 + .../admin/api/members/[id]/avatar.post.js | 52 ++++++ server/utils/member-avatar-upload.js | 160 ++++++++++++++++++ 15 files changed, 309 insertions(+), 163 deletions(-) create mode 100644 server/routes/admin/api/member-avatar.post.js create mode 100644 server/routes/admin/api/members/[id]/avatar.post.js create mode 100644 server/utils/member-avatar-upload.js diff --git a/components/admin/AdminMemberForm.vue b/components/admin/AdminMemberForm.vue index 9db3600..082c9bb 100644 --- a/components/admin/AdminMemberForm.vue +++ b/components/admin/AdminMemberForm.vue @@ -191,12 +191,23 @@ const uploadAvatar = async (event) => { try { const formData = new FormData() - formData.append('files', file) - const result = await $fetch('/admin/api/uploads', { - method: 'POST', - body: formData - }) - form.avatarUrl = result.files?.[0]?.url || '' + formData.append('file', file) + const result = isNewMember.value + ? await $fetch('/admin/api/member-avatar', { + method: 'POST', + body: formData + }) + : await $fetch(`/admin/api/members/${props.member.id}/avatar`, { + method: 'POST', + body: formData + }) + form.avatarUrl = result.avatarUrl || '' + + if (!isNewMember.value) { + emit('saved', result) + savedMemberSnapshot.value = serializeMemberPayload() + saveMessage.value = '썸네일이 변경되었습니다.' + } } catch (error) { saveError.value = error?.data?.message || '썸네일 업로드에 실패했습니다.' } finally { diff --git a/docs/changelog.md b/docs/changelog.md index aceb9e8..27836d6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,11 @@ # 업데이트 요약 +## v1.1.4 + +- 관리자 멤버 썸네일 업로드가 회원 전용 `/uploads/members/avatars` 경로를 사용하도록 수정. +- 관리자 계정과 일반 회원 모두 같은 회원 썸네일 저장 규칙(WebP 변환, 1:1 크롭)을 쓰도록 정리. +- 태그 목록 카드 그리드 여백 수정 반영. + ## v1.0.19 - Shift+Enter 줄바꿈이 수정 모드에서도 보이도록 줄끝 백슬래시 hard break 방식으로 변경. diff --git a/docs/history.md b/docs/history.md index 036d7d5..412cb0a 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,11 @@ # 의사결정 이력 +## 2026-05-15 v1.1.4 + +### 관리자 멤버 썸네일 업로드 경로 분리 + +회원 프로필 썸네일은 관리자 계정인지 일반 회원인지와 무관하게 회원 자산이므로 `/uploads/members/avatars`에 저장해야 한다. 관리자 멤버 편집 화면이 공용 게시물 이미지 업로드 API를 사용하면 `/uploads/posts`에 저장되어 미디어 분류와 썸네일 생명주기 규칙이 어긋난다. 회원 설정 업로드와 관리자 멤버 업로드가 같은 검증·WebP 변환·1:1 크롭 로직을 쓰도록 공통 유틸로 분리하고, 관리자 멤버 화면은 회원 전용 업로드 API를 사용하도록 정리했다. + ## 2026-05-13 v1.1.3 ### 사이드바 행 호버 배경 분리 diff --git a/docs/map.md b/docs/map.md index fa91b04..3981496 100644 --- a/docs/map.md +++ b/docs/map.md @@ -190,7 +190,8 @@ | server/routes/admin/api/media.delete.js | 관리자 미디어 삭제 API | | server/routes/admin/api/media-folders.get.js | 관리자 미디어 폴더 목록 API | | server/routes/admin/api/media-folders.post.js | 관리자 미디어 폴더 생성 API | -| server/routes/admin/api/uploads.post.js | 관리자 이미지 업로드 API(원본명 기반 파일명·충돌 시 넘버링, 성공 시 `media_metadata`를 `미분류`로 기록) | +| server/routes/admin/api/uploads.post.js | 관리자 게시물·페이지용 이미지 업로드 API(원본명 기반 파일명·충돌 시 넘버링, `/uploads/posts` 저장, 성공 시 `media_metadata`를 `미분류`로 기록) | +| server/routes/admin/api/member-avatar.post.js | 관리자 새 회원 생성 전 썸네일 사전 업로드 API(`/uploads/members/avatars` 저장, WebP 변환·1:1 크롭) | | server/routes/admin/api/tags.get.js | 관리자 태그 목록 API(`tagType`, `q`, `limit` 검색 옵션) | | server/routes/admin/api/tags.post.js | 관리자 태그 생성 API | | server/routes/admin/api/tags/[id].get.js | 관리자 태그 상세 API | @@ -204,13 +205,15 @@ | server/routes/admin/api/members.get.js | 관리자 멤버 목록 API | | server/routes/admin/api/members.post.js | 관리자 멤버 생성 API | | server/routes/admin/api/members/[id].get.js | 관리자 멤버 상세 API | -| server/routes/admin/api/members/[id].put.js | 관리자 멤버 기본 정보 수정 API | +| server/routes/admin/api/members/[id].put.js | 관리자 멤버 기본 정보 수정 API(회원 전용 썸네일 교체·제거 시 메타 연결 분리) | +| server/routes/admin/api/members/[id]/avatar.post.js | 관리자 멤버 썸네일 업로드 및 즉시 반영 API | | server/routes/admin/api/members/[id]/role.put.js | 관리자 멤버 권한 변경 API | | server/utils/content-schema.js | Zod 콘텐츠 스키마 | | server/utils/sample-content.js | 샘플 콘텐츠 저장소 | | server/utils/admin-auth.js | 관리자 세션 쿠키 인증 유틸리티 | | server/utils/member-auth.js | 회원 세션 쿠키 인증 유틸리티 | | server/utils/member-avatar.js | 회원 전용 썸네일 경로 검증·프로필 분리 시 `media_metadata`만 제거(디스크는 관리자 미디어에서 정리) | +| server/utils/member-avatar-upload.js | 회원 썸네일 공통 업로드 검증·WebP 변환·중앙 1:1 크롭·저장 유틸 | | server/utils/admin-post-input.js | 관리자 게시물 입력값 검증 스키마 | | server/utils/admin-page-input.js | 관리자 고정 페이지 입력값 검증 스키마 | | server/utils/admin-site-settings-input.js | 관리자 사이트 설정 입력값 검증 스키마 | diff --git a/docs/spec.md b/docs/spec.md index b83fca5..461c10d 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -416,7 +416,8 @@ components/content/ - `GET /admin/api/media-folders` - 미디어 폴더 목록 - `POST /admin/api/media-folders` - 미디어 폴더 생성 - `DELETE /admin/api/media-folders` - 미디어 폴더 삭제(해당 경로·하위 경로로 분류된 `media_metadata`는 `미분류`로 되돌림) -- `POST /admin/api/uploads` - 관리자 이미지 업로드(저장 파일명은 원본명 기반·동일 월 폴더 내 충돌 시 `이름-2` 등 넘버링) +- `POST /admin/api/uploads` - 관리자 게시물·페이지용 이미지 업로드(저장 파일명은 원본명 기반·동일 월 폴더 내 충돌 시 `이름-2` 등 넘버링, `/uploads/posts/YYYY/MM` 저장) +- `POST /admin/api/member-avatar` - 관리자 새 회원 생성 전 썸네일 사전 업로드(`/uploads/members/avatars/YYYY/MM`, WebP 변환·중앙 1:1 크롭) - `GET /admin/api/tags` - 태그 목록(옵션: `tagType`, `q`, `limit`) - `POST /admin/api/tags` - 태그 생성 - `GET /admin/api/tags/:id` - 태그 상세 @@ -430,7 +431,8 @@ components/content/ - `GET /admin/api/members` - 회원 목록(권한 코드, 최근 접속, 접속 IP, 댓글 수 포함) - `POST /admin/api/members` - 관리자 회원 생성. 본문: `username`, `email`, 선택 `avatarUrl`, `labels`, `note`. 생성된 회원은 `member` 권한이며 초기 비밀번호는 임의 해시로 저장한다. - `GET /admin/api/members/:id` - 관리자 회원 상세(썸네일, 이름, 이메일, 레이블, 관리자 노트, 활동 요약 포함) -- `PUT /admin/api/members/:id` - 관리자 회원 기본 정보 수정. 본문: `username`, `email`, 선택 `avatarUrl`, `labels`, `note` +- `PUT /admin/api/members/:id` - 관리자 회원 기본 정보 수정. 본문: `username`, `email`, 선택 `avatarUrl`, `labels`, `note`. 이전 값이 회원 전용 썸네일 URL이고 새 값과 달라지면 `media_metadata` 연결을 분리한다. +- `POST /admin/api/members/:id/avatar` - 관리자 회원 썸네일 업로드 및 즉시 반영(`/uploads/members/avatars/YYYY/MM`, WebP 변환·중앙 1:1 크롭) - `PUT /admin/api/members/:id/role` - 회원 권한 변경(`owner`/`admin`/`member`) > 글 발행/초안/비공개 전환은 현재 `PUT /admin/api/posts/:id`의 `status` 값으로 처리한다. diff --git a/docs/update.md b/docs/update.md index 8db61a4..c5c1466 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,14 @@ # 업데이트 이력 +## v1.1.4 + +- 관리자 멤버 썸네일 업로드가 게시물용 `/uploads/posts`가 아니라 회원 전용 `/uploads/members/avatars` 경로를 사용하도록 수정. +- 회원 썸네일 업로드 검증·WebP 변환·1:1 크롭 로직을 공통 유틸로 분리. +- 관리자 멤버 편집 전용 썸네일 업로드 API와 새 멤버 생성 전 썸네일 사전 업로드 API 추가. +- 관리자 회원 기본 정보 저장에서 기존 회원 전용 썸네일 URL이 교체·제거되면 `media_metadata` 연결을 분리하도록 정리. +- 태그 목록 카드 그리드에 사용자 수정 `px-6` 반영. +- 패키지 버전 `1.1.4`로 갱신. + ## v1.1.3 - 왼쪽 사이드바 1차 네비·태그 카테고리·테마 점 행 호버를 `site-sidebar-nav-row`로 분리하고, 라이트 테마에서 배경 `#F7F4EF`로 완화. 다크 테마는 기존 `color-mix` 패널 호버 유지. diff --git a/package-lock.json b/package-lock.json index 71a50dc..359c2a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5920,7 +5920,7 @@ } }, "node_modules/dlv": { - "version": "1.1.3", + "version": "1.1.4", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" @@ -9929,7 +9929,7 @@ } }, "node_modules/readdir-glob": { - "version": "1.1.3", + "version": "1.1.4", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "license": "Apache-2.0", diff --git a/package.json b/package.json index 14235fe..dfb9a27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.1.3", + "version": "1.1.4", "private": true, "type": "module", "imports": { diff --git a/pages/tags/index.vue b/pages/tags/index.vue index a338068..dd6a55d 100644 --- a/pages/tags/index.vue +++ b/pages/tags/index.vue @@ -31,7 +31,7 @@ const getPostCount = (slug) => posts.value.filter((post) => post.tags.includes(s
-