diff --git a/docs/history.md b/docs/history.md index 2924160..ac1d842 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,11 @@ # 의사결정 이력 +## 2026-05-11 v0.0.72 + +### 회원 썸네일 미디어 분리 + +회원 썸네일은 운영자(작가) 콘텐츠 제작용 미디어와 목적이 다르므로, 업로드 경로를 `/uploads/members/avatars/YYYY/MM`로 분리했다. 관리자 미디어 목록에서는 해당 경로를 숨겨, 작가용 미디어 라이브러리와 회원 프로필 자산이 섞이지 않도록 했다. + ## 2026-05-11 v0.0.71 ### 회원 UX를 헤더 중심으로 전환 diff --git a/docs/map.md b/docs/map.md index c10662f..6996904 100644 --- a/docs/map.md +++ b/docs/map.md @@ -123,6 +123,7 @@ | server/api/auth/logout.post.js | 회원 로그아웃 API | | server/api/auth/profile.get.js | 회원 프로필 조회 API | | server/api/auth/profile.put.js | 회원 프로필 수정 API | +| server/api/auth/avatar.post.js | 회원 썸네일 업로드 API | | server/api/auth/check-username.get.js | 닉네임 중복 확인 API | | server/api/auth/password.put.js | 회원 비밀번호 변경 API | | server/api/auth/account.delete.js | 회원 탈퇴 API | @@ -168,7 +169,7 @@ | server/utils/admin-tag-input.js | 관리자 태그 입력값 검증 스키마 | | server/utils/site-settings.js | 사이트 설정 기본값 유틸리티 | | server/utils/navigation-items.js | 네비게이션 기본값과 그룹 유틸리티 | -| server/utils/media-library.js | 업로드 미디어 파일과 폴더 메타데이터 관리 유틸리티 | +| server/utils/media-library.js | 업로드 미디어 파일과 폴더 메타데이터 관리 유틸리티(회원 썸네일 경로 제외) | | server/repositories/postgres-client.js | PostgreSQL 클라이언트 | | server/repositories/content-repository.js | 콘텐츠 조회 저장소 | | server/repositories/member-repository.js | 회원 조회/생성 저장소 | diff --git a/docs/spec.md b/docs/spec.md index 6096e01..a8a4dea 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -339,10 +339,13 @@ components/content/ - `POST /api/auth/logout` - 회원 로그아웃 - `GET /api/auth/profile` - 회원 설정 조회 - `PUT /api/auth/profile` - 회원 프로필 수정(닉네임, 썸네일) +- `POST /api/auth/avatar` - 회원 썸네일 이미지 업로드 - `GET /api/auth/check-username?username=` - 닉네임 중복 확인 - `PUT /api/auth/password` - 회원 비밀번호 변경 - `DELETE /api/auth/account` - 회원 탈퇴 +> 회원 썸네일 이미지는 `/uploads/members/avatars/YYYY/MM` 경로로 저장하며, 관리자 미디어 목록에서는 제외한다. + ### 관리자 API (`/admin/api/`) - `POST /admin/api/auth/login` - 로그인 diff --git a/docs/update.md b/docs/update.md index f4df57c..258fe50 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 이력 +## v0.0.72 + +- 회원 썸네일 업로드 API(`POST /api/auth/avatar`)를 추가하고 업로드 경로를 `/uploads/members/avatars/YYYY/MM`으로 분리. +- 회원 설정 페이지에서 썸네일 파일 업로드를 직접 처리하고, 업로드 후 프로필 저장 흐름으로 연결. +- 관리자 미디어 목록에서 회원 썸네일 경로(`/uploads/members/avatars/`)를 제외해 작가용 미디어와 분리. + ## v0.0.71 - 헤더 사용자 영역에서 구독 버튼을 제거하고, 로그인 상태 기반 아바타/드롭다운(설정, 로그아웃 / 비로그인 시 Sign up, Sign in)으로 정리. diff --git a/package.json b/package.json index b61b326..8eaf9f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "0.0.71", + "version": "0.0.72", "private": true, "type": "module", "imports": { diff --git a/pages/settings/index.vue b/pages/settings/index.vue index 90a00eb..fe42927 100644 --- a/pages/settings/index.vue +++ b/pages/settings/index.vue @@ -3,6 +3,7 @@ const loading = ref(true) const savingProfile = ref(false) const savingPassword = ref(false) const deletingAccount = ref(false) +const uploadingAvatar = ref(false) const profileMessage = ref('') const passwordMessage = ref('') const deleteMessage = ref('') @@ -98,6 +99,41 @@ const saveProfile = async () => { } } +/** + * 썸네일 파일을 업로드한다. + * @param {Event} event - 파일 선택 이벤트 + * @returns {Promise} + */ +const uploadAvatar = async (event) => { + const target = /** @type {HTMLInputElement | null} */ (event.target instanceof HTMLInputElement ? event.target : null) + const file = target?.files?.[0] + + if (!file || uploadingAvatar.value) { + return + } + + uploadingAvatar.value = true + profileMessage.value = '' + + try { + const formData = new FormData() + formData.append('file', file) + const result = await $fetch('/api/auth/avatar', { + method: 'POST', + body: formData + }) + profileForm.avatarUrl = result.avatarUrl || '' + profileMessage.value = '썸네일이 업로드되었습니다. 프로필 저장을 눌러 반영하세요.' + } catch (error) { + profileMessage.value = error?.data?.message || '썸네일 업로드에 실패했습니다.' + } finally { + uploadingAvatar.value = false + if (target) { + target.value = '' + } + } +} + /** * 비밀번호를 변경한다. * @returns {Promise} @@ -195,6 +231,14 @@ onMounted(loadProfile) class="h-10 rounded-[8px] border border-[var(--site-line)] bg-transparent px-3 text-sm outline-none focus-visible:border-[var(--site-accent)]" placeholder="https://..." > + +