관리자 미디어 라이브러리·썸네일 탭 분리 및 논리 폴더 정책(v0.0.90)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-12 10:40:27 +09:00
parent 05176609ee
commit 21024602b0
10 changed files with 488 additions and 95 deletions

View File

@@ -1,5 +1,11 @@
# 의사결정 이력
## 2026-05-12 v0.0.90
### 관리자 미디어 라이브러리와 썸네일 탭 분리
게시물 이미지는 디스크상 `posts/`에 두더라도 논리 분류는 `미분류`로 통일해 `posts` 트리와 이중 표기를 없앤다. 회원 프로필 이미지는 디스크 경로는 유지하되 논리 폴더를 예약명 `썸네일`로 고정하고, 관리자 화면에서는 탭을 나눠 검색·탐색 대상을 분리했다. 썸네일 파일은 URL이 회원 콘텐츠와 직결되므로 관리자에서 임의 삭제·이름 변경이 되면 프로필이 깨지기 쉬워 API·UI에서 막는다.
## 2026-05-12 v0.0.89
### 미디어 선택 토글 가시성

View File

@@ -81,7 +81,7 @@
| pages/admin/pages/index.vue | 페이지 목록 |
| pages/admin/pages/new.vue | 페이지 작성, 저장 토스트 |
| pages/admin/pages/[id].vue | 페이지 수정, 저장/삭제 토스트 |
| pages/admin/media/index.vue | 미디어 관리, 폴더 트리·폴더 추가 모달·폴더 삭제, 썸네일 클릭 미리보기 모달, 좌상단 선택 토글·Shift 범위, 드래그로 폴더 이동 |
| pages/admin/media/index.vue | 미디어 관리, **미디어 라이브러리/썸네일** 상단 탭, 라이브러리: 폴더 트리·폴더 추가 모달·폴더 삭제·드래그 이동·썸네일 탭: 회원 아바타만·검색(닉네임 등), 썸네일 클릭 미리보기 모달(연결 회원 블록), 좌상단 선택 토글·Shift 범위 |
| pages/admin/navigation/index.vue | 메뉴/네비게이션 관리(변경 시에만 메뉴 저장 활성) |
| pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬·일반 강등, 일반 태그 검색/메인 전환/삭제), 액션 피드백 토스트, 순서 변경 시에만 정렬 저장 활성 |
| pages/admin/tags/new.vue | 태그 생성 |
@@ -124,7 +124,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(WebP 변환, 최소 해상도 검증, 중앙 1:1 강제 크롭, 품질 보정) |
| server/api/auth/avatar.post.js | 회원 썸네일 업로드 API(WebP 변환, 최소 해상도 검증, 중앙 1:1 강제 크롭, 품질 보정, `media_metadata` 논리 폴더 `썸네일`) |
| server/api/auth/avatar.delete.js | 회원 썸네일 삭제 API |
| server/api/auth/check-username.get.js | 닉네임 중복 확인 API |
| server/api/auth/password.put.js | 회원 비밀번호 변경 API |
@@ -150,7 +150,7 @@
| 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 |
| server/routes/admin/api/uploads.post.js | 관리자 이미지 업로드 API(성공 시 `media_metadata``미분류`로 기록) |
| 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 |
@@ -175,7 +175,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 | 업로드 미디어·논리 폴더(`미분류`, 예약 `썸네일``avatarOwner` 부착·아바타 삭제/이름변경 차단 |
| server/repositories/postgres-client.js | PostgreSQL 클라이언트 |
| server/repositories/content-repository.js | 콘텐츠 조회 저장소 |
| server/repositories/member-repository.js | 회원 조회/생성 저장소 |
@@ -192,6 +192,7 @@
| db/migrations/005_add_navigation_items.sql | 네비게이션 항목 테이블 추가 |
| db/migrations/006_add_media_metadata.sql | 미디어 메타데이터 테이블 추가 |
| db/migrations/007_add_media_folders.sql | 미디어 폴더 테이블 추가 |
| db/migrations/016_media_category_normalize.sql | `media_metadata` 레거시 `posts``미분류`, `회원/썸네일``썸네일` 정리 |
| db/migrations/008_add_post_seo_fields.sql | 게시물 SEO 필드 추가 |
| db/migrations/009_add_post_og_image.sql | 게시물 OG 이미지 필드 추가 |
| db/migrations/010_add_members_and_comments.sql | 회원/댓글 테이블 추가 |

View File

@@ -300,7 +300,7 @@ components/content/
| 필드 | 타입 | 설명 |
|------|------|------|
| url | String | 업로드 미디어 URL |
| category | String | 관리자 미디어 폴더 경로 |
| category | String | 논리 폴더 경로(게시물 업로드 이미지는 `미분류`, 회원 아바타는 예약값 `썸네일` 등) |
| created_at | DateTime | 생성일 |
| updated_at | DateTime | 수정일 |
@@ -366,7 +366,7 @@ components/content/
> 최종 저장 크기는 `AVATAR_MAX_WIDTH`, `AVATAR_MAX_HEIGHT` 중 작은 값을 사용해 `N x N` 정사각형으로 맞춘다.
> `AVATAR_WEBP_QUALITY`는 1~100 범위로 보정하며, 최대 해상도 설정이 최소 해상도보다 작으면 서버에서 최소값 이상으로 자동 보정한다.
> 회원 썸네일을 새로 업로드하거나 제거/탈퇴할 때 기존 회원 썸네일 파일과 메타데이터는 자동 정리한다.
> 관리자 미디어 화면에서 회원 썸네일도 `회원/썸네일` 폴더로 조회 가능하다.
> 관리자 미디어 화면의 **썸네일** 탭에서 회원 썸네일을 목록·검색하며, `GET /admin/api/media` 응답에 `avatarOwner`(닉네임·이메일·마지막 접속·IP)가 포함될 수 있다.
### 관리자 API (`/admin/api/`)
@@ -383,9 +383,9 @@ components/content/
- `GET /admin/api/pages/:id` - 고정 페이지 상세
- `PUT /admin/api/pages/:id` - 고정 페이지 수정
- `DELETE /admin/api/pages/:id` - 고정 페이지 삭제
- `GET /admin/api/media` - 업로드 미디어 목록
- `PUT /admin/api/media` - 업로드 미디어 파일명 변경 또는 단일/복수 미디어 폴더 변경
- `DELETE /admin/api/media` - 업로드 미디어 삭제
- `GET /admin/api/media` - 업로드 미디어 목록(게시물용 이미지와 회원 아바타 포함; 회원 아바타에는 `avatarOwner` 요약이 붙을 수 있음)
- `PUT /admin/api/media` - 업로드 미디어 파일명 변경 또는 단일/복수 미디어 폴더 변경(회원 아바타 URL은 논리 폴더 `썸네일`만 허용, 일반 미디어를 `썸네일`로 옮기는 것은 거부)
- `DELETE /admin/api/media` - 업로드 미디어 삭제(회원 아바타 디스크 경로 파일은 거부)
- `GET /admin/api/media-folders` - 미디어 폴더 목록
- `POST /admin/api/media-folders` - 미디어 폴더 생성
- `DELETE /admin/api/media-folders` - 미디어 폴더 삭제(해당 경로·하위 경로로 분류된 `media_metadata``미분류`로 되돌림)
@@ -559,23 +559,25 @@ components/content/
/uploads/system/favicon.png
```
- 디스크 상대 경로의 **첫 번째 디렉터리 이름**(`posts`, `pages`, `members` 등)이 `media_metadata`가 없을 때 미디어 라이브러리에 보이는 **기본 폴더(논리 분류)**가 된다. 관리자 에디터 업로드 API는 현재 `public/uploads/posts/YYYY/MM/`에만 저장하므로, 메타가 없는 새 이미지는 기본적으로 **`posts`** 트리 아래로 잡힌다.
- `미분류`는 예약된 논리 폴더이며, 사용자가 폴더를 지정하지 않았거나 폴더 삭제 후 되돌림 등으로 `media_metadata.category``미분류`로 저장된 항목이 여기에 모인다. 디스크 경로가 `posts/...`인데도 화면에 `미분류`로 보이려면 메타가 `미분류`로 저장돼 있어야 한다.
- 회원 썸네일은 `public/uploads/members/avatars/YYYY/MM/`에 WebP로 저장되며, 업로드 시 서버가 `media_metadata` **`회원/썸네일`** 카테고리를 넣어 미디어 라이브러리에서 회원 영역으로 묶인다(자동으로 `미분류`에 들어가지 않는다).
- 관리자 에디터 이미지 업로드 API는 디스크상 `public/uploads/posts/YYYY/MM/`에 저장하지만, DB가 있을 때 `media_metadata`에는 논리 폴더 **`미분류`**로 기록한다. 메타가 없을 때도 서버 목록에서는 `posts/...` 경로를 **`미분류`**로 표시한다(디스크 1단계 `posts`와 이중 표기하지 않음).
- `미분류`는 예약된 논리 폴더이며, 사용자가 폴더를 지정하지 않았거나 폴더 삭제 후 되돌림 등으로 `media_metadata.category``미분류`로 저장된 항목이 여기에 모인다.
- 회원 썸네일은 `public/uploads/members/avatars/YYYY/MM/`에 WebP로 저장되며, 업로드 시 `media_metadata.category`는 논리 폴더 **`썸네일`**로 저장한다. `썸네일`은 예약 폴더로 `media_folders`에 수동 생성·삭제할 수 없다.
- 레거시 메타 값 `posts`, `회원/썸네일`은 마이그레이션 `016_media_category_normalize.sql` 및 서버 정규화로 각각 `미분류`, `썸네일`에 맞춘다.
- 관리자 이미지 업로드 API는 `image/jpeg`, `image/png`, `image/webp`, `image/gif`만 허용한다.
- 업로드 파일 크기 제한은 `MAX_FILE_SIZE` 환경 변수를 따른다.
- 로컬 개발 업로드 파일은 `public/uploads/posts/YYYY/MM/` 아래 저장하고 `/uploads/posts/YYYY/MM/filename` URL로 제공한다.
- 관리자 미디어 화면은 왼쪽 폴더 트리와 오른쪽 고밀도 썸네일 갤러리, 검색, 파일명 변경, 개별 삭제를 제공한다.
- 관리자는 폴더 추가 버튼으로 모달에서 새 폴더·하위 폴더 이름을 입력해 만들 수 있으며, `미분류`를 제외한 폴더는 목록에서 삭제할 수 있다. 폴더 삭제 시 해당 경로 및 하위 경로로 분류돼 있던 미디어 메타는 모두 `미분류`로 되돌린다.
- 관리자 미디어 화면 상단에 **미디어 라이브러리** 탭과 **썸네일** 탭을 두어, 라이브러리 탭에서는 게시물·기타 이미지만, 썸네일 탭에서는 `/members/avatars/` 파일만 검색·탐색한다.
- 미디어 라이브러리 탭은 왼쪽 폴더 트리와 오른쪽 고밀도 썸네일 갤러리, 검색, 파일명 변경, 개별 삭제를 제공한다.
- 관리자는 폴더 추가 버튼으로 모달에서 새 폴더·하위 폴더 이름을 입력해 만들 수 있으며, `미분류`·`썸네일`(및 그 하위)을 제외한 폴더는 목록에서 삭제할 수 있다. 폴더 삭제 시 해당 경로 및 하위 경로로 분류돼 있던 미디어 메타는 모두 `미분류`로 되돌린다.
- 썸네일 본문(이미지·파일명) 한 번 클릭 시 상세(미리보기) 모달이 열리고, 썸네일 좌측 상단 **선택 토글**로 개별 선택한다. Shift+클릭으로 범위 선택이 가능하다.
- 선택한 미디어를 폴더로 드래그하면 해당 미디어들의 폴더 경로가 일괄 변경된다.
- 미디어 파일 경로, 사용 현황, 용량 등 세부 정보는 상세 모달에서 표시한다.
- 미디어 라이브러리 탭에서만 선택한 미디어를 폴더로 드래그하면 해당 미디어들의 폴더 경로가 일괄 변경된다.
- 미디어 파일 경로, 사용 현황(라이브러리), 연결 회원(썸네일 탭), 용량 등 세부 정보는 상세 모달에서 표시한다.
- 미디어 폴더는 실제 파일 경로를 옮기지 않고 `media_metadata` 테이블에 URL별 경로 메타데이터로 저장한다.
- 글쓰기 미디어 선택 창은 업로드 미디어 목록에서 이미지를 선택해 단일 이미지 또는 갤러리에 삽입한다.
- 미디어 사용 현황은 게시물/페이지의 대표 이미지와 본문 내 URL을 기준으로 표시한다.
- 사용 중인 미디어는 저장된 콘텐츠 URL이 깨지지 않도록 파일명 변경과 삭제를 차단한다.
- 향후 미디어 라이브러리는 프로필/사이트 설정 이미지 사용처 추적을 제공한다.
- 회원 프로필 썸네일 파일은 관리자 화면에서 파일명 변경·삭제를 차단한다.
---

View File

@@ -1,9 +1,16 @@
# 업데이트 이력
## v0.0.90
- 관리자 미디어: 상단 탭으로 **미디어 라이브러리**와 **썸네일**(회원 `/members/avatars/`만) 분리, 썸네일 검색에 닉네임·이메일·IP 반영.
- 게시물용 관리자 업로드는 디스크 `posts/연/월` 유지하되 `media_metadata` 및 목록 논리 폴더는 **`미분류`**로 통일; 회원 아바타 메타는 **`썸네일`**.
- `listMediaItems``avatarOwner` 부착, 썸네일 파일의 관리자 삭제·이름 변경·임의 논리 폴더 이동 차단, 예약 폴더 `썸네일``media_folders` 생성·삭제 차단.
- 마이그레이션 `016_media_category_normalize.sql`로 레거시 `posts`/`회원/썸네일` 카테고리 문자열 정리.
## v0.0.89
- 관리자 미디어 썸네일 선택 컨트롤을 네이티브 체크박스에서 대비가 분명한 토글 버튼(미선택: 흰 배경·진한 테두리, 선택: 진한 배경·흰 체크)으로 교체.
- `docs/spec.md``posts`·`미분류`·`회원/썸네일` 디스크 경로·`media_metadata` 관계를 명시.
- `docs/spec.md`미디어 디스크 경로`media_metadata` 논리 폴더 관계를 명시(이후 v0.0.90에서 `미분류`/`썸네일` 정책으로 갱신).
## v0.0.88