diff --git a/docs/changelog.md b/docs/changelog.md index 502b6b9..b4deae3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,11 @@ # 업데이트 요약 +## v1.5.26 + +- 관리자 사이트 설정의 게시물 Import/Export 영역을 기본적으로 접힌 액션 중심 UI로 정리했다. +- Export 설정과 Import 안내는 각각 버튼을 눌렀을 때만 펼쳐지도록 개선했다. +- 설정 화면 셀렉트 화살표 아이콘과 간격을 통일했다. + ## v1.5.25 - 게시물 Export 분할을 고정 개수 대신 목표 ZIP 용량 기준으로 나누도록 개선했다. diff --git a/docs/deploy.md b/docs/deploy.md index eb76084..9b3fded 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,6 +1,6 @@ # 배포 가이드 -> 로컬 기준 v1.5.25에서 `npm run lint`, `npm run build`, `npm run db:migrate:dev` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. +> 로컬 기준 v1.5.26에서 `npm run lint`, `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. ## 빌드 유형 diff --git a/docs/history.md b/docs/history.md index 1f6d781..f7617c1 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-06-02 v1.5.26 — Import/Export 설정은 필요할 때만 펼친다 + +게시물 Import/Export는 운영자가 매일 조작하는 기본 설정이 아니라 필요할 때 요청하거나 다운로드하는 백업 도구다. 상세 범위와 분할 설정이 항상 카드 상단을 크게 차지하면 다른 설정을 훑는 흐름이 무거워지므로, 기본 화면은 `Export 요청`과 `Import 하기` 진입 버튼, 그리고 요청된 작업의 다운로드 상태 중심으로 줄였다. Export 조건 입력은 버튼을 눌렀을 때만 펼치고, 아직 실제 가져오기 API가 연결되지 않은 Import는 접힌 안내 패널로만 둔다. + ## 2026-06-01 v1.5.25 — Export 분할은 용량 기준이 우선이다 기존 100개 단위 분할은 대략적인 안내값으로는 충분하지만, 게시물마다 첨부 자산 크기가 다르면 실제 ZIP 용량을 예측하기 어렵다. Export 계획은 게시물 본문 크기와 내부 업로드 자산 크기를 추산해 목표 ZIP 용량을 넘기기 전에 새 파일로 나누도록 바꿨다. ZIP당 최대 게시물 수는 용량 기준을 대신하는 규칙이 아니라 너무 많은 작은 글이 한 파일에 몰리는 상황을 막는 안전 상한으로만 남겼다. 실패 원인은 재시도 여부를 판단하는 운영 정보이므로 작업 카드에서 확인할 수 있게 별도 상세 로그로 저장한다. diff --git a/docs/map.md b/docs/map.md index f06c540..23e296c 100644 --- a/docs/map.md +++ b/docs/map.md @@ -140,7 +140,7 @@ | pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬 자동 저장·more vert 메뉴, 일반 태그 배지 more vert 메뉴·검색/정렬, 태그 추가 버튼), 액션 피드백 토스트 | | pages/admin/tags/new.vue | 태그 생성 | | pages/admin/tags/[id].vue | 태그 수정 | -| pages/admin/settings/index.vue | 사이트 설정 Ghost형 전체 화면, **POST 설정**(`showPostUpdatedAt` 토글·읽기 모드 비활성 톤), 블로그 제목·설명·기타(로고·URL·저작권), **메인 화면**(라이트·다크 커버 이미지·오버레이 텍스트), **어나운스 바**(사용 토글·맞춤 설정·배경색·읽기 모드 비활성 톤), **스팸 필터**(가입 금지 닉네임), 타임존, 게시물 Import/Export 전체·연도·월·직접 날짜 범위 작업 요청·목표 ZIP 용량·ZIP당 최대 게시물 수·최근 작업·진행도·준비 완료 분할 파일 다운로드·브라우저 순차 일괄 다운로드·실패 작업 재시도·실패 상세 오류·작업 삭제, 진행 중 요청 버튼 잠금 | +| pages/admin/settings/index.vue | 사이트 설정 Ghost형 전체 화면, **POST 설정**(`showPostUpdatedAt` 토글·읽기 모드 비활성 톤), 블로그 제목·설명·기타(로고·URL·저작권), **메인 화면**(라이트·다크 커버 이미지·오버레이 텍스트), **어나운스 바**(사용 토글·맞춤 설정·배경색·읽기 모드 비활성 톤), **스팸 필터**(가입 금지 닉네임), 타임존, 게시물 Import/Export 기본 액션 버튼, 펼침형 Export 전체·연도·월·직접 날짜 범위 작업 요청·목표 ZIP 용량·ZIP당 최대 게시물 수, 접힌 Import 안내 패널, 최근 작업·진행도·준비 완료 분할 파일 다운로드·브라우저 순차 일괄 다운로드·실패 작업 재시도·실패 상세 오류·작업 삭제, 진행 중 요청 버튼 잠금 | | lib/signup-blocked-usernames.js | 가입 금지 닉네임 정리·매칭·안내 문구 | | pages/admin/members/index.vue | 관리자 멤버 목록(Ghost형 테이블, 글 목록과 같은 테두리형 검색, 조건 필터, 멤버 추가 버튼, 닉네임+이메일, 등급+비활성 상태, 가입일+최근 활동, IP, 댓글 수) | | pages/admin/members/new.vue | 관리자 멤버 추가(썸네일 URL, 이름, 이메일, 레이블, 관리자 노트) | diff --git a/docs/spec.md b/docs/spec.md index b7e0f10..1875b61 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -696,7 +696,7 @@ components/content/ ### 사이트 설정 -- 관리자 사이트 설정 UI는 `/admin/settings`에서 제공한다. Ghost Admin과 유사하게 **전체 화면**으로 표시하며, 좌측 내비와 우측 본문을 **한 덩어리로 중앙 정렬**(`max-w` 래퍼, 본문 카드 영역은 약 760px 상한)하고, 페이지 배경은 밝은 회색·본문 열은 흰색으로 구분한다. 우측 본문을 스크롤하면 현재 보이는 구역에 맞춰 좌측 메뉴 활성 배경을 갱신한다. **우측 상단 고정** 닫기 버튼과 `Escape` 키로 설정 화면을 닫으며, 브라우저 히스토리가 있으면 `뒤로 가기`, 없으면 `/admin`으로 이동한다. 타임존은 메뉴·안내 카드만 제공한다. 게시물 Import/Export는 전체·특정년·특정월·직접 지정 범위 Export 요청, 목표 ZIP 용량과 ZIP당 최대 게시물 수 지정, 최근 작업 목록, 진행도, 준비 완료 분할 파일 다운로드, 브라우저 순차 일괄 다운로드, 실패 작업 재시도, 실패 상세 오류, 완료·실패 작업 삭제를 표시하며 대기 중·생성 중 작업이 있으면 새 Export 요청 버튼을 비활성화한다. **스팸 필터**는 가입 금지 닉네임을 저장한다. **블로그 제목·설명** 카드는 기본적으로 사이트 이름·설명을 읽기 전용으로 보여 주고, `편집`을 눌렀을 때만 입력 필드·미리보기·저장/취소가 나타난다. 제목·설명 편집 중 `Escape`는 설정 닫기 대신 편집 취소로 동작한다. **POST 설정**·**어나운스 바**는 읽기 모드에서도 토글 UI를 비활성화 상태로 보여 주며, 켜짐 상태도 조작 가능한 활성 스위치처럼 보이지 않도록 낮은 대비와 `cursor-not-allowed`를 사용한다. +- 관리자 사이트 설정 UI는 `/admin/settings`에서 제공한다. Ghost Admin과 유사하게 **전체 화면**으로 표시하며, 좌측 내비와 우측 본문을 **한 덩어리로 중앙 정렬**(`max-w` 래퍼, 본문 카드 영역은 약 760px 상한)하고, 페이지 배경은 밝은 회색·본문 열은 흰색으로 구분한다. 우측 본문을 스크롤하면 현재 보이는 구역에 맞춰 좌측 메뉴 활성 배경을 갱신한다. **우측 상단 고정** 닫기 버튼과 `Escape` 키로 설정 화면을 닫으며, 브라우저 히스토리가 있으면 `뒤로 가기`, 없으면 `/admin`으로 이동한다. 타임존은 메뉴·안내 카드만 제공한다. 게시물 Import/Export 기본 화면은 `Export 요청`·`Import 하기` 액션 버튼과 최근 작업 목록을 중심으로 표시한다. Export 상세 설정은 `Export 요청` 버튼을 눌렀을 때만 펼쳐지며, 전체·특정년·특정월·직접 지정 범위 Export 요청, 목표 ZIP 용량과 ZIP당 최대 게시물 수 지정을 제공한다. Import 상세는 `Import 하기` 버튼을 눌렀을 때 안내 패널로 펼쳐진다. 최근 작업 목록은 진행도, 준비 완료 분할 파일 다운로드, 브라우저 순차 일괄 다운로드, 실패 작업 재시도, 실패 상세 오류, 완료·실패 작업 삭제를 표시하며 대기 중·생성 중 작업이 있으면 새 Export 요청 버튼을 비활성화한다. **스팸 필터**는 가입 금지 닉네임을 저장한다. **블로그 제목·설명** 카드는 기본적으로 사이트 이름·설명을 읽기 전용으로 보여 주고, `편집`을 눌렀을 때만 입력 필드·미리보기·저장/취소가 나타난다. 제목·설명 편집 중 `Escape`는 설정 닫기 대신 편집 취소로 동작한다. **POST 설정**·**어나운스 바**는 읽기 모드에서도 토글 UI를 비활성화 상태로 보여 주며, 켜짐 상태도 조작 가능한 활성 스위치처럼 보이지 않도록 낮은 대비와 `cursor-not-allowed`를 사용한다. - 게시물 Import/Export 1차 포맷은 Obsidian 호환 백업 번들을 기준으로 한다. Export는 게시물마다 별도 폴더를 만들고, 폴더 안에 `제목.md` 메인 파일과 `images/`, `files/` 같은 자산 폴더를 함께 둔다. 본문은 기존 Markdown을 최대한 유지하되 `/uploads/...`로 연결된 내부 이미지·파일은 번들 안의 로컬 파일로 복사하고, Markdown 참조는 `./images/...` 또는 `./files/...` 같은 상대 경로로 재작성한다. 제목·슬러그·상태·발행일·요약·대표 이미지·SEO·태그는 YAML frontmatter에 저장한다. Import는 같은 구조의 폴더/zip을 읽어 frontmatter를 게시물 메타데이터로 복원하고, 로컬 자산은 미디어 업로드 저장소로 가져온 뒤 본문 경로를 새 `/uploads/...` URL로 다시 매핑한다. - 대용량 게시물 Export는 즉시 응답 다운로드가 아니라 백그라운드 작업으로 처리한다. 관리자가 Export를 요청하면 서버는 작업 레코드를 만들고, 게시물을 목표 zip 용량 기준으로 나누어 여러 zip 파일을 순차 생성한다. 계획 단계에서는 본문 문자열 바이트와 내부 `/uploads` 자산 파일 크기를 합산해 `max_file_size_bytes`를 넘기기 전에 새 분할 파일을 만들며, `chunk_size`는 한 ZIP에 들어갈 게시물 수의 안전 상한으로만 사용한다. 현재 구현은 요청 시 `post_export_jobs` 작업과 `post_export_files` 분할 파일 계획을 생성하고, 서버 프로세스 안에서 대기 작업을 순차 실행해 `processed_count`, `current_part_index`, `progress_message`, `started_at`을 갱신한다. Export 대상은 전체 또는 `COALESCE(published_at, created_at)` 기준 특정년·특정월·직접 지정 날짜 범위로 제한할 수 있다. 설정 화면은 대기 중·생성 중 작업이 있을 때 5초 간격으로 작업 목록을 새로고침한다. 파일명은 기본적으로 `사이트명_범위_시작번호-끝번호.zip` 형식을 사용한다(예: `sori.studio_2026-05_1-100.zip`). 각 zip에는 해당 범위 게시물 폴더와 자산 폴더가 들어가며, 준비 완료된 분할 파일은 관리자 다운로드 API로 받을 수 있다. 준비 완료 파일은 일괄 다운로드 버튼으로 브라우저에서 순차 다운로드할 수 있다. 실패 작업은 이미 준비된 파일을 유지하고 실패·대기 파일만 다시 생성하도록 재시도할 수 있으며, 실패 상세 로그는 작업 카드에서 확인한다. Resend 환경 변수가 설정되어 있으면 모든 분할 파일 생성 완료 후 요청 관리자 이메일로 알림을 보낸다. 완료·실패한 작업은 관리자가 즉시 삭제할 수 있고, 삭제 시 DB 레코드와 생성 ZIP 파일을 함께 정리한다. 서버 용량 보호를 위해 export 산출물 보존 기간은 최대 100일로 제한하며, 만료된 완료·실패 작업은 목록 조회나 새 요청 시 생성 ZIP 파일과 함께 자동 정리된다. - 사이트 설정은 `site_settings` 테이블의 단일 레코드로 관리한다. diff --git a/docs/update.md b/docs/update.md index c8a6bee..45b2b85 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,12 @@ # 업데이트 이력 +## v1.5.26 + +- 관리자 사이트 설정: 게시물 Import/Export 섹션 기본 화면을 요청 버튼과 최근 작업 중심으로 축소. +- 관리자 사이트 설정: Export 범위·분할 설정은 `Export 요청` 버튼을 눌렀을 때만 펼쳐지도록 수정. +- 관리자 사이트 설정: Import 영역은 `Import 하기` 버튼을 눌렀을 때 접힌 안내 패널로 표시하도록 정리. +- 관리자 사이트 설정: 구역 이동·Export 범위 셀렉트의 화살표 SVG와 오른쪽 간격 통일. + ## v1.5.25 - 게시물 Export: 분할 ZIP 계획을 고정 100개 단위가 아니라 목표 ZIP 용량 기준으로 나누도록 수정. diff --git a/package-lock.json b/package-lock.json index d8391c6..a7085d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.5.25", + "version": "1.5.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.5.25", + "version": "1.5.26", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index 4e2fcfe..6334ca9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.5.25", + "version": "1.5.26", "private": true, "type": "module", "imports": { diff --git a/pages/admin/settings/index.vue b/pages/admin/settings/index.vue index b60d36c..4875501 100644 --- a/pages/admin/settings/index.vue +++ b/pages/admin/settings/index.vue @@ -31,6 +31,7 @@ const postExportDateFrom = ref('') const postExportDateTo = ref('') const postExportChunkSize = ref(500) const postExportMaxFileSizeMb = ref(500) +const postImportExportPanel = ref('') const errorMessage = ref('') const toast = ref(null) const logoInputRef = ref(null) @@ -254,6 +255,15 @@ const postExportRequestTitle = computed(() => { return '게시물 Export 작업을 요청합니다.' }) +/** + * Import/Export 상세 패널을 전환한다. + * @param {'import'|'export'} panelName - 열 패널 이름 + * @returns {void} + */ +const togglePostImportExportPanel = (panelName) => { + postImportExportPanel.value = postImportExportPanel.value === panelName ? '' : panelName +} + /** * Export 요청 범위 입력을 만든다. * @returns {Object} Export 범위 입력 @@ -1299,19 +1309,22 @@ onBeforeUnmount(() => { -
-Obsidian 호환 백업 준비 @@ -2046,51 +2109,60 @@ onBeforeUnmount(() => {
+ 백업 ZIP 가져오기 +
++ Export ZIP 구조를 다시 게시물로 가져오는 기능은 다음 단계에서 연결합니다. +
+