"오늘 아침, 당신의 출근길은 어땠나요?"
같은 호선, 같은 방향으로 향하는 사람들을 연결하는 디지털 대나무 숲.
Live Demo: https://gagisiro.com
홈 화면 호선별 실시간 혼잡도 + 접속자 수 |
실시간 채팅 답장 · 타이핑 표시 · 스와이프 UX |
대기실 (잡지식) 운영시간 외 카운트다운 + 지하철 잡지식 |
대기실 (퀴즈) 4지선다 지하철 퀴즈 + 정답 피드백 |
운영 시간: 평일 오전 7시 ~ 9시 (KST)에만 채팅 가능. 그 외 시간에는 서울 지하철 잡지식과 퀴즈를 즐길 수 있어요.
| 항목 | 내용 |
|---|---|
| 프로젝트명 | 가기싫어 (출근길 익명 채팅방) |
| 개발 기간 | 2025.12 ~ 현재 (12주+) |
| 개발 인원 | 1인 풀스택 (기획 / 디자인 / 개발 / 배포 / 운영) |
| 서비스 URL | 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 적응 |
| 법적 페이지 | 개인정보처리방침, 이용약관 |
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]
| 기술 | 버전 | 용도 |
|---|---|---|
| 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 클라이언트 |
| 기술 | 버전 | 용도 |
|---|---|---|
| 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) |
| 서비스 | 용도 |
|---|---|
| Railway | 백엔드 API + PostgreSQL + Redis 호스팅 |
| Vercel | 프론트엔드 SPA 호스팅 |
| Apps in Toss | 토스 앱 내 미니앱 배포 (.ait 패키징) |
| GitHub Actions | CI/CD 파이프라인 |
flowchart TB
subgraph Client["Client Browser"]
ReactApp["React SPA"]
WS_Client["WebSocket Client"]
end
subgraph Vercel["Vercel (Frontend)"]
SPA["gagisiro.com<br/>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
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)
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)
| 경로 | 페이지 | 설명 |
|---|---|---|
/ |
HomePage | 호선 선택 그리드 |
/line/:lineId |
LinePage | 실시간 채팅방 |
/admin |
AdminDashboard | 관리자 대시보드 (인증 필요) |
/privacy |
PrivacyPage | 개인정보처리방침 |
/terms |
TermsPage | 이용약관 |
/about |
AboutPage | 서비스 소개 |
/preview |
PreviewHome | 미리보기 홈 |
/preview/chat/:lineId |
PreviewChat | 미리보기 채팅 |
| 카테고리 | 주요 엔드포인트 |
|---|---|
| 인증 | 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
# 저장소 클론
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:3000cd frontend
npm run build:ait # .ait 파일 생성 (granite build)
npm run deploy:ait # 토스 개발자 콘솔 배포 (ait deploy)# 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 dashboard passwords should be stored as bcrypt hashes (12 rounds):
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 | - |
| 메트릭 | 백엔드 | 프론트엔드 |
|---|---|---|
| Lines | 80% | 80% |
| Statements | 80% | 80% |
| Branches | 70% | 70% |
| Functions | 70% | 70% |
# 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| 파일 | 커버리지 |
|---|---|
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 |
세션 복구 시나리오 |
테스트 환경: 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
| 영역 | 구현 |
|---|---|
| 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 | 자동 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 위반 수정 |
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 | 전체 버전 히스토리 |
| HANDOFF.md | 프로덕션 품질 업그레이드 + Apps in Toss 배포 상세 |
| 관리자 대시보드 가이드 | 대시보드 사용법 + SQL 쿼리 모음 |
| E2E Testing Guide | Playwright E2E 테스트 |
| Load Testing Guide | k6 부하 테스트 |
| Load Testing Results | 부하 테스트 상세 결과 |
| Web Vitals Runbook | Web Vitals 베이스라인 런북 |
MIT License - 자유롭게 사용, 수정, 배포 가능합니다.
이 프로젝트는 개인 포트폴리오 목적으로 제작되었으며, 실제 지하철 운영 주체와는 무관합니다.
Made with care by a developer who also hates Monday mornings



