From 00e05dbe3f62a8db2202413e12205f26178ae355 Mon Sep 17 00:00:00 2001 From: zenn Date: Tue, 23 Jun 2026 13:42:38 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=B2=B4=EA=B3=84=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ai-rules.md | 54 ++--- .cursorrules | 2 +- AGENTS.md | 480 ++++++++++++++++++++++++++++++++++++++++ VERSION | 1 + api/package.json | 2 +- api/src/index.js | 4 +- docs/api-integration.md | 6 +- docs/changelog.md | 37 ++++ docs/codex-handoff.md | 116 +++++----- docs/convention.md | 100 +++++---- docs/deploy.md | 331 +++++++++++++-------------- docs/history.md | 185 ++++++++++------ docs/map.md | 205 ++++++++++++----- docs/spec.md | 349 ++++++++++++----------------- docs/todo.md | 57 ++--- docs/update.md | 17 ++ docs/widget.md | 78 +++++++ login.html | 2 +- 18 files changed, 1344 insertions(+), 682 deletions(-) create mode 100644 AGENTS.md create mode 100644 VERSION create mode 100644 docs/changelog.md create mode 100644 docs/widget.md diff --git a/.ai-rules.md b/.ai-rules.md index ae866a6..616f1d5 100644 --- a/.ai-rules.md +++ b/.ai-rules.md @@ -1,37 +1,27 @@ -# 프로젝트 AI 가이드라인 및 문서화 규칙 +# 프로젝트 AI / 문서 규칙 -## 🌐 기본 응답 원칙 -- 모든 대화와 문서는 **한국어**로 작성한다. -- 코드 내 주석은 반드시 **JSDoc 형식**을 사용한다. +이 문서는 AI 에이전트가 이 저장소에서 작업할 때 참고하는 규칙과 문서 관리 원칙을 간결하게 정리합니다. 실제 작업 시 우선 적용 대상은 루트 `AGENTS.md`입니다. -## 🧾 Git 및 버전 관리 규칙 -- Git 작성자 정보는 프로젝트 기준 계정으로 통일한다. -- Git 커밋 메시지는 반드시 **한국어**로 작성한다. -- 버전이 올라가는 작업은 `docs/update.md`에 먼저 반영하고, 같은 버전명을 Git 태그에도 맞춘다. -- 원격 저장소에 푸시하기 전, 민감 정보(실명, 개인 이메일, 비밀키, 로컬 경로)가 포함되지 않았는지 확인한다. +## 기본 응답 원칙 +- 모든 대화와 문서는 한국어로 작성한다. +- 코드 주석은 JSDoc 형식으로 작성한다. +- 답변은 짧고 명확하게 작성한다. +- 추측성 답변 금지. +- 불확실한 내용은 불확실하다고 명시한다. -## 📂 문서 자동 관리 규칙 -모든 작업 수행 후, AI는 관련 내용을 아래 지정된 파일에 즉시 반영해야 한다. +## Git / 버전 규칙 +- 커밋 메시지는 한국어로 작성한다. +- 버전이 올라가는 작업은 `docs/update.md`에 먼저 기록한다. +- 원격 푸시 전 민감 정보(실명, 이메일, 비밀번호, 로컬 경로)가 포함되지 않았는지 확인한다. -1. **작업 이력 (docs/update.md)** - - 수행한 작업 내용을 상세히 기록한다. - - 당일 업데이트라도 수정 시마다 버전을 갱신하여 하나의 파일에 누적 관리한다. (별도 파일 생성 금지) +## 문서 자동 관리 +- 작업 후 반드시 `docs/update.md`, `docs/todo.md`, `docs/spec.md`, `docs/history.md`, `docs/map.md`, `docs/convention.md`를 확인하고 필요한 내용을 반영한다. +- `docs/update.md`는 수행 작업을 기록한다. +- `docs/history.md`는 설계 결정 이유를 기록한다. +- `docs/todo.md`는 남은 작업을 정리한다. +- `docs/spec.md`는 구현·API·데이터 구조를 최신화한다. +- `docs/map.md`는 파일-화면/기능 매핑을 최신화한다. -2. **할 일 및 이슈 (docs/todo.md)** - - 현재 직면한 문제점과 다음에 이어서 진행할 작업 목록을 정리한다. - -3. **기술 명세 (docs/spec.md)** - - API 명세, 기획 내용, 데이터베이스 스키마 구조를 최신 상태로 유지한다. - -4. **코딩 컨벤션 (docs/convention.md)** - - 프로젝트 전용 코딩 스타일 및 네이밍 규칙(Variable, Function, Class 등)을 정의하고 준수한다. - -5. **의사결정 이력 (docs/history.md)** - - 주요 설계 결정 사항 및 시스템 아키텍처 변경 이력을 기록한다. - -6. **파일-화면 매핑 가이드 (docs/map.md)** - - 연결성 기록: 특정 파일(Ex: Login.jsx)이 브라우저 화면의 어느 경로(/login)와 어느 기능(로그인 버튼 등)을 담당하는지 초보자 관점에서 기록한다. - -## ⚠️ 실행 지침 -- 새로운 코드를 작성하거나 수정하기 전, 반드시 `docs/` 내 관련 문서들을 먼저 참조한다. -- 작업 완료 후 위 문서들의 업데이트가 누락되지 않도록 확인한다. +## 실행 지침 +- 수정 전에 관련 `docs/` 문서를 먼저 확인한다. +- 작업 완료 후 문서 반영 누락이 없도록 확인한다. diff --git a/.cursorrules b/.cursorrules index bfd082f..11bb6f4 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1 +1 @@ -모든 작업 시 프로젝트 루트의 .ai-rules.md 지침을 엄격히 준수하고, 작업 종료 시마다 docs/ 폴더 내 문서를 업데이트할 것. \ No newline at end of file +모든 작업 시 프로젝트 루트의 AGENTS.md 지침을 엄격히 준수할 것. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..04412b8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,480 @@ + +# AGENTS.md + +## 목적 + +이 파일은 이 프로젝트에서 작업하는 AI 에이전트(Codex, Claude Code 등)가 반드시 따라야 하는 공통 작업 규칙을 정의한다. + +모든 작업은 본 문서의 지침을 우선 적용한다. + + +## 기본 응답 원칙 + +* 모든 대화, 설명, 보고, 문서 작성은 한국어로 작성한다. +* 사용자가 별도 형식을 요구하지 않는 한 답변은 짧고 명확하게 작성한다. +* 추측성 답변을 하지 않는다. +* 불확실한 내용은 반드시 불확실하다고 명시한다. +* 작업 완료를 선언하기 전에 실제 수정 파일과 문서 반영 여부를 다시 확인한다. + + +## 프로젝트 기본 원칙 + +* 운영 환경은 Docker Compose 기반으로 구성한다. +* 환경 변수는 반드시 `.env`로 관리한다. +* 운영 URL, API Key, 비밀번호, DB 정보는 하드코딩하지 않는다. +* 개발 환경과 운영 환경은 분리한다. +* 로컬 개발 DB와 운영 DB는 반드시 분리한다. +* 사람이 구조를 빠르게 이해할 수 있도록 작성한다. +* 유지보수성을 우선 고려한다. + + +## 문서 구조 + +```text +docs/ + +spec.md +todo.md + +update.md +history.md + +deploy.md +widget.md + +map.md + +changelog.md +convention.md +``` + + +## 문서 역할 + +### spec.md + +현재 무엇이 구현되어 있는가 +기록한다. + +포함 내용 +* 기능 명세 +* API 명세 +* 데이터 구조 +* 화면 동작 +* 권한 정책 + +작업 로그는 기록하지 않는다. + +--- + +### update.md + +이번 작업에서 무엇을 수정했는가 +기록한다. + +예시 +```md +# v0.0.3 + +- 로그인 API 추가. +- 회원가입 화면 수정. +- 비밀번호 검증 로직 개선. +``` + +개발자 작업 로그이다. + +--- + +### history.md + +왜 그렇게 구현했는가 +기록한다. +작업 로그를 기록하지 않는다. + +형식 +```md +## YYYY-MM-DD vX.X.X — 한 줄 요약 + +💡 결정 이유 +🔧 실제 적용 내용 +``` + +예시 +```md +## 2026-06-16 v1.5.105 — 텍스트 색상은 제한된 인라인 토큰으로 저장 + +💡 다크모드 색상 보정과 라이브 편집 직렬화를 통합 관리하기 위함. +🔧 `::red[텍스트]` 형식 채택, 지원 색상은 gray, blue, green, yellow, red, purple. +``` + +규칙 +* 장문의 서술형 작성 금지 +* 3~5줄 이내 작성 +* 회의록 작성 금지 +* 최신 항목이 항상 상단 + +--- + +### todo.md + +앞으로 해야 할 작업만 기록한다. + +규칙 +* 완료된 작업은 즉시 제거 +* 더 이상 필요 없는 작업 제거 +* 현재 기준 유효한 작업만 유지 + +--- + +### deploy.md + +운영 서버 배포 방법을 기록한다. +비개발자도 복사 후 실행만 하면 배포할 수 있어야 한다. + +반드시 포함 +* 프로젝트 위치 +* Git 저장소 주소 +* 환경 변수 위치 +* 개발 실행 방법 +* 빌드 방법 +* Docker 실행 방법 +* Docker 재시작 방법 +* 운영 배포 방법 +* 롤백 방법 + +--- + +### widget.md + +외부 서비스 연동 방법을 기록한다. + +목적 +* Homepage 위젯 +* Grafana +* RSS +* OpenAPI +* Discord +* Telegram +* Webhook + +등 외부 서비스와 연결하는 방법 제공 + +규칙 +* 실제 사용 가능한 URL 제공 +* curl 예시 제공 +* YAML 예시 제공 +* Docker 환경 주의사항 제공 +* 비개발자도 따라할 수 있도록 작성 + +--- + +### map.md + +어느 파일이 어느 기능을 담당하는가 +기록한다. + +예시 +```md +/posts +pages/posts/index.vue + +/posts/[slug] +pages/posts/[slug].vue + +SiteHeader +components/site/SiteHeader.vue +``` + +--- + +### changelog.md + +사용자에게 공개할 변경사항을 기록한다. +공지사항 또는 업데이트 게시글로 바로 사용할 수 있어야 한다. + +규칙 +* 개발 로그 작성 금지 +* 사용자 관점으로 작성 +* 기능 추가 시 사용 방법 포함 +* UI 변경 시 변경 이유 포함 +* 복사 후 공지사항으로 바로 게시 가능한 수준으로 작성 + +예시 +```md +## v1.5.109 + +### 추천 사이트 영역 위치 변경 + +추천 사이트 목록을 왼쪽 사이드바로 이동하였습니다. + +### 사이드바 구조 개선 + +광고와 게시물 목차가 보다 안정적으로 표시되도록 개선하였습니다. +``` + +--- + +### convention.md + +프로젝트 규칙만 기록한다. +반복적으로 적용되는 규칙만 기록한다. + + +## 문서 관계 + +```text +spec.md +현재 무엇이 구현되어 있는가 + +update.md +이번 작업에서 무엇을 수정했는가 + +history.md +왜 그렇게 구현했는가 + +todo.md +앞으로 무엇을 해야 하는가 + +deploy.md +어떻게 배포하는가 + +widget.md +외부 서비스와 어떻게 연동하는가 + +map.md +어느 파일이 어느 기능을 담당하는가 + +changelog.md +사용자 입장에서 무엇이 변경되었는가 + +convention.md +프로젝트에서 어떤 규칙을 따라야 하는가 +``` + + +## 문서 선참조 규칙 + +코드를 작성하거나 수정하기 전에 아래 문서를 먼저 확인한다. + +```text +docs/spec.md +docs/todo.md +docs/history.md +docs/convention.md +docs/map.md +docs/deploy.md +``` + +문서와 코드가 충돌하면 +1. 실제 코드를 확인한다. +2. 코드를 기준으로 판단한다. +3. 문서를 최신 상태로 수정한다. + + +## 작업 수행 절차 + +모든 작업은 아래 순서를 따른다. +1. 관련 문서 확인 +2. 관련 코드 확인 +3. 기존 패턴 파악 +4. 최소 범위 수정 +5. 검증 수행 +6. 문서 갱신 +7. 결과 정리 + + +## 버전 관리 규칙 + +프로젝트 루트에 VERSION 파일을 유지한다. + +예시 +```text +v0.0.1 +``` + +작업 완료 후 커밋 시 반드시 버전을 증가시킨다. + +예시 +```text +v0.0.1 → v0.0.2 +v0.0.2 → v0.0.3 +``` + +버전 변경 시 반드시 갱신 +* VERSION +* docs/update.md +* docs/changelog.md (필요 시) + +Git 커밋 메시지는 반드시 한국어로 작성한다. + +예시 +```text +회원가입 검증 로직 개선 +게시물 관리 기능 추가 +``` + +민감 정보는 절대 커밋하지 않는다. + +예시 +* API Key +* 비밀키 +* 토큰 +* 비밀번호 +* 내부 서버 주소 +* 개인정보 + + +## Homepage API 규칙 + +서비스가 운영 단계에 진입하면 Homepage(getHomepage.dev) 연동용 API 제공을 고려한다. + +기본 엔드포인트 +```http +GET /api/homepage +``` + +기본 응답 예시 +```json +{ + "status": "online", + "version": "1.0.0", + "lastUpdated": "2026-06-23T00:00:00+09:00" +} +``` + +추가 데이터는 서비스 특성에 맞게 설계한다. + +예시 +* 방문자 수 +* 게시물 수 +* 회원 수 +* 주문 수 +* 활성 사용자 수 +* 서버 상태 +* 스토리지 사용량 +* 백업 상태 + +AI는 프로젝트 특성에 맞는 운영 지표를 제안해야 한다. +Homepage 위젯에서 사용할 수 있는 YAML 예시를 widget.md에 함께 작성한다. + + +## Docker 규칙 + +* Docker Compose 사용을 기본으로 한다. +* compose.yml 또는 docker-compose.yml 사용 +* 가능한 경우 서비스 분리 + +예시 +```text +app +db +redis +nginx +``` + +* 컨테이너 이름 +* 포트 +* 볼륨 +* URL + +은 환경 변수로 관리한다. + +배포 문서에는 실제 서버에서 사용하는 명령어를 기록한다. +sudo 사용 여부는 서버 환경을 따른다. + + +## 코드 작성 규칙 + +### 공통 + +* 기존 코드 스타일을 우선 따른다. +* 불필요한 리팩토링 금지 +* 요청받지 않은 대규모 구조 변경 금지 +* 작은 수정으로 해결 가능한 문제를 전면 재구성하지 않는다. +* 의미 없는 이름 사용 금지 +* 하드코딩 금지 + +--- + +### JavaScript + +* JavaScript 사용 시 JSDoc 작성 +* 함수 및 클래스는 문서화 주석 작성 + +예시 + +```javascript +/** + * 게시물 목록 조회 + * @param {Object} options 조회 옵션 + * @returns {Promise} + */ +``` + +--- + +### 스타일 + +* Tailwind CSS를 기본으로 사용한다. +* Tailwind Utility Class를 우선 사용한다. +* 단순 스타일을 위해 별도 CSS 파일을 생성하지 않는다. +* 반복되는 스타일은 컴포넌트화 또는 공통 클래스화한다. +* 의미 없는 클래스명 사용 금지 + +금지 예시 + +```text +box1 +box2 +temp +test +``` + +* 의미 있는 고유 클래스명을 사용한다. +* 전역 스타일 사용을 최소화한다. +* 다크모드를 고려한다. +* 기본 폰트는 Pretendard를 사용한다. + + +## 검증 규칙 + +가능한 범위 내에서 반드시 검증한다. + +예시 + +* 빌드 확인 +* API 동작 확인 +* 실패 케이스 확인 + +검증하지 못한 경우 명시한다. + + +## 작업 결과 보고 형식 + +반드시 아래 형식으로 보고한다. + +1. 수정 내용 +2. 변경 파일 +3. 실행 방법 +4. 빌드 방법 +5. 배포 방법 +6. 검증 결과 +7. 문서 반영 여부 +8. 남은 이슈 + + +## 최종 체크리스트 + +* docs 확인 여부 +* 코드 수정 완료 여부 +* 검증 수행 여부 +* VERSION 증가 여부 +* update.md 기록 여부 +* history.md 기록 여부 +* deploy.md 갱신 여부 +* widget.md 갱신 여부 +* Homepage API 갱신 여부 +* 문서 반영 여부 +* 민감 정보 포함 여부 +* Git 커밋 여부 +* Git Push 여부 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..82f24fd --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +v4.0.1 diff --git a/api/package.json b/api/package.json index ed78c5b..2c4d154 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "uadeck-api", - "version": "4.0.0", + "version": "4.0.1", "type": "module", "main": "src/index.js", "scripts": { diff --git a/api/src/index.js b/api/src/index.js index 0455be3..562dc41 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -29,7 +29,7 @@ app.use(cors({ origin: corsOrigins, credentials: true })); app.use(express.json({ limit: '20mb' })); app.get('/api/v1/health', (_req, res) => { - res.json({ ok: true, version: '4.0.0' }); + res.json({ ok: true, version: '4.0.1' }); }); app.use('/api/v1/auth', createAuthRouter(db)); @@ -47,7 +47,7 @@ app.use((err, _req, res, _next) => { }); app.listen(PORT, () => { - console.log(`UNIARE API v4.0.0 listening on :${PORT}`); + console.log(`UNIARE API v4.0.1 listening on :${PORT}`); console.log(`DB: ${DB_PATH}`); console.log(`DATAS: ${process.env.DATAS_PATH || '(default ../datas)'}`); }); diff --git a/docs/api-integration.md b/docs/api-integration.md index 70433f1..bfbfc48 100644 --- a/docs/api-integration.md +++ b/docs/api-integration.md @@ -143,7 +143,7 @@ const deck = await client.fetchDeck(code); const cards = deck.cards; ``` -## 프리셋(메타 덱) 목록 +## 공개 덱 목록 ```http GET /api/v1/decks/presets @@ -163,7 +163,9 @@ GET /api/v1/decks/presets } ``` -프리셋도 동일하게 `GET /decks/:shareCode`로 상세 조회합니다. +엔드포인트 이름은 레거시 호환을 위해 `presets`를 유지하지만, 현재 응답 대상은 관리자가 등록한 프리셋이 아니라 사용자가 공개한 미만료 덱입니다. + +상세 조회는 동일하게 `GET /decks/:shareCode`를 사용합니다. ## 에러 코드 diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..e906cf5 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,37 @@ +# 변경사항 + +## v4.0.1 + +### 프로젝트 운영 문서 정비 + +기능 동작은 변경하지 않고 개발·배포 문서를 현재 서비스 구조에 맞게 정리했습니다. + +로컬 개발과 NAS 운영, 카드 이미지·데이터 업데이트, 백업·복구 절차를 구분해 이후 업데이트를 더 안전하게 진행할 수 있습니다. + +## v4.0.0 + +### 회원 계정과 내 덱 관리 + +회원가입과 로그인을 지원합니다. 프로필에서 사용자명, 이메일, 프로필 이미지, 비밀번호와 덱 공개 기본값을 관리할 수 있습니다. + +완성한 덱은 결과 화면의 `내 덱으로 저장` 버튼으로 저장하고, `내 덱` 화면에서 이름 변경, 공개 전환, 삭제, 다시 불러오기를 할 수 있습니다. + +### 짧은 공유 링크 + +긴 카드 데이터 URL 대신 `/d/공유코드` 형식의 링크를 사용합니다. 공개 덱은 링크를 받은 누구나 불러올 수 있으며 비공개 덱은 소유자와 관리자만 볼 수 있습니다. + +### 공개 덱 탐색과 즐겨찾기 + +`공개 덱` 화면에서 다른 사용자가 공개한 덱을 검색하고 시리즈별로 필터링할 수 있습니다. 썸네일을 길게 눌러 크게 보고, 자주 확인하는 덱은 즐겨찾기에 보관할 수 있습니다. + +### 덱 이미지 저장 개선 + +저장되는 덱 미리보기 크기를 1200px 기준으로 통일했습니다. 브라우저별로 썸네일 크기가 달라지거나 큰 이미지 저장이 실패하던 문제를 줄였습니다. + +### 다국어 화면 확대 + +로그인, 회원가입, 프로필, 내 덱, 공개 덱과 주요 저장 안내를 한국어, 일본어, 영어로 제공합니다. + +### UNIARE 브랜드 적용 + +서비스 표시 이름과 헤더 로고를 UNIARE로 통일하고, 페이지 이동 시 헤더와 본문 너비가 안정적으로 유지되도록 개선했습니다. diff --git a/docs/codex-handoff.md b/docs/codex-handoff.md index a84d34a..532bc4e 100644 --- a/docs/codex-handoff.md +++ b/docs/codex-handoff.md @@ -1,61 +1,71 @@ -# Codex Handoff Guide +# 작업 인계 가이드 -## 목적 -- 이 문서는 다른 작업 환경이나 다른 Codex 세션에서 프로젝트를 이어받더라도 같은 품질 기준으로 작업하기 위한 운영 기준서다. -- 특히 이 저장소는 정적 HTML/JS 프로젝트이면서 카드 데이터와 이미지 자산 비중이 크기 때문에, "코드 수정"보다 "데이터-번역-이미지 일치"가 더 중요하다. +## Handoff(핸드오프)란? +다른 개발자나 운영 담당자가 지금 작업을 이어받을 때 빠르게 상황을 이해하도록 정리한 문서입니다. 비개발자에게는 '작업 인수인계 노트'라고 생각하면 됩니다. -## 현재 기준 릴리즈 -- 현재 작업 기준 버전: `v4.0.0` -- 이번 릴리즈 핵심 반영: -- 회원·인증·덱 API (`api/`, Docker) -- 공유 코드 기반 덱 공유 (`/d/:code`, `GET /api/v1/decks/:shareCode`) -- 관리자 화면 (`admin.html`) -- `?cards=` URL 방식 폐기 +## 우선 확인 -## 다음 작업자에게 반드시 전달할 것 -- 이 프로젝트는 빌드 시스템 중심 앱이 아니라 `index.html`, `cardList.html`, `result.html` 3개 페이지가 직접 동작하는 정적 사이트다. -- 신규 시리즈를 추가할 때는 항상 아래 3가지를 한 세트로 본다. -- `datas/.js` -- `images//` -- `i18n/translations.ko.json`, `i18n/translations.ja.json`, `i18n/translations.en.json` -- 이미지 자산은 `images/**` 전체가 `Git LFS` 대상이다. 대용량 이미지가 일반 Git 객체로 들어가지 않게 주의한다. -- `cardList.html`은 `?series=` 값을 기준으로 `./datas/${seriesKey}.js`를 동적 import 한다. 즉, 시리즈 키 불일치가 있으면 화면에서 바로 깨진다. -- 카드 이미지 렌더링은 최종적으로 `images/${seriesKey}/${fileName}` 규칙을 사용하므로, `imgSrc`에는 파일명 기준 일관성이 중요하다. -- 릴리즈 버전이 바뀌면 최소한 아래 두 곳은 함께 맞춘다. -- `index.html`의 `` 및 상단 배지 -- `docs/spec.md`, `docs/update.md`, 필요 시 `docs/history.md` +1. 프로젝트 루트 `AGENTS.md` +2. `docs/spec.md` +3. `docs/todo.md` +4. `docs/history.md` +5. `docs/convention.md` +6. `docs/map.md` +7. `docs/deploy.md` -## 품질 기준 -- 신규 시리즈 반영 시 데이터 파일만 추가하고 번역 키를 빼먹지 않는다. -- 카드 번호(`Number`) 그룹과 실제 파일명 규칙이 시리즈 키와 일치해야 한다. -- 병렬 카드(`parallel: true`)는 일반 카드와 같은 카드 번호를 공유하되 이미지 파일만 다르게 유지한다. -- `trigger`는 `null`, `COLOR`, `FINAL`, `SPECIAL`만 사용한다. -- AP 카드의 `color`는 `AP`를 사용한다. -- 릴리즈 전에 `git diff --stat`으로 변경 범위를 보고, 데이터/번역/이미지 중 빠진 축이 없는지 확인한다. +현재 버전은 루트 `VERSION`을 기준으로 확인한다. -## 작업 절차 -1. 변경 요청이 신규 시리즈 추가인지, 기존 시리즈 수정인지 먼저 구분한다. -2. 관련 `datas/*.js`와 `images/<series>/` 상태를 확인한다. -3. `i18n/translations.*.json`에 시리즈명 또는 UI 문구 변경이 필요한지 확인한다. -4. 화면에서 버전 표기를 바꿔야 하는 성격의 변경인지 판단한다. -5. `docs/update.md`에 이번 반영 내용을 남긴다. -6. 릴리즈성 변경이면 `docs/history.md` 또는 이 문서에도 기준 변화를 남긴다. -7. 커밋 전 `git status`, `git diff --stat`, 필요한 경우 간단한 구문 체크를 수행한다. +## 프로젝트 핵심 -## 릴리즈 전 체크리스트 -- `datas/<series>.js`가 실제로 import 가능한 형식인지 확인 -- `images/<series>/` 파일이 데이터의 `imgSrc`와 대응되는지 확인 -- `translations.ko/ja/en`에 시리즈 키가 모두 존재하는지 확인 -- 버전 표기가 문서와 UI에서 일치하는지 확인 -- `images/**`가 계속 LFS로 잡히는지 확인 -- 커밋 메시지에 릴리즈 의도가 드러나는지 확인 +- 정적 HTML·JavaScript 프론트와 Express·SQLite API를 함께 사용한다. +- 운영은 `docker-compose.prod.yml`의 `uniare-web`, `uniare-api` 구성이 기본이다. +- 덱 공유는 `/d/<shareCode>`와 `GET /api/v1/decks/:shareCode`를 사용한다. +- `?cards=<JSON>` 공유 방식은 사용하지 않는다. +- 표시 브랜드는 UNIARE지만 `UaDeckApi`, `uadeck_token`, `uadeck.db` 등 내부 식별자는 호환을 위해 유지한다. -## 커밋 메시지 가이드 -- 릴리즈 커밋 예시: `v3.1.0: add ua51 series and handoff docs` -- 수정 커밋 예시: `fix: add missing ua49 parallel images` -- 되돌림 커밋 예시: `revert: restore original card list interactions` +## 카드 데이터 작업 -## 주의할 점 -- 이 저장소는 이미지 비중이 매우 크다. 원격 업로드 전에 대용량 변경이 의도된 것인지 다시 확인한다. -- 현재 문서 체계는 `docs/update.md`가 작업 로그, `docs/history.md`가 의사결정, `docs/spec.md`가 구조 명세 역할을 맡는다. -- 후속 작업자가 빠르게 맥락을 잡게 하려면, "무엇을 바꿨는지"보다 "왜 같이 바꿔야 하는지"를 문서에 남기는 편이 더 중요하다. +신규 시리즈 또는 카드는 아래 항목을 한 세트로 확인한다. + +```text +datas/<series>.js +images/<series>/ +i18n/translations.ko.json +i18n/translations.ja.json +i18n/translations.en.json +``` + +- `imgSrc`와 실제 이미지 파일명이 일치해야 한다. +- 동일 카드의 패러렐 판본은 같은 `Number`를 사용한다. +- `images/**`는 Git LFS 대상이다. +- `datas/` 변경 후 API 카드 캐시를 갱신하려면 `uniare-api`를 재시작한다. + +## 변경 후 문서 + +- 현재 구현 변경: `spec.md` +- 이번 작업 기록: `update.md` +- 결정 이유: `history.md` +- 남은 작업: `todo.md` +- 사용자 공개 변경: `changelog.md` +- 파일 역할 변경: `map.md` +- 배포 절차 변경: `deploy.md` + +## 검증 + +```bash +git diff --check +git status --short +``` + +변경 성격에 따라 다음도 확인한다. + +- HTML·정적 자산: 브라우저 새로고침 +- `datas/`: API 재시작 후 카드 목록과 저장 덱 조회 +- API 코드: Docker 재빌드와 `/api/v1/health` +- Tailwind 클래스: `npm run build:css` + +## 커밋 + +- 커밋 메시지는 한국어로 작성한다. +- 커밋하는 완료 작업은 `VERSION`을 증가시킨다. +- 민감 정보와 `api/data/` 운영 파일을 커밋하지 않는다. diff --git a/docs/convention.md b/docs/convention.md index 5589587..39e9eb2 100644 --- a/docs/convention.md +++ b/docs/convention.md @@ -1,46 +1,66 @@ -# 코딩 컨벤션 +# 프로젝트 컨벤션 -## 기본 원칙 -- 모든 문서와 커뮤니케이션은 한국어 기준으로 유지한다. -- 코드 주석은 필요 시 `JSDoc` 형식을 우선한다. -- 기존 정적 사이트 구조를 존중하고, 대규모 구조 변경 시에는 먼저 `docs/history.md`에 의사결정을 기록한다. +## 공통 -## 파일 및 폴더 규칙 -- 시리즈 데이터 파일은 `datas/uaXX.js`, `datas/uapr.js`처럼 시리즈 키와 일치하게 관리한다. -- 이미지 폴더는 `images/<seriesKey>/` 규칙을 유지한다. -- 새 문서는 `docs/` 하위에서 목적별 단일 파일에 누적 관리한다. +- 모든 문서와 커뮤니케이션은 한국어로 작성한다. +- 실제 코드와 문서가 다르면 코드를 기준으로 문서를 갱신한다. +- 요청 범위를 넘는 대규모 리팩터링을 하지 않는다. +- 운영 URL, 비밀번호, 토큰, DB 정보는 코드에 하드코딩하지 않는다. +- JavaScript 함수와 클래스에는 필요한 JSDoc을 작성한다. -## JavaScript 규칙 -- 기존 코드 스타일에 맞춰 기본 들여쓰기는 4칸을 사용한다. -- 상수는 `const`, 재할당 변수는 `let`을 사용한다. -- DOM 참조가 반복될 경우 `getEl()`처럼 묶어서 관리하는 패턴을 우선 고려한다. -- URL 파라미터, `localStorage`, 동적 import는 예외 처리와 함께 사용한다. -- 카드 데이터 객체의 필드명은 기존 호환성을 위해 현재 형태(`imgSrc`, `Number`, `count`, `key`)를 유지한다. +## 버전 -## HTML 규칙 -- 정적 페이지는 의미 단위별로 `header`, `main`, `aside` 등을 우선 사용한다. -- 페이지별 스크립트가 길어질 경우 신규 기능은 별도 JS 파일 분리 여부를 먼저 검토한다. -- 인라인 이벤트(`onclick`)는 기존 파일에 이미 존재하므로 즉시 제거 대상은 아니지만, 신규 대형 기능은 `addEventListener` 방식 우선 검토한다. - -## CSS 규칙 -- 공통 UI는 가능한 한 Tailwind 유틸리티 클래스를 우선 사용한다. -- **헤더 로고**는 `style/brand.css`의 `.app-brand-logo` + `--brand-logo-height`(32px)만 사용한다. 페이지마다 `h-8` 등 Tailwind로 크기를 주지 않는다. -- `style/output.css`는 **빌드 산출물**이다. HTML 등에 새 Tailwind 클래스를 추가한 뒤 `npm run build:css`로 재생성한다. -- `style/output.css`에 포함되지 않은 유틸리티 클래스는 적용되지 않으므로, 빌드 전까지는 페이지 특화 스타일을 인라인 `<style>` 또는 전용 CSS 파일(`style/auth-pages.css` 등)에 명시한다. -- 페이지 특화 보정 스타일만 인라인 `<style>` 또는 전용 CSS 파일에 둔다. -- 캡처 안정성처럼 이유가 분명한 예외 스타일은 주석으로 목적을 남긴다. - -## 데이터 관리 규칙 -- 카드 데이터 추가 시 최소 필드는 `imgSrc`, `Number`, `count`, `key`, `color`, `parallel`, `rarity`, `trigger`를 맞춘다. -- 동일 카드의 패러렐 판본은 동일 `Number`를 유지하고 `parallel` 값으로 구분한다. -- 파일명 변경 시 `datas/*.js`와 `images/<seriesKey>/`가 함께 일치하는지 확인한다. - -## Git 규칙 +- 현재 버전은 프로젝트 루트 `VERSION`을 기준으로 한다. +- 커밋을 포함하는 완료 작업은 버전을 증가시키고 `VERSION`, `docs/update.md`, 필요 시 `docs/changelog.md`를 함께 갱신한다. - 커밋 메시지는 한국어로 작성한다. -- 버전 상승 작업은 `docs/update.md` 반영 후 태그명과 맞춘다. -- 민감 정보, 로컬 경로, 개인 식별 정보가 커밋되지 않도록 확인한다. -## 이미지 자산 규칙 -- `images/**`는 `Git LFS`로 관리한다. -- 이미지 파일을 새로 추가하거나 교체할 때는 일반 Git 객체로 들어가지 않도록 `.gitattributes` 설정을 유지한다. -- 대량 이미지 추가 전에는 원격 저장소의 LFS 정책과 비용 한도를 확인한다. +## 카드 데이터 + +- 시리즈 파일은 `datas/uaXX.js` 또는 `datas/uapr.js` 형식을 사용한다. +- 카드 객체의 호환 필드 `imgSrc`, `Number`, `count`, `key` 이름은 변경하지 않는다. +- 동일 카드의 패러렐 판본은 같은 `Number`를 사용하고 `parallel`로 구분한다. +- 신규 시리즈는 데이터, 이미지, 한국어·일본어·영어 번역을 한 작업 단위로 검증한다. +- 이미지 파일명 변경 시 관련 `datas/*.js`의 `imgSrc`도 함께 갱신한다. + +## 프론트 + +- 현재 정적 HTML/JavaScript 구조를 유지한다. +- 신규 대형 기능은 페이지 인라인 스크립트보다 `script/` 모듈 분리를 우선 검토한다. +- URL 파라미터, 동적 import, `localStorage` 접근에는 실패 처리를 둔다. +- 기존 인라인 이벤트는 즉시 전면 교체하지 않으며 신규 기능은 `addEventListener`를 우선한다. + +## 스타일 + +- 현재 CSS를 Tailwind로 일괄 전환하지 않는다. +- 새 단순 스타일은 Tailwind 유틸리티를 우선 검토하되 기존 페이지 패턴과 빌드 여부를 함께 확인한다. +- 반복되는 공통 스타일은 공통 CSS 또는 컴포넌트 역할의 클래스로 분리한다. +- `style/output.css`를 직접 편집하지 않고 `npm run build:css`로 생성한다. +- 헤더 로고는 `style/brand.css`의 `.app-brand-logo`만 사용한다. +- 다크모드와 Pretendard 계열 기본 글꼴을 고려한다. + +## API·DB + +- API 베이스 경로는 `/api/v1`을 유지한다. +- 운영 설정은 `.env`로 관리하며 `.env.example`에는 예시값만 둔다. +- 로컬 DB와 운영 DB를 같은 파일 또는 볼륨으로 공유하지 않는다. +- DB에는 최소 카드 정보만 저장하고 카드 메타는 `datas/`에서 합성한다. +- 권한 검사는 화면뿐 아니라 API에서도 수행한다. + +## 이미지·Git + +- `images/**`는 Git LFS로 관리한다. +- `api/data/`의 DB, 아바타, 덱 미리보기는 커밋하지 않는다. +- 대량 이미지 추가 전 변경 수와 LFS 추적 여부를 확인한다. +- 민감 정보와 개인 로컬 경로가 diff에 포함되지 않았는지 확인한다. + +## 문서 + +- `spec.md`: 현재 구현 상태 +- `update.md`: 개발자 작업 로그 +- `history.md`: 유지할 의사결정과 이유 +- `todo.md`: 미완료 작업만 +- `deploy.md`: 실행·배포·롤백 +- `widget.md`: 외부 서비스 연동 +- `map.md`: 파일과 기능 연결 +- `changelog.md`: 사용자 공개 변경사항 +- `convention.md`: 반복 적용 규칙 diff --git a/docs/deploy.md b/docs/deploy.md index 5feea79..d800cb3 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,195 +1,180 @@ -# NAS 배포 가이드 (UNIARE v4) +# UNIARE 실행·배포 가이드 -## 1. 사전 준비 +필요한 부분만 골라서 읽으면 된다. -- Ugreen NAS에 Docker(또는 Container Manager) 설치 -- 프로젝트를 NAS에 클론 또는 동기화 -- `Git LFS`로 이미지 자산 pull 완료 확인 +1. 내 컴퓨터에서 실행: `1. 로컬 실행` +2. NAS에 처음 설치: `2. NAS 최초 배포` +3. 이미지나 카드 데이터 반영: `3. 정적 파일 업데이트` +4. API나 Docker 설정 반영: `4. 전체 업데이트` -## 2. 환경변수 설정 +로컬 주소는 `http://localhost:44082`, 운영 주소는 `https://uniare.sori.studio`이다. +Git 저장소는 `https://git.sori.studio/zenn/union-arena-deck-builder.git`이다. + +## 시작 전 확인 + +- 명령은 프로젝트 폴더에서 실행한다. +- 로컬과 NAS는 각각 다른 `.env`와 DB를 사용한다. +- `.env`, DB, 비밀번호는 Git에 올리지 않는다. +- 카드 이미지는 Git LFS로 관리한다. +- 로컬 위치는 `<LOCAL_PROJECT_DIR>/uniare.sori.studio`, NAS 위치는 `<NAS_PROJECT_DIR>/uniare.sori.studio`로 표기한다. + +## 1. 로컬 실행 + +최초 한 번 프로젝트 폴더에서 `cp .env.example .env`를 실행한다. + +`.env`에서 다음 값을 변경한다. + +```env +JWT_SECRET=32자_이상의_랜덤_문자열 +ADMIN_EMAIL=관리자_이메일 +ADMIN_PASSWORD=8자_이상의_비밀번호 +WEB_PORT=44082 +``` + +랜덤 문자열은 `openssl rand -hex 32`로 만들 수 있다. + +실행은 `npm start`, 종료는 `npm stop`, 로그 확인은 `npm run logs`를 사용한다. +실행 후 브라우저에서 `http://localhost:44082`에 접속한다. + +## 2. NAS 최초 배포 + +NAS에 Git, Git LFS, Docker, Docker Compose가 설치되어 있어야 한다. + +저장소를 받는다. ```bash +cd <NAS_PROJECT_DIR> +git clone https://git.sori.studio/zenn/union-arena-deck-builder.git uniare.sori.studio +cd uniare.sori.studio +git lfs pull cp .env.example .env ``` -| 변수 | 설명 | -|---|---| -| `JWT_SECRET` | 32자 이상 랜덤 문자열 (필수) | -| `ADMIN_EMAIL` | 최초 관리자 이메일 | -| `ADMIN_PASSWORD` | 최초 관리자 비밀번호 (8자+) | -| `WEB_PORT` | 웹 노출 포트 (기본 `44082`, 기존 정적 사이트 포트 유지) | -| `CORS_ORIGINS` | 옆 프로젝트 도메인 포함, 쉼표 구분 | - -예시: +`.env`에서 다음 값을 운영 환경에 맞게 변경한다. ```env -JWT_SECRET=your-random-secret-at-least-32-chars -ADMIN_EMAIL=admin@uniare.sori.studio -ADMIN_PASSWORD=your-secure-password +JWT_SECRET=32자_이상의_랜덤_문자열 +ADMIN_EMAIL=관리자_이메일 +ADMIN_PASSWORD=8자_이상의_비밀번호 +ADMIN_USERNAME=admin WEB_PORT=44082 -CORS_ORIGINS=https://uniare.sori.studio,https://your-other-app.sori.studio,http://localhost:44082,http://127.0.0.1:44082,http://localhost:5500,http://127.0.0.1:5500 +CORS_ORIGINS=https://uniare.sori.studio ``` -## 3. 로컬 테스트 +Docker 실행: `docker compose -f docker-compose.prod.yml up -d --build` -> **한 줄 요약:** 프로젝트 루트에서 `npm start` → 브라우저 **http://localhost:44082** +상태 확인: `docker compose -f docker-compose.prod.yml ps` -회원가입·로그인·덱 저장은 **API 서버**가 떠 있어야 합니다. -HTML만 Live Server 등으로 열면 저장·로그인이 **405** 로 실패합니다. +API 확인: `curl -fsS http://localhost:44082/api/v1/health` -### 빠른 시작 (권장) - -```bash -npm start # 백그라운드 실행 (터미널 점유 안 함) -# 브라우저: http://localhost:44082 - -npm run logs # 로그 보기 (종료: Ctrl+C) -npm stop # 서버 종료 -``` - -| 명령 | 설명 | -|---|---| -| `npm start` | Docker로 웹+API 백그라운드 기동 (**일상 개발용**) | -| `npm stop` | 서버 종료 | -| `npm run logs` | API·Nginx 로그 스트리밍 | -| `npm run dev` | 포그라운드 실행 (로그 붙어 있음, `Ctrl+C`로 **서버도 종료**) | -| `npm run api:dev` | API만 Node로 실행 (Live Server 병행 시) | - -### 저장이 안 될 때 - -1. `curl http://localhost:44082/api/v1/health` → `{"ok":true,...}` 가 나와야 함 -2. 안 나오면 `npm start` 다시 실행 -3. `npm run dev` 후 `Healthy`에서 멈춘 것처럼 보여도 **정상**이지만, `Ctrl+C`하면 서버가 꺼짐 → **`npm start` 사용 권장** -4. Live Server(`:5500`)로 HTML만 연 경우 → 아래 방법 B 또는 **반드시 `:44082` 사용** - -### 방법 A — Docker (`npm start`와 동일) - -```bash -npm start -``` - -브라우저: **http://localhost:44082** (Live Server 포트가 아님) - -### 방법 B — API만 로컬 실행 (Live Server / Cursor 미리보기 사용 시) - -터미널 1 — API: - -```bash -cd api && npm install # 최초 1회 -cd .. && npm run api:dev -``` - -터미널 2 — HTML은 Live Server 등으로 열기 (예: `:5500`) - -`.env`의 `CORS_ORIGINS`에 Live Server 포트를 추가하세요: - -```env -CORS_ORIGINS=...,http://localhost:5500,http://127.0.0.1:5500 -``` - -확인: - -```bash -curl http://localhost:3000/api/v1/health -# → {"ok":true,"version":"4.0.0"} -``` - -### 405 오류가 나올 때 - -| 원인 | 해결 | -|---|---| -| API 미실행 | `npm start` (또는 `npm run dev`) | -| Live Server로 HTML만 연 경우 | POST `/api/v1/...`가 정적 서버로 가서 405 발생 → 방법 A 또는 B 사용 | -| Docker 미기동 | `docker ps`로 `uniare-api`, `uniare-web` 확인 | - -## 4. Docker 기동 (NAS / 프로덕션) - -```bash -# 프로덕션 스택 (Nginx + API) -docker compose -f docker-compose.prod.yml up -d --build - -# API만 단독 (기존 웹서버가 정적 파일 서빙 중일 때) -docker compose up -d --build -``` - -확인: - -```bash -curl http://localhost:44082/api/v1/health -# → {"ok":true,"version":"4.0.0"} -``` - -## 5. 기존 리버스 프록시 연동 - -이미 NAS에서 Nginx Proxy Manager, Traefik, Caddy 등을 쓰는 경우: - -### 옵션 A — `uniare-web` 포트만 프록시 (권장) - -``` -https://uniare.sori.studio → http://NAS_IP:44082 -``` - -`uniare-web` 컨테이너가 `/api/`와 정적 파일을 모두 처리합니다. - -`docker-compose.prod.yml`은 프로젝트 루트 전체를 웹 루트로 열지 않고, 필요한 HTML·정적 폴더만 read-only로 마운트합니다. -따라서 `.env`, `.git`, `api/`, `docs/` 같은 내부 파일은 Nginx에서 직접 노출되지 않습니다. - -### 옵션 B — 기존 정적 호스팅 + API만 Docker - -1. `docker compose up -d` (API만, 포트 3000) -2. 기존 웹서버에 `nginx/uadeck.conf` 규칙 추가: - -```nginx -location ~ ^/d/([A-Za-z0-9]+)$ { - return 302 /cardList.html?d=$1; -} -location /api/ { - proxy_pass http://127.0.0.1:3000; - # proxy_set_header ... (uadeck.conf 참고) -} -``` - -## 6. 최초 관리자 로그인 - -1. `https://uniare.sori.studio/login.html` 접속 -2. `.env`의 `ADMIN_EMAIL` 계정으로 로그인 - - API 최초 기동 시 해당 이메일이 없으면 `ADMIN_PASSWORD`로 계정이 자동 생성된다. - - **이미 `ADMIN_EMAIL`로 회원가입한 경우** API 재시작 시 해당 계정이 `admin`으로 승격된다. (가입 시 설정한 비밀번호 그대로 사용) -3. 프로필 메뉴 → **관리자** → `/admin.html` - -> 관리자 메뉴가 안 보이면 **로그아웃 후 다시 로그인**하세요. 브라우저에 이전 `role`이 캐시되어 있을 수 있습니다. - -## 7. 운영 명령 - -```bash -# 로그 -docker compose -f docker-compose.prod.yml logs -f uniare-api - -# 재시작 -docker compose -f docker-compose.prod.yml restart - -# DB 백업 (볼륨 내 SQLite) -docker cp uniare-api:/data/uadeck.db ./backup/uadeck-$(date +%Y%m%d).db -``` - -## 8. 카드/이미지 업데이트 - -`datas/`, `images/`, HTML 파일은 **허용된 경로만 read-only 볼륨 마운트**이므로: - -1. Git pull (또는 파일 동기화) -2. **재빌드 불필요** — 브라우저 새로고침만으로 반영 - -API 컨테이너는 `datas/`를 read-only 마운트하므로 새 시리즈 파일 추가 후 API 재시작만 하면 됩니다: +정상이면 `{"ok":true,"version":"4.0.1"}`이 표시된다. + +NAS 리버스 프록시는 `https://uniare.sori.studio`을 `http://<NAS_IP>:44082`로 연결한다. + +## 3. 정적 파일 업데이트 + +이미지, 카드 데이터, HTML, CSS, 프론트 JavaScript는 Docker 이미지를 다시 빌드하지 않아도 된다. + +### 이미지만 변경 + +NAS 프로젝트 폴더에서 `git pull --ff-only`와 `git lfs pull`을 실행한 뒤 브라우저를 강력 새로고침한다. 컨테이너 재시작은 필요 없다. + +### `datas/*.js` 변경 + +NAS 프로젝트 폴더에서 다음 명령을 실행한다. ```bash +git pull --ff-only +git lfs pull docker compose -f docker-compose.prod.yml restart uniare-api ``` -## 9. 트러블슈팅 +API가 카드 데이터를 메모리에 저장하므로 `datas/` 변경 시 API만 재시작한다. 이미지와 `datas/*.js`를 함께 변경한 경우도 같은 절차를 사용한다. -| 증상 | 확인 | -|---|---| -| 로그인 안 됨 | `JWT_SECRET` 설정, API health 확인 | -| 공유 링크 404 | Nginx `/d/` 리다이렉트 규칙 | -| CORS 오류 | `CORS_ORIGINS`에 옆 프로젝트 도메인 추가 | -| 덱 카드 메타 누락 | `datas/<series>.js` 존재 여부, API `DATAS_PATH` | +확인할 내용: + +- `imgSrc`와 실제 이미지 파일명이 같은가? +- 파일명 대소문자가 같은가? +- 신규 시리즈가 번역 파일 3개에 등록되어 있는가? +- 카드 목록과 저장 덱에서 모두 정상 표시되는가? + +### HTML·일반 CSS·프론트 JavaScript 변경 + +`git pull --ff-only`와 `git lfs pull`을 실행하고 브라우저를 강력 새로고침한다. 컨테이너 재시작은 필요 없다. + +## 4. 전체 업데이트 + +`api/`, `nginx/`, Dockerfile, Compose 설정을 변경했다면 NAS 프로젝트 폴더에서 다음 명령을 실행한다. + +```bash +git pull --ff-only +git lfs pull +docker compose -f docker-compose.prod.yml up -d --build +``` + +상태 확인은 `docker compose -f docker-compose.prod.yml ps`, API 확인은 `curl -fsS http://localhost:44082/api/v1/health`를 사용한다. + +`.env`를 변경했다면 `docker compose -f docker-compose.prod.yml up -d --force-recreate`로 컨테이너를 재생성한다. + +## 5. CSS 빌드 + +Tailwind 클래스를 새로 추가했을 때만 로컬에서 `npm install`과 `npm run build:css`를 실행한다. +생성된 `style/output.css`도 Git에 포함한다. +이미지, 카드 데이터, 기존 CSS만 수정했다면 실행하지 않아도 된다. + +## 6. 자주 쓰는 운영 명령 + +- 전체 재시작: `docker compose -f docker-compose.prod.yml restart` +- API만 재시작: `docker compose -f docker-compose.prod.yml restart uniare-api` +- 로그 확인: `docker compose -f docker-compose.prod.yml logs -f` +- 종료: `docker compose -f docker-compose.prod.yml down` +- 다시 실행: `docker compose -f docker-compose.prod.yml up -d` + +## 7. 백업 + +DB는 Docker 볼륨의 `/data/uadeck.db`에 있다. +덱 미리보기와 아바타는 `api/data/previews/`, `api/data/avatars/`에 있다. + +DB 백업: + +```bash +mkdir -p backup +BACKUP_FILE="uadeck-$(date +%Y%m%d-%H%M%S).db" +docker exec uniare-api node --input-type=module -e "import Database from 'better-sqlite3'; const db = new Database('/data/uadeck.db'); await db.backup('/data/$BACKUP_FILE'); db.close();" +docker cp "uniare-api:/data/$BACKUP_FILE" "./backup/$BACKUP_FILE" +docker exec uniare-api rm "/data/$BACKUP_FILE" +``` + +미리보기와 아바타 백업: `tar -czf "./backup/uploads-$(date +%Y%m%d-%H%M%S).tar.gz" api/data/previews api/data/avatars` + +`.env`는 Git이 아닌 접근 제한된 별도 위치에 보관한다. + +## 8. 롤백 + +문제가 발생하면 DB를 먼저 백업하고 `git log --oneline -10`으로 이전 정상 커밋을 확인한다. + +```bash +git checkout <정상_커밋_해시> +git lfs pull +docker compose -f docker-compose.prod.yml up -d --build +``` + +API 확인: `curl -fsS http://localhost:44082/api/v1/health` + +최신 `main`으로 돌아갈 때는 `git checkout main`과 `git pull --ff-only`를 실행한다. + +## 9. 문제가 생겼을 때 + +웹이 열리지 않으면 `docker compose -f docker-compose.prod.yml ps`와 `docker compose -f docker-compose.prod.yml logs --tail=100`을 확인한다. + +로그인이나 저장이 안 되면 `curl -fsS http://localhost:44082/api/v1/health`로 API 상태를 확인한다. + +카드 이미지가 보이지 않으면 `git lfs pull`을 실행하고 `datas/*.js`의 `imgSrc`와 실제 이미지 파일명을 비교한다. + +## 완료 확인 + +- `uniare-web`, `uniare-api`가 실행 중인가? +- API가 `ok: true`를 반환하는가? +- 홈과 카드 이미지가 열리는가? +- 로그인과 덱 저장이 동작하는가? +- `.env`와 DB가 Git에 포함되지 않았는가? diff --git a/docs/history.md b/docs/history.md index bcf3b34..d9db48a 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,82 +1,121 @@ # 의사결정 이력 -## 2026-06-22 / NAS 운영 네이밍 정리 (`uniare-*`) -- 배경: NAS에 기존 `uniare-web` 컨테이너와 신규 `uadeck-web` 컨테이너가 공존하면서 포트 충돌 및 식별 혼선이 발생했다. -- 결정: -- Docker Compose 서비스/컨테이너 명칭을 프로젝트 식별자 기준 `uniare-*`로 통일한다. -- Nginx 내부 프록시 대상도 서비스명 변경에 맞춰 `uniare-api`로 함께 정렬한다. -- 이유: -- 운영 시 `docker ps`, 로그 확인, 재시작 명령에서 컨테이너 목적을 즉시 식별하기 위함. -- 기존 운영 스택(`uniare`)과 명칭 일관성을 맞춰 재배포 실수를 줄이기 위함. +## 2026-06-23 v4.0.0 — SQLite 백업은 온라인 백업 API를 사용 -## 2026-06-19 / v4.0.0 구현 완료 -- 배경: 설계 확정 후 회원·API·공유 시스템 1차 구현. -- 반영: `api/` 백엔드, Docker Compose, 인증/덱/관리자 API, 프론트 신규 페이지 및 헤더 프로필 UI. -- 배포: NAS Docker + Nginx `/api/`, `/d/` 프록시 필요. +💡 WAL 모드에서 DB 본파일만 복사하면 아직 병합되지 않은 최신 트랜잭션이 누락될 수 있기 때문. +🔧 운영 중에는 `better-sqlite3`의 `backup()`으로 일관된 백업본을 만든 뒤 컨테이너 밖으로 복사. -## 2026-06-19 / v4.0.0 덱 공유·API 설계 확정 -- 배경: v3는 덱을 `?cards=<JSON>` URL에 실어 공유했으나 URL이 수천 자로 비대해지고, 옆 프로젝트도 개발 중이라 계약을 새로 잡을 수 있는 시점이다. -- 결정: -- URL 쿼리 기반 덱 공유는 **폐기**하고, 공유 코드 + REST API 방식으로 전환한다. -- **개인 덱**(`share_type=user`): 저장할 때마다 nanoid 8자 고유 코드 발급. 같은 카드 구성이라도 저장자마다 URL이 다르다. -- **메타 덱·프리셋**(`share_type=preset`): 카드 구성 해시 기반 고정 코드. 동일 구성이면 항상 같은 URL. -- DB에는 `{number, count, key}`만 저장하고, API 응답 시 `datas/`에서 카드 메타를 lookup하여 합성한다. -- 작성자, 생성일, 수정일, 조회수, 공개 여부 등 메타데이터를 덱 레코드와 함께 관리한다. -- 외부 연동(옆 프로젝트) 공식 계약: `GET /api/v1/decks/:shareCode` -- 프론트는 Vue 전환 없이 정적 HTML + API 호출 + Docker 백엔드로 확장한다. -- 이유: -- URL 길이·중복 데이터 문제 해소. -- 회원 덱 소유권·수정·비공개 설정이 자연스럽다. -- DB 용량은 덱 본문 기준 건당 수 KB 수준으로 경미하다. +## 2026-06-23 v4.0.0 — 헤더 로고 규격을 공통 스타일로 관리 -## 2026-04-19 / v3.1.0 릴리즈 기준 확정 -- 배경: 저장소의 마지막 명시 릴리즈 기록은 `v3.0.1`인데, 메인 화면은 이미 `v3.1`을 노출하고 있어 실제 릴리즈 이력과 화면 표기가 어긋나 있었다. -- 결정: -- 이번 반영분을 `v3.1.0`으로 정의하고, 화면 표기와 문서 표기를 같은 버전으로 통일한다. -- 이후 버전 증가는 `docs/update.md`에 먼저 기록한 뒤 화면 표기와 함께 반영한다. -- 이유: -- 외부 협업자나 후속 Codex 작업자가 "현재 배포 기준이 무엇인지" 즉시 판단할 수 있어야 한다. -- 자산 중심 정적 프로젝트 특성상 코드보다 데이터/이미지 변경이 잦기 때문에, 명시 릴리즈 기준이 없으면 누락 여부를 추적하기 어렵다. +💡 페이지마다 로고 크기를 따로 지정하면 화면 이동 시 높이가 달라지고 수정 지점도 늘어나기 때문. +🔧 `style/brand.css`의 `.app-brand-logo`와 `--brand-logo-height`를 모든 헤더의 단일 기준으로 채택. -## 2026-03-30 / 문서 체계 초기화 -- 배경: 프로젝트 루트에 AI 작업 규칙은 있었지만 `docs/` 실체가 없어 규칙과 실제 운영 상태가 분리되어 있었다. -- 결정: 규칙 문서가 요구하는 핵심 문서 6종을 먼저 생성하고, 이후 변경 시 이 파일들을 기준 문서로 유지한다. -- 기대 효과: 작업 내역, 기술 구조, 화면-파일 연결, 향후 할 일을 한곳에서 추적할 수 있다. +## 2026-06-23 v4.0.0 — 표시 브랜드는 UNIARE, 내부 식별자는 호환 유지 -## 2026-03-30 / 이미지 관리 방식 1차 판단 -- 배경: `images/` 디렉터리 용량이 약 `1.7G`, 파일 수가 `9,370개` 수준으로 증가했다. -- 관찰: -- 개별 파일이 아주 크지 않아도 Git 일반 객체로 누적되면 clone 및 fetch 비용이 계속 증가한다. -- 이미지 추가가 잦은 프로젝트 특성상 저장소 히스토리 비대화가 반복될 가능성이 높다. -- 결정: -- 신규 이미지 유입 관리 목적이라면 `Git LFS` 도입을 권장한다. -- 단, 이미 커진 저장소를 즉시 가볍게 만드는 해결책으로 `Git LFS`만 기대해서는 안 된다. -- 히스토리 크기까지 줄여야 하면 `git lfs migrate` 또는 자산 저장소 분리/CDN 이전을 별도 의사결정으로 다룬다. -- 보류 사유: -- 원격 저장소의 LFS 지원 여부, 무료 용량, 대역폭 정책을 아직 확인하지 않았다. -- 기존 협업자들의 개발 환경에 `git lfs` 설치를 요구하게 되므로 도입 공지가 필요하다. +💡 사용자에게 보이는 제품명은 통일하되 기존 API 클라이언트와 저장 데이터의 호환성은 깨지 않기 위함. +🔧 화면·문서·다운로드 파일명은 `UNIARE`로 변경하고 `uadeck_token`, `uadeck.db`, `UaDeckApi`, `/api/v1`은 유지. -## 2026-03-30 / 이미지 히스토리 전체 LFS 마이그레이션 결정 -- 배경: 목적이 "앞으로 예방"이 아니라 "기존 저장소 용량 절감"으로 명확해졌다. -- 확인: -- 로컬 환경에 `git lfs`가 설치되어 있다. -- 원격 저장소에 LFS 엔드포인트가 설정되어 있다. -- 결정: -- `images/**` 전체 히스토리를 `Git LFS`로 마이그레이션한다. -- 현재 작업 중인 `ua27` 데이터 및 이미지 추가분도 함께 반영한다. -- 영향: -- 기존 커밋 해시가 모두 재작성된다. -- 원격 반영 시 강제 푸시가 필요하다. -- 협업 중인 다른 클론은 재동기화 절차가 필요하다. +## 2026-06-23 v4.0.0 — 주요 화면 너비를 1280px로 통일 -## 2026-03-30 / 새 루트 커밋 기준 저장소 재시작 결정 -- 배경: `images/**`는 LFS 포인터로 전환됐지만, 저장소 자체는 과거 Git 객체 영향으로 여전히 비대했다. -- 결정: -- 현재 로컬 작업본을 단일 기준 스냅샷으로 보고 새 루트 커밋을 만든다. -- 원격 `main`은 새 루트 커밋으로 교체한다. -- 기존 히스토리는 로컬 백업 브랜치에서만 보존한다. -- 새 버전 체계는 `v3.0.0`부터 다시 시작한다. -- 이미지 자산은 새 시작 이후에도 `Git LFS` 정책을 유지한다. -- 이유: -- 가장 확실하게 Git 일반 히스토리 용량을 줄일 수 있다. -- 추가적인 서버 GC 상태나 숨은 과거 객체를 더 추적하지 않고도 정리 목적을 달성할 수 있다. +💡 페이지 이동 때 헤더와 본문 폭이 달라지는 시각적 흔들림을 줄이고 광고 레일을 안정적으로 배치하기 위함. +🔧 홈·편집·결과·프로필·덱 목록의 공통 최대 너비를 `80rem` 기준으로 정렬. + +## 2026-06-22 v4.0.0 — Docker 서비스 이름을 `uniare-*`로 통일 + +💡 NAS에서 구·신규 컨테이너가 혼재할 때 포트 충돌과 운영 명령 실수를 줄이기 위함. +🔧 Compose 서비스·컨테이너·볼륨과 Nginx 프록시 대상을 `uniare-web`, `uniare-api`, `uniare-data`로 정렬. + +## 2026-06-22 v4.0.0 — 운영 웹 컨테이너는 허용 자산만 읽기 전용 공개 + +💡 프로젝트 루트 전체를 웹 루트로 노출하면 `.env`, `.git`, `docs`, API 소스가 외부에 노출될 수 있기 때문. +🔧 HTML과 `script`, `style`, `i18n`, `datas`, `images`만 Nginx에 read-only 볼륨으로 연결. + +## 2026-06-22 v4.0.0 — 공개 덱은 사용자 공개 설정을 기준으로 구성 + +💡 관리자 전용 프리셋보다 사용자가 직접 공개한 덱을 탐색하는 방식이 서비스 흐름에 더 자연스럽기 때문. +🔧 `/preset.html`과 `GET /api/v1/decks/presets`는 만료되지 않은 공개 사용자 덱을 최신순으로 제공. + +## 2026-06-22 v4.0.0 — 덱 미리보기는 1200px PNG로 정규화 + +💡 브라우저와 옵션에 따라 저장 썸네일 크기가 달라지고 큰 base64 요청이 413 오류를 일으켰기 때문. +🔧 저장 이미지는 1200px 기준 PNG로 만들고 Nginx 요청 한도 25MB, Express JSON 한도 20MB를 적용. + +## 2026-06-22 v4.0.0 — 즐겨찾기는 브라우저 로컬 상태로 우선 제공 + +💡 서버 스키마를 늘리지 않고도 공개 덱과 내 덱 탐색 편의를 빠르게 제공하기 위함. +🔧 공유 코드를 키로 `localStorage`에 저장하며 계정 간 동기화는 후속 과제로 남김. + +## 2026-06-22 v4.0.0 — 정적 프론트를 유지하며 회원·덱 API를 추가 + +💡 기존 카드 데이터와 화면을 전면 재작성하지 않고 로그인, 소유권, 공유 기능을 확장하기 위함. +🔧 정적 HTML/JavaScript는 유지하고 Express·SQLite API와 Docker Compose 운영 구성을 추가. + +## 2026-06-22 v4.0.0 — 덱 공유는 공유 코드와 REST API를 사용 + +💡 `?cards=<JSON>` 방식의 긴 URL과 중복 데이터를 없애고 공개 범위·작성자·수정 권한을 관리하기 위함. +🔧 DB에는 `{number,count,key}`만 저장하고 `GET /api/v1/decks/:shareCode` 응답에서 카드 메타를 합성. + +## 2026-06-22 v4.0.0 — 회원 덱과 비회원 공유의 보존 기간을 분리 + +💡 계정 소유 덱은 영구 관리하고 익명 공유 데이터는 무기한 누적되지 않게 하기 위함. +🔧 회원 덱은 만료 없이 저장하고 비회원 덱은 공개 상태로 30일 뒤 만료되도록 구성. + +## 2026-06-22 v4.0.0 — 프로필 이미지는 클라이언트에서 PNG로 정규화 + +💡 JPEG 등 원본 바이트를 `.png` 확장자로 저장해 브라우저 표시가 실패하는 문제를 막기 위함. +🔧 업로드 전에 Canvas로 PNG 변환·리사이즈하고 `updatedAt` 쿼리로 캐시를 갱신. + +## 2026-06-17 v3.1.2 — 유니티켓은 기존 시리즈 데이터에 병합 + +💡 별도 시리즈를 만들기보다 동일 작품의 카드 번호 체계와 필터 흐름을 유지하는 편이 검색과 덱 구성이 일관되기 때문. +🔧 `ua03`, `ua06`, `ua10` 데이터와 이미지에 헌터×헌터·테일즈 오브 어라이즈·히로아카 유니티켓을 추가. + +## 2026-06-15 v3.1.2 — 후속 부스터도 기존 시리즈 키를 재사용 + +💡 작품 단위 진입 구조를 유지하면서 신규 부스터와 기존 카드를 한 화면에서 다루기 위함. +🔧 마크로스 2nd는 `ua36`에 병합하고 무직전생 1st는 신규 `ua54` 데이터·이미지·번역 세트로 추가. + +## 2026-05-26 v3.1.2 — 신규 시리즈 반영은 데이터·이미지·번역을 한 세트로 관리 + +💡 세 축 중 하나라도 빠지면 홈 노출, 카드 로딩, 이미지 표시 중 일부가 즉시 깨지기 때문. +🔧 `ua50`, `ua53` 추가와 `ua52` 병렬 이미지 보강을 같은 릴리즈 단위로 반영. + +## 2026-05-16 v3.1.1 — 신규 시리즈와 기존 누락 자산을 함께 보강 + +💡 새 시리즈 배포 시 인접 시리즈의 확인된 누락 이미지도 함께 정리해 데이터 신뢰도를 유지하기 위함. +🔧 `ua52` 데이터·이미지·3개 언어명을 추가하고 `ua51` 병렬 이미지 누락분을 보강. + +## 2026-04-19 v3.1.0 — 릴리즈 기준과 작업 인계 문서를 명시 + +💡 화면 버전과 실제 변경 이력이 어긋나 후속 작업자가 현재 배포 기준을 판단하기 어려웠기 때문. +🔧 `ua51` 릴리즈를 `v3.1.0`으로 정의하고 데이터·이미지·번역 검증 절차를 문서화. + +## 2026-04-08 v3.0.1 — 카드 목록 상호작용은 기존 동작을 우선 보존 + +💡 구조 개선 과정에서 수량 조절과 카드 선택 UX가 달라져 기존 사용 흐름이 훼손되었기 때문. +🔧 변경된 목록 상호작용을 되돌리고 이후 UI 수정은 기존 동작 검증을 선행하도록 기준화. + +## 2026-04-08 v3.0.1 — 카드 번호와 덱 집계는 동일 식별자를 기준으로 처리 + +💡 잘못된 카드 번호 하나가 패러렐 묶음과 덱 수량 계산까지 연쇄적으로 어긋나게 하기 때문. +🔧 `ua27` 카드 번호를 정정하고 동일 `Number` 그룹의 수량 표시를 다시 동기화. + +## 2026-03-30 v3.0.1 — 데이터 경로는 실제 이미지 파일명을 기준으로 정합성 유지 + +💡 데이터의 `imgSrc`와 저장 파일명이 다르면 카드가 존재해도 화면에서는 누락으로 보이기 때문. +🔧 오기입 경로를 수정하고 누락 이미지 추가와 사용하지 않는 주석 데이터 제거를 함께 수행. + +## 2026-03-30 v3.0.0 — 저장소를 현재 스냅샷 기준 새 루트로 재시작 + +💡 과거 대용량 이미지 객체로 비대해진 Git 이력을 가장 확실하게 줄이기 위함. +🔧 현재 작업본을 새 루트 커밋으로 삼고 버전은 `v3.0.0`부터 재시작하며 기존 이력은 로컬 백업 브랜치에 보존. + +## 2026-03-30 v3.0.0 — 이미지 자산은 Git LFS로 관리 + +💡 수천 개 카드 이미지가 일반 Git 객체로 누적되는 비용을 줄이고 코드 이력과 대용량 자산 관리를 분리하기 위함. +🔧 `.gitattributes`에서 `images/**`를 LFS 대상으로 지정하고 신규 이미지도 같은 정책을 적용. + +## 2026-03-30 v3.0.0 — 문서를 목적별 기준 파일로 분리 + +💡 구현 상태, 작업 로그, 결정 이유, 할 일을 한 문서에 섞지 않고 후속 작업자가 빠르게 맥락을 찾게 하기 위함. +🔧 `spec`, `update`, `history`, `todo`, `deploy`, `map`, `convention`을 역할별 기준 문서로 운영. diff --git a/docs/map.md b/docs/map.md index 901b293..9dba502 100644 --- a/docs/map.md +++ b/docs/map.md @@ -1,63 +1,164 @@ -# 파일-화면 매핑 가이드 +# 파일-기능 지도 -## `/` -- 진입 파일: `index.html` -- 화면 역할: 시리즈 선택 홈 화면 -- 연결: `script/auth-header.js`, `script/api.js` +## 화면 -## `/cardList.html?series=<key>&d=<shareCode>` -- 진입 파일: `cardList.html` -- 화면 역할: 카드 검색·필터·덱 편집·저장·공유 -- `?d=` 있으면 API에서 덱 복원 -- 연결: `datas/<seriesKey>.js`, `script/api.js` +```text +/ +index.html -## `/result.html` -- 진입 파일: `result.html` -- 화면 역할: 덱 미리보기·PNG 저장·내 덱 저장 -- 데이터 소스: `localStorage.selectedCards` -- 하단 고정 바: `홈으로` | `이미지로 다운로드` | `내 덱으로 저장` (공개 여부 확인 모달) -- `script/deck-prefs.js`: 덱 공개 기본값·프롬프트 설정 +/cardList.html +cardList.html -## `/d/<shareCode>` (Nginx 리다이렉트) -- `nginx/uadeck.conf` 규칙으로 `cardList.html?d=` 로 이동 -- 옆 프로젝트 연동: `GET /api/v1/decks/<shareCode>` +/result.html +result.html -## `/login.html`, `/signup.html` -- 인증 화면 -- 연결: `script/page-i18n.js` (`data-i18n` 속성) +/login.html +login.html -## `/profile.html` (내 프로필) -- 로그인 사용자: 이메일·사용자명·프로필 사진·비밀번호 변경 -- 연결: `script/api.js` (`updateProfile`, `changePassword`), `script/image-utils.js`, `script/page-i18n.js`, `script/ads.js`, `style/profile.css` +/signup.html +signup.html -## `/deckHistory.html` -- 로그인 사용자의 저장 덱 목록·불러오기·삭제 -- 카드 그리드 + 미리보기 썸네일 (`preview_url`), 클릭·길게 눌러 확대 (`deck-preview-hold.js`) -- 시리즈 필터·검색 (`style/deck-history.css`) -- 연결: `script/page-i18n.js`, `script/ads.js` (우측 AdSense `4260332296`) +/profile.html +profile.html -## `/preset.html` (공개 덱) -- 사용자가 공개 전환한 덱 목록 (카드 그리드) -- 썸네일 클릭·길게 눌러 확대 (`deck-preview-hold.js`, 안내 문구 없음) -- API: `GET /api/v1/decks/presets` → `is_public=1` user 덱 -- 연결: `script/page-i18n.js`, `script/ads.js` +/deckHistory.html +deckHistory.html -## `/admin.html` -- 관리자: 회원 관리, 덱 관리 (`style/admin.css`) +/preset.html +preset.html -## API (`api/`) -- Docker: `docker-compose.yml` -- 엔드포인트 베이스: `/api/v1` +/admin.html +admin.html -## 공통 -- `style/app-shell.css`: 헤더·본문 최대 너비 1280px (`max-w-7xl`), 데스크톱 우측 광고 레일 -- `script/ads.js`: Google AdSense `ins.adsbygoogle` 슬롯 초기화 -- `script/page-help.js`: 페이지 제목 `?` 툴팁 -- `script/page-i18n.js`: `data-i18n` / `data-i18n-html` / `data-i18n-placeholder`, `UaPageI18n.t()` -- `script/deck-favorites.js`: 덱 즐겨찾기 (localStorage) -- `script/deck-preview-hold.js`: 덱 썸네일 길게 눌러 확대 (내 덱·공개 덱) -- `style/brand.css`: 헤더 UNIARE 로고 크기 통일 (32px) -- `script/api.js`: API 클라이언트 (`updateProfile`, `changePassword` 포함) -- `script/auth-header.js`: 헤더 프로필 드롭다운 (i18n), 로그인 시 **썸네일 옆 닉네임** 표시 (`auth-profile-trigger`) -- `script/image-utils.js`: 클라이언트 이미지 PNG 리사이즈 (`UaImageUtils.resizeImageToPng`) -- `style/auth-header.css` +/d/<shareCode> +nginx/default.conf +nginx/uadeck.conf +``` + +## 프론트 공통 모듈 + +```text +API 요청 +script/api.js + +인증 헤더 +script/auth-header.js +style/auth-header.css + +페이지 공통 레이아웃 +style/app-shell.css + +브랜드 로고 규격 +style/brand.css + +페이지 번역 +script/page-i18n.js +i18n/translations.ko.json +i18n/translations.ja.json +i18n/translations.en.json + +페이지 도움말 +script/page-help.js + +덱 공개 기본값 +script/deck-prefs.js + +덱 즐겨찾기 +script/deck-favorites.js + +덱 썸네일 확대 +script/deck-preview-hold.js + +이미지 PNG 변환 +script/image-utils.js + +광고 초기화 +script/ads.js +``` + +## 페이지 전용 스타일 + +```text +로그인·회원가입 +style/auth-pages.css + +프로필 +style/profile.css + +내 덱·공개 덱 +style/deck-history.css + +관리자 +style/admin.css + +Tailwind 입력·산출물 +style/input.css +style/output.css +``` + +## API + +```text +서버 진입·헬스 체크 +api/src/index.js + +SQLite 스키마·관리자 시드 +api/src/db.js + +JWT 인증·권한 +api/src/middleware/auth.js + +회원가입·로그인·프로필 +api/src/routes/auth.js + +덱 저장·조회·공개 목록 +api/src/routes/decks.js + +회원·덱 관리자 API +api/src/routes/admin.js + +카드 메타 합성 +api/src/services/cardResolver.js + +아바타 저장 +api/src/services/avatarImage.js + +덱 미리보기 저장 +api/src/services/previewImage.js +``` + +## 데이터·자산 + +```text +카드 데이터 +datas/uaXX.js +datas/uapr.js + +카드 이미지 +images/<seriesKey>/ + +브랜드·파비콘 +images/assets/ +favicon.ico +``` + +## 운영·외부 연동 + +```text +API 단독 Compose +docker-compose.yml + +Nginx + API Compose +docker-compose.prod.yml + +Nginx 이미지 +nginx/Dockerfile +nginx/default.conf + +외부 덱 API 클라이언트 +integrations/uadeck-deck-client.js + +외부 연동 문서 +docs/api-integration.md +docs/widget.md +``` diff --git a/docs/spec.md b/docs/spec.md index 6bc8893..31a99c1 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1,45 +1,38 @@ # 기술 명세 -## 1. 프로젝트 개요 -- 프로젝트명: `UNIARE - Union Arena Deck Builder` (표시명 UNIARE, 내부 API 식별자 일부는 `uadeck` 레거시 유지) -- 형태: 정적 HTML/CSS/JavaScript 프론트 + Docker API 백엔드 -- 패키지 의존성 (프론트): `tailwindcss`, `@tailwindcss/cli` -- 패키지 의존성 (API): `express`, `better-sqlite3`, `bcryptjs`, `jsonwebtoken`, `cors` -- 빌드 산출 스타일: `style/output.css` +## 프로젝트 개요 -## 2. 화면 구조 +- 서비스명: `UNIARE - Union Arena Deck Builder` +- 현재 버전: `v4.0.1` +- 프론트: 정적 HTML, CSS, JavaScript +- 백엔드: Express, SQLite(`better-sqlite3`) +- 운영: Docker Compose 기반 Nginx + API +- 지원 언어: 한국어, 일본어, 영어 +- 표시 브랜드는 `UNIARE`이며 `UaDeckApi`, `uadeck_token`, `uadeck.db` 등 내부 레거시 식별자는 호환을 위해 유지한다. -### `/index.html` -- 시리즈 선택 랜딩 페이지다. -- `i18n/translations.<lang>.json`을 직접 fetch하여 시리즈명과 소개 문구를 렌더링한다. -- 사용자가 시리즈를 선택하면 `cardList.html?series=<key>&seriesName=<name>&lang=<lang>`로 이동한다. +## 화면 기능 -### `/cardList.html` -- 특정 시리즈의 카드 목록을 보여주고 덱을 편집한다. -- 쿼리스트링의 `series` 값을 기준으로 `./datas/${seriesKey}.js`를 동적 import 한다. -- 필터 기능: -- 검색어 -- 색상(`All`, `Red`, `Blue`, `Green`, `Yellow`, `Purple`) -- 시리즈 하위 그룹 -- 타입(`Basic`, `AP`, `Parallel`) -- 트리거(`All`, `Special`, `Color`, `Final`) -- 카드 수량 증감, 키 카드 지정, 초기화, 상세 오버레이, 결과 페이지 이동을 담당한다. +| 경로 | 기능 | +|---|---| +| `/` | 시리즈 선택, 언어 전환, 인증 헤더 | +| `/cardList.html?series=<key>` | 카드 검색·필터, 수량 조절, 키 카드 지정, 덱 편집 | +| `/cardList.html?d=<shareCode>` | API 공유 덱 불러오기 및 편집 | +| `/result.html` | 덱 미리보기, PNG 다운로드, 덱 저장 | +| `/login.html` | 로그인 | +| `/signup.html` | 회원가입 | +| `/profile.html` | 이메일·사용자명·아바타·비밀번호·덱 공개 기본값 관리 | +| `/deckHistory.html` | 내 덱 검색·필터·이름 변경·공개 설정·삭제·불러오기 | +| `/preset.html` | 공개 사용자 덱 검색·필터·즐겨찾기·불러오기 | +| `/admin.html` | 회원과 덱 관리 | +| `/d/<shareCode>` | Nginx에서 `cardList.html?d=<shareCode>`로 연결 | -### `/result.html` -- 선택한 카드 목록을 미리보기하고 이미지로 저장한다. -- 스타일 모드: -- `type1`: 키 카드 강조형 -- `type2`: 클래식 5열형 -- `html-to-image` CDN을 사용해 덱 이미지를 PNG로 저장한다. +## 카드 데이터 -## 3. 데이터 구조 +- 데이터 위치: `datas/*.js` +- 실제 시리즈 데이터: `ua01`~`ua54` 중 현재 존재하는 파일과 `uapr.js` +- 샘플 데이터: `ua99 sample.js`, `ua99 sample mini.js` +- 모듈 형식: `export const cardList = [...]` -### 카드 데이터 파일 -- 위치: `datas/*.js` -- 파일 수: 현재 `52개` -- 각 파일은 `export const cardList = [...]` 형태를 사용한다. - -### 카드 객체 스키마 ```js { imgSrc: '/ua24/UA24BT_SHY-1-001.png', @@ -50,209 +43,143 @@ parallel: false, rarity: 'C', trigger: null, + maxCount: 4, } ``` -### 필드 의미 -- `imgSrc`: 원본 상대 식별자. 실제 렌더링 시 `images/${seriesKey}/${fileName}` 형태로 재구성한다. -- `Number`: 카드 번호. 동일 카드 판본 묶음과 필터 기준으로 사용한다. -- `count`: 현재 덱에 담긴 수량. -- `key`: 키 카드 여부. -- `color`: 카드 색상 또는 일부 특수 분류 값. -- `parallel`: 패러렐 여부. -- `rarity`: 희귀도 표기. -- `trigger`: `null`, `COLOR`, `FINAL`, `SPECIAL` 등 트리거 정보. -- `maxCount`: 일부 카드만 선택적으로 사용하며 기본값은 `4`로 동작한다. +- `Number`가 같은 판본은 동일 카드 수량 그룹으로 처리한다. +- `parallel`은 패러렐 판본 구분에 사용한다. +- `trigger`는 `null`, `COLOR`, `FINAL`, `SPECIAL` 값을 사용한다. +- `maxCount`가 없으면 기본 최대 수량은 4장이다. -## 4. 이미지 규칙 -- 기본 위치: `images/<seriesKey>/` -- 저장 정책: `images/**`는 `Git LFS` 대상으로 관리한다. -- 렌더링 규칙: -- 카드 목록 화면: `images/${seriesKey}/${fileName}` -- 결과 화면: `/images/${seriesKey}/${fileName}` +## 이미지 + +- 카드 이미지: `images/<seriesKey>/<fileName>` - 폴백 이미지: `/images/uapr/comingsoon.png` -- 현재 용량 현황: -- `images/` 약 `1.7G` -- 이미지 파일 약 `9,370개` +- 브랜드 자산: `images/assets/` +- `images/**`는 Git LFS로 관리한다. +- 아바타와 덱 미리보기는 API 데이터 디렉터리에 저장하며 Git에 포함하지 않는다. +- 덱 저장 미리보기는 1200px 기준 PNG로 정규화한다. -## 5. 다국어 -- 지원 언어: `ko`, `ja`, `en` -- 번역 파일: -- `i18n/translations.ko.json` -- `i18n/translations.ja.json` -- `i18n/translations.en.json` -- `index.html`은 번역 JSON을 직접 읽는다. -- 공통 텍스트 치환은 `i18n/i18n.js`의 `data-i18n` 기반 로직을 사용한다. +## 브라우저 상태 -## 6. 상태 저장 -- `localStorage.lang`: 현재 언어 -- `localStorage.selectedCards`: 편집 중인 덱 (임시, `encodeURIComponent` JSON) -- `localStorage.selectedSeries`: 현재 시리즈 키 -- `localStorage.uadeck_token` / `uadeck_user`: 로그인 세션 -- 덱 공유·영구 저장: API + DB (`GET /api/v1/decks/:shareCode`) +| 키 | 용도 | +|---|---| +| `lang` | 선택 언어 | +| `selectedCards` | 편집 중인 덱 | +| `selectedSeries` | 현재 시리즈 | +| `uadeck_token` | JWT | +| `uadeck_user` | 로그인 사용자 캐시 | +| 덱 즐겨찾기 키 | 공유 코드 기반 로컬 즐겨찾기 | -## 7. 스타일 및 자산 -- Tailwind 유틸리티 클래스와 페이지별 인라인 스타일을 혼용한다. -- 공통 스타일 파일: -- `style/output.css` -- `style/style.css` -- `style/navigation.css` +## 인증 및 권한 -## 8. 알려진 제약 -- `cardList.html`과 `result.html`의 스크립트가 페이지 내부에 크게 포함되어 있어 모듈 분리 전까지는 변경 영향 범위가 넓다. -- `script/navigation.js`의 일부 이동 대상 페이지는 현재 저장소에 존재하지 않는다. -- 이미지가 Git 일반 객체로 계속 누적되면 저장소 히스토리 크기가 지속 증가한다. +- 비밀번호는 bcrypt 비용 12로 해시한다. +- JWT 기본 만료는 7일이며 `JWT_EXPIRES`로 변경할 수 있다. +- `user`는 자신의 프로필과 덱만 수정·삭제할 수 있다. +- `admin`은 회원 상태·역할·비밀번호와 전체 덱을 관리할 수 있다. +- 관리자는 자신의 관리자 역할을 해제하거나 자신의 계정을 비활성화할 수 없다. +- 비공개 덱은 소유자와 관리자만 조회할 수 있다. ---- +## 덱 저장 정책 -## 9. v4.0.0 계획 — 회원·덱 API·공유 +- 공유 코드는 저장 시 생성되는 8자 코드다. +- 회원 덱은 만료 없이 저장하며 공개 여부를 선택할 수 있다. +- 비회원 덱은 공개 상태로 저장되고 30일 뒤 만료된다. +- DB의 `cards_json`에는 `{number, count, key}`만 저장한다. +- API 응답 시 `datas/<series>.js`에서 이미지·색상·희귀도·트리거 정보를 합성한다. +- 공개 덱 목록은 `share_type='user'`, `is_public=1`, 미만료 조건을 사용한다. +- `preset` 타입과 관리자 프리셋 API는 레거시 호환용으로 코드에 남아 있으며 현재 공개 덱 화면에서는 사용하지 않는다. -> 상태: **v4.0.0 1차 구현 완료** (2026-06-19) +## 데이터베이스 -### 9.1 아키텍처 개요 -- 프론트: 기존 정적 HTML/JS 유지. 헤더에 프로필·인증 UI 추가. -- 백엔드: Docker 컨테이너 API 서버 (NAS 배포). -- 카드 DB: `datas/*.js` + `images/` 정적 유지 (변경 없음). -- 사용자·덱 데이터: API + SQLite(또는 PostgreSQL) DB. -- 외부 연동(옆 프로젝트): `GET /api/v1/decks/:shareCode` 단일 계약. +### `users` -### 9.2 덱 공유 방식 (확정) +| 컬럼 | 설명 | +|---|---| +| `id` | 기본 키 | +| `email`, `username` | 고유 로그인·표시 식별자 | +| `password_hash` | bcrypt 해시 | +| `display_name` | 레거시 표시명, 현재 `username`과 함께 갱신 | +| `avatar_url` | 아바타 API 경로 | +| `role` | `user` 또는 `admin` | +| `is_active` | 계정 활성 상태 | +| `default_deck_public` | 덱 저장 기본 공개 여부 | +| `skip_deck_public_prompt` | 저장 시 공개 확인 생략 여부 | +| `created_at`, `updated_at` | 생성·수정 시각 | -기존 `?cards=<JSON>` URL 방식은 **폐기**한다. 공유는 공유 코드 + API만 사용한다. +### `decks` -| 종류 | `share_type` | 코드 생성 | URL 예시 | 설명 | -|---|---|---|---|---| -| 개인 덱 | `user` | nanoid 8자 (저장마다 고유) | `/d/x7Kp9m2Q` | 같은 카드 구성이라도 저장자마다 URL이 다름 | -| 메타 덱·프리셋 | `preset` | 내용 해시 8자 (동일 구성 = 동일 URL) | `/d/a7f3b2c1` | 관리자 등록 공식 템플릿, 중복 저장 없음 | +| 컬럼 | 설명 | +|---|---| +| `share_code` | 고유 공유 코드 | +| `share_type` | `user` 또는 레거시 `preset` | +| `content_hash` | 레거시 프리셋 중복 판별 | +| `user_id` | 소유 회원, 비회원은 `null` | +| `series`, `series_name` | 시리즈 키와 표시명 | +| `name` | 덱 이름 | +| `cards_json` | 최소 카드 구성 JSON | +| `is_public` | 공개 여부 | +| `view_count` | 상세 조회 횟수 | +| `expires_at` | 비회원 공유 만료 시각 | +| `preview_image` | 저장된 미리보기 파일 정보 | +| `created_at`, `updated_at` | 생성·수정 시각 | -브라우저 진입: -- `/d/:shareCode` → `cardList.html?d=:shareCode` 리다이렉트 또는 동일 페이지에서 API 로드 -- `cardList.html`은 `?d=` 파라미터가 있으면 API에서 덱 복원 +## API -### 9.3 DB 저장 vs API 응답 +### 공통 -**DB에 저장하는 최소 덱 본문 (`cards_json`):** -```json -[ - { "number": "UA36BT/MCR-1-052", "count": 4, "key": true }, - { "number": "EX14BT/MCR-2-011", "count": 2, "key": false } -] -``` +| Method | Path | 권한 | 기능 | +|---|---|---|---| +| GET | `/api/v1/health` | 공개 | API 상태와 버전 | -**API 응답 시 서버가 `datas/<series>.js`를 lookup하여 풀 카드 정보를 합성한다.** +### 인증 -```json -{ - "v": 1, - "shareCode": "x7Kp9m2Q", - "shareType": "user", - "series": "ua36", - "seriesName": "마크로스", - "name": "블루 aggro", - "totalCards": 50, - "cards": [ - { - "number": "UA36BT/MCR-1-052", - "count": 4, - "key": true, - "imgSrc": "/ua36/UA36BT_MCR-1-052.png", - "color": "Blue", - "rarity": "SR", - "trigger": null, - "parallel": false - } - ], - "author": { - "id": 12, - "displayName": "닉네임", - "avatarUrl": "/uploads/avatars/12.png" - }, - "createdAt": "2026-06-19T12:00:00Z", - "updatedAt": "2026-06-19T14:30:00Z", - "viewCount": 42, - "isPublic": true -} -``` +| Method | Path | 권한 | 기능 | +|---|---|---|---| +| POST | `/api/v1/auth/register` | 공개 | 회원가입 | +| POST | `/api/v1/auth/login` | 공개 | 로그인과 JWT 발급 | +| GET | `/api/v1/auth/me` | 회원 | 내 정보 | +| PUT | `/api/v1/auth/me` | 회원 | 프로필·덱 저장 설정 변경 | +| POST | `/api/v1/auth/me/password` | 회원 | 비밀번호 변경 | +| GET | `/api/v1/auth/avatar/:userId` | 공개 | PNG 아바타 | -비회원 임시 공유: `author`는 `null`, `expiresAt` 필드로 만료 관리. +### 덱 -### 9.4 DB 스키마 (초안) +| Method | Path | 권한 | 기능 | +|---|---|---|---| +| GET | `/api/v1/decks/presets` | 공개 | 공개 사용자 덱 목록 | +| GET | `/api/v1/decks/:shareCode/preview` | 공개 | 덱 미리보기 PNG | +| GET | `/api/v1/decks/:shareCode` | 조건부 | 공개 덱 또는 소유 덱 상세 | +| GET | `/api/v1/decks` | 회원 | 내 덱 목록 | +| POST | `/api/v1/decks` | 공개 | 회원 또는 비회원 덱 저장 | +| PUT | `/api/v1/decks/:shareCode` | 소유자·관리자 | 덱 수정 | +| DELETE | `/api/v1/decks/:shareCode` | 소유자·관리자 | 덱 삭제 | -#### `users` -| 컬럼 | 타입 | 설명 | +### 관리자 + +| Method | Path | 기능 | |---|---|---| -| `id` | INTEGER PK | | -| `email` | TEXT UNIQUE | 로그인 ID | -| `username` | TEXT UNIQUE | 표시용 닉네임 | -| `password_hash` | TEXT | bcrypt | -| `display_name` | TEXT | 프로필 이름 | -| `avatar_url` | TEXT | 프로필 썸네일 | -| `default_deck_public` | INTEGER | 덱 저장 기본 공개 여부 (1=공개, 0=비공개) | -| `skip_deck_public_prompt` | INTEGER | 저장 시 공개 확인 생략 (1=생략) | -| `role` | TEXT | `user` \| `admin` | -| `is_active` | INTEGER | 0=비활성, 1=활성 | -| `created_at` | TEXT | 가입일 | -| `updated_at` | TEXT | | - -#### `decks` -| 컬럼 | 타입 | 설명 | -|---|---|---| -| `id` | INTEGER PK | | -| `share_code` | TEXT UNIQUE | URL에 쓰이는 8자 코드 | -| `share_type` | TEXT | `user` \| `preset` | -| `content_hash` | TEXT | preset 타입용. user 타입은 null 가능 | -| `user_id` | INTEGER FK | null=비회원 임시 공유 | -| `series` | TEXT | 예: `ua36` | -| `name` | TEXT | 덱 이름 | -| `cards_json` | TEXT | `[{number,count,key}]` JSON | -| `is_public` | INTEGER | 0=비공개, 1=공개 | -| `view_count` | INTEGER | 조회수 | -| `expires_at` | TEXT | null=만료 없음 | -| `created_at` | TEXT | 생성일 | -| `updated_at` | TEXT | 최종 수정일 | - -덱 1건 용량 추정: 카드 50장 × 약 60바이트 ≈ 3KB. 수만 건이어도 DB 부담은 경미하다. - -### 9.5 API 엔드포인트 (초안) - -#### 인증 -| Method | Path | 설명 | -|---|---|---| -| POST | `/api/v1/auth/register` | 회원가입 | -| POST | `/api/v1/auth/login` | 로그인 → JWT | -| GET | `/api/v1/auth/me` | 내 정보 | -| PUT | `/api/v1/auth/me` | 프로필 수정 (이메일, 사용자명, 아바타, `defaultDeckPublic`, `skipDeckPublicPrompt`) | -| POST | `/api/v1/auth/me/password` | 비밀번호 변경 | -| GET | `/api/v1/auth/avatar/:userId` | 프로필 이미지 (PNG) | - -#### 덱 -| Method | Path | 설명 | -|---|---|---| -| GET | `/api/v1/decks/:shareCode` | 덱 조회 (외부 연동 공식 계약) | -| GET | `/api/v1/decks` | 내 덱 목록 (인증 필요) | -| POST | `/api/v1/decks` | 덱 저장·공유 코드 발급 | -| PUT | `/api/v1/decks/:shareCode` | 덱 수정 (소유자만) | -| DELETE | `/api/v1/decks/:shareCode` | 덱 삭제 (소유자만) | - -#### 관리자 -| Method | Path | 설명 | -|---|---|---| -| GET | `/api/v1/admin/users` | 회원 목록 | +| GET | `/api/v1/admin/users` | 회원 목록·검색 | | GET | `/api/v1/admin/users/:id` | 회원 상세 | -| PUT | `/api/v1/admin/users/:id` | 회원 정보·역할·활성 수정 | +| PUT | `/api/v1/admin/users/:id` | 회원 정보·역할·활성 상태 변경 | | POST | `/api/v1/admin/users/:id/reset-password` | 비밀번호 초기화 | -| POST | `/api/v1/admin/decks/preset` | 메타 덱(프리셋) 등록 | +| GET | `/api/v1/admin/decks` | 전체 덱 목록 | +| DELETE | `/api/v1/admin/decks/:shareCode` | 덱 삭제 | +| POST | `/api/v1/admin/decks/preset` | 레거시 프리셋 생성 | -### 9.6 프론트 변경 (예정) -- `script/auth-header.js`: 헤더 프로필 드롭다운 (로그인/회원가입, 내 덱 관리) -- `login.html`, `signup.html`, `deckHistory.html`, `admin.html` 신규 -- `cardList.html`: `?cards=` 제거, `?d=` API 로드로 전환 -- `localStorage.selectedCards`: 편집 중 임시 저장만 (공유와 분리) +## 스타일 -### 9.7 배포 -- `docker-compose.yml`: API만 (기존 웹서버 사용 시) -- `docker-compose.prod.yml`: Nginx + API 풀스택 (NAS 권장) -- `docs/deploy.md`: NAS 배포 상세 가이드 -- `docs/api-integration.md`: 옆 프로젝트 연동 가이드 -- `integrations/uadeck-deck-client.js`: 연동용 JS 클라이언트 -- `nginx/default.conf`: `/api/`, `/d/` 라우팅 +- 현재는 Tailwind 유틸리티, 공통 CSS, 페이지 전용 CSS, 일부 인라인 스타일을 혼용한다. +- 이번 단계에서는 기존 CSS를 Tailwind로 일괄 전환하지 않는다. +- `style/output.css`는 `style/input.css`에서 생성하는 빌드 산출물이다. +- 헤더 로고 크기는 `style/brand.css`만 기준으로 사용한다. +- 기본 글꼴은 Pretendard 계열을 우선 사용한다. + +## 알려진 제약 + +- `cardList.html`, `result.html`의 인라인 스크립트가 커서 변경 영향 범위가 넓다. +- `admin.html`은 3개 언어 번역 적용이 완료되지 않았다. +- 즐겨찾기는 브라우저별 로컬 상태라 계정 간 동기화되지 않는다. +- Homepage 전용 `GET /api/homepage` 엔드포인트는 아직 구현되지 않았다. +- 현재 Compose의 컨테이너명(`uniare-api`, `uniare-web`)과 데이터 볼륨명(`uniare-data`)은 고정값이며 환경 변수화되지 않았다. diff --git a/docs/todo.md b/docs/todo.md index a382535..26fcada 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,47 +1,22 @@ -# 할 일 및 이슈 +# 할 일 -## 현재 이슈 -- `images/` 디렉터리가 약 `1.7G`까지 증가해 저장소 clone, fetch, checkout 비용이 커졌다. -- 이미지 파일 수가 `9,000+` 단위라서 카드 추가 빈도가 올라갈수록 Git 객체 수와 히스토리 부담이 계속 누적될 가능성이 높다. -- `script/navigation.js`에는 현재 프로젝트에 없는 페이지(`deckHistory.html`, `preset.html`, `myDatabase.html`)로 이동하는 코드가 남아 있다. -- `cardList.html`과 `result.html` 내부 스크립트 비중이 커서 기능 확장 시 유지보수 난도가 올라갈 수 있다. -- 데이터 파일 네이밍과 샘플 파일(`datas/ua99 sample.js`, `datas/ua99 sample mini.js`) 처리 기준이 문서상 명확하지 않았다. +## 운영 -## v4.0.0 구현 완료 항목 -- [x] Docker API 서버 + DB (`users`, `decks` 테이블) -- [x] 인증: 회원가입, 로그인, JWT, 헤더 프로필 드롭다운 -- [x] 덱 API: 저장, 조회, 공유 코드 (`GET /api/v1/decks/:shareCode`) -- [x] `cardList.html`: `?d=` API 로드, 저장·공유 버튼 -- [x] `deckHistory.html`, `login.html`, `signup.html`, `preset.html` -- [x] `admin.html`: 회원 목록·수정, 비밀번호 초기화 -- [x] `index.html` 버전 `v4.0.0` +- NAS에서 `uniare-web`, `uniare-api` 스택의 재기동과 헬스 체크를 최종 확인한다. +- 운영 `.env`의 `JWT_SECRET`, 관리자 계정, CORS 허용 도메인을 실제 환경 기준으로 점검한다. +- DB와 업로드 이미지(`api/data/previews`, `api/data/avatars`)의 정기 백업·복구 절차를 검증한다. +- 원격 저장소의 Git LFS 업로드 상태와 용량 정책을 확인한다. +- Compose의 컨테이너명과 데이터 볼륨명을 환경 변수로 관리할지 결정하고, 적용 시 운영 마이그레이션 절차를 작성한다. -## v4 후속 작업 -- [ ] NAS에서 구 컨테이너(`uniare-web`) 정리 후 신규 스택(`uniare-web`, `uniare-api`) 재기동 확인 -- [ ] `.env` 설정 및 기존 리버스 프록시 연동 -- [ ] 옆 프로젝트에 `integrations/uadeck-deck-client.js` 적용 -- [ ] `cardList.html` 인라인 스크립트 모듈 분리 -- [x] i18n: 모달·홈 버튼·auth·덱 관리·프로필·로그인/가입 3개국어 (v4.0.0 후속) -- [x] 덱 즐겨찾기·필터 (내 덱·공개 덱) -- [x] 공개 덱: 홀드 미리보기 + 하단 즐겨찾기/불러오기 버튼 -- [x] 내 덱: 썸네일 클릭·홀드 확대, 저장 썸네일 1200px 정규화 -- [ ] i18n: `admin.html` 문구 +## 기능 -## v4.0.0 최근 완료 -- [x] `profile.html`: 일반 사용자 프로필 화면 (아바타·닉네임·이메일·비밀번호) -- [x] 헤더 드롭다운「내 프로필」링크 -- [x] 아바타 업로드 시 PNG 리사이즈 (`script/image-utils.js`) — JPEG 등 형식 깨짐 방지 -- [x] 아바타 URL `updatedAt` 캐시 무효화 -- [x] 내 덱 관리: `/d/코드` 경로 표시 제거 -- [x] `result.html` 하단 버튼 개편 + 공개 확인 모달 + 홈 이동 -- [x] 프로필·API: 덱 저장 공개 기본값 설정 +- `admin.html`의 고정 한국어 문구를 `ko`, `ja`, `en` 번역 체계에 연결한다. +- 옆 프로젝트에 `integrations/uadeck-deck-client.js`를 적용하고 실제 공유 덱 조회를 검증한다. +- Homepage 연동이 필요해지면 `GET /api/homepage`와 운영 지표 응답을 구현한다. -## 다음 작업 제안 -- 원격 저장소에서 `Git LFS` 업로드가 정상 완료되었는지 확인한다. -- 협업자가 있다면 히스토리 재작성 이후 동기화 방법을 공지한다. -- `Git LFS` 도입 후에도 저장소 운영 비용이 크면 이미지 저장소 분리/CDN 이전을 재검토한다. -- `cardList.html`의 인라인 스크립트를 별도 JS 모듈로 분리하는 리팩터링을 검토한다. +## 유지보수 -## 판단 가이드 -- 신규 이미지 추가 관리가 목적이면 `Git LFS` 추적만으로도 충분하다. -- 기존 clone 크기까지 줄이는 것이 목적이면 `Git LFS` 히스토리 마이그레이션 또는 자산 저장소 분리가 필요하다. +- `cardList.html`, `result.html`의 대형 인라인 스크립트를 기능 단위 모듈로 분리한다. +- `datas/ua99 sample.js`, `datas/ua99 sample mini.js`의 보존 목적과 배포 제외 여부를 확정한다. +- 즐겨찾기의 계정 간 동기화가 필요하면 사용자별 API 저장 방식으로 확장한다. +- 이미지 규모가 Git LFS 운영 한도를 넘으면 자산 저장소 또는 CDN 분리를 검토한다. diff --git a/docs/update.md b/docs/update.md index f819623..78b3e09 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,22 @@ # 업데이트 이력 +## 2026-06-23 / v4.0.1 — 프로젝트 문서 체계 정비 + +### 수행 작업 +- 실제 코드와 Git 이력을 기준으로 `spec.md`, `todo.md`, `history.md`, `map.md`, `convention.md`를 현재 상태에 맞게 재작성 +- 과거 주요 구현 결정을 `history.md` 형식에 맞춰 날짜·버전·결정 이유·적용 내용으로 복원 +- 누락된 `VERSION`, `docs/widget.md`, `docs/changelog.md` 추가 +- `deploy.md`를 최초 설치·실행·업데이트·백업·롤백 순서의 운영 가이드로 전면 재작성 +- `deploy.md`에서 로컬 개발과 NAS 운영을 분리하고 이미지·`datas/`·API 변경별 반영 명령을 구분 +- `AGENTS.md` 재검수 결과를 반영해 SQLite WAL 안전 백업·복구 절차와 Compose 고정 이름 제약을 문서화 +- Homepage Custom API YAML을 공식 문서 기준으로 재확인하고 참고 URL 추가 +- `.cursorrules`의 잘못된 `.AGENTS.md` 참조를 `AGENTS.md`로 수정하고 배포 문서의 개인 로컬 절대 경로를 제거 +- `deploy.md`의 중복 설명을 제거하고 초심자가 상황별 절차를 바로 선택할 수 있도록 간소화 +- `deploy.md`의 Markdown 표를 제거하고 불필요한 줄바꿈을 줄여 명령 중심으로 재정리 +- `deploy.md`의 한 줄 명령은 인라인 코드로 변경하고 여러 명령을 연속 실행하는 경우만 코드 블록 유지 +- 완료 항목이 남아 있던 `todo.md`를 미완료 작업만 유지하도록 정리 +- 현재 단계에서는 기존 CSS 구조를 유지하고 Tailwind 일괄 전환을 제외한다고 명시 + ## 2026-06-22 / v4.0.0 — 헤더 로고 크기 통일 ### 수행 작업 diff --git a/docs/widget.md b/docs/widget.md new file mode 100644 index 0000000..311c998 --- /dev/null +++ b/docs/widget.md @@ -0,0 +1,78 @@ +# 외부 서비스 연동 + +## 기본 정보 + +- 운영 URL: `https://uniare.sori.studio` +- API 상태: `https://uniare.sori.studio/api/v1/health` +- 공유 덱: `https://uniare.sori.studio/api/v1/decks/<shareCode>` +- Homepage 전용 `GET /api/homepage`는 아직 구현되지 않았다. + +## 상태 확인 + +```bash +curl -fsS https://uniare.sori.studio/api/v1/health +``` + +```json +{ + "ok": true, + "version": "4.0.0" +} +``` + +로컬 Docker에서는 다음 주소를 사용한다. + +```bash +curl -fsS http://localhost:44082/api/v1/health +``` + +## Homepage 위젯 + +현재 구현된 헬스 API를 사용하는 예시다. + +Homepage Custom API 공식 문서: + +```text +https://gethomepage.dev/widgets/services/customapi/ +``` + +```yaml +- UNIARE: + icon: mdi-cards-playing + href: https://uniare.sori.studio + description: Union Arena Deck Builder + widget: + type: customapi + url: https://uniare.sori.studio/api/v1/health + refreshInterval: 30000 + mappings: + - field: ok + label: 상태 + format: text + - field: version + label: 버전 + format: text +``` + +Docker 내부의 Homepage 컨테이너에서 같은 Compose 네트워크를 사용할 경우 공개 도메인 대신 `http://uniare-web/api/v1/health`를 사용할 수 있다. 서로 다른 Compose 프로젝트라면 공통 외부 네트워크 연결이 필요하다. + +## 공유 덱 조회 + +```bash +curl -fsS https://uniare.sori.studio/api/v1/decks/x7Kp9m2Q +``` + +브라우저와 Node.js 연동은 `integrations/uadeck-deck-client.js` 또는 `docs/api-integration.md`를 사용한다. + +## 운영 지표 제안 + +`GET /api/homepage`를 구현할 때 다음 지표를 우선 제공한다. + +- API 상태와 현재 버전 +- 전체 회원 수 +- 전체 덱 수와 공개 덱 수 +- 최근 24시간 생성 덱 수 +- DB 백업 시각과 성공 여부 +- 미리보기·아바타 저장 공간 사용량 + +회원 수나 덱 수처럼 운영 정보가 포함된 엔드포인트는 공개 범위를 검토하고, 필요하면 내부 네트워크 또는 별도 토큰으로 제한한다. diff --git a/login.html b/login.html index d7fb36a..8d44784 100644 --- a/login.html +++ b/login.html @@ -42,7 +42,7 @@ </p> </main> - <footer class="auth-version-footer" aria-hidden="true">v4.0.0</footer> + <footer class="auth-version-footer" aria-hidden="true">v4.0.1</footer> <script src="/script/page-i18n.js"></script> <script src="/script/api.js"></script>