프로젝트 문서 체계 정비

This commit is contained in:
2026-06-23 13:42:38 +09:00
parent e11b1dc6df
commit 00e05dbe3f
18 changed files with 1344 additions and 682 deletions

View File

@@ -1,37 +1,27 @@
# 프로젝트 AI 가이드라인 및 문서화 규칙 # 프로젝트 AI / 문서 규칙
## 🌐 기본 응답 원칙 이 문서는 AI 에이전트가 이 저장소에서 작업할 때 참고하는 규칙과 문서 관리 원칙을 간결하게 정리합니다. 실제 작업 시 우선 적용 대상은 루트 `AGENTS.md`입니다.
- 모든 대화와 문서는 **한국어**로 작성한다.
- 코드 내 주석은 반드시 **JSDoc 형식**을 사용한다.
## 🧾 Git 및 버전 관리 규칙 ## 기본 응답 원칙
- Git 작성자 정보는 프로젝트 기준 계정으로 통일한다. - 모든 대화와 문서는 한국어로 작성한다.
- Git 커밋 메시지는 반드시 **한국어**로 작성한다. - 코드 주석은 JSDoc 형식으로 작성한다.
- 버전이 올라가는 작업은 `docs/update.md`에 먼저 반영하고, 같은 버전명을 Git 태그에도 맞춘다. - 답변은 짧고 명확하게 작성한다.
- 원격 저장소에 푸시하기 전, 민감 정보(실명, 개인 이메일, 비밀키, 로컬 경로)가 포함되지 않았는지 확인한다. - 추측성 답변 금지.
- 불확실한 내용은 불확실하다고 명시한다.
## 📂 문서 자동 관리 규칙 ## Git / 버전 규칙
모든 작업 수행 후, AI는 관련 내용을 아래 지정된 파일에 즉시 반영해야 한다. - 커밋 메시지는 한국어로 작성한다.
- 버전이 올라가는 작업은 `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)** ## 실행 지침
- 현재 직면한 문제점과 다음에 이어서 진행할 작업 목록을 정리한다. - 수정 전에 관련 `docs/` 문서를 먼저 확인한다.
- 작업 완료 후 문서 반영 누락이 없도록 확인한다.
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/` 내 관련 문서들을 먼저 참조한다.
- 작업 완료 후 위 문서들의 업데이트가 누락되지 않도록 확인한다.

View File

@@ -1 +1 @@
모든 작업 시 프로젝트 루트의 .ai-rules.md 지침을 엄격히 준수하고, 작업 종료 시마다 docs/ 폴더 내 문서를 업데이트할 것. 모든 작업 시 프로젝트 루트의 AGENTS.md 지침을 엄격히 준수할 것.

480
AGENTS.md Normal file
View File

@@ -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<Array>}
*/
```
---
### 스타일
* 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 여부

1
VERSION Normal file
View File

@@ -0,0 +1 @@
v4.0.1

View File

@@ -1,6 +1,6 @@
{ {
"name": "uadeck-api", "name": "uadeck-api",
"version": "4.0.0", "version": "4.0.1",
"type": "module", "type": "module",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@@ -29,7 +29,7 @@ app.use(cors({ origin: corsOrigins, credentials: true }));
app.use(express.json({ limit: '20mb' })); app.use(express.json({ limit: '20mb' }));
app.get('/api/v1/health', (_req, res) => { 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)); app.use('/api/v1/auth', createAuthRouter(db));
@@ -47,7 +47,7 @@ app.use((err, _req, res, _next) => {
}); });
app.listen(PORT, () => { 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(`DB: ${DB_PATH}`);
console.log(`DATAS: ${process.env.DATAS_PATH || '(default ../datas)'}`); console.log(`DATAS: ${process.env.DATAS_PATH || '(default ../datas)'}`);
}); });

View File

@@ -143,7 +143,7 @@ const deck = await client.fetchDeck(code);
const cards = deck.cards; const cards = deck.cards;
``` ```
## 프리셋(메타 덱) 목록 ## 공개 덱 목록
```http ```http
GET /api/v1/decks/presets GET /api/v1/decks/presets
@@ -163,7 +163,9 @@ GET /api/v1/decks/presets
} }
``` ```
프리셋도 동일하게 `GET /decks/:shareCode`로 상세 조회합니다. 엔드포인트 이름은 레거시 호환을 위해 `presets`를 유지하지만, 현재 응답 대상은 관리자가 등록한 프리셋이 아니라 사용자가 공개한 미만료 덱입니다.
상세 조회는 동일하게 `GET /decks/:shareCode`를 사용합니다.
## 에러 코드 ## 에러 코드

37
docs/changelog.md Normal file
View File

@@ -0,0 +1,37 @@
# 변경사항
## v4.0.1
### 프로젝트 운영 문서 정비
기능 동작은 변경하지 않고 개발·배포 문서를 현재 서비스 구조에 맞게 정리했습니다.
로컬 개발과 NAS 운영, 카드 이미지·데이터 업데이트, 백업·복구 절차를 구분해 이후 업데이트를 더 안전하게 진행할 수 있습니다.
## v4.0.0
### 회원 계정과 내 덱 관리
회원가입과 로그인을 지원합니다. 프로필에서 사용자명, 이메일, 프로필 이미지, 비밀번호와 덱 공개 기본값을 관리할 수 있습니다.
완성한 덱은 결과 화면의 `내 덱으로 저장` 버튼으로 저장하고, `내 덱` 화면에서 이름 변경, 공개 전환, 삭제, 다시 불러오기를 할 수 있습니다.
### 짧은 공유 링크
긴 카드 데이터 URL 대신 `/d/공유코드` 형식의 링크를 사용합니다. 공개 덱은 링크를 받은 누구나 불러올 수 있으며 비공개 덱은 소유자와 관리자만 볼 수 있습니다.
### 공개 덱 탐색과 즐겨찾기
`공개 덱` 화면에서 다른 사용자가 공개한 덱을 검색하고 시리즈별로 필터링할 수 있습니다. 썸네일을 길게 눌러 크게 보고, 자주 확인하는 덱은 즐겨찾기에 보관할 수 있습니다.
### 덱 이미지 저장 개선
저장되는 덱 미리보기 크기를 1200px 기준으로 통일했습니다. 브라우저별로 썸네일 크기가 달라지거나 큰 이미지 저장이 실패하던 문제를 줄였습니다.
### 다국어 화면 확대
로그인, 회원가입, 프로필, 내 덱, 공개 덱과 주요 저장 안내를 한국어, 일본어, 영어로 제공합니다.
### UNIARE 브랜드 적용
서비스 표시 이름과 헤더 로고를 UNIARE로 통일하고, 페이지 이동 시 헤더와 본문 너비가 안정적으로 유지되도록 개선했습니다.

View File

@@ -1,61 +1,71 @@
# Codex Handoff Guide # 작업 인계 가이드
## 목적 ## Handoff(핸드오프)란?
- 이 문서는 다른 작업 환경이나 다른 Codex 세션에서 프로젝트를 이어받더라도 같은 품질 기준으로 작업하기 위한 운영 기준서다. 다른 개발자나 운영 담당자가 지금 작업을 이어받을 때 빠르게 상황을 이해하도록 정리한 문서입니다. 비개발자에게는 '작업 인수인계 노트'라고 생각하면 됩니다.
- 특히 이 저장소는 정적 HTML/JS 프로젝트이면서 카드 데이터와 이미지 자산 비중이 크기 때문에, "코드 수정"보다 "데이터-번역-이미지 일치"가 더 중요하다.
## 현재 기준 릴리즈 ## 우선 확인
- 현재 작업 기준 버전: `v4.0.0`
- 이번 릴리즈 핵심 반영:
- 회원·인증·덱 API (`api/`, Docker)
- 공유 코드 기반 덱 공유 (`/d/:code`, `GET /api/v1/decks/:shareCode`)
- 관리자 화면 (`admin.html`)
- `?cards=` URL 방식 폐기
## 다음 작업자에게 반드시 전달할 것 1. 프로젝트 루트 `AGENTS.md`
- 이 프로젝트는 빌드 시스템 중심 앱이 아니라 `index.html`, `cardList.html`, `result.html` 3개 페이지가 직접 동작하는 정적 사이트다. 2. `docs/spec.md`
- 신규 시리즈를 추가할 때는 항상 아래 3가지를 한 세트로 본다. 3. `docs/todo.md`
- `datas/<series>.js` 4. `docs/history.md`
- `images/<series>/` 5. `docs/convention.md`
- `i18n/translations.ko.json`, `i18n/translations.ja.json`, `i18n/translations.en.json` 6. `docs/map.md`
- 이미지 자산은 `images/**` 전체가 `Git LFS` 대상이다. 대용량 이미지가 일반 Git 객체로 들어가지 않게 주의한다. 7. `docs/deploy.md`
- `cardList.html`은 `?series=<seriesKey>` 값을 기준으로 `./datas/${seriesKey}.js`를 동적 import 한다. 즉, 시리즈 키 불일치가 있으면 화면에서 바로 깨진다.
- 카드 이미지 렌더링은 최종적으로 `images/${seriesKey}/${fileName}` 규칙을 사용하므로, `imgSrc`에는 파일명 기준 일관성이 중요하다.
- 릴리즈 버전이 바뀌면 최소한 아래 두 곳은 함께 맞춘다.
- `index.html`의 `<title>` 및 상단 배지
- `docs/spec.md`, `docs/update.md`, 필요 시 `docs/history.md`
## 품질 기준 현재 버전은 루트 `VERSION`을 기준으로 확인한다.
- 신규 시리즈 반영 시 데이터 파일만 추가하고 번역 키를 빼먹지 않는다.
- 카드 번호(`Number`) 그룹과 실제 파일명 규칙이 시리즈 키와 일치해야 한다.
- 병렬 카드(`parallel: true`)는 일반 카드와 같은 카드 번호를 공유하되 이미지 파일만 다르게 유지한다.
- `trigger`는 `null`, `COLOR`, `FINAL`, `SPECIAL`만 사용한다.
- AP 카드의 `color`는 `AP`를 사용한다.
- 릴리즈 전에 `git diff --stat`으로 변경 범위를 보고, 데이터/번역/이미지 중 빠진 축이 없는지 확인한다.
## 작업 절차 ## 프로젝트 핵심
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`, 필요한 경우 간단한 구문 체크를 수행한다.
## 릴리즈 전 체크리스트 - 정적 HTML·JavaScript 프론트와 Express·SQLite API를 함께 사용한다.
- `datas/<series>.js`가 실제로 import 가능한 형식인지 확인 - 운영은 `docker-compose.prod.yml`의 `uniare-web`, `uniare-api` 구성이 기본이다.
- `images/<series>/` 파일이 데이터의 `imgSrc`와 대응되는지 확인 - 덱 공유는 `/d/<shareCode>`와 `GET /api/v1/decks/:shareCode`를 사용한다.
- `translations.ko/ja/en`에 시리즈 키가 모두 존재하는지 확인 - `?cards=<JSON>` 공유 방식은 사용하지 않는다.
- 버전 표기가 문서와 UI에서 일치하는지 확인 - 표시 브랜드는 UNIARE지만 `UaDeckApi`, `uadeck_token`, `uadeck.db` 등 내부 식별자는 호환을 위해 유지한다.
- `images/**`가 계속 LFS로 잡히는지 확인
- 커밋 메시지에 릴리즈 의도가 드러나는지 확인
## 커밋 메시지 가이드 ## 카드 데이터 작업
- 릴리즈 커밋 예시: `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/` 운영 파일을 커밋하지 않는다.

View File

@@ -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 규칙 - 현재 버전은 프로젝트 루트 `VERSION`을 기준으로 한다.
- 정적 페이지는 의미 단위별로 `header`, `main`, `aside` 등을 우선 사용한다. - 커밋을 포함하는 완료 작업은 버전을 증가시키고 `VERSION`, `docs/update.md`, 필요 시 `docs/changelog.md`를 함께 갱신한다.
- 페이지별 스크립트가 길어질 경우 신규 기능은 별도 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 규칙
- 커밋 메시지는 한국어로 작성한다. - 커밋 메시지는 한국어로 작성한다.
- 버전 상승 작업은 `docs/update.md` 반영 후 태그명과 맞춘다.
- 민감 정보, 로컬 경로, 개인 식별 정보가 커밋되지 않도록 확인한다.
## 이미지 자산 규칙 ## 카드 데이터
- `images/**`는 `Git LFS`로 관리한다.
- 이미지 파일을 새로 추가하거나 교체할 때는 일반 Git 객체로 들어가지 않도록 `.gitattributes` 설정을 유지한다. - 시리즈 파일은 `datas/uaXX.js` 또는 `datas/uapr.js` 형식을 사용한다.
- 대량 이미지 추가 전에는 원격 저장소의 LFS 정책과 비용 한도를 확인한다. - 카드 객체의 호환 필드 `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`: 반복 적용 규칙

View File

@@ -1,195 +1,180 @@
# NAS 배포 가이드 (UNIARE v4) # UNIARE 실행·배포 가이드
## 1. 사전 준비 필요한 부분만 골라서 읽으면 된다.
- Ugreen NAS에 Docker(또는 Container Manager) 설치 1. 내 컴퓨터에서 실행: `1. 로컬 실행`
- 프로젝트를 NAS에 클론 또는 동기화 2. NAS에 처음 설치: `2. NAS 최초 배포`
- `Git LFS`로 이미지 자산 pull 완료 확인 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 ```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 cp .env.example .env
``` ```
| 변수 | 설명 | `.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 JWT_SECRET=32자_이상의_랜덤_문자열
ADMIN_EMAIL=admin@uniare.sori.studio ADMIN_EMAIL=관리자_이메일
ADMIN_PASSWORD=your-secure-password ADMIN_PASSWORD=8자_이상의_비밀번호
ADMIN_USERNAME=admin
WEB_PORT=44082 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 서버**가 떠 있어야 합니다. API 확인: `curl -fsS http://localhost:44082/api/v1/health`
HTML만 Live Server 등으로 열면 저장·로그인이 **405** 로 실패합니다.
### 빠른 시작 (권장) 정상이면 `{"ok":true,"version":"4.0.1"}`이 표시된다.
```bash NAS 리버스 프록시는 `https://uniare.sori.studio`을 `http://<NAS_IP>:44082`로 연결한다.
npm start # 백그라운드 실행 (터미널 점유 안 함)
# 브라우저: http://localhost:44082 ## 3. 정적 파일 업데이트
npm run logs # 로그 보기 (종료: Ctrl+C) 이미지, 카드 데이터, HTML, CSS, 프론트 JavaScript는 Docker 이미지를 다시 빌드하지 않아도 된다.
npm stop # 서버 종료
``` ### 이미지만 변경
| 명령 | 설명 | NAS 프로젝트 폴더에서 `git pull --ff-only`와 `git lfs pull`을 실행한 뒤 브라우저를 강력 새로고침한다. 컨테이너 재시작은 필요 없다.
|---|---|
| `npm start` | Docker로 웹+API 백그라운드 기동 (**일상 개발용**) | ### `datas/*.js` 변경
| `npm stop` | 서버 종료 |
| `npm run logs` | API·Nginx 로그 스트리밍 | NAS 프로젝트 폴더에서 다음 명령을 실행한다.
| `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 재시작만 하면 됩니다:
```bash ```bash
git pull --ff-only
git lfs pull
docker compose -f docker-compose.prod.yml restart uniare-api docker compose -f docker-compose.prod.yml restart uniare-api
``` ```
## 9. 트러블슈팅 API가 카드 데이터를 메모리에 저장하므로 `datas/` 변경 시 API만 재시작한다. 이미지와 `datas/*.js`를 함께 변경한 경우도 같은 절차를 사용한다.
| 증상 | 확인 | 확인할 내용:
|---|---|
| 로그인 안 됨 | `JWT_SECRET` 설정, API health 확인 | - `imgSrc`와 실제 이미지 파일명이 같은가?
| 공유 링크 404 | Nginx `/d/` 리다이렉트 규칙 | - 파일명 대소문자가 같은가?
| CORS 오류 | `CORS_ORIGINS`에 옆 프로젝트 도메인 추가 | - 신규 시리즈가 번역 파일 3개에 등록되어 있는가?
| 덱 카드 메타 누락 | `datas/<series>.js` 존재 여부, API `DATAS_PATH` | - 카드 목록과 저장 덱에서 모두 정상 표시되는가?
### 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에 포함되지 않았는가?

View File

@@ -1,82 +1,121 @@
# 의사결정 이력 # 의사결정 이력
## 2026-06-22 / NAS 운영 네이밍 정리 (`uniare-*`) ## 2026-06-23 v4.0.0 — SQLite 백업은 온라인 백업 API를 사용
- 배경: NAS에 기존 `uniare-web` 컨테이너와 신규 `uadeck-web` 컨테이너가 공존하면서 포트 충돌 및 식별 혼선이 발생했다.
- 결정:
- Docker Compose 서비스/컨테이너 명칭을 프로젝트 식별자 기준 `uniare-*`로 통일한다.
- Nginx 내부 프록시 대상도 서비스명 변경에 맞춰 `uniare-api`로 함께 정렬한다.
- 이유:
- 운영 시 `docker ps`, 로그 확인, 재시작 명령에서 컨테이너 목적을 즉시 식별하기 위함.
- 기존 운영 스택(`uniare`)과 명칭 일관성을 맞춰 재배포 실수를 줄이기 위함.
## 2026-06-19 / v4.0.0 구현 완료 💡 WAL 모드에서 DB 본파일만 복사하면 아직 병합되지 않은 최신 트랜잭션이 누락될 수 있기 때문.
- 배경: 설계 확정 후 회원·API·공유 시스템 1차 구현. 🔧 운영 중에는 `better-sqlite3`의 `backup()`으로 일관된 백업본을 만든 뒤 컨테이너 밖으로 복사.
- 반영: `api/` 백엔드, Docker Compose, 인증/덱/관리자 API, 프론트 신규 페이지 및 헤더 프로필 UI.
- 배포: NAS Docker + Nginx `/api/`, `/d/` 프록시 필요.
## 2026-06-19 / v4.0.0 덱 공유·API 설계 확정 ## 2026-06-23 v4.0.0 — 헤더 로고 규격을 공통 스타일로 관리
- 배경: 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-04-19 / v3.1.0 릴리즈 기준 확정 💡 페이지마다 로고 크기를 따로 지정하면 화면 이동 시 높이가 달라지고 수정 지점도 늘어나기 때문.
- 배경: 저장소의 마지막 명시 릴리즈 기록은 `v3.0.1`인데, 메인 화면은 이미 `v3.1`을 노출하고 있어 실제 릴리즈 이력과 화면 표기가 어긋나 있었다. 🔧 `style/brand.css`의 `.app-brand-logo`와 `--brand-logo-height`를 모든 헤더의 단일 기준으로 채택.
- 결정:
- 이번 반영분을 `v3.1.0`으로 정의하고, 화면 표기와 문서 표기를 같은 버전으로 통일한다.
- 이후 버전 증가는 `docs/update.md`에 먼저 기록한 뒤 화면 표기와 함께 반영한다.
- 이유:
- 외부 협업자나 후속 Codex 작업자가 "현재 배포 기준이 무엇인지" 즉시 판단할 수 있어야 한다.
- 자산 중심 정적 프로젝트 특성상 코드보다 데이터/이미지 변경이 잦기 때문에, 명시 릴리즈 기준이 없으면 누락 여부를 추적하기 어렵다.
## 2026-03-30 / 문서 체계 초기화 ## 2026-06-23 v4.0.0 — 표시 브랜드는 UNIARE, 내부 식별자는 호환 유지
- 배경: 프로젝트 루트에 AI 작업 규칙은 있었지만 `docs/` 실체가 없어 규칙과 실제 운영 상태가 분리되어 있었다.
- 결정: 규칙 문서가 요구하는 핵심 문서 6종을 먼저 생성하고, 이후 변경 시 이 파일들을 기준 문서로 유지한다.
- 기대 효과: 작업 내역, 기술 구조, 화면-파일 연결, 향후 할 일을 한곳에서 추적할 수 있다.
## 2026-03-30 / 이미지 관리 방식 1차 판단 💡 사용자에게 보이는 제품명은 통일하되 기존 API 클라이언트와 저장 데이터의 호환성은 깨지 않기 위함.
- 배경: `images/` 디렉터리 용량이 약 `1.7G`, 파일 수가 `9,370개` 수준으로 증가했다. 🔧 화면·문서·다운로드 파일명은 `UNIARE`로 변경하고 `uadeck_token`, `uadeck.db`, `UaDeckApi`, `/api/v1`은 유지.
- 관찰:
- 개별 파일이 아주 크지 않아도 Git 일반 객체로 누적되면 clone 및 fetch 비용이 계속 증가한다.
- 이미지 추가가 잦은 프로젝트 특성상 저장소 히스토리 비대화가 반복될 가능성이 높다.
- 결정:
- 신규 이미지 유입 관리 목적이라면 `Git LFS` 도입을 권장한다.
- 단, 이미 커진 저장소를 즉시 가볍게 만드는 해결책으로 `Git LFS`만 기대해서는 안 된다.
- 히스토리 크기까지 줄여야 하면 `git lfs migrate` 또는 자산 저장소 분리/CDN 이전을 별도 의사결정으로 다룬다.
- 보류 사유:
- 원격 저장소의 LFS 지원 여부, 무료 용량, 대역폭 정책을 아직 확인하지 않았다.
- 기존 협업자들의 개발 환경에 `git lfs` 설치를 요구하게 되므로 도입 공지가 필요하다.
## 2026-03-30 / 이미지 히스토리 전체 LFS 마이그레이션 결정 ## 2026-06-23 v4.0.0 — 주요 화면 너비를 1280px로 통일
- 배경: 목적이 "앞으로 예방"이 아니라 "기존 저장소 용량 절감"으로 명확해졌다.
- 확인:
- 로컬 환경에 `git lfs`가 설치되어 있다.
- 원격 저장소에 LFS 엔드포인트가 설정되어 있다.
- 결정:
- `images/**` 전체 히스토리를 `Git LFS`로 마이그레이션한다.
- 현재 작업 중인 `ua27` 데이터 및 이미지 추가분도 함께 반영한다.
- 영향:
- 기존 커밋 해시가 모두 재작성된다.
- 원격 반영 시 강제 푸시가 필요하다.
- 협업 중인 다른 클론은 재동기화 절차가 필요하다.
## 2026-03-30 / 새 루트 커밋 기준 저장소 재시작 결정 💡 페이지 이동 때 헤더와 본문 폭이 달라지는 시각적 흔들림을 줄이고 광고 레일을 안정적으로 배치하기 위함.
- 배경: `images/**`는 LFS 포인터로 전환됐지만, 저장소 자체는 과거 Git 객체 영향으로 여전히 비대했다. 🔧 홈·편집·결과·프로필·덱 목록의 공통 최대 너비를 `80rem` 기준으로 정렬.
- 결정:
- 현재 로컬 작업본을 단일 기준 스냅샷으로 보고 새 루트 커밋을 만든다. ## 2026-06-22 v4.0.0 — Docker 서비스 이름을 `uniare-*`로 통일
- 원격 `main`은 새 루트 커밋으로 교체한다.
- 기존 히스토리는 로컬 백업 브랜치에서만 보존한다. 💡 NAS에서 구·신규 컨테이너가 혼재할 때 포트 충돌과 운영 명령 실수를 줄이기 위함.
- 새 버전 체계는 `v3.0.0`부터 다시 시작한다. 🔧 Compose 서비스·컨테이너·볼륨과 Nginx 프록시 대상을 `uniare-web`, `uniare-api`, `uniare-data`로 정렬.
- 이미지 자산은 새 시작 이후에도 `Git LFS` 정책을 유지한다.
- 이유: ## 2026-06-22 v4.0.0 — 운영 웹 컨테이너는 허용 자산만 읽기 전용 공개
- 가장 확실하게 Git 일반 히스토리 용량을 줄일 수 있다.
- 추가적인 서버 GC 상태나 숨은 과거 객체를 더 추적하지 않고도 정리 목적을 달성할 수 있다. 💡 프로젝트 루트 전체를 웹 루트로 노출하면 `.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`을 역할별 기준 문서로 운영.

View File

@@ -1,63 +1,164 @@
# 파일-화면 매핑 가이드 # 파일-기능 지도
## `/` ## 화면
- 진입 파일: `index.html`
- 화면 역할: 시리즈 선택 홈 화면
- 연결: `script/auth-header.js`, `script/api.js`
## `/cardList.html?series=<key>&d=<shareCode>` ```text
- 진입 파일: `cardList.html` /
- 화면 역할: 카드 검색·필터·덱 편집·저장·공유 index.html
- `?d=` 있으면 API에서 덱 복원
- 연결: `datas/<seriesKey>.js`, `script/api.js`
## `/result.html` /cardList.html
- 진입 파일: `result.html` cardList.html
- 화면 역할: 덱 미리보기·PNG 저장·내 덱 저장
- 데이터 소스: `localStorage.selectedCards`
- 하단 고정 바: `홈으로` | `이미지로 다운로드` | `내 덱으로 저장` (공개 여부 확인 모달)
- `script/deck-prefs.js`: 덱 공개 기본값·프롬프트 설정
## `/d/<shareCode>` (Nginx 리다이렉트) /result.html
- `nginx/uadeck.conf` 규칙으로 `cardList.html?d=` 로 이동 result.html
- 옆 프로젝트 연동: `GET /api/v1/decks/<shareCode>`
## `/login.html`, `/signup.html` /login.html
- 인증 화면 login.html
- 연결: `script/page-i18n.js` (`data-i18n` 속성)
## `/profile.html` (내 프로필) /signup.html
- 로그인 사용자: 이메일·사용자명·프로필 사진·비밀번호 변경 signup.html
- 연결: `script/api.js` (`updateProfile`, `changePassword`), `script/image-utils.js`, `script/page-i18n.js`, `script/ads.js`, `style/profile.css`
## `/deckHistory.html` /profile.html
- 로그인 사용자의 저장 덱 목록·불러오기·삭제 profile.html
- 카드 그리드 + 미리보기 썸네일 (`preview_url`), 클릭·길게 눌러 확대 (`deck-preview-hold.js`)
- 시리즈 필터·검색 (`style/deck-history.css`)
- 연결: `script/page-i18n.js`, `script/ads.js` (우측 AdSense `4260332296`)
## `/preset.html` (공개 덱) /deckHistory.html
- 사용자가 공개 전환한 덱 목록 (카드 그리드) deckHistory.html
- 썸네일 클릭·길게 눌러 확대 (`deck-preview-hold.js`, 안내 문구 없음)
- API: `GET /api/v1/decks/presets` → `is_public=1` user 덱
- 연결: `script/page-i18n.js`, `script/ads.js`
## `/admin.html` /preset.html
- 관리자: 회원 관리, 덱 관리 (`style/admin.css`) preset.html
## API (`api/`) /admin.html
- Docker: `docker-compose.yml` admin.html
- 엔드포인트 베이스: `/api/v1`
## 공통 /d/<shareCode>
- `style/app-shell.css`: 헤더·본문 최대 너비 1280px (`max-w-7xl`), 데스크톱 우측 광고 레일 nginx/default.conf
- `script/ads.js`: Google AdSense `ins.adsbygoogle` 슬롯 초기화 nginx/uadeck.conf
- `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) ```text
- `script/api.js`: API 클라이언트 (`updateProfile`, `changePassword` 포함) API 요청
- `script/auth-header.js`: 헤더 프로필 드롭다운 (i18n), 로그인 시 **썸네일 옆 닉네임** 표시 (`auth-profile-trigger`) script/api.js
- `script/image-utils.js`: 클라이언트 이미지 PNG 리사이즈 (`UaImageUtils.resizeImageToPng`)
- `style/auth-header.css` 인증 헤더
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
```

View File

@@ -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 한다. | `/` | 시리즈 선택, 언어 전환, 인증 헤더 |
- 필터 기능: | `/cardList.html?series=<key>` | 카드 검색·필터, 수량 조절, 키 카드 지정, 덱 편집 |
- 검색어 | `/cardList.html?d=<shareCode>` | API 공유 덱 불러오기 및 편집 |
- 색상(`All`, `Red`, `Blue`, `Green`, `Yellow`, `Purple`) | `/result.html` | 덱 미리보기, PNG 다운로드, 덱 저장 |
- 시리즈 하위 그룹 | `/login.html` | 로그인 |
- 타입(`Basic`, `AP`, `Parallel`) | `/signup.html` | 회원가입 |
- 트리거(`All`, `Special`, `Color`, `Final`) | `/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 ```js
{ {
imgSrc: '/ua24/UA24BT_SHY-1-001.png', imgSrc: '/ua24/UA24BT_SHY-1-001.png',
@@ -50,209 +43,143 @@
parallel: false, parallel: false,
rarity: 'C', rarity: 'C',
trigger: null, trigger: null,
maxCount: 4,
} }
``` ```
### 필드 의미 - `Number`가 같은 판본은 동일 카드 수량 그룹으로 처리한다.
- `imgSrc`: 원본 상대 식별자. 실제 렌더링 시 `images/${seriesKey}/${fileName}` 형태로 재구성한다. - `parallel`은 패러렐 판본 구분에 사용한다.
- `Number`: 카드 번호. 동일 카드 판본 묶음과 필터 기준으로 사용한다. - `trigger`는 `null`, `COLOR`, `FINAL`, `SPECIAL` 값을 사용한다.
- `count`: 현재 덱에 담긴 수량. - `maxCount`가 없으면 기본 최대 수량은 4장이다.
- `key`: 키 카드 여부.
- `color`: 카드 색상 또는 일부 특수 분류 값.
- `parallel`: 패러렐 여부.
- `rarity`: 희귀도 표기.
- `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/uapr/comingsoon.png`
- 현재 용량 현황: - 브랜드 자산: `images/assets/`
- `images/` 약 `1.7G` - `images/**`는 Git LFS로 관리한다.
- 이미지 파일 약 `9,370개` - 아바타와 덱 미리보기는 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) | `lang` | 선택 언어 |
- `localStorage.selectedSeries`: 현재 시리즈 키 | `selectedCards` | 편집 중인 덱 |
- `localStorage.uadeck_token` / `uadeck_user`: 로그인 세션 | `selectedSeries` | 현재 시리즈 |
- 덱 공유·영구 저장: API + DB (`GET /api/v1/decks/:shareCode`) | `uadeck_token` | JWT |
| `uadeck_user` | 로그인 사용자 캐시 |
| 덱 즐겨찾기 키 | 공유 코드 기반 로컬 즐겨찾기 |
## 7. 스타일 및 자산 ## 인증 및 권한
- Tailwind 유틸리티 클래스와 페이지별 인라인 스타일을 혼용한다.
- 공통 스타일 파일:
- `style/output.css`
- `style/style.css`
- `style/navigation.css`
## 8. 알려진 제약 - 비밀번호는 bcrypt 비용 12로 해시한다.
- `cardList.html`과 `result.html`의 스크립트가 페이지 내부에 크게 포함되어 있어 모듈 분리 전까지는 변경 영향 범위가 넓다. - JWT 기본 만료는 7일이며 `JWT_EXPIRES`로 변경할 수 있다.
- `script/navigation.js`의 일부 이동 대상 페이지는 현재 저장소에 존재하지 않는다. - `user`는 자신의 프로필과 덱만 수정·삭제할 수 있다.
- 이미지가 Git 일반 객체로 계속 누적되면 저장소 히스토리 크기가 지속 증가한다. - `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 아키텍처 개요 ### `users`
- 프론트: 기존 정적 HTML/JS 유지. 헤더에 프로필·인증 UI 추가.
- 백엔드: Docker 컨테이너 API 서버 (NAS 배포).
- 카드 DB: `datas/*.js` + `images/` 정적 유지 (변경 없음).
- 사용자·덱 데이터: API + SQLite(또는 PostgreSQL) DB.
- 외부 연동(옆 프로젝트): `GET /api/v1/decks/:shareCode` 단일 계약.
### 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이 다름 | | `share_code` | 고유 공유 코드 |
| 메타 덱·프리셋 | `preset` | 내용 해시 8자 (동일 구성 = 동일 URL) | `/d/a7f3b2c1` | 관리자 등록 공식 템플릿, 중복 저장 없음 | | `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` | 생성·수정 시각 |
브라우저 진입: ## API
- `/d/:shareCode` → `cardList.html?d=:shareCode` 리다이렉트 또는 동일 페이지에서 API 로드
- `cardList.html`은 `?d=` 파라미터가 있으면 API에서 덱 복원
### 9.3 DB 저장 vs API 응답 ### 공통
**DB에 저장하는 최소 덱 본문 (`cards_json`):** | Method | Path | 권한 | 기능 |
```json |---|---|---|---|
[ | GET | `/api/v1/health` | 공개 | API 상태와 버전 |
{ "number": "UA36BT/MCR-1-052", "count": 4, "key": true },
{ "number": "EX14BT/MCR-2-011", "count": 2, "key": false }
]
```
**API 응답 시 서버가 `datas/<series>.js`를 lookup하여 풀 카드 정보를 합성한다.** ### 인증
```json | Method | Path | 권한 | 기능 |
{ |---|---|---|---|
"v": 1, | POST | `/api/v1/auth/register` | 공개 | 회원가입 |
"shareCode": "x7Kp9m2Q", | POST | `/api/v1/auth/login` | 공개 | 로그인과 JWT 발급 |
"shareType": "user", | GET | `/api/v1/auth/me` | 회원 | 내 정보 |
"series": "ua36", | PUT | `/api/v1/auth/me` | 회원 | 프로필·덱 저장 설정 변경 |
"seriesName": "마크로스", | POST | `/api/v1/auth/me/password` | 회원 | 비밀번호 변경 |
"name": "블루 aggro", | GET | `/api/v1/auth/avatar/:userId` | 공개 | PNG 아바타 |
"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
}
```
비회원 임시 공유: `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 | | | GET | `/api/v1/admin/users` | 회원 목록·검색 |
| `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/:id` | 회원 상세 | | 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/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 배포 - 현재는 Tailwind 유틸리티, 공통 CSS, 페이지 전용 CSS, 일부 인라인 스타일을 혼용한다.
- `docker-compose.yml`: API만 (기존 웹서버 사용 시) - 이번 단계에서는 기존 CSS를 Tailwind로 일괄 전환하지 않는다.
- `docker-compose.prod.yml`: Nginx + API 풀스택 (NAS 권장) - `style/output.css`는 `style/input.css`에서 생성하는 빌드 산출물이다.
- `docs/deploy.md`: NAS 배포 상세 가이드 - 헤더 로고 크기는 `style/brand.css`만 기준으로 사용한다.
- `docs/api-integration.md`: 옆 프로젝트 연동 가이드 - 기본 글꼴은 Pretendard 계열을 우선 사용한다.
- `integrations/uadeck-deck-client.js`: 연동용 JS 클라이언트
- `nginx/default.conf`: `/api/`, `/d/` 라우팅 ## 알려진 제약
- `cardList.html`, `result.html`의 인라인 스크립트가 커서 변경 영향 범위가 넓다.
- `admin.html`은 3개 언어 번역 적용이 완료되지 않았다.
- 즐겨찾기는 브라우저별 로컬 상태라 계정 간 동기화되지 않는다.
- Homepage 전용 `GET /api/homepage` 엔드포인트는 아직 구현되지 않았다.
- 현재 Compose의 컨테이너명(`uniare-api`, `uniare-web`)과 데이터 볼륨명(`uniare-data`)은 고정값이며 환경 변수화되지 않았다.

View File

@@ -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 구현 완료 항목 - NAS에서 `uniare-web`, `uniare-api` 스택의 재기동과 헬스 체크를 최종 확인한다.
- [x] Docker API 서버 + DB (`users`, `decks` 테이블) - 운영 `.env`의 `JWT_SECRET`, 관리자 계정, CORS 허용 도메인을 실제 환경 기준으로 점검한다.
- [x] 인증: 회원가입, 로그인, JWT, 헤더 프로필 드롭다운 - DB와 업로드 이미지(`api/data/previews`, `api/data/avatars`)의 정기 백업·복구 절차를 검증한다.
- [x] 덱 API: 저장, 조회, 공유 코드 (`GET /api/v1/decks/:shareCode`) - 원격 저장소의 Git LFS 업로드 상태와 용량 정책을 확인한다.
- [x] `cardList.html`: `?d=` API 로드, 저장·공유 버튼 - Compose의 컨테이너명과 데이터 볼륨명을 환경 변수로 관리할지 결정하고, 적용 시 운영 마이그레이션 절차를 작성한다.
- [x] `deckHistory.html`, `login.html`, `signup.html`, `preset.html`
- [x] `admin.html`: 회원 목록·수정, 비밀번호 초기화
- [x] `index.html` 버전 `v4.0.0`
## 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 최근 완료 - `admin.html`의 고정 한국어 문구를 `ko`, `ja`, `en` 번역 체계에 연결한다.
- [x] `profile.html`: 일반 사용자 프로필 화면 (아바타·닉네임·이메일·비밀번호) - 옆 프로젝트에 `integrations/uadeck-deck-client.js`를 적용하고 실제 공유 덱 조회를 검증한다.
- [x] 헤더 드롭다운「내 프로필」링크 - Homepage 연동이 필요해지면 `GET /api/homepage`와 운영 지표 응답을 구현한다.
- [x] 아바타 업로드 시 PNG 리사이즈 (`script/image-utils.js`) — JPEG 등 형식 깨짐 방지
- [x] 아바타 URL `updatedAt` 캐시 무효화
- [x] 내 덱 관리: `/d/코드` 경로 표시 제거
- [x] `result.html` 하단 버튼 개편 + 공개 확인 모달 + 홈 이동
- [x] 프로필·API: 덱 저장 공개 기본값 설정
## 다음 작업 제안 ## 유지보수
- 원격 저장소에서 `Git LFS` 업로드가 정상 완료되었는지 확인한다.
- 협업자가 있다면 히스토리 재작성 이후 동기화 방법을 공지한다.
- `Git LFS` 도입 후에도 저장소 운영 비용이 크면 이미지 저장소 분리/CDN 이전을 재검토한다.
- `cardList.html`의 인라인 스크립트를 별도 JS 모듈로 분리하는 리팩터링을 검토한다.
## 판단 가이드 - `cardList.html`, `result.html`의 대형 인라인 스크립트를 기능 단위 모듈로 분리한다.
- 신규 이미지 추가 관리가 목적이면 `Git LFS` 추적만으로도 충분하다. - `datas/ua99 sample.js`, `datas/ua99 sample mini.js`의 보존 목적과 배포 제외 여부를 확정한다.
- 기존 clone 크기까지 줄이는 것이 목적이면 `Git LFS` 히스토리 마이그레이션 또는 자산 저장소 분리가 필요하다. - 즐겨찾기의 계정 간 동기화가 필요하면 사용자별 API 저장 방식으로 확장한다.
- 이미지 규모가 Git LFS 운영 한도를 넘으면 자산 저장소 또는 CDN 분리를 검토한다.

View File

@@ -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 — 헤더 로고 크기 통일 ## 2026-06-22 / v4.0.0 — 헤더 로고 크기 통일
### 수행 작업 ### 수행 작업

78
docs/widget.md Normal file
View File

@@ -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 백업 시각과 성공 여부
- 미리보기·아바타 저장 공간 사용량
회원 수나 덱 수처럼 운영 정보가 포함된 엔드포인트는 공개 범위를 검토하고, 필요하면 내부 네트워크 또는 별도 토큰으로 제한한다.

View File

@@ -42,7 +42,7 @@
</p> </p>
</main> </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/page-i18n.js"></script>
<script src="/script/api.js"></script> <script src="/script/api.js"></script>