# 기술 명세 ## 프로젝트 개요 - 서비스명: `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` 등 내부 레거시 식별자는 호환을 위해 유지한다. ## 화면 기능 | 경로 | 기능 | |---|---| | `/` | 시리즈 선택, 언어 전환, 인증 헤더 | | `/cardList.html?series=` | 카드 검색·필터, 수량 조절, 키 카드 지정, 덱 편집 | | `/cardList.html?d=` | API 공유 덱 불러오기 및 편집 | | `/result.html` | 덱 미리보기, PNG 다운로드, 덱 저장 | | `/login.html` | 로그인 | | `/signup.html` | 회원가입 | | `/profile.html` | 이메일·사용자명·아바타·비밀번호·덱 공개 기본값 관리 | | `/deckHistory.html` | 내 덱 검색·필터·이름 변경·공개 설정·삭제·불러오기 | | `/preset.html` | 공개 사용자 덱 검색·필터·즐겨찾기·불러오기 | | `/admin.html` | 회원과 덱 관리 | | `/d/` | Nginx에서 `cardList.html?d=`로 연결 | ## 카드 데이터 - 데이터 위치: `datas/*.js` - 실제 시리즈 데이터: `ua01`~`ua54` 중 현재 존재하는 파일과 `uapr.js` - 샘플 데이터: `ua99 sample.js`, `ua99 sample mini.js` - 모듈 형식: `export const cardList = [...]` ```js { imgSrc: '/ua24/UA24BT_SHY-1-001.png', Number: 'UA24BT/SHY-1-001', count: 0, key: false, color: 'Red', parallel: false, rarity: 'C', trigger: null, maxCount: 4, } ``` - `Number`가 같은 판본은 동일 카드 수량 그룹으로 처리한다. - `parallel`은 패러렐 판본 구분에 사용한다. - `trigger`는 `null`, `COLOR`, `FINAL`, `SPECIAL` 값을 사용한다. - `maxCount`가 없으면 기본 최대 수량은 4장이다. ## 이미지 - 카드 이미지: `images//` - 폴백 이미지: `/images/uapr/comingsoon.png` - 브랜드 자산: `images/assets/` - `images/**`는 Git LFS로 관리한다. - 아바타와 덱 미리보기는 API 데이터 디렉터리에 저장하며 Git에 포함하지 않는다. - 덱 저장 미리보기는 1200px 기준 PNG로 정규화한다. ## 브라우저 상태 | 키 | 용도 | |---|---| | `lang` | 선택 언어 | | `selectedCards` | 편집 중인 덱 | | `selectedSeries` | 현재 시리즈 | | `uadeck_token` | JWT | | `uadeck_user` | 로그인 사용자 캐시 | | 덱 즐겨찾기 키 | 공유 코드 기반 로컬 즐겨찾기 | ## 인증 및 권한 - 비밀번호는 bcrypt 비용 12로 해시한다. - JWT 기본 만료는 7일이며 `JWT_EXPIRES`로 변경할 수 있다. - `user`는 자신의 프로필과 덱만 수정·삭제할 수 있다. - `admin`은 회원 상태·역할·비밀번호와 전체 덱을 관리할 수 있다. - 관리자는 자신의 관리자 역할을 해제하거나 자신의 계정을 비활성화할 수 없다. - 비공개 덱은 소유자와 관리자만 조회할 수 있다. ## 덱 저장 정책 - 공유 코드는 저장 시 생성되는 8자 코드다. - 회원 덱은 만료 없이 저장하며 공개 여부를 선택할 수 있다. - 비회원 덱은 공개 상태로 저장되고 30일 뒤 만료된다. - DB의 `cards_json`에는 `{number, count, key}`만 저장한다. - API 응답 시 `datas/.js`에서 이미지·색상·희귀도·트리거 정보를 합성한다. - 공개 덱 목록은 `share_type='user'`, `is_public=1`, 미만료 조건을 사용한다. - `preset` 타입과 관리자 프리셋 API는 레거시 호환용으로 코드에 남아 있으며 현재 공개 덱 화면에서는 사용하지 않는다. ## 데이터베이스 ### `users` | 컬럼 | 설명 | |---|---| | `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` | 생성·수정 시각 | ### `decks` | 컬럼 | 설명 | |---|---| | `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` | 생성·수정 시각 | ## API ### 공통 | Method | Path | 권한 | 기능 | |---|---|---|---| | GET | `/api/v1/health` | 공개 | API 상태와 버전 | ### 인증 | 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 아바타 | ### 덱 | 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` | 소유자·관리자 | 덱 삭제 | ### 관리자 | Method | Path | 기능 | |---|---|---| | GET | `/api/v1/admin/users` | 회원 목록·검색 | | GET | `/api/v1/admin/users/:id` | 회원 상세 | | PUT | `/api/v1/admin/users/:id` | 회원 정보·역할·활성 상태 변경 | | POST | `/api/v1/admin/users/:id/reset-password` | 비밀번호 초기화 | | GET | `/api/v1/admin/decks` | 전체 덱 목록 | | DELETE | `/api/v1/admin/decks/:shareCode` | 덱 삭제 | | POST | `/api/v1/admin/decks/preset` | 레거시 프리셋 생성 | ## 스타일 - 현재는 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`)은 고정값이며 환경 변수화되지 않았다.