Skip to content

doublesilver/subway-board

Repository files navigation

가기싫어 - 출근길 익명 채팅방

"오늘 아침, 당신의 출근길은 어땠나요?"

같은 호선, 같은 방향으로 향하는 사람들을 연결하는 디지털 대나무 숲.


스크린샷

홈 - 호선 선택
홈 화면
호선별 실시간 혼잡도 + 접속자 수
채팅방 - 2호선
실시간 채팅
답장 · 타이핑 표시 · 스와이프 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]
Loading

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 파이프라인

시스템 아키텍처

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
Loading

실시간 채팅 흐름

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)
Loading

데이터베이스 설계

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
    }
Loading

프로젝트 구조

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

# 저장소 클론
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 빌드

cd frontend
npm run build:ait           # .ait 파일 생성 (granite build)
npm run deploy:ait          # 토스 개발자 콘솔 배포 (ait deploy)

Railway 배포 (Production)

# 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):

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%
# 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 세션 복구 시나리오

성능

테스트 환경: 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


보안

애플리케이션 (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 위반 수정

개발 타임라인

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
Loading

운영 스크립트

스크립트 설명
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 베이스라인 런북

License

MIT License - 자유롭게 사용, 수정, 배포 가능합니다.


이 프로젝트는 개인 포트폴리오 목적으로 제작되었으며, 실제 지하철 운영 주체와는 무관합니다.

Made with care by a developer who also hates Monday mornings

About

출근길 지하철 실시간 익명 채팅 서비스 '가기싫어' | React 19 + Express 5 + Socket.IO + PostgreSQL + Apps in Toss

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors