Compare commits

..

2 Commits

Author SHA1 Message Date
d5d4974751 수정: 우측 레일 Teleport 라우트 전환 안정화 2026-04-03 14:52:25 +09:00
46fcb0e2cf NAS DB 초기화 배포 절차를 문서화 2026-04-03 14:40:19 +09:00
6 changed files with 69 additions and 4 deletions

View File

@@ -1,5 +1,9 @@
# 의사결정 이력
## 2026-04-03 v1.4.62
- 운영 서버를 새 DB로 다시 시작하는 절차는 “일반 업데이트 재빌드”와 “볼륨까지 삭제하는 완전 초기화”가 같은 문서 안에 섞이면 실수로 데이터를 날릴 위험이 크므로, 배포 문서에서 두 흐름을 별도 섹션으로 나누는 편이 맞다고 판단했다.
- DB만 비우고 업로드 볼륨을 남기는 방식도 가능하지만, 현재 서비스는 DB 레코드와 업로드 파일 참조가 강하게 연결되어 있으므로 이 방법 역시 “운영 데이터를 전부 버리는 전제”라는 경고를 같이 적어두는 쪽으로 정리했다.
## 2026-04-03 v1.4.61
- 운영자가 쓰는 템플릿 주소를 `topics.id` 자체로 두면 나중에 이름/URL을 다듬고 싶을 때 참조 FK와 기존 링크까지 같이 흔들릴 수 있으므로, 내부 참조용 랜덤 `id`와 공개/관리용 `slug`를 분리하는 구조가 더 안전하다고 판단했다.
- 운영 DB와 로컬 DB를 모두 새로 시작할 수 있는 상황이라면 예전 `id -> slug` 백필이나 레거시 호환 코드를 남기는 편이 오히려 유지보수 비용만 늘리므로, 이번 변경은 새 스키마 기준으로 깔끔하게 정리하고 기존 데이터 호환 마이그레이션은 두지 않기로 했다.
@@ -641,6 +645,10 @@
- 작성자가 자기 티어표를 직접 삭제할 수 있어야 관리 흐름이 완결되므로, `내 티어표` 화면에 삭제 기능을 추가하기로 결정했다.
- 사용자 커스텀 이미지는 서버 용량을 계속 차지하므로, 참조가 하나도 남지 않은 이미지(미사용 항목)를 식별하고 관리자 화면에서 개별/일괄 정리할 수 있도록 하기로 결정했다.
## 2026-04-03 v1.4.63
- 관리자/에디터 화면의 우측 패널은 Teleport로 공통 셸의 레일 DOM에 끼워 넣는 구조이므로, 라우트 변경 시 Teleport 대상 노드 자체를 조건부로 없애면 Vue 언마운트/패치 순서에 따라 DOM 기준점이 깨질 수 있다고 판단했다.
- 따라서 `#local-right-rail-root`는 항상 렌더링해두고, 일반 화면에서는 숨김 클래스만 적용하는 방식으로 유지해 라우트 전환 안정성을 우선 확보하기로 결정했다.
## 2026-03-19 v0.1.13
- 관리자 페이지는 기능 수가 늘어난 만큼 게임, 아이템, 회원 관리 탭으로 나누는 편이 더 안전하다고 판단했다.
- 사용자 커스텀 아이템은 수량이 빠르게 커질 수 있으므로, 검색과 페이지네이션을 기본 제공하는 관리 화면으로 보는 방향을 채택했다.

View File

@@ -57,7 +57,7 @@
## 공통 레이아웃
- 앱 셸 파일: `frontend/src/App.vue`
- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, `preview=1` 공유 프리뷰에서도 같은 좌우 레일과 중앙 헤더 유지, 로그인 상태 반영, 최근 즐겨찾기 바로가기와 전역 검색 입력, 관리자 메뉴 노출 제어, 실제 SVG 에셋과 선형 SVG 아이콘이 혼합된 레일 UI, 전역 우측 상단 토스트 렌더링
- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, `preview=1` 공유 프리뷰에서도 같은 좌우 레일과 중앙 헤더 유지, 로그인 상태 반영, 최근 즐겨찾기 바로가기와 전역 검색 입력, 관리자 메뉴 노출 제어, 실제 SVG 에셋과 선형 SVG 아이콘이 혼합된 레일 UI, 전역 우측 상단 토스트 렌더링, 관리자/에디터 화면이 Teleport로 사용하는 `#local-right-rail-root` 대상 DOM을 상시 유지해 라우트 전환 중 우측 레일 언마운트 순서를 안정화
- 세부: 좌측 패널은 `248px` 기준 폭을 사용하되 축소 시 아이콘 중심의 좁은 레일로 전환하고, 우측 패널은 `320px` 기준 폭을 사용한다. 세 컬럼 모두 상단에 높이 `56px`의 헤더 블록을 유지한다. 중앙 헤더에는 고정 브랜드 `Tier Maker`와 서비스 설명이 표시되고, 우측 패널 토글은 닫혀 있을 때 중앙 헤더, 열려 있을 때 우측 헤더에 아이콘만 표시된다. 좌우 레일의 주요 액션은 각각 하단 `56px` 푸터 안에서 항상 보이도록 유지하면서 아래쪽 패딩으로 여백을 확보한다.
## 백엔드 진입점

View File

@@ -1,6 +1,8 @@
# 할 일 및 이슈
## 단기 확인
- `v1.4.62`에서 NAS 배포 문서에 운영 DB 완전 초기화 절차를 추가했으므로, 실제 NAS에서 `git pull → docker compose ... down -v → up -d --build` 순서로 재배포했을 때 빈 DB가 현재 스키마로 다시 올라오고 `freeform`만 생성되는지 확인한다.
- `docker volume rm tier-maker_tmaker_mariadb_data` 방식은 프로젝트 디렉터리명에 따라 실제 볼륨 이름이 달라질 수 있으므로, 운영 NAS에서는 먼저 `docker volume ls | grep tmaker`로 이름을 확인한 뒤 문서 명령이 그대로 맞는지 점검한다.
- `v1.4.61`에서 템플릿 공개 주소를 `slug`로 분리했으므로, 홈 카드/주제 상세/나의 티어표/즐겨찾기/검색 결과/팔로우 피드/사용자 프로필에서 열리는 URL이 `/topics/:slug`, `/editor/:slug/...` 형태로 바뀌고, 실제 화면 내용도 같은 주제 템플릿으로 정확히 열리는지 QA한다.
- 관리자 템플릿 생성/설정은 이제 내부 ID가 아니라 `slug + 이름`만 입력하므로, 새 템플릿 생성, 기존 템플릿 이름/slug 저장, 중복 slug 입력, 대문자/특수문자 slug 입력, 공개/비공개 토글, 썸네일/기본 아이템 관리가 모두 같은 템플릿에 정상 반영되는지 확인한다.
- 신규 빈 DB 초기화 시 `topics``freeform` 한 건만 생성되고 `example-topic`, `another-topic` 같은 예시 템플릿이 더 이상 자동으로 생기지 않는지 운영/로컬 재배포 후 확인한다.
@@ -114,6 +116,8 @@
- 검색 결과 화면도 `pageHead` 구조로 맞췄으므로, 주요 목록 화면들 간 상단 여백과 타이포 리듬이 자연스러운지 한 번 더 비교 QA한다.
- 주제 상세 컬렉션 화면은 `pageHead` 공통 레이아웃과 `/topics` 기본 경로로 옮겼으므로, 직접 진입·뒤로가기·검색 후 재진입 시 주소와 헤더 흐름이 자연스러운지 한 번 더 QA한다.
- `/topics/:gameId`를 기본 경로로 세우고 `/games/:gameId`는 alias로 남겼으므로, 다음 단계에서는 에디터/검색/공유 흐름에서 어떤 링크를 새 경로로 더 전환할지 범위를 정한다.
- 운영 환경에서 `/favicon.svg`, `/favicon-32x32.png``403 Forbidden`으로 떨어지는 현상이 남아 있으므로, 배포 컨테이너 안의 `/usr/share/nginx/html` 실제 파일/권한, 프런트 빌드 산출물 복사 상태, NAS 또는 Cloudflare 앞단 보안 규칙을 순서대로 확인한다.
- 우측 레일 Teleport 대상 DOM을 상시 유지하는 방식으로 바꿨으므로, 관리자 `/admin/...` → 설정 `/profile` → 홈 `/`처럼 전용 우측 레일과 일반 우측 레일을 오가는 라우트 전환에서 콘솔 오류가 더 이상 재현되지 않는지 운영 브라우저로 한 번 더 QA한다.
- 내부 리네이밍 2단계로 관리자 `selectedTemplate / templates / loadTemplate / refreshTemplates` 묶음까지 정리했으므로, 다음 단계에서는 `/games/:gameId` 라우트와 프런트 API 호출부를 어디까지 `topic/template` 의미로 감쌀지 범위를 먼저 정리한다.
- 화면/문서 층의 용어 정리는 거의 마무리됐으므로, 다음 단계에서는 내부 `gameId / games` 모델을 실제로 옮길지, 아니면 라우트 alias/리다이렉트부터 둘지 점진 전환 순서를 정한다.
- 관리자 화면까지 포함한 사용자 노출 `게임` 문구를 3차까지 정리했으므로, 실제 운영 흐름에서 뜨는 확인창/토스트/선택 모달까지 표현이 자연스러운지 한 번 더 QA한다.

View File

@@ -93,7 +93,41 @@ docker compose --env-file .env.production -f docker-compose.prod.yml up -d --bui
- Dockerfile, Nginx 설정, 프런트 소스, 백엔드 소스가 바뀐 경우에는 `--build`를 유지한다.
- 단순 재시작만 필요할 때도 있지만, 운영에서는 실수 방지를 위해 `up -d --build`를 기본값으로 두는 편이 안전하다.
## 3-2. 이번 v0.1.34까지 적용하는 예시
## 3-2. 운영 DB/업로드/세션까지 완전 초기화하고 새로 빌드하기
- 운영 데이터를 전부 버리고 새 DB로 다시 시작할 때만 사용한다.
- 아래 명령은 MariaDB 데이터, 업로드 이미지, 세션 파일 볼륨까지 같이 삭제하므로 실행 전에 정말 초기화해도 되는지 반드시 확인한다.
- `.env.production`은 프로젝트 폴더에 그대로 남고, Docker volume 데이터만 제거된다.
```bash
cd /volume1/docker/projects/apps/tier-maker
git pull origin main
docker compose --env-file .env.production -f docker-compose.prod.yml down -v
docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build
```
- 이렇게 올리면 백엔드가 빈 MariaDB에 현재 스키마를 새로 만들고, 초기 템플릿은 시스템용 `freeform` 한 건만 생성한다.
- `down -v` 후 첫 기동은 MariaDB 초기화 때문에 조금 더 오래 걸릴 수 있으니, 아래 명령으로 상태를 확인한다.
```bash
docker compose --env-file .env.production -f docker-compose.prod.yml ps
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f mariadb
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f backend
```
## 3-3. DB만 비우고 업로드/세션 볼륨은 유지하기
- 이미지 파일과 세션 볼륨은 유지하고 MariaDB 데이터만 새로 시작하고 싶다면 `tmaker_mariadb_data` 볼륨만 지운다.
- 이 경우에도 기존 티어표/유저 DB 레코드와 업로드 파일 참조가 끊길 수 있으므로, 현재 운영 데이터를 전부 버리는 전제에서만 사용한다.
```bash
cd /volume1/docker/projects/apps/tier-maker
docker compose --env-file .env.production -f docker-compose.prod.yml down
docker volume rm tier-maker_tmaker_mariadb_data
docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build
```
- 만약 볼륨 이름이 다르게 잡혀 있는지 확인하고 싶다면 먼저 `docker volume ls | grep tmaker`로 실제 이름을 확인한 뒤 지운다.
## 3-4. 이번 최신 main까지 적용하는 예시
- 이미 NAS 폴더가 Git clone 상태라면:
```bash
@@ -153,6 +187,8 @@ docker compose --env-file .env.production -f docker-compose.prod.yml down -v
docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build
```
- 이 명령도 `down -v` 때문에 DB/업로드/세션 볼륨을 모두 삭제한다. 데이터를 유지해야 하는 상황이면 `-v`를 빼고 다시 올린다.
## 9. 참고
- 현재 업로드 이미지는 서버 저장 전에 리사이즈/압축하지 않는다.
- 운영 중 원본 이미지가 많이 쌓이면 이후 `sharp` 기반 최적화 단계를 추가하는 것이 좋다.

View File

@@ -1,5 +1,10 @@
# 업데이트 로그
## 2026-04-03 v1.4.62
- UGREEN NAS 운영 배포 문서에 `git pull origin main` 후 일반 재빌드하는 절차와, 운영 데이터를 전부 버리고 `docker compose ... down -v`로 MariaDB/업로드/세션 볼륨까지 초기화한 뒤 새로 `up -d --build` 하는 절차를 분리해서 추가했다.
- DB만 비우고 싶을 때 `tmaker_mariadb_data` 볼륨만 삭제하는 방법과, 실제 볼륨 이름이 다를 수 있으니 `docker volume ls | grep tmaker`로 먼저 확인하는 안내도 함께 적었다.
- 새로 초기화한 운영 DB로 올리면 현재 스키마가 다시 생성되고 시스템 템플릿은 `freeform` 한 건만 들어간다는 점을 배포 문서에 명시했다.
## 2026-04-03 v1.4.61
- 템플릿 공개 URL과 내부 참조를 분리해, `topics.id`는 서버가 자동 생성하는 랜덤 내부 ID로 두고 운영자가 직접 관리하는 공개 주소는 `topics.slug`로 저장하도록 바꿨다.
- 공개 주제/에디터 경로는 `slug`를 우선 사용하고, 백엔드는 `/api/topics/:topicId`, `/api/tierlists/public?topicId=...`, 티어표 저장/템플릿 요청의 `topicId` 입력을 `slug` 또는 내부 ID에서 실제 템플릿 레코드로 해석한 뒤 내부 `topic_id`를 저장하도록 정리했다.
@@ -1194,6 +1199,10 @@
- **아이템 카드 레이아웃 개선**: 아이템 목록과 추가 미리보기를 1:1 비율 기준으로 재구성하고 더 촘촘한 카드 그리드로 조정
- **레거시 파일 역할 정리**: `db.json`과 lowdb 관련 코드는 현재 MariaDB 기본 런타임에는 필수가 아니며, 마이그레이션/예외 fallback 용도임을 문서에 명시
## 2026-04-03 v1.4.63
- **우측 레일 Teleport 전환 안정화**: 관리자/에디터 전용 우측 패널이 사용하는 `#local-right-rail-root` DOM을 라우트에 따라 생성/삭제하지 않고 항상 유지하도록 바꿔, `/admin/...`에서 설정/다른 페이지로 이동하거나 새로고침 후 화면을 바꿀 때 Vue가 `nextSibling`/`emitsOptions` 기준점을 잃고 크래시하는 문제를 방지
- **정적 favicon 403 분리 확인**: 프런트 빌드 기준 `favicon.svg`, `favicon-32x32.png`, `apple-touch-icon.png` 파일은 레포와 Vite `public/` 출력에 존재함을 확인했고, 운영 환경의 favicon `403 Forbidden`은 코드 누락보다 컨테이너/정적 서빙/프록시 권한 쪽 후속 점검 항목으로 분리
## 2026-03-19 v0.1.7
- **AI 작업 규칙 보강**: `ai-rules.md`에 Git 작성자 정보, 한국어 커밋 메시지, 버전/태그 동기화, 민감 정보 확인 규칙 추가
- **관리자 화면 재구성**: `/admin`을 좌우 병렬 구조에서 `모드 선택 → 게임 선택/생성 → 선택된 게임 상세 관리` 흐름으로 재구성

View File

@@ -659,8 +659,12 @@ function reloadApp() {
</div>
<div class="rightRail__body">
<div class="rightRail__content">
<div v-if="usesLocalRightRail" id="local-right-rail-root" class="localRightRailRoot"></div>
<template v-else>
<div
id="local-right-rail-root"
class="localRightRailRoot"
:class="{ 'localRightRailRoot--hidden': !usesLocalRightRail }"
></div>
<template v-if="!usesLocalRightRail">
<section v-if="showSettingsThemePanel" class="settingsThemePanel">
<div class="settingsThemePanel__eyebrow">Appearance</div>
<div class="settingsThemePanel__title">테마 설정</div>
@@ -1780,6 +1784,10 @@ function reloadApp() {
gap: 14px;
}
.localRightRailRoot--hidden {
display: none;
}
.toastStack {
position: fixed;
top: 18px;