https://github.com/doublesilver/subway-board
출근길 지하철 실시간 익명 채팅 서비스 '가기싫어' | React 19 + Express 5 + Socket.IO + PostgreSQL + Apps in Toss
https://github.com/doublesilver/subway-board
anonymous apps-in-toss chat-application commute express fullstack korean postgresql railway react realtime socket-io subway typescript vercel
Last synced: 3 months ago
JSON representation
출근길 지하철 실시간 익명 채팅 서비스 '가기싫어' | React 19 + Express 5 + Socket.IO + PostgreSQL + Apps in Toss
- Host: GitHub
- URL: https://github.com/doublesilver/subway-board
- Owner: doublesilver
- License: mit
- Created: 2025-12-30T04:03:25.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-02-27T11:30:34.000Z (4 months ago)
- Last Synced: 2026-02-27T12:58:20.522Z (4 months ago)
- Topics: anonymous, apps-in-toss, chat-application, commute, express, fullstack, korean, postgresql, railway, react, realtime, socket-io, subway, typescript, vercel
- Language: TypeScript
- Homepage: https://gagisiro.com
- Size: 21.4 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# 가기싫어 - 출근길 익명 채팅방
> **"오늘 아침, 당신의 출근길은 어땠나요?"**
>
> 같은 호선, 같은 방향으로 향하는 사람들을 연결하는 **디지털 대나무 숲**.
[](https://gagisiro.com)
[](https://github.com/doublesilver/subway-board/actions)
[](https://www.typescriptlang.org/)
[](https://react.dev/)
[](https://expressjs.com/)
[](https://github.com)
[](https://github.com)
[](https://apps-in-toss.toss.im/)
**Live Demo**: [https://gagisiro.com](https://gagisiro.com)
---
## 스크린샷
홈 화면
호선별 실시간 혼잡도 + 접속자 수
실시간 채팅
답장 · 타이핑 표시 · 스와이프 UX
대기실 (잡지식)
운영시간 외 카운트다운 + 지하철 잡지식
대기실 (퀴즈)
4지선다 지하철 퀴즈 + 정답 피드백
> **운영 시간**: 평일 오전 7시 ~ 9시 (KST)에만 채팅 가능. 그 외 시간에는 서울 지하철 잡지식과 퀴즈를 즐길 수 있어요.
---
## 프로젝트 개요
| 항목 | 내용 |
|------|------|
| **프로젝트명** | 가기싫어 (출근길 익명 채팅방) |
| **개발 기간** | 2025.12 ~ 현재 (12주+) |
| **개발 인원** | 1인 풀스택 (기획 / 디자인 / 개발 / 배포 / 운영) |
| **서비스 URL** | [gagisiro.com](https://gagisiro.com) |
| **운영 시간** | 평일 07:00 ~ 09:00 (KST) — 출근 시간대 한정 운영 |
| **현재 버전** | v4.6 — 운영시간 전체 차단 + 잡지식/퀴즈 대기실, Request ID tracing, Prometheus metrics |
---
## 주요 기능
| 기능 | 설명 |
|------|------|
| **호선별 실시간 채팅** | 1~9호선 독립 채널, Socket.IO 양방향 통신, 실시간 접속자 카운팅 |
| **완전한 익명성** | 회원가입 불필요, UUID 세션 관리, 매일 자정 메시지 자동 삭제 |
| **답장(Reply)** | 특정 메시지에 답장, 원본 미리보기, 스와이프 UX |
| **AI 콘텐츠 필터링** | 1차 로컬 Regex + 2차 OpenAI Moderation API, Fail-Open 전략 |
| **관리자 대시보드** | DAU/WAU/MAU 시각화, 호선별/시간대별 분석, 커스텀 SQL 쿼리 |
| **모바일 최적화** | 모바일 퍼스트 반응형, iOS/Android 키보드 대응, 다크모드 |
| **게시글 검색** | pg_trgm 기반 한국어 트라이그램 검색 |
| **신고 시스템** | 게시글 신고 + 관리자 리뷰 워크플로우 |
| **운영시간 차단 + 대기실** | 서비스 전체 운영시간 외 차단, 잡지식/퀴즈 탭으로 대기 중 재미 요소 제공 |
| **실시간 혼잡도** | 서울 열린데이터 API 연동 (1~8호선) |
| **푸시 알림** | Web Push (VAPID) 구독 + DB 영속화 |
| **Observability** | Request ID tracing, Prometheus `/metrics` 엔드포인트 |
| **앱인토스 미니앱** | 토스 앱 내 미니앱 배포, 인앱 광고 3종 (배너/전면/보상형), WebView UX 적응 |
| **법적 페이지** | 개인정보처리방침, 이용약관 |
---
## 기술 스택
```mermaid
flowchart LR
subgraph Frontend["Frontend (Vercel + Apps in Toss)"]
React[React 19 + TSX]
Vite[Vite 6]
Router[React Router 7]
SIO_C[Socket.IO Client]
AiT[Apps in Toss SDK]
end
subgraph Backend["Backend (Railway)"]
Node[Node.js 22 LTS]
Express[Express 5]
SIO_S[Socket.IO Server]
Helmet[Helmet 8]
end
subgraph Database["Database (Railway)"]
PG[(PostgreSQL 15)]
Redis[(Redis Cache)]
end
React --> SIO_C
SIO_C <-->|WebSocket| SIO_S
React -->|HTTP| Express
Express --> PG
Express --> Redis
Express -.->|Content Filter| OpenAI[OpenAI Moderation]
```
### Frontend
| 기술 | 버전 | 용도 |
|------|------|------|
| React | 19.x | UI 컴포넌트 (100% TSX) |
| Vite | 6.x | 빌드 도구, HMR |
| React Router | 7.x | SPA 라우팅 |
| Socket.IO Client | 4.8 | 실시간 통신 |
| @apps-in-toss/web-framework | 1.13 | 토스 미니앱 SDK (광고, 빌드, 배포) |
| Vitest | 3.1 | 단위 테스트 (844 tests) |
| Playwright | 1.50 | E2E 테스트 (9 specs) |
| Axios | 1.x | HTTP 클라이언트 |
### Backend
| 기술 | 버전 | 용도 |
|------|------|------|
| Node.js | 22 LTS | 런타임 |
| Express | 5.0 | API 서버 (async/await 네이티브) |
| Socket.IO | 4.8 | WebSocket 서버 |
| PostgreSQL | 15 | 메인 DB |
| Redis | 5.10 | 캐싱 (Fail-safe, optional) |
| Winston | 3.x | 구조화 로깅 |
| bcrypt | 5.x | 관리자 비밀번호 해싱 |
| Sentry | 10.35 | 에러 모니터링 (breadcrumbs, WebSocket context) |
| Jest | 30.x | 테스트 (63 suites, 1,003 tests) |
### Infrastructure
| 서비스 | 용도 |
|--------|------|
| Railway | 백엔드 API + PostgreSQL + Redis 호스팅 |
| Vercel | 프론트엔드 SPA 호스팅 |
| Apps in Toss | 토스 앱 내 미니앱 배포 (.ait 패키징) |
| GitHub Actions | CI/CD 파이프라인 |
---
## 시스템 아키텍처
```mermaid
flowchart TB
subgraph Client["Client Browser"]
ReactApp["React SPA"]
WS_Client["WebSocket Client"]
end
subgraph Vercel["Vercel (Frontend)"]
SPA["gagisiro.com
Static SPA Hosting"]
end
subgraph Railway["Railway (Backend)"]
ExpressAPI["Express API :5001"]
WS_Server["Socket.IO Server"]
PostgreSQL[("PostgreSQL 15")]
RedisCache[("Redis Cache")]
end
subgraph External["External"]
OpenAI["OpenAI Moderation API"]
end
ReactApp -->|HTTPS| SPA
SPA -->|API Calls| ExpressAPI
WS_Client <-->|WebSocket| ExpressAPI
ExpressAPI --> PostgreSQL
ExpressAPI --> RedisCache
ExpressAPI -.->|Content Filter| OpenAI
```
### 실시간 채팅 흐름
```mermaid
sequenceDiagram
participant U as 사용자
participant C as React Client
participant S as Socket.IO Server
participant AI as OpenAI Moderation
participant DB as PostgreSQL
U->>C: 호선 선택 (2호선)
C->>S: join_line(lineId: 2)
S-->>C: user_count 업데이트
U->>C: 메시지 입력
C->>S: POST /api/posts
S->>S: 1차 Local Regex 필터링
S->>AI: 2차 문맥 분석
AI-->>S: { safe: true }
S->>DB: INSERT message
S-->>C: 201 Created
S->>S: io.to("line_2").emit()
S-->>C: new_message (broadcast)
```
### 데이터베이스 설계
```mermaid
erDiagram
SUBWAY_LINES ||--o{ POSTS : contains
SUBWAY_LINES ||--o{ DAILY_VISITS : tracks
SUBWAY_LINES ||--o{ HOURLY_VISITS : tracks
SUBWAY_LINES ||--o{ UNIQUE_VISITORS : first_visit
SUBWAY_LINES {
int id PK
string line_name
string color
}
POSTS {
uuid id PK
int subway_line_id FK
int reply_to FK
text content
string message_type
int user_id FK
string anonymous_id
timestamp created_at
timestamp deleted_at
}
DAILY_VISITS {
int id PK
date visit_date
int subway_line_id FK
int visit_count
}
UNIQUE_VISITORS {
int id PK
string visitor_hash
date visit_date
int first_line_id FK
}
FEEDBACK {
int id PK
text content
string user_session_id
timestamp created_at
}
POSTS ||--o{ REPORTS : has
POSTS ||--o{ REACTIONS : has
POSTS ||--o{ COMMENTS : has
POSTS ||--o{ PUSH_SUBSCRIPTIONS : notifies
REPORTS {
int id PK
int post_id FK
string reason
string status
timestamp created_at
}
REACTIONS {
int id PK
int post_id FK
string anonymous_id
string type
}
COMMENTS {
int id PK
int post_id FK
text content
string anonymous_id
timestamp created_at
}
PUSH_SUBSCRIPTIONS {
int id PK
string endpoint
string auth_key
string p256dh_key
timestamp created_at
}
```
---
## 프로젝트 구조
```
subway-board/
├── frontend/ # React 19 SPA (Vercel 배포, 100% TypeScript)
│ ├── src/
│ │ ├── components/
│ │ │ ├── admin/ # 대시보드 컴포넌트 분리
│ │ │ │ ├── AdminLogin.tsx
│ │ │ │ ├── DashboardHeader.tsx
│ │ │ │ ├── OverviewTab.tsx
│ │ │ │ ├── HourlyTab.tsx
│ │ │ │ ├── LinesTab.tsx
│ │ │ │ ├── QueryBuilder.tsx
│ │ │ │ ├── QueryTab.tsx
│ │ │ │ ├── ReportsTab.tsx
│ │ │ │ ├── ResultsTable.tsx
│ │ │ │ └── SummaryCard.tsx
│ │ │ ├── chat/ # 채팅 컴포넌트 분리
│ │ │ │ ├── ChatComposer.tsx
│ │ │ │ ├── ChatHeader.tsx
│ │ │ │ ├── ChatMessageArea.tsx
│ │ │ │ ├── DateDivider.tsx
│ │ │ │ ├── MessageBubble.tsx
│ │ │ │ ├── MessageList.tsx
│ │ │ │ ├── MessageMenu.tsx
│ │ │ │ ├── MessageReactions.tsx
│ │ │ │ └── ScrollToBottom.tsx
│ │ │ ├── ads/ # Apps in Toss 광고 컴포넌트
│ │ │ │ ├── BannerAdSlot.tsx
│ │ │ │ └── RewardedAdButton.tsx
│ │ │ ├── layout/ # Header, Footer, MainLayout
│ │ │ ├── AnimatedBackground.tsx
│ │ │ ├── ClosedAlertModal.tsx # 운영시간 외 대기실 (잡지식/퀴즈)
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── FeedbackModal.tsx
│ │ │ ├── OperatingHoursGuard.tsx # 운영시간 가드 (홈+채팅방)
│ │ │ ├── LinkifyText.tsx
│ │ │ ├── RouteSkeleton.tsx
│ │ │ ├── SessionExpiredModal.tsx
│ │ │ └── Toast.tsx
│ │ ├── config/ # constants, storageKeys, adConstants
│ │ ├── contexts/ # AuthContext, ThemeContext
│ │ ├── hooks/ # useChatSocket, useChatScroll, useChatState, useSwipeReply, useToast, useAitAds, useDocumentMeta, useFocusTrap, useMessageSearch 등
│ │ ├── pages/ # HomePage, LinePage, AdminDashboard, PrivacyPage, TermsPage, AboutPage, PreviewHome, PreviewChat
│ │ ├── services/api/ # Axios 인스턴스 + 인터셉터 + 자동 unwrap
│ │ ├── data/ # subwayTrivia (잡지식/퀴즈 데이터)
│ │ └── utils/ # socket, temporaryUser, linkify, errorUtils, pushNotification, serviceWorker, aitDetect, analytics, notifications, routePrefetch
│ ├── e2e/ # Playwright E2E (9 spec files)
│ ├── granite.config.ts # Apps in Toss 빌드 설정
│ └── vite.config.js
│
├── backend/ # Express 5 API (Railway 배포, 100% TypeScript)
│ ├── src/
│ │ ├── config/ # constants, env, patterns, rateLimiters, redis, swagger, validateEnv, middleware, socketSetup, gracefulShutdown
│ │ ├── controllers/ # post, comment, visit, feedback, dashboard, auth, admin, reaction, topic, congestion, push, report, subwayLine
│ │ ├── db/ # connection.ts, migrate.ts, migrations/
│ │ ├── middleware/ # auth, requireAuth, admin, validator, error
│ │ ├── services/ # postService, cacheService, aiService, visitService, dashboardService, reactionService, topicService, congestionService, pushService, reportService, commentService, feedbackService, adminService, subwayLineService, analyticsQueryBuilder
│ │ ├── socket/ # handlers, state, index (Socket.IO 이벤트 핸들링)
│ │ ├── routes/index.ts
│ │ └── utils/ # logger, scheduler, AppError, asyncHandler, errorCodes, response, httpCache, queryParser, profanityFilter, hashPassword, userHelper
│ └── tests/ # 59 suites, 966 tests (100% .ts)
│
├── scripts/ # release_gate.sh, web_vitals_baseline.sh, slo_budget_gate.sh
├── docs/ # E2E, Load Testing, Admin Dashboard 가이드
├── .github/workflows/ # ci.yml, security.yml, load-testing.yml
└── backend/Dockerfile # Railway 빌드용 (multi-stage)
```
---
## 앱 라우트
### Frontend (React Router)
| 경로 | 페이지 | 설명 |
|------|--------|------|
| `/` | HomePage | 호선 선택 그리드 |
| `/line/:lineId` | LinePage | 실시간 채팅방 |
| `/admin` | AdminDashboard | 관리자 대시보드 (인증 필요) |
| `/privacy` | PrivacyPage | 개인정보처리방침 |
| `/terms` | TermsPage | 이용약관 |
| `/about` | AboutPage | 서비스 소개 |
| `/preview` | PreviewHome | 미리보기 홈 |
| `/preview/chat/:lineId` | PreviewChat | 미리보기 채팅 |
### Backend API (`api.gagisiro.com`)
| 카테고리 | 주요 엔드포인트 |
|----------|----------------|
| 인증 | `POST /api/auth/anonymous` |
| 지하철 | `GET /api/subway-lines`, `GET /api/congestion/:lineId` |
| 게시글 | `GET/POST/DELETE /api/posts/*`, `POST /api/posts/join`, `POST /api/posts/leave` |
| 댓글 | `GET/POST /api/posts/:postId/comments`, `DELETE /api/comments/:commentId` |
| 반응 | `POST/GET /api/posts/:postId/reactions` |
| 토픽 | `GET /api/topic/today` |
| 피드백 | `POST /api/feedback` |
| 신고 | `POST /api/posts/:postId/report` |
| 푸시 | `GET /api/push/vapid-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe` |
| 대시보드 | `POST /api/dashboard/login`, `GET /api/dashboard/data`, `POST /api/dashboard/query` |
| 관리자 | `GET /api/admin/reports`, `GET /api/admin/feedback`, `GET /api/admin/stats` |
| 헬스/모니터링 | `GET /health`, `GET /metrics` (Prometheus) |
---
## 로컬 실행 방법
### 요구 사항
- Node.js 22.x 이상, PostgreSQL 15.x, npm 10.x
### Quick Start
```bash
# 저장소 클론
git clone https://github.com/doublesilver/subway-board.git
cd subway-board
# Backend
cd backend
cp .env.example .env # DATABASE_URL, JWT_SECRET 등 설정
npm install
npm run dev # http://localhost:5001
# Frontend (새 터미널)
cd frontend
npm install
npm run dev # http://localhost:3000
```
### Apps in Toss 빌드
```bash
cd frontend
npm run build:ait # .ait 파일 생성 (granite build)
npm run deploy:ait # 토스 개발자 콘솔 배포 (ait deploy)
```
### Railway 배포 (Production)
```bash
# Railway CLI 설치 후
cd backend
railway login
railway link
railway up
```
| 서비스 | 설명 |
|--------|------|
| subway-board-backend | Express API + Socket.IO (Railway) |
| PostgreSQL | Railway 내장 DB |
| Redis | Railway 내장 캐시 |
**Backend URL**: `https://subway-board-backend-production.up.railway.app`
### Admin Password Hashing
Admin dashboard passwords should be stored as bcrypt hashes (12 rounds):
```bash
cd backend
npx ts-node src/utils/hashPassword.ts "your-plain-password"
# → $2b$12$... (paste this into ADMIN_DASHBOARD_PASSWORD env var)
```
The server auto-detects hashed vs. plaintext passwords. Plaintext is accepted with a warning in non-production environments.
---
## 테스트
**Coverage**: 127 Suites | 1,847 Tests (Backend 1,003 + Frontend 844) + E2E 66
| Layer | Tool | Count | Coverage |
|-------|------|-------|----------|
| Backend Unit/Integration | Jest 30.x | 1,003 tests (63 suites) | 80%+ lines |
| Frontend Unit/Integration | Vitest 3.1 | 844 tests (64 suites) | 80%+ lines |
| E2E | Playwright 1.50 | 9 specs (2 projects) | 주요 유저 플로우 전체 |
| Load | k6 | 4 scenarios | - |
### Coverage Thresholds
| 메트릭 | 백엔드 | 프론트엔드 |
|--------|--------|-----------|
| Lines | 80% | 80% |
| Statements | 80% | 80% |
| Branches | 70% | 70% |
| Functions | 70% | 70% |
```bash
# Backend 테스트
cd backend && npm test
cd backend && npm run test:coverage
# Frontend 테스트
cd frontend && npm run test:run
cd frontend && npm run test:coverage
# Frontend E2E (Chromium + Mobile Chrome)
cd frontend && npm run test:e2e
# 부하 테스트 (k6 필요)
cd backend && k6 run tests/load/load-test.js
```
### E2E 테스트 스펙
| 파일 | 커버리지 |
|------|----------|
| `home.spec.ts` | 홈페이지 렌더링, 호선 선택 |
| `navigation.spec.ts` | SPA 라우팅, 페이지 전환 |
| `user-flow.spec.ts` | 전체 유저 시나리오 (입장→채팅→퇴장) |
| `chat-features.spec.ts` | 메시지 전송, 답장, 반응 |
| `admin.spec.ts` | 관리자 로그인, 권한 |
| `admin-dashboard.spec.ts` | 대시보드 기능 전체 |
| `accessibility.spec.ts` | 접근성 (axe-core) 검증 |
| `reactions.spec.ts` | 리액션 이모지 기능 |
| `session-recovery.spec.ts` | 세션 복구 시나리오 |
- [E2E Testing Guide](docs/E2E_TESTING.md)
- [Load Testing Guide](docs/LOAD_TESTING.md)
---
## 성능
**테스트 환경**: Railway | **도구**: k6 v1.5.0
| 시나리오 | 동시 사용자 | 처리량 | P95 응답 | 결과 |
|---------|-----------|--------|---------|------|
| Smoke | 1 | 0.73 req/s | 73ms | Pass |
| Load | 50 | 9.87 req/s | 28ms | Pass |
| Stress | 200 | 235 req/s | 77ms | Pass |
| Spike | 200 | 363 req/s | 81ms | Pass |
- **최대 동시 사용자**: 200명 안정 처리
- **캐시 효율**: 98.64% hit rate (Redis)
- **API 응답**: 평균 16~35ms
> 상세 결과: [Load Testing Results](docs/LOAD_TESTING_RESULTS.md)
---
## 보안
### 애플리케이션 (OWASP Top 10 대응)
| 영역 | 구현 |
|------|------|
| HTTP 헤더 | Helmet.js 보안 헤더 (CSP, X-Frame-Options 등) |
| Rate Limiting | 쓰기 50/15min, 읽기 100/min; Redis-backed store (분산 환경 지원, memory fallback) |
| CORS | 허용 도메인만 접근 (gagisiro.com, tossmini.com) |
| Input Validation | XSS 필터링, 길이 검증, 위험 프로토콜 차단 |
| SQL Injection | Parameterized Query |
| 콘텐츠 필터링 | Local Regex + OpenAI AI 필터 (Fail-Open) |
| URL Sanitization | `javascript:`, `data:`, `vbscript:` 프로토콜 차단 |
| Admin Auth | bcrypt 12-round hashing for dashboard password; plaintext fallback w/ warning |
| DB Connection | SSL enforced in production (`DB_SSL_REJECT_UNAUTHORIZED=true` default) |
### 인프라 (Railway)
| 영역 | 구현 |
|------|------|
| Railway | 자동 HTTPS, 도메인 관리 |
| Docker Multi-stage Build | 최소 이미지, 보안 비root 사용자 |
| Health Check | `/health` 엔드포인트 (DB/Redis 상태 포함) |
---
## 기술적 도전과 해결
| 도전 | 해결 |
|------|------|
| Express 5 마이그레이션 | 와일드카드 라우팅 `/{*path}`, async 에러 네이티브 지원 |
| CRA → Vite | 개발 서버 10초→1초, HMR 2초→50ms |
| 모바일 키보드 대응 | visualViewport API로 입력창 위치 동적 조정 |
| 연속 메시지 전송 UX | preventDefault로 textarea 포커스 유지 |
| 운영 시간 제한 | 평일 07:00~09:00 서버 사이드 검증 |
| AI 필터링 비용 최적화 | Local Regex 1차 → OpenAI 2차, Fail-Open |
| Redis Fail-Safe | 장애 시 자동 DB fallback, 가용성 100% |
| 낙관적 업데이트 | 즉시 UI 반영 + 실패 시 롤백 + WebSocket 중복 제거 |
| JSX→TSX 전체 마이그레이션 | Frontend/Backend 100% TypeScript 달성 |
| 컴포넌트 분리 | LinePage 405줄 → chat/ 모듈, AdminDashboard → admin/ 모듈 |
| 테스트 커버리지 달성 | Backend 40%→80%+, Frontend 25%→80%+ (1,762 tests) |
| 프론트엔드/백엔드 분리 배포 | Vercel(SPA) + Railway(API) 아키텍처 |
| Apps in Toss WebView 적응 | safe-area, 44px 터치 타겟, CSP 화이트리스트, overscroll 제어, 조건부 레이아웃 |
| 인앱 광고 3종 통합 | 배너/전면/보상형, 쿨다운 시스템, 기존 웹 영향 없는 isAppsInToss() 분기 설계 |
| API 응답 포맷 통합 | 29개 엔드포인트를 `{ success, data/error }` 단일 envelope으로 표준화; Axios 인터셉터 자동 unwrap |
| 모놀리식 파일 분해 | backend/index.ts 468줄 → middleware.ts + socketSetup.ts + gracefulShutdown.ts; socket/ 디렉토리 통합 |
| Redis rate-limit store | distributed rate limiting (graceful memory fallback); comments/reactions/dashboard 캐시 추가 |
| 번들 최적화 | recharts separate chunk (AdminDashboard 409KB → 23KB); gzip + Brotli (vite-plugin-compression2) |
| 접근성 개선 | FeedbackModal 중복 h1 제거, Header 비상호작용 h1 수정, a11y 회귀 테스트 추가 |
| 서비스 전체 운영시간 차단 | OperatingHoursGuard로 홈+채팅방 통합 차단, optional onClose 패턴으로 context별 UI 분기 |
| 대기실 잡지식/퀴즈 | 10개 지하철 잡지식 + 5개 퀴즈, 탭 전환, 4지선다 정답/오답 피드백 UX |
| Request ID tracing | 요청별 고유 ID 부여, 로그 추적성 향상, Prometheus `/metrics` 엔드포인트 |
| CI/CD 파이프라인 최적화 | Playwright 캐시, 중복 트리거 제거, E2E strict mode 위반 수정 |
---
## 개발 타임라인
```mermaid
gantt
title 개발 일정
dateFormat YYYY-MM-DD
section 기획/개발
서비스 기획 + 와이어프레임 :done, 2025-12-01, 14d
Frontend + Backend 구축 :done, 2025-12-15, 14d
Socket.IO 실시간 채팅 :done, 2025-12-22, 7d
section 최적화
UI/UX + Vite + Express 5 :done, 2026-01-01, 11d
section v2.x
Hooks 리팩토링 + 통계 :done, 2026-01-14, 3d
CI/CD + Swagger 문서화 :done, 2026-01-21, 1d
TypeScript + Redis 캐싱 :done, 2026-01-28, 5d
section v3.x
E2E + 부하 테스트 :done, 2026-02-05, 2d
70% 커버리지 달성 :done, 2026-02-06, 1d
Prometheus + Grafana :done, 2026-02-07, 1d
section v4.0
Full TypeScript Migration :done, 2026-02-07, 1d
컴포넌트 분리 :done, 2026-02-07, 1d
레거시 문서 정리 :done, 2026-02-07, 1d
section v4.2
검색 + 신고 + 혼잡도 API :done, 2026-02-08, 2d
푸시 알림 운영 안정화 :done, 2026-02-10, 2d
Docker 배포 최적화 :done, 2026-02-12, 1d
section v4.3
백엔드 커버리지 80%+ 달성 :done, 2026-02-20, 1d
프론트엔드 커버리지 80%+ 달성 :done, 2026-02-20, 1d
E2E 시나리오 확장 :done, 2026-02-20, 1d
OWASP Top 10 보안 강화 :done, 2026-02-20, 1d
성능 최적화 + 접근성 :done, 2026-02-20, 1d
section v4.4
Apps in Toss 광고 통합 :done, 2026-02-24, 1d
토스 WebView UX 적응 :done, 2026-02-24, 1d
.ait 빌드 + 배포 설정 :done, 2026-02-24, 1d
section v4.5 (Phase 7-8)
모놀리식 분해 + 코드 정리 :done, 2026-02-25, 1d
API 응답 포맷 통합 :done, 2026-02-26, 1d
Redis rate-limit + 캐시 :done, 2026-02-26, 1d
번들 최적화 + a11y :done, 2026-02-26, 1d
bcrypt + DB SSL + Sentry :done, 2026-02-26, 1d
section v4.6 (Phase 9)
운영시간 전체 차단 + 대기실 :done, 2026-02-27, 1d
잡지식/퀴즈 탭 구현 :done, 2026-02-27, 1d
Request ID + Prometheus :done, 2026-02-28, 1d
CI 파이프라인 최적화 :done, 2026-02-28, 1d
```
---
## 운영 스크립트
| 스크립트 | 설명 |
|---------|------|
| `release_gate.sh` | 릴리스 게이트 검증 |
| `web_vitals_baseline.sh` | Web Vitals 베이스라인 측정 |
| `slo_budget_gate.sh` | SLO 예산 게이트 |
---
## 문서
| 문서 | 설명 |
|------|------|
| [CHANGELOG.md](CHANGELOG.md) | 전체 버전 히스토리 |
| [HANDOFF.md](HANDOFF.md) | 프로덕션 품질 업그레이드 + Apps in Toss 배포 상세 |
| [관리자 대시보드 가이드](docs/ADMIN_DASHBOARD_GUIDE.md) | 대시보드 사용법 + SQL 쿼리 모음 |
| [E2E Testing Guide](docs/E2E_TESTING.md) | Playwright E2E 테스트 |
| [Load Testing Guide](docs/LOAD_TESTING.md) | k6 부하 테스트 |
| [Load Testing Results](docs/LOAD_TESTING_RESULTS.md) | 부하 테스트 상세 결과 |
| [Web Vitals Runbook](docs/WEB_VITALS_BASELINE_RUNBOOK.md) | Web Vitals 베이스라인 런북 |
---
## License
MIT License - 자유롭게 사용, 수정, 배포 가능합니다.
---
*이 프로젝트는 개인 포트폴리오 목적으로 제작되었으며, 실제 지하철 운영 주체와는 무관합니다.*
**Made with care by a developer who also hates Monday mornings**