Skip to content

Instantly share code, notes, and snippets.

@neguse
Created August 22, 2025 17:20
Show Gist options
  • Save neguse/023ebd71b5001cf11ab50b983263fd5d to your computer and use it in GitHub Desktop.
Save neguse/023ebd71b5001cf11ab50b983263fd5d to your computer and use it in GitHub Desktop.
unkonowの最初の仕様

Unkonow - 完全仕様書 (Turso + Vercel + SSE構成)

🎯 サービス概要

リアルタイムでうんこ状況を共有し、相互応援できるWebアプリケーション。匿名制で気軽に参加でき、定型文による安全な応援システムを提供。

📋 機能要件

🚽 うんこする人の機能

  • うんこ開始: ボタン押下でうんこセッション開始
  • うんこ終了: ボタン押下でうんこセッション終了
  • 応援受信: リアルタイムで応援メッセージを受信・表示
  • セッション管理: 重複開始防止、異常終了時の自動クリーンアップ

👥 見てる人の機能

  • 現在うんこ中リスト: リアルタイムでうんこ中ユーザー一覧表示
  • 開始通知: 「○○がうんこ開始しました」リアルタイム通知
  • 応援送信: 定型文メッセージでうんこ中ユーザーを応援
  • 応援履歴: 送信した応援の記録表示

🏆 共通機能

  • 統計表示: 累計うんこ数、ランキング、個人記録
  • 匿名システム: アカウント登録不要、自動生成ニックネーム
  • リアルタイム同期: Server-Sent Eventsによる即座な状態共有
  • アクティビティ履歴: 最近のうんこ活動一覧

📱 応援メッセージ(定型文8種類)

1. 💪 がんばれ〜!
2. 🎉 ナイスうんこ!  
3. ⚡ スッキリして〜!
4. 🌟 お疲れさま!
5. 🔥 いいぞ〜!
6. ✨ 素晴らしい!
7. 🎊 やったね!
8. 💝 愛してる!

🏗️ インフラ構成

アーキテクチャ概要

[ユーザー] → [Vercel Edge Network] → [Next.js] → [Turso]
                                       ↓
                                [Server-Sent Events]

技術スタック

フロントエンド:
├── Next.js 14 (App Router)
├── React 18 + TypeScript
├── Tailwind CSS
├── EventSource API (SSE)
└── PWA設定

バックエンド:
├── Next.js API Routes
├── Server-Sent Events
├── @libsql/client (Turso SDK)
└── UUID生成

インフラ:
├── Vercel (ホスティング + Edge Network)
├── Turso (グローバル分散SQLite)
└── GitHub (ソースコード管理)

デプロイ構成

GitHub → Vercel (自動デプロイ + グローバル配信)
      → Turso (データベース)

🗄️ データベース設計

Tursoテーブル構造

-- ユーザーテーブル
CREATE TABLE users (
  id TEXT PRIMARY KEY,
  nickname TEXT NOT NULL,
  total_unko_count INTEGER DEFAULT 0,
  created_at INTEGER DEFAULT (unixepoch()),
  last_unko_at INTEGER,
  last_active_at INTEGER DEFAULT (unixepoch())
);

-- うんこセッションテーブル
CREATE TABLE unko_sessions (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  status TEXT CHECK(status IN ('in_progress', 'completed', 'abandoned')) NOT NULL,
  start_time INTEGER NOT NULL,
  end_time INTEGER,
  duration_seconds INTEGER,
  total_cheers INTEGER DEFAULT 0,
  created_at INTEGER DEFAULT (unixepoch()),
  FOREIGN KEY (user_id) REFERENCES users (id)
);

-- 応援テーブル
CREATE TABLE cheers (
  id TEXT PRIMARY KEY,
  session_id TEXT NOT NULL,
  from_user_id TEXT NOT NULL,
  cheer_type_id INTEGER NOT NULL CHECK(cheer_type_id BETWEEN 1 AND 8),
  created_at INTEGER DEFAULT (unixepoch()),
  FOREIGN KEY (session_id) REFERENCES unko_sessions (id),
  FOREIGN KEY (from_user_id) REFERENCES users (id)
);

-- アクティビティログテーブル
CREATE TABLE activity_logs (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  action_type TEXT NOT NULL, -- 'unko_start', 'unko_end', 'cheer_sent'
  target_session_id TEXT,
  metadata TEXT, -- JSON形式の追加データ
  created_at INTEGER DEFAULT (unixepoch()),
  FOREIGN KEY (user_id) REFERENCES users (id)
);

-- インデックス
CREATE INDEX idx_unko_sessions_status ON unko_sessions(status);
CREATE INDEX idx_unko_sessions_user_id ON unko_sessions(user_id);
CREATE INDEX idx_unko_sessions_start_time ON unko_sessions(start_time);
CREATE INDEX idx_cheers_session_id ON cheers(session_id);
CREATE INDEX idx_cheers_from_user_id ON cheers(from_user_id);
CREATE INDEX idx_activity_logs_user_id ON activity_logs(user_id);
CREATE INDEX idx_activity_logs_created_at ON activity_logs(created_at);

🔌 API設計

REST API エンドポイント

ユーザー管理

POST   /api/users                    # 匿名ユーザー作成
GET    /api/users/[userId]           # ユーザー情報取得
PUT    /api/users/[userId]           # ユーザー情報更新
GET    /api/users/[userId]/stats     # ユーザー統計取得

うんこセッション管理

GET    /api/unko/current            # 現在うんこ中リスト
POST   /api/unko/start              # うんこ開始
PUT    /api/unko/[sessionId]/finish # うんこ終了
GET    /api/unko/[sessionId]        # セッション詳細
DELETE /api/unko/[sessionId]        # セッション削除(管理用)

応援機能

POST   /api/cheer                   # 応援送信
GET    /api/cheer/[sessionId]       # セッションの応援一覧
GET    /api/cheer/messages          # 応援メッセージ定義取得

統計・その他

GET    /api/stats                   # 全体統計
GET    /api/stats/ranking           # ランキング
GET    /api/activity               # 最近のアクティビティ
GET    /api/health                 # ヘルスチェック

リアルタイム通信

GET    /api/events                 # Server-Sent Events エンドポイント

API詳細仕様

POST /api/users

リクエスト:

{
  "nickname": "うんこマスター#1234" // オプション、未指定時は自動生成
}

レスポンス:

{
  "id": "user_abc123",
  "nickname": "うんこマスター#1234",
  "totalUnkoCount": 0,
  "createdAt": 1672531200000
}

POST /api/unko/start

リクエスト:

{
  "userId": "user_abc123"
}

レスポンス:

{
  "sessionId": "session_xyz789",
  "userId": "user_abc123",
  "status": "in_progress",
  "startTime": 1672531200000
}

POST /api/cheer

リクエスト:

{
  "sessionId": "session_xyz789",
  "fromUserId": "user_def456",
  "cheerTypeId": 1
}

レスポンス:

{
  "cheerId": "cheer_uvw012",
  "cheerMessage": "💪 がんばれ〜!",
  "timestamp": 1672531200000
}

GET /api/events (Server-Sent Events)

ストリームイベント:

// うんこ開始イベント
data: {
  "type": "unko_started",
  "sessionId": "session_xyz789",
  "user": {
    "id": "user_abc123",
    "nickname": "うんこマスター#1234"
  },
  "timestamp": 1672531200000
}

// 応援受信イベント
data: {
  "type": "cheer_received",
  "sessionId": "session_xyz789",
  "fromUser": {
    "id": "user_def456",
    "nickname": "応援団長#5678"
  },
  "cheerMessage": "💪 がんばれ〜!",
  "timestamp": 1672531200000
}

// うんこ終了イベント
data: {
  "type": "unko_finished",
  "sessionId": "session_xyz789",
  "user": {
    "id": "user_abc123",
    "nickname": "うんこマスター#1234"
  },
  "duration": 180,
  "totalCheers": 5,
  "timestamp": 1672531200000
}

// ユーザー参加イベント
data: {
  "type": "user_joined",
  "user": {
    "id": "user_ghi789",
    "nickname": "新参者#9999"
  },
  "timestamp": 1672531200000
}

💻 フロントエンド実装

ページ構成

/                    # メイン画面
├── うんこボタン       # 大きな💩ボタン
├── 現在うんこ中リスト   # リアルタイム更新
├── 応援パネル        # 定型文ボタン8個
├── 統計ダッシュボード  # 個人・全体統計
└── アクティビティ     # 最近の活動

状態管理

interface AppState {
  user: User | null
  currentUnkoUsers: UnkoSession[]
  userSession: string | null
  onlineUsers: number
  todayStats: DailyStats
  cheerMessages: CheerMessage[]
}

interface User {
  id: string
  nickname: string
  totalUnkoCount: number
  lastUnkoAt?: number
}

interface UnkoSession {
  sessionId: string
  userId: string
  nickname: string
  startTime: number
  totalCheers: number
}

interface CheerMessage {
  id: number
  text: string
  emoji: string
}

リアルタイム接続管理

// lib/sse.ts
export class SSEConnection {
  private eventSource: EventSource | null = null
  private reconnectDelay = 1000
  private maxReconnectDelay = 30000

  connect(onMessage: (event: SSEEvent) => void) {
    this.eventSource = new EventSource('/api/events')
    
    this.eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data)
      onMessage(data)
    }

    this.eventSource.onerror = () => {
      this.handleReconnect()
    }
  }

  private handleReconnect() {
    setTimeout(() => {
      this.connect()
      this.reconnectDelay = Math.min(
        this.reconnectDelay * 2, 
        this.maxReconnectDelay
      )
    }, this.reconnectDelay)
  }

  disconnect() {
    this.eventSource?.close()
  }
}

🔐 セキュリティ・制限

Rate Limiting

// Rate limiting設定
const rateLimits = {
  unkoStart: { maxRequests: 10, windowMs: 60000 }, // 1分間に10回
  cheerSend: { maxRequests: 30, windowMs: 60000 }, // 1分間に30回
  userCreation: { maxRequests: 5, windowMs: 300000 }, // 5分間に5回
  apiGeneral: { maxRequests: 100, windowMs: 60000 } // 1分間に100回
}

データ保護

  • 匿名ユーザーID: UUID v4使用
  • IPアドレス: ハッシュ化保存(必要時のみ)
  • 個人特定情報: 一切保存しない
  • XSS対策: すべてのユーザー入力をサニタイズ
  • CSRF対策: Next.js標準保護機能

セッション管理

// セッション自動クリーンアップ
const cleanupAbandonedSessions = async () => {
  const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000)
  
  await turso.execute({
    sql: `UPDATE unko_sessions 
          SET status = 'abandoned' 
          WHERE status = 'in_progress' 
          AND start_time < ?`,
    args: [thirtyMinutesAgo]
  })
}

// 5分ごとに実行
setInterval(cleanupAbandonedSessions, 5 * 60 * 1000)

📊 パフォーマンス・スケーリング

キャッシュ戦略

// Next.js API Routes キャッシュ
export async function GET() {
  const stats = await getGlobalStats()
  
  return Response.json(stats, {
    headers: {
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
    }
  })
}

// Turso接続プール
const tursoPool = {
  connections: [],
  maxConnections: 10,
  getConnection: () => { /* プール管理 */ }
}

モニタリング指標

// 監視対象メトリクス
const metrics = {
  concurrentUnkoUsers: () => getCurrentUnkoUsers().length,
  totalActiveConnections: () => sseConnections.size,
  averageUnkoDuration: () => calculateAverageUsingDB(),
  dailyUnkoCount: () => getTodayUnkoCount(),
  cheersSentPerDay: () => getTodayCheersCount(),
  responseTime: () => measureAPILatency(),
  errorRate: () => calculateErrorRate()
}

スケーリング戦略

想定負荷:
├── ~100同時接続: 現構成で余裕
├── ~1000同時接続: Vercel Proプラン
├── ~10000同時接続: Redis追加検討
└── ~50000同時接続: マイクロサービス化

💰 コスト試算

無料枠活用

Vercel Free Tier:
├── 100GB帯域幅/月
├── Function実行時間: 100時間/月
├── Edge Functions: 500KB-秒/日
└── データベース接続: 無制限

Turso Free Tier:
├── 9GBストレージ
├── 1億行読み取り/月
├── 2500万行書き込み/月
├── 最大500データベース
└── 3拠点レプリケーション

GitHub:
└── パブリックリポジトリ無料

成長段階別コスト

Phase 1 (~1000ユーザー):
├── Vercel: $0
├── Turso: $0
├── ドメイン: $12/年
└── 合計: $1/月

Phase 2 (~10000ユーザー):
├── Vercel Pro: $20/月
├── Turso: $0-8.25/月
├── 監視ツール: $0-10/月
└── 合計: $20-38/月

Phase 3 (~100000ユーザー):
├── Vercel Enterprise: $400/月
├── Turso Scale: $50-200/月
├── 追加インフラ: $100-300/月
└── 合計: $550-900/月

🚀 開発・デプロイフロー

開発スケジュール

Day 1: 基盤構築
├── Next.js プロジェクト作成
├── Turso データベース設定
├── 基本API実装
└── 匿名ユーザー機能

Day 2: 完成・リリース
├── SSE実装
├── フロントエンドUI
├── 応援機能
├── 統計機能
├── PWA設定
└── 本番デプロイ

デプロイフロー

# 1. 環境構築
git clone https://github.com/your/unkonow
cd unkonow
npm install

# 2. Turso設定
turso auth login
turso db create unkonow
turso db tokens create unkonow

# 3. 環境変数設定
echo "TURSO_DATABASE_URL=..." > .env.local
echo "TURSO_AUTH_TOKEN=..." >> .env.local

# 4. ローカル開発
npm run dev

# 5. Vercel デプロイ
vercel --prod

CI/CD設定

# .github/workflows/deploy.yml
name: Deploy to Vercel
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run build
      - run: npm run test
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}

🔧 運用・保守

ログ管理

// 構造化ログ
const logger = {
  info: (event, metadata) => {
    console.log(JSON.stringify({
      level: 'info',
      event,
      timestamp: new Date().toISOString(),
      ...metadata
    }))
  },
  error: (error, context) => {
    console.error(JSON.stringify({
      level: 'error',
      message: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString(),
      ...context
    }))
  }
}

ヘルスチェック

// GET /api/health
export async function GET() {
  const checks = {
    database: await checkTursoConnection(),
    sse: checkSSEConnections(),
    memory: process.memoryUsage(),
    uptime: process.uptime()
  }
  
  const isHealthy = Object.values(checks).every(check => check.status === 'ok')
  
  return Response.json({
    status: isHealthy ? 'healthy' : 'unhealthy',
    timestamp: new Date().toISOString(),
    checks
  }, {
    status: isHealthy ? 200 : 503
  })
}

バックアップ戦略

# Turso自動バックアップ(標準機能)
# 追加のデータエクスポート
turso db dump unkonow --output backup-$(date +%Y%m%d).sql

# 定期バックアップスクリプト
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
turso db dump unkonow --output "backups/unkonow_backup_$DATE.sql"
aws s3 cp "backups/unkonow_backup_$DATE.sql" s3://unkonow-backups/

🎯 成功指標(KPI)

技術指標

  • 可用性: 99.9%以上
  • レスポンス時間: API 200ms以下、SSE 100ms以下
  • 同時接続数: 1000接続まで安定動作
  • エラー率: 0.1%以下

ビジネス指標

  • DAU (Daily Active Users): 目標100人
  • 平均セッション時間: 5分以上
  • 応援送信率: うんこ1回あたり平均3回
  • 継続率: 7日後30%、30日後10%

運用指標

  • デプロイ頻度: 週1回以上
  • MTTR (平均復旧時間): 30分以内
  • カバレッジ: テストカバレッジ80%以上
  • 技術負債: 月1回の定期リファクタリング

結論: この構成により、最小コストで最大効果のうんこ共有サービスを構築可能。技術的制約が少なく、スケーラビリティも確保されている。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment