Files
tier-maker/docs/spec.md

18 KiB

기술 명세

현재 아키텍처

  • 프런트엔드: Vue 3 + Vite + Pinia + Vue Router
  • 백엔드: Express 5
  • 데이터 저장소: MariaDB(MySQL 호환)
  • 세션 저장소: session-file-store 기반 파일 세션
  • 업로드 저장소: 로컬 파일 시스템(backend/uploads/)
  • 운영 배포: frontend(Nginx 정적 서빙 + /api,/uploads 프록시) + backend + mariadb Docker Compose 구조
  • NAS HTTPS 리버스 프록시 운영 시 프런트 Nginx는 백엔드로 X-Forwarded-Proto: https를 전달하고, Express 세션은 프록시 환경에서 secure 쿠키를 허용하도록 설정한다.
  • 프런트 파비콘은 운영 정적 파일 차단 영향을 줄이기 위해 index.html의 인라인 데이터 URL로 제공한다.
  • 프런트 앱 셸은 좌측 내비게이션 / 중앙 워크스페이스 / 우측 컨텍스트 패널 3단 구조로 재정의되었고, preview 모드에서는 이 셸을 숨기고 콘텐츠만 렌더링한다.
  • 좌측 패널은 248px, 우측 패널은 320px 기준 폭을 사용하며, 우측 패널은 상단 토글 버튼으로 접고 펼칠 수 있다.
  • 좌측 패널은 필요 시 축소형 레일로 접을 수 있으며, 접힌 상태에서는 아이콘 중심 내비게이션과 축약된 바로가기만 유지한다.
  • 이 3단 셸 구조는 홈, 게임 허브, 에디터, 관리자 등 일반 페이지 전반의 공통 뼈대로 유지하고, 페이지별 차이는 중앙/우측에 어떤 콘텐츠를 넣는지만 달라지도록 관리한다.
  • 비로그인 상태의 로그인 유도는 좌측 하단 버튼으로만 노출하고, 좌측 상단 사용자 카드 영역에는 별도 게스트 안내 카드를 렌더링하지 않는다.
  • 공통 셸의 좌측 내비, 우측 패널, 빠른 점프 버튼은 간단한 선형 SVG 아이콘과 두꺼운 카드형 버튼 문법을 공유한다.

데이터 저장 구조

  • 메인 데이터베이스: MariaDB tier_cursor (기본값)
  • 세션 파일: backend/.sessions/*.json
  • 업로드 파일:
    • 게임 이미지: backend/uploads/games/
    • 아바타: backend/uploads/avatars/
    • 커스텀 아이템: backend/uploads/custom/
    • 시드 이미지: backend/uploads/seeds/

화면 구조

  • 좌측 패널
    • 사용자 요약, 전체 공개 티어표 검색 입력, 주요 라우트 내비게이션, 최근 즐겨찾기 티어표 바로가기, 관리자 진입 버튼을 배치한다.
    • 상단 토글 버튼은 항상 고정되어 있고, 패널을 축소하면 텍스트를 숨기고 아이콘 중심 레일로 전환한다.
    • Settings는 별도 메뉴 항목으로만 진입하며, 사용자 카드 자체는 정보 표시 용도로만 사용한다.
    • 사용자 아바타는 원형 보더 스타일을 유지하고, Favorites 영역은 최근 즐겨찾기 티어표 최대 10개를 메인 메뉴보다 작은 밀도의 바로가기 목록으로 보여준 뒤 하단 즐겨찾기 더 보기 링크로 전체 즐겨찾기 화면에 연결한다.
  • 중앙 워크스페이스
    • 현재 라우트의 핵심 콘텐츠를 렌더링하는 영역이며, 홈/목록 계열 화면은 카드형 대시보드 레이아웃을 우선 적용한다.
    • 공통 workspaceBody는 별도 외곽 카드 테두리 없이 셸 여백만 제공하고, 실제 카드/패널 레이어는 각 화면 내부에서만 구성한다.
    • 홈, 게임 허브, 내 티어표, 즐겨찾기, 검색 결과 화면은 같은 카드 문법(상단 16:9 썸네일, 제목+좋아요 1행, 작성자+최종 수정일 1행)을 공유하며, 데스크톱 기준 기본 4열 카드 그리드를 사용한다.
    • 단, 홈 게임 선택 카드는 템플릿 선택용이므로 상단 메인 썸네일은 유지하되, 하단 메타는 게임명 + 작은 ID만 간결하게 표시한다.
    • 목록 계열 화면의 상단 도구 영역은 통계 카드와 액션 버튼을 공통 높이/반경으로 맞춰, 같은 라이브러리 대시보드로 읽히도록 정리한다.
  • 우측 패널
    • 현재 화면 문맥에 맞는 설명, 빠른 액션, 계정 상태 같은 보조 정보를 배치한다.
    • 에디터/관리자 세부 옵션은 후속 단계에서 이 패널로 점진 이관한다.
    • 공통 토글 버튼은 패널이 닫혀 있을 때 중앙 헤더, 열려 있을 때 우측 헤더에 각각 아이콘만 표시하는 방식으로 동작한다.
    • 오른쪽 패널 토글은 열기/닫기 모두 dock_to_left, 왼쪽 패널 토글은 dock_to_right 아이콘으로 통일한다.
    • 좌우 레일의 주요 CTA는 스크롤되는 본문과 분리된 하단 56px 액션 영역에 배치한다.
    • 하단 액션은 화면 바닥에 바로 붙지 않도록 푸터 내부에 추가 하단 여백을 둔다.
    • 홈 화면 기준 우측 패널은 임시 정보 카드 여러 개보다 핵심 CTA 하나만 남겨, 시안처럼 단순한 보조 레일 역할을 우선 유지한다.
  • 티어표 편집 화면
    • 공통 우측 패널 대신 전용 로컬 편집 패널을 사용한다.
    • 공통 workspaceBody 카드 컨테이너를 벗기고, 중앙 보드 영역은 메인 컬럼에, 우측 320px 편집 패널은 공통 셸의 세 번째 컬럼 aside에 배치한다.
    • 공통 상단 토글 버튼은 Teleport로 이동한 로컬 편집 패널의 접힘/펼침 상태와도 연결되어, 우측 패널을 숨기면 중앙 보드 영역이 확장된다.
    • 제목, 설명, 대표 썸네일, 공개 여부, 저장/삭제/요청 액션을 우측 로컬 패널에 배치한다.
    • 보드 바로 옆에는 드래그용 아이템 풀을 별도 패널로 두고, 커스텀 아이템 이름 정리 목록은 우측 편집 패널 내부에서 관리한다.
  • 관리자 화면
    • 공통 우측 패널 대신 전용 로컬 운영 패널을 사용한다.
    • 공통 workspaceBody 카드 컨테이너를 벗기고, 중앙 관리 목록은 메인 컬럼에, 우측 운영 패널은 공통 셸의 세 번째 컬럼 aside에 배치한다.
    • 우측 로컬 패널에는 게임/아이템/티어표/회원 관리 탭, 검색, 필터, 새로고침, 빠른 작업 제어를 배치하고, 중앙 영역에는 실제 관리 대상 목록과 상세만 렌더링한다.
    • 상단 헤더에는 현재 탭 기준 요약 통계 카드를 배치해 운영 상태를 먼저 읽고, 각 관리 카드는 공통 대시보드 카드 문법(두꺼운 반경, 얕은 레이어 배경, 강조된 액션 버튼)을 공유한다.

DB 스키마

  • users
    • id: string
    • email: string
    • nickname: string
    • passwordHash: string
    • isAdmin: boolean
    • avatarSrc: string
    • createdAt: number
  • games
    • id: string
    • name: string
    • thumbnailSrc: string
    • createdAt: number
    • 시스템 전용 freeform 레코드는 홈 화면의 직접 티어표 만들기 저장 대상이며 일반 게임 목록에서는 숨긴다.
  • gameItems
    • id: string
    • gameId: string
    • src: string
    • label: string
    • createdAt: number
  • customItems
    • id: string
    • ownerId: string
    • src: string
    • label: string
    • createdAt: number
  • tierLists
    • id: string
    • authorId: string
    • gameId: string
    • title: string
    • thumbnailSrc: string
      • 사용자가 직접 지정하지 않으면 저장 시 티어표 대표 아이템 이미지로 자동 채운다.
    • description: string
    • isPublic: boolean
    • groups: { id, name, itemIds[] }[]
    • pool: { id, src, label, origin }[]
    • createdAt: number
    • updatedAt: number
  • favoriteTierLists
    • userId: string
    • tierListId: string
    • createdAt: number
  • gameSuggestions
    • id: string
    • name: string
    • createdAt: number

주요 API

  • 인증
    • POST /api/auth/signup
    • POST /api/auth/login
    • POST /api/auth/logout
    • GET /api/auth/me
    • GET /api/auth/meta
    • POST /api/auth/profile
  • 게임
    • GET /api/games
    • GET /api/games/:gameId
  • 티어표
    • GET /api/tierlists/public
      • gameId 없이 q만 전달하면 전체 공개 티어표 검색에 사용한다.
    • GET /api/tierlists/me
    • GET /api/tierlists/favorites/me
    • GET /api/tierlists/:id
    • POST /api/tierlists/:id/template-request
    • POST /api/tierlists/:id/favorite
    • DELETE /api/tierlists/:id/favorite
    • DELETE /api/tierlists/:id
    • POST /api/tierlists/thumbnail
    • POST /api/tierlists/custom-items
    • POST /api/tierlists
  • 관리자
    • POST /api/admin/games
    • POST /api/admin/games/:gameId/thumbnail
    • POST /api/admin/games/:gameId/images
      • 여러 이미지를 한 번에 업로드할 수 있고, 별도 라벨이 없으면 파일명 기준으로 기본 아이템 이름을 만든다.
    • PATCH /api/admin/games/:gameId/items/:itemId
    • GET /api/admin/tierlists
    • GET /api/admin/template-requests
    • POST /api/admin/template-requests/:requestId/approve
    • POST /api/admin/template-requests/:requestId/reject
    • POST /api/admin/tierlists/:tierListId/promote-items
    • POST /api/admin/tierlists/:tierListId/create-game-template
    • GET /api/admin/custom-items
    • POST /api/admin/custom-items/:itemId/promote
    • DELETE /api/admin/custom-items/:itemId
    • DELETE /api/admin/custom-items
    • GET /api/admin/users
    • PATCH /api/admin/users/:userId
    • PATCH /api/admin/users/:userId/password
    • DELETE /api/admin/users/:userId
    • DELETE /api/admin/games/:gameId/items/:itemId
    • DELETE /api/admin/games/:gameId

관리자 화면 메모

  • 썸네일은 16:9 비율 미리보기 후 썸네일 적용 버튼으로 실제 반영한다.
  • 게임 기본 아이템 추가는 드래그 앤 드롭 또는 다중 파일 선택으로 처리하고, 미리보기 카드에서 여러 장을 함께 확인할 수 있다.
  • 현재 기본 아이템 목록에서는 등록된 아이템 이름을 직접 수정하고 저장할 수 있다.
  • 기본 아이템 이름 저장 버튼은 값이 실제로 바뀐 경우에만 활성화된다.
  • 아이템 미리보기는 반응형 환경에서도 최대 192px 크기 안에서 표시한다.
  • 게임 전환 또는 업로드 성공 뒤에는 파일 입력값을 초기화해 같은 파일도 다시 선택할 수 있다.
  • 게임 관리 탭에서는 홈 화면 상단에 먼저 노출할 게임을 최대 50개까지 지정하고, 드래그 또는 위/아래 버튼으로 순서를 저장할 수 있다.
  • 사용자 업로드 커스텀 아이템은 관리자 화면의 아이템 관리 탭에서 검색, 페이지네이션, 다운로드할 수 있다.
  • 사용자 커스텀 아이템은 선택한 게임의 기본 템플릿으로 복제해 가져올 수 있다.
  • 커스텀 아이템은 사용 횟수(usageCount)를 표시하며, 미사용 항목만 필터링해 개별/일괄 삭제할 수 있다.
  • 관리자 화면에는 별도 티어표 관리 탭이 있으며, 내부에서 템플릿 요청 관리 / 전체 티어표 관리를 분리해 볼 수 있고, 확인용 완성본은 탐색 UI 없는 preview 전용 모달로 연다.
  • 티어표 관리 탭의 추가 아이템은 작은 그리드 카드로 표시하고, 클릭 시 기존 템플릿에 추가 / 새 템플릿 만들기 모달을 통해 목적지를 선택한다.
  • 티어표 관리 탭에서는 티어표 안의 커스텀 아이템을 개별 또는 일괄로 기존 게임 템플릿에 복제할 수 있다.
  • freeform 티어표는 관리자 화면에서 새 게임 ID/이름을 입력해 새로운 게임 템플릿으로 복제 생성할 수 있다.
  • 관리자 티어표 관리 상단에는 사용자가 보낸 템플릿 등록/업데이트 요청 목록이 별도로 표시되며, 여기서 승인/반려를 바로 처리할 수 있다.
  • 관리자 템플릿 요청 목록에서 반려 후 숨김을 누르면 해당 요청은 pending 목록에서 즉시 제외된다.
  • 관리자 화면에서는 회원 이메일/닉네임/권한 수정, 비밀번호 초기화, 계정 삭제가 가능하다.
  • 회원 관리 카드에는 아바타, 작성 티어표 수, 최근 활동 시각을 함께 표시한다.

티어표 접근 메모

  • new 작성 경로는 로그인한 사용자만 진입할 수 있다.
  • 비로그인 사용자는 공개된 티어표를 열람만 할 수 있고, 편집 UI와 저장 동작은 비활성화된다.
  • 비공개 티어표라도 관리자는 편집 화면에서 완성본을 열람할 수 있다.
  • 공개 티어표는 목록과 상세 화면에서 즐겨찾기 토글 및 개수를 함께 표시한다.
  • 카드형 목록에서는 즐겨찾기 수/상태만 표시하고, 실제 토글은 상세 화면에서 처리한다.
  • 공개 티어표 목록은 현재 게임 기준으로 제목/작성자 검색을 지원한다.
  • 내 즐겨찾기 화면에서는 즐겨찾기한 순, 최신 업데이트순, 인기순 정렬을 제공한다.
  • 커스텀 이미지 추가는 다중 파일 선택과 드래그 앤 드롭을 모두 지원한다.
  • 사용자가 직접 추가한 커스텀 아이템 이름은 편집 화면 우측 목록에서 정리할 수 있고, 저장 시 원본 커스텀 아이템 라벨과 함께 동기화된다.
  • 티어 행은 기본 5단으로 시작하지만, 사용자가 직접 추가하거나 제거할 수 있다.
  • 티어 행에 넣은 아이템은 작은 제거 버튼으로 다시 우측 아이템 풀로 되돌릴 수 있다.
  • 신규 티어표의 공개 여부 기본값은 ON이며, 기존 티어표는 편집 화면과 내 티어표 목록에서 직접 삭제할 수 있다.
  • 제목이 비어 있는 상태로 저장하면 내부 제목은 현재 게임명을 기본값으로 사용한다.
  • 제목 입력이 비어 있는 동안에는 무분별한 도배 방지를 위한 관리자 임의 삭제 가능성 안내 문구를 표시한다.
  • freeform 티어표는 커스텀 아이템이 준비된 상태에서 템플릿 등록 요청을 보낼 수 있다.
  • 템플릿 등록 요청 전에는 체크리스트 모달로 제목 직접 입력 여부를 확인하고, 관리자가 식별하기 쉬운 게임 이름을 입력하도록 안내한다.
  • 신규 티어표를 막 저장한 직후에도, 템플릿 요청은 새로 발급된 실제 티어표 ID를 기준으로 이어서 처리한다.
  • 기존 게임 티어표는 커스텀 아이템이 포함되어 있으면 템플릿 업데이트 요청을 보낼 수 있다.
  • 티어표는 편집 화면에서 16:9 썸네일 이미지를 별도로 선택해 저장할 수 있고, 목록 카드에서는 그 이미지를 상단 대표 썸네일로 사용한다.
  • 티어표에 썸네일을 직접 지정하지 않으면 저장 시 대표 아이템 이미지 하나를 기본 썸네일로 자동 선택한다.
  • 편집 화면 상단 헤더는 좌측 제목/설명, 우측 썸네일 카드 구조를 사용하며 모바일에서는 한 열로 접힌다.
  • 티어표 편집 화면의 우측 패널은 공통 rightRaillocalRightRailRoot에 직접 section들을 쌓는 구조이며, 별도 외곽 래퍼 카드 없이 공통 오른쪽 컬럼 문법을 그대로 따른다.
  • 공통 앱 셸은 좌측/중앙/우측 컬럼마다 높이 56px의 상단 헤더 블록을 유지하며, 중앙 헤더에는 고정 사이트 타이틀 Tier Maker by zenn을 표시한다.
  • 티어표 편집기의 아이콘 기본 크기는 80px이며, 사용자가 48 / 60 / 80 / 100 / 120 단계로 즉시 조절할 수 있다.
  • 공개 티어표 목록과 내 티어표 목록은 제목 옆에 작성자 아바타와 표시명을 함께 보여준다.
  • 작성자 아바타 이미지가 없을 경우 목록 썸네일 fallback은 닉네임이 아니라 계정명 기준 첫 글자를 사용한다.
  • 티어표 목록 메타 정보는 최종 업데이트 시각만 간략하게 표시한다.
  • 저장 성공 시에는 에디터 안에서 반투명 오버레이 기반 확인 모달을 띄우고, PNG export 이미지는 약 960px 보드 폭과 pixelRatio 1.5, 외곽 여백, 작성자/날짜 하단 메타를 포함해 생성한다.
  • 저장/삭제/가져오기 같은 사용자 행동 피드백은 전역 우측 상단 토스트로 표시한다.
  • 전역 토스트는 동일 메시지/타입이 연속 발생하면 하나로 합쳐 카운트를 올리고, 종료 시 짧은 페이드아웃 애니메이션을 사용한다.
  • 홈 게임 목록은 관리자 상단 고정 순서가 있으면 그 순서를 먼저 적용하고, 그 외 게임은 최근 생성순으로 뒤에 이어진다.
  • 커스텀 티어표 만들기는 카드가 아니라 홈 우측 상단 버튼으로 진입한다.

업로드 제한 메모

  • 프로필 아바타 업로드는 파일당 최대 3MB다.
  • 관리자 게임 썸네일/기본 아이템 업로드와 사용자 커스텀 이미지 업로드는 파일당 최대 6MB다.
  • 현재는 업로드 전에 이미지 리사이즈/압축을 하지 않고 원본 파일을 그대로 저장한다.

운영 환경 변수

  • 프런트엔드
    • VITE_API_ORIGIN: API 및 업로드 파일 절대 기준 주소
  • 백엔드
    • DB_HOST: MariaDB 호스트
    • DB_PORT: MariaDB 포트
    • DB_USER: MariaDB 계정
    • DB_PASSWORD: MariaDB 비밀번호
    • DB_NAME: 데이터베이스명
    • PORT: 서버 포트
    • SESSION_SECRET: 세션 서명 키
    • CORS_ORIGINS: 허용할 프런트 도메인 목록(쉼표 구분)
    • TRUST_PROXY: 프록시 홉 수
    • SESSION_COOKIE_SECURE: true면 HTTPS 전용 쿠키
    • SESSION_COOKIE_SAME_SITE: 기본 lax

운영 배포 메모

  • 프로덕션 컴포즈 파일은 docker-compose.prod.yml이다.
  • 외부 도메인 tmaker.sori.studiofrontend 컨테이너의 18080 포트로 리버스 프록시하고, /api, /uploads, /health는 프런트 Nginx가 내부 backend:5179로 전달한다.
  • 운영 볼륨은 MariaDB 데이터, 업로드 파일, 세션 파일을 각각 분리해 유지한다.
  • MariaDB healthcheck는 NAS 첫 기동 지연을 고려해 root 기준 ping과 긴 start_period/retries를 사용한다.

NAS 배포 메모

  • 현재 구조는 MariaDB/MySQL 계열이므로 NAS에 MariaDB를 올리면 phpMyAdmin 또는 Adminer로 직접 데이터 확인이 가능하다.
  • 추천 구성:
    • MariaDB 컨테이너 또는 NAS 패키지 설치
    • phpMyAdmin 또는 Adminer 설치
    • 앱은 환경변수로 해당 DB에 연결

로컬 개발 기준

  • 기본 로컬 개발도 docker compose로 띄운 MariaDB를 사용한다.
  • 기본 백엔드 실행 스크립트 backend/package.jsondev, start는 로컬 MariaDB(127.0.0.1:3307) 기준으로 맞춰져 있다.
  • backend/src/db.js는 MariaDB만 대상으로 동작하며, 파일 기반 fallback은 제거되었다.