프로젝트명: 페타스터디
프로젝트 주제: 수험생(수능, 한능검, 공무원(군무원, 계리직, 국가직, 지방직), 각종 자격증 등)들을 위한 커뮤니티
도메인: peta.study
Frontend: Nuxt.js
Backend: Nest.js
Database: MySQL(sqlite in local, Prisma)
Cache/Realtime: Redis
Storage: Local File System
Library: Nuxt UI, Shadcn-vue, Pinia, Nuxt Use, Tiptap, Editor.js, Passport.js, class-validator, class-transformer, socket.io 등
SEO 최적화, Scoped 필수, JS 등 난독화 필수
주의: Nuxt UI 등의 UI 프레임워크를 사용할 때에는 일부만 사용하여야 함. 커뮤니티 전체 스타일이 UI 프레임워크에 의존하면 안됨.

---

## 구조

```
backend/   NestJS API (Prisma, Passport-JWT, socket.io, Redis 캐시, 로컬 파일 업로드)
frontend/  Nuxt 4 SSR (Pinia, VueUse, Tiptap, Nuxt UI 일부, scoped CSS)
```

## 주요 기능

- 게시판: 커뮤니티(공지/자유/질문/합격수기/스터디 모집), 수능·한능검, 공무원(국가직/지방직/군무원/계리직), 자격증·어학
- 글쓰기: Tiptap 에디터, 이미지 업로드(붙여넣기·드래그 지원), 말머리, 서버 측 HTML sanitize
- 댓글/대댓글, 추천, 북마크, 조회수(Redis로 1시간 중복 방지), 포인트
- 검색·정렬(최신/추천/댓글/조회)·말머리 필터·페이지네이션
- 실시간(socket.io): 댓글·답글·추천 알림, 주제별 스터디 채팅방, 접속자 수
- 시험 D-day, 주간 인기글(Redis 캐시), 프로필/마이페이지, 다크 모드
- 반응형: PC 3단(게시판 메뉴 / 본문 / 사이드), 태블릿 2단 + 드로어, 모바일 하단 탭바

## URL · 글 번호 (XE 방식)

| 주소 | 내용 |
|---|---|
| `/notice`, `/free`, `/qna` … | 게시판 목록 (`/all` 은 전체글) |
| `/notice/1234` | 게시글 |
| `/board/free`, `/post/1234` | 이전 주소 → 새 주소로 301 이동 |

- 회원·게시글·댓글·첨부파일이 XE의 `member_srl / document_srl / comment_srl / file_srl` 처럼 **하나의 전역 시퀀스**(`Sequence` 테이블)에서 번호를 받습니다. 그래서 게시글 번호는 1씩 오르지 않고, 사이에 생긴 댓글·가입·첨부만큼 건너뜁니다.
- 번호가 사이트 전체에서 유일하므로 다른 게시판 경로로 들어온 경우(`/qna/1234`) 올바른 게시판 주소로 301 이동합니다.
- 게시판 slug 는 최상위 경로로 쓰이므로 `user`, `write`, `search` 등 고정 페이지 경로는 사용할 수 없습니다 (`prisma/seed.ts` 의 `RESERVED_SLUGS`).

## SEO

- 전 페이지 SSR, 페이지별 `title`/`description`/OG/Twitter 메타, canonical
- JSON-LD: `WebSite`(검색), `DiscussionForumPosting`, `BreadcrumbList`
- `/sitemap.xml`(게시판·게시글 자동 생성, `/{게시판}/{번호}` 정식 주소), `/robots.txt`, `manifest.webmanifest`, OG 이미지

## 난독화

- 프론트: production 빌드 시 앱 코드가 포함된 클라이언트 청크를 `javascript-obfuscator`로 난독화 (`frontend/build/obfuscate.ts`, `NO_OBFUSCATE=1`로 끌 수 있음)
- 백엔드: `npm run build:prod` 시 `dist`를 난독화 (`backend/scripts/obfuscate.js`)

## 스타일 원칙

- 레이아웃·컴포넌트 스타일은 모두 `<style scoped>` + `assets/css/main.css`의 CSS 변수(디자인 토큰)로 작성
- Nuxt UI는 토스트, 헤더 드롭다운 메뉴, 아이콘만 사용 (UI 프레임워크에 의존하지 않음)
- 브라우저 기본 UI 미사용 — 모두 자체 컴포넌트(`frontend/app/components/ui`)로 대체
  - `<select>` → `UiSelect` (키보드·스크린리더 지원, PC 드롭다운 / 모바일 바텀시트, 그룹·아이콘·설명)
  - `confirm()` / `prompt()` → `useDialog()` + `UiDialogHost` (포커스 가두기, Esc 닫기, 입력 검증)
  - 기본 검증 말풍선(`required`) → `UiField` 오류 메시지, `title` 툴팁 → `UiTooltip`
  - `UiInput`(지우기·비밀번호 보기·글자 수), `UiTextarea`(자동 높이), 스크롤바·자동완성 배경 커스텀
- 스켈레톤 로딩 (`frontend/app/components/skeleton`)
  - 실제 화면과 같은 구조의 스켈레톤: 게시글 목록·상세·댓글·D-day·인기글·프로필·채팅·에디터·알림
  - 화면 전체에 하나의 빛줄기가 지나가도록 동기화, 120ms 지연 페이드인으로 짧은 로딩에서는 깜빡이지 않음
  - 클라이언트 이동은 즉시 화면 전환 + 스켈레톤, SSR은 완성된 HTML과 올바른 404 상태 코드 유지

## 로컬 실행

요구사항: Node 20+, (선택) Redis — 없으면 메모리 캐시로 자동 대체

```bash
npm run install:all
npm run setup            # backend/.env 생성 + SQLite 스키마 반영 + 시드 데이터
npm run dev:api          # http://localhost:4000/api
npm run dev:web          # http://localhost:3000
```

기존 DB 를 쓰던 경우에도 `npm run setup` 을 다시 실행하면 데이터는 유지된 채 스키마가 반영되고, 번호 시퀀스가 기존 최대 번호 다음부터 이어집니다. 깨끗하게 새로 시작하려면 `npm --prefix backend run db:reset` (모든 데이터 삭제).

시드 계정 (비밀번호 `petastudy1234`): `admin@peta.study`(관리자), `demo@peta.study`

## 운영 배포 (cPanel · GitHub 경유)

웹과 API 가 **하나의 Node 프로세스, 하나의 포트(기본 3000)** 로 실행됩니다 (`deploy/server.cjs` → 번들의 `app.js`).

```
main 푸시 ─▶ GitHub Actions(빌드·난독화) ─▶ deploy 브랜치 ─▶ cPanel Git Version Control ─▶ Node.js App(app.js)
```

| 경로 | 처리 |
|---|---|
| `/api/**`, `/uploads/**` | NestJS |
| `/socket.io/**` | 실시간 (socket.io) |
| 그 외 | Nuxt SSR |
| `/init` | 최초 설치 마법사 (설정 전에는 모든 페이지가 여기로 이동) |

> ⚠️ cPanel 에서는 **터미널로 실행하지 마세요.** 터미널에서 띄운 3000 포트는 외부에서 접속할 수 없고,
> 남아 있으면 포트 충돌(EADDRINUSE)만 일으킵니다. (종료: `pkill -u "$USER" -f "app.js|nuxt|nest"`)
> 도메인 요청은 웹서버(LiteSpeed `lsnode` 또는 Apache Passenger) → **Setup Node.js App 에 등록된 app.js** 로 전달되며,
> 이때는 포트 대신 웹서버 소켓에 연결되어 **도메인 루트(`/`)** 에서 서비스됩니다. 실행 로그는 앱 폴더의 `stderr.log` 에 남습니다.

### 최초 설정

1. **MySQL** — cPanel › *MySQL Databases* 에서 DB·사용자 생성 → 사용자를 DB 에 추가(ALL PRIVILEGES)
2. **Git** — cPanel › *Git Version Control* › Create
   - Clone URL: 이 저장소 (비공개면 cPanel › SSH Access 에서 키를 만들어 GitHub Deploy key 로 등록 후 SSH 주소 사용)
   - Repository Path: 예) `petastudy`
   - 생성 후 *Manage* 에서 Checked-Out Branch 를 **`deploy`** 로 변경
3. **Node.js 앱** — cPanel › *Setup Node.js App* › Create Application
   - Node.js version **20 이상(22 권장)**, Application mode **Production**
   - Application root: `petastudy` (2번의 경로), Application URL: 도메인
   - Application startup file: **`app.js`** → CREATE
4. **배포** — *Git Version Control* › Manage › Pull or Deploy › **Deploy HEAD Commit**
   (`.cpanel.yml` → 의존성 설치 → 앱 재시작)
5. **설치** — 브라우저에서 도메인 접속 → 자동으로 **`/init`**
   환경 점검 → DB 입력·연결 테스트(MySQL 또는 SQLite) → 사이트 주소·관리자 계정 → 설치
   설치 마법사가 `.env` 생성, 테이블 생성, 게시판·관리자 생성 후 자동 재시작합니다. 완료 후 `/init` 은 잠깁니다.

### 업데이트

`main` 에 푸시 → Actions 가 `deploy` 브랜치 갱신 → cPanel › Git Version Control › **Update from Remote** → **Deploy HEAD Commit**
(설치 이후 배포에서는 DB 스키마 변경도 자동 반영 — 데이터가 손실되는 변경은 자동으로 거부됩니다)

### 직접 빌드/실행

```bash
npm run install:all
npm run build:deploy     # .deploy/ 에 배포 번들 생성
cd .deploy && npm ci --omit=dev && npm start   # http://localhost:3000 → /init
```

- 설정은 앱 폴더의 `.env` (설치 마법사가 생성, Git 에 포함되지 않음). 다시 설치하려면 `.env` 를 지우고 재시작
- Redis 가 없어도 메모리 캐시로 동작
