メインコンテンツまでスキップ

Sho-Room 仕様書(詳細)

1. システム概要

1.1 目的

本システムは、会津大学学園祭(通称: 蒼翔祭)実行委員会 における統合管理ツールである。

来場者管理、イベント運営、備品管理、外部連携など、学祭運営に必要な機能を一元的に提供する。

1.2 システム構成

  • フロントエンド: Vite + React(TypeScript)/ Cloudflare Pages
  • BFF: Cloudflare Pages Functions
  • バックエンド: Hono / Cloudflare Workers
  • データベース: Cloudflare D1 (soshosai-system-db)
  • UI: Shadcn UI (Radix UI base) + Tailwind CSS

1.3 対象ユーザー

  • 学祭委員全員: Sho-Room Webアプリを使用
  • 管理者: スタッフ管理、BAN操作、システム設定

2. アーキテクチャ構成

2.1 技術スタック

レイヤー技術
FrontendVite + React (TypeScript)
HostingCloudflare Pages
BFFCloudflare Pages Functions
APIHono / Cloudflare Workers
DatabaseCloudflare D1
ORMDrizzle ORM
ValidationZod / drizzle-orm/zod
TestVitest

2.2 ドメイン構成

本システムは複数のドメイン構成で運用される:

  • login-api: loginapi.soshosai.com - 認証フロントエンド
  • auth-workers: 認証バックエンドサービス群(Service Binding経由)
    • discord-auth-worker: Discord OAuth2認証
    • line-auth-worker: LINE OAuth2認証
    • unified-auth-worker: JWT検証・BAN管理
  • Sho-Room: room.soshosai.com - 学祭委員向け管理画面(本システム)
  • API: v3.api.soshosai.com - バックエンドAPI(共通)

2.3 認証アーキテクチャ

認証は login-apiauth-workers の2層構成で実装する。

認証システムの詳細は認証システム仕様書を参照

概要:

コンポーネント役割
login-api認証フロントエンド。OAuthリダイレクト、Cookie設定
discord-auth-workerDiscord OAuth2認証・ロール検証
line-auth-workerLINE OAuth2認証
unified-auth-workerJWT検証・BAN管理・レート制限

BFFパターン:

Sho-RoomではCloudflare Pages Functionsを使用したBFFパターンを採用。 CookieからJWTを取得し、Authorization: Bearer xxx ヘッダーに変換してAPIに転送する。

Browser → Pages (React) → Pages Functions (BFF) → API → unified-auth-worker
Cookie Header変換

2.4 ID戦略

  • システム内部ID: 原則 UUIDv7 を使用(時系列ソート可能)
  • Discord関連: Discord User ID(文字列)をそのまま使用
  • 学籍番号: s1234567 形式

2.5 システム構成図

2.6 BFF Service Binding設定

Sho-RoomのPages FunctionsはService Bindingで直接Workerを呼び出す。

wrangler.jsonc(room.soshosai.com):

{
"name": "sho-room",
"pages_build_output_dir": "./dist",
"compatibility_date": "2025-02-01",
"services": [
{ "binding": "AUTH_SERVICE", "service": "unified-auth-worker" },
{ "binding": "GATEWAY", "service": "system-gateway-worker" },
],
}

Pages Functions実装例(functions/api/[[path]].ts):

interface Env {
AUTH_SERVICE: Fetcher;
GATEWAY: Fetcher;
}

export const onRequest: PagesFunction<Env> = async (context) => {
const { request, env, params } = context;

// CookieからJWTを取得
const cookies = request.headers.get('Cookie') || '';
const staffJwt = cookies.match(/soshosai_staff_jwt=([^;]+)/)?.[1];

if (!staffJwt) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}

// Service Binding経由でGateway Workerを呼び出し
const path = Array.isArray(params.path)
? params.path.join('/')
: params.path;
return env.GATEWAY.fetch(`https://gateway/${path}`, {
method: request.method,
headers: {
Authorization: `Bearer ${staffJwt}`,
'Content-Type':
request.headers.get('Content-Type') || 'application/json',
},
body: request.body,
});
};

3. データベース設計

詳細はDB設計を参照


4. ユースケース図

権限ビット(permission bits)に基づくアクセス制御を表す。上位権限は下位権限を包含する。


5. 機能要件詳細

F-001: 認証基盤

概要: Discord OAuth2による認証。BFFパターンによるセキュアな通信。

認証フロー:

  1. ユーザーがroom.soshosai.comにアクセス
  2. Pages FunctionsでCookieの有無を確認
  3. 分岐処理:
    • 未ログイン: loginapi.soshosai.comへリダイレクト
    • BANユーザー: 403 Forbidden画面を表示
    • 正常: アクセス許可

Cookieの設定(login-apiが設定):

Set-Cookie: soshosai_staff_jwt=eyJhbGc...;
Domain=.soshosai.com;
Path=/;
HttpOnly;
Secure;
SameSite=Lax;
Max-Age=259200

権限更新:

  • ログイン時、Discordのロール情報を再取得
  • Departmentsテーブルでロールに対応する権限を確認
  • 最高権限をStaffs.authorityに設定
  • JWTを再発行

F-002: 入場者データ表示

概要: 全来場者データの閲覧。

UI仕様:

  • ページネーション: 1ページあたり50〜100件
  • 検索: ID、氏名、属性等で絞り込み(実行ボタン必須)
  • レスポンシブ: PC=テーブル、Mobile=カードリスト

F-003: 入場者グループ管理

概要: グループ情報の閲覧・修正。

UI仕様:

  • グループID検索機能(実行ボタン必須)
  • グループ詳細画面で紐づくメンバー情報を展開表示

F-004: 入場者数管理(ダッシュボード)

概要: リアルタイム集計データの可視化。

UI仕様:

  • 自動更新: 60秒ポーリングまたは手動リロードボタン
  • グラフ描画: Recharts使用

F-005: 入場者確認

概要: 予約・入場資格の照会(Read-only)。

API: v3.api.soshosai.com(BFF経由)

UI仕様:

  • 検索バー固定配置
  • 検索結果にステータスバッジ(入場済み/未入場)

概要: 短縮URLプロジェクトの一覧・作成・QR生成。

API: fwd.soshosai.com

UI仕様:

  • プロジェクト一覧にページネーション適用

F-007: 管理者システム

概要: スタッフ管理・BAN操作。

画面構成:

  1. スタッフ検索・一覧画面:

    • 氏名、ID、所属部署での検索(実行ボタン必須)
    • ページネーション
    • BAN済みユーザーは行をグレーアウト
  2. スタッフ詳細・操作モーダル:

    • 基本情報表示(名前、Discord名、部署、権限)
    • BAN操作エリア(理由入力、確認ダイアログ)

ロジック:

  • 操作者と対象者の権限レベルを比較
  • 同等以上の権限を持つ対象者の操作は不可(super_adminは例外)

F-008: イベント管理

概要: 学内イベントデータの操作。

UI仕様:

  • 参加者一覧はページネーション適用

F-009: LINE連携

概要: LINE公式アカウントとの連携。

機能:

  • 受信メッセージ一覧表示
  • 返信管理

UI仕様:

  • メッセージ履歴はページネーション取得

F-010: 備品管理

概要: 備品一覧、検索、編集、CSV出力。

詳細は備品管理システム仕様書を参照


6. 非機能要件

6.1 パフォーマンス

  • 一覧画面: 大量データでも高速表示
  • 想定同時接続数: 最大30人

6.2 セキュリティ

  • 認証: Discord OAuth2 + JWT(HttpOnly Cookie)
  • BFF: Pages FunctionsによるCookie→Header変換
  • 認可: 権限レベルによるアクセス制御
  • 権限レベル:
    • staff: 一般スタッフ
    • dept_admin: 部門管理者
    • system_admin: システム管理者
    • super_admin: 最高管理者

6.3 可用性

  • Cloudflare Pages/Workersを使用し、基本的に常時稼働

7. API仕様

7.1 共通仕様

ベースURL: https://v3.api.soshosai.com(BFF経由でアクセス)

認証: BFFがCookieからJWTを取得し、Authorization: Bearer <JWT> ヘッダーを付与

レスポンス形式:

// 成功時
{
"status": 200,
"data": { ... }
}

// エラー時
{
"status": 400,
"message": "エラーメッセージ"
}

ステータスコード:

  • 200 OK: 成功
  • 201 Created: 作成成功
  • 204 No Content: 削除成功
  • 400 Bad Request: リクエスト不正
  • 401 Unauthorized: 認証失敗
  • 403 Forbidden: 権限不足 / BAN
  • 404 Not Found: リソース不在
  • 500 Internal Server Error: サーバーエラー

クエリパラメータ:

  • limit: 取得件数(Default: 50)
  • offset / cursor: ページネーション用
  • q: 検索キーワード

8. 開発環境

8.1 プロジェクト構造

sho-room/
├── functions/ # Pages Functions (BFF)
│ └── api/
│ └── [[path]].ts # APIプロキシ
├── src/
│ ├── components/ # Reactコンポーネント
│ ├── hooks/ # カスタムフック
│ ├── utils/ # ユーティリティ関数
│ ├── services/ # API通信ロジック
│ ├── types/ # TypeScript型定義
│ ├── App.tsx
│ └── main.tsx
├── tests/ # テストファイル
│ ├── unit/ # 単体テスト
│ └── integration/ # 統合テスト
├── public/
├── package.json
├── vite.config.ts
├── vitest.config.ts
└── tsconfig.json

8.2 BFF実装

functions/api/[[path]].ts:

interface Env {
AUTH_SERVICE: Fetcher;
GATEWAY: Fetcher;
}

export const onRequest: PagesFunction<Env> = async (context) => {
const { request, env, params } = context;

// CookieからJWTを取得
const cookies = request.headers.get('Cookie') || '';
const staffJwt = cookies.match(/soshosai_staff_jwt=([^;]+)/)?.[1];

if (!staffJwt) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}

// Service Binding経由でGateway Workerを呼び出し
const path = Array.isArray(params.path)
? params.path.join('/')
: params.path;
return env.GATEWAY.fetch(`https://gateway/${path}`, {
method: request.method,
headers: {
Authorization: `Bearer ${staffJwt}`,
'Content-Type':
request.headers.get('Content-Type') || 'application/json',
},
body: request.body,
});
};

8.3 開発コマンド

# 開発サーバー起動
npm run dev

# または(Pages Functions含む)
wrangler pages dev -- npm run dev

# テスト実行
npm run test

# カバレッジ
npm run test:coverage

# ビルド&デプロイ
npm run build
wrangler pages deploy dist

9. UI/UXルール

9.1 デザインシステム

  • Component Library: Shadcn UI (Radix UI base)
  • Styling: Tailwind CSS
  • Color Palette:
    • Primary: #008578(ブランドカラー)
    • Base: Slate / Zinc

9.2 データ操作ルール

  • 動的フィルタ禁止: リアルタイム検索は行わない
  • 実行トリガー: 検索条件入力後、「検索/適用」ボタンでクエリ発行

10. 用語集

用語意味
JWTJSON Web Token。改ざん防止署名付きの認証トークン
BFFBackend for Frontend。フロント専用のプロキシ層
UUID一意識別子。UUIDv7は時系列ソート可能
PKPrimary Key(主キー)
FKForeign Key(外部キー)
BANアカウント停止措置
OAuth2認証プロトコル。Discord認証で使用