🗄️ Tsuwa Database Schema Reference

PostgreSQL 17.6 Supabase Cloud (Tokyo)

TSUWA ハイブリッド通話プラットフォームの統一データベーススキーマ仕様書です。WebRTC 通話番号(6桁 `peer_id`)、Push デバイストークン、Kamailio SIP 代理登録(`uacreg`)、ポッド・グループ、SFU ボイスルーム、および API キー認証を含む全 16 テーブルの定義・制約・リレーションを提供します。

総テーブル数
16
Unified Schema 20260906 + API Keys
ホスティング環境
Supabase Cloud
AWS 東京リージョン (ap-northeast-1)
通話番号空間
6桁 TSUWA番号
device_secrets.peer_id (240+ 端末)
セキュリティ保護
RLS 有効
PostgREST anon 不正アクセス完全遮断
📊 エンティティ・リレーション図 (ER Diagram)
erDiagram
    users ||--o{ sip_accounts : "owns"
    users ||--o{ groups : "hosts"
    groups ||--o{ group_members : "contains"
    groups ||--o{ membership_periods : "tracks"
    groups ||--o{ group_timeline_events : "records"
    groups ||--o| voice_rooms : "manages"
    voice_rooms ||--o{ voice_room_participants : "participates"

    device_secrets ||--o| device_tokens : "peer_id"
    device_secrets ||--o| closed_group_policies : "peer_id"
    device_secrets ||--o| sip_account_activity : "peer_id"
    
    users {
        uuid id PK
        varchar username UK
        varchar display_name
        varchar status
    }
    device_secrets {
        varchar peer_id PK
        varchar secret_hash
        timestamptz updated_at
    }
    device_tokens {
        uuid id PK
        varchar peer_id UK
        text voip_token
        text apns_token
        jsonb raw_data
    }
    sip_accounts {
        uuid id PK
        uuid user_id FK
        varchar extension
        varchar domain
        varchar auth_username
    }
    uacreg {
        serial id PK
        varchar l_uuid UK
        varchar r_username
        varchar r_domain
        varchar auth_proxy
    }
    groups {
        uuid id PK
        varchar group_code UK
        uuid host_user_id FK
        varchar host_peer_id
        varchar name
    }
    group_members {
        uuid id PK
        uuid group_id FK
        varchar peer_id
        varchar sub_extension
        varchar role
    }
    voice_rooms {
        uuid id PK
        uuid group_id UK,FK
        varchar sfu_room_id
        boolean is_active
    }
    call_logs {
        uuid id PK
        varchar call_session_id UK
        varchar caller_peer_id
        varchar callee_peer_id
        varchar status
    }
    api_keys {
        uuid id PK
        varchar key_name
        varchar key_hash UK
        jsonb permissions
    }
                    
device_secrets 端末認証シークレット・通話番号マスター
端末・通話番号 RLS 有効
TSUWA 通話番号(6桁 peer_id)の全件マスターテーブルです。端末の登録時に生成された認証シークレットのソルト付きハッシュ値を保持し、他端末による番号の不正乗っ取りを暗号学的に防止します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
peer_id VARCHAR(50) PK NO - 6桁の通話番号(例: 590004)。システム内での端末識別子。
secret_hash VARCHAR(128) - NO - 端末認証シークレットの SHA-256 ソルト付きハッシュ(SHA256(peer_id:secret))。
created_at TIMESTAMPTZ - YES CURRENT_TIMESTAMP 番号初回発行日時。
updated_at TIMESTAMPTZ - YES CURRENT_TIMESTAMP 最終シークレット更新・再ログイン日時。
RLS ポリシー: anon_deny_device_secrets FOR ALL USING (false); (PostgREST anon 直接参照禁止)
CREATE TABLE IF NOT EXISTS device_secrets (
    peer_id VARCHAR(50) PRIMARY KEY,
    secret_hash VARCHAR(128) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
device_tokens Push通知デバイストークン
端末・通話番号 RLS 有効
アプリの着信時 CallKit (Apple Push Notification VoIP) および FCM (Firebase Cloud Messaging) の通知トークンを保持します。iOS では通話用と通常アラート用トークンが独立して登録されます。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() レコード一意識別子。
peer_id VARCHAR(50) UNIQUE NO - 通話番号(例: 590004)。1通話番号につき1つの最新トークンが紐付きます。
token TEXT - YES '' 代表 Push トークン(VoIP / FCM)。
platform VARCHAR(20) CHECK YES 'ios' OS プラットフォーム ('ios', 'android', 'ios_voip', 'ios_alert', 'android_fcm')。
voip_token TEXT - YES '' iOS CallKit 専用 VoIP PushKit トークン。
apns_token TEXT - YES '' iOS 通常 Alert / バッジ更新用 APNs トークン。
raw_data JSONB - YES '{}'::jsonb バッジ数や端末メタデータ等の生 JSON 辞書。
bundle_id VARCHAR(100) - YES '' アプリバンドル ID(例: uk.kicensa.tsuwa)。
updated_at TIMESTAMPTZ - YES CURRENT_TIMESTAMP トークン最終登録・リフレッシュ日時。
インデックス: CREATE INDEX device_tokens_peer_id_idx ON device_tokens(peer_id);
CREATE TABLE IF NOT EXISTS device_tokens (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    peer_id VARCHAR(50) UNIQUE NOT NULL,
    token TEXT DEFAULT '',
    platform VARCHAR(20) DEFAULT 'ios' CHECK (platform IN ('ios', 'android', 'ios_voip', 'ios_alert', 'android_fcm')),
    bundle_id VARCHAR(100) DEFAULT '',
    voip_token TEXT DEFAULT '',
    apns_token TEXT DEFAULT '',
    raw_data JSONB DEFAULT '{}'::jsonb,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS device_tokens_peer_id_idx ON device_tokens(peer_id);
closed_group_policies クローズドグループ招待制限ポリシー
端末・通話番号
指定された番号が所属するクローズドポッドへの招待制限・着信許可ポリシーを保持します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
peer_id VARCHAR(50) PK NO - 対象端末の通話番号。
group_code VARCHAR(20) - NO - 対象ポッド番号(8桁)。
policy VARCHAR(30) - YES 'closed_members_only' 制限種別(クローズドメンバー限定など)。
updated_at TIMESTAMPTZ - YES CURRENT_TIMESTAMP ポリシー最終設定日時。
CREATE TABLE IF NOT EXISTS closed_group_policies (
    peer_id VARCHAR(50) PRIMARY KEY,
    group_code VARCHAR(20) NOT NULL,
    policy VARCHAR(30) DEFAULT 'closed_members_only',
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
users ユーザー基本情報テーブル
ユーザー・認証
契約ユーザーまたは端末 UUID に紐付くアカウント基本情報を管理します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() ユーザー一意 ID。
username VARCHAR(50) UNIQUE NO - ユーザー名または端末識別文字列。
display_name VARCHAR(100) - NO - アプリ上での表示名。
password_hash VARCHAR(255) - NO '' パスワードハッシュ。
status VARCHAR(20) CHECK YES 'active' ステータス ('active', 'disabled')。
created_at TIMESTAMPTZ - YES CURRENT_TIMESTAMP 作成日時。
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL,
    display_name VARCHAR(100) NOT NULL,
    password_hash VARCHAR(255) NOT NULL DEFAULT '',
    status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS users_username_idx ON users(username);
api_keys APIキー管理基盤
ユーザー・認証 RLS 有効
外部連携や将来のマルチテナント運用・管理者操作・シグナリング API 呼び出しにおけるセキュアな認証キーを管理します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() API キー一意 ID。
key_name VARCHAR(100) - NO - キーの識別名称(例: "Production Signaling Server")。
key_hash VARCHAR(64) UNIQUE NO - SHA-256 ハッシュ化された API キー本体。
key_prefix VARCHAR(16) - NO - キー識別用のプレフィックス(例: tsw_live_)。
permissions JSONB - YES '["signaling", "push", "uacreg"]' 許可されたスコープ一覧。
is_active BOOLEAN - YES true 有効フラグ。
last_used_at TIMESTAMPTZ - YES - 最終 API 実行日時。
CREATE TABLE IF NOT EXISTS api_keys (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    key_name VARCHAR(100) NOT NULL,
    key_hash VARCHAR(64) NOT NULL UNIQUE,
    key_prefix VARCHAR(16) NOT NULL,
    permissions JSONB DEFAULT '["signaling", "push", "uacreg"]'::jsonb,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    last_used_at TIMESTAMP WITH TIME ZONE NULL
);
sip_accounts SIP アカウント・内線情報テーブル
SIP・Kamailio
ユーザーに紐付く外部 PBX / Asterisk の SIP 内線番号設定を保持します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() レコード一意 ID。
user_id UUID FK YES - 所有ユーザー ID(users.id を参照)。
extension VARCHAR(50) - NO - 内線番号(例: 208)。
domain VARCHAR(100) - NO - SIP ドメイン(例: kicensa.uk)。
auth_ha1 VARCHAR(128) - YES '' Kamailio 5.8 ダイジェスト認証用 MD5 HA1 ハッシュ。
transport VARCHAR(10) CHECK YES 'udp' トランスポートプロトコル ('udp', 'tcp', 'tls', 'ws', 'wss')。
CREATE TABLE IF NOT EXISTS sip_accounts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    extension VARCHAR(50) NOT NULL,
    domain VARCHAR(100) NOT NULL,
    auth_username VARCHAR(50) NOT NULL DEFAULT '',
    auth_password_hash VARCHAR(255) NOT NULL DEFAULT '',
    auth_ha1 VARCHAR(128) NOT NULL DEFAULT '',
    realm VARCHAR(100) NOT NULL DEFAULT '',
    proxy_host VARCHAR(100) NOT NULL DEFAULT '',
    proxy_port INT DEFAULT 5060,
    transport VARCHAR(10) DEFAULT 'udp' CHECK (transport IN ('udp', 'tcp', 'tls', 'ws', 'wss')),
    is_enabled BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(user_id, extension, domain)
);
uacreg Kamailio 代理レジストラ (UAC Registrant)
SIP・Kamailio RLS 有効
Kamailio SIP サーバーが上位 PBX(Asterisk 等)へ常時代理ログイン(REGISTER)するためのレコードを管理します。アプリがバックグラウンド時でも本テーブルを参照して Kamailio が着信を代理受信し、PushKit 経由でスマホを起床させます。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id SERIAL PK NO - 自動連番 ID。
l_uuid VARCHAR(64) UNIQUE NO '' 代理登録一意キー(形式: uac_{peer_id}_{sip_username})。
r_username VARCHAR(64) INDEX NO '' 対向 PBX の内線番号(例: 208)。
r_domain VARCHAR(128) - NO '' 対向 PBX の IP / ホスト名(例: 172.29.13.1)。
auth_ha1 VARCHAR(128) - NO '' Kamailio 5.8 認証ハッシュ(MD5(user:realm:pass))。
auth_proxy VARCHAR(128) - NO '' 送信先 SIP URI(例: sip:172.29.13.1:5060)。空文字不可。
expires INT - NO 300 SIP レジストレーション有効期間(秒)。
CREATE TABLE IF NOT EXISTS uacreg (
    id SERIAL PRIMARY KEY,
    l_uuid VARCHAR(64) DEFAULT '' NOT NULL,
    l_username VARCHAR(64) DEFAULT '' NOT NULL,
    l_domain VARCHAR(128) DEFAULT '' NOT NULL,
    r_username VARCHAR(64) DEFAULT '' NOT NULL,
    r_domain VARCHAR(128) DEFAULT '' NOT NULL,
    realm VARCHAR(64) DEFAULT '' NOT NULL,
    auth_username VARCHAR(64) DEFAULT '' NOT NULL,
    auth_password VARCHAR(64) DEFAULT '' NOT NULL,
    auth_ha1 VARCHAR(128) DEFAULT '' NOT NULL,
    auth_proxy VARCHAR(128) DEFAULT '' NOT NULL,
    expires INT DEFAULT 300 NOT NULL,
    flags INT DEFAULT 0 NOT NULL,
    reg_delay INT DEFAULT 0 NOT NULL,
    contact_addr VARCHAR(128) DEFAULT '' NOT NULL,
    socket VARCHAR(128) DEFAULT '' NOT NULL,
    CONSTRAINT uacreg_l_uuid_idx UNIQUE (l_uuid)
);
sip_account_activity SIP アカウント最終アクセス履歴
SIP・Kamailio
各端末の最終オンライン・アクティビティ時刻を記録します。7 日間以上アクセスのない休眠アカウントの代理登録(uacreg)を自動パージするために使用されます。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
peer_id VARCHAR(50) PK NO - 通話番号。
last_seen TIMESTAMPTZ - YES CURRENT_TIMESTAMP 最終アクセス日時。
CREATE TABLE IF NOT EXISTS sip_account_activity (
    peer_id VARCHAR(50) PRIMARY KEY,
    last_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
version Kamailio スキーマバージョン
SIP・Kamailio
Kamailio モジュールが起動時にテーブル構造の整合性を確認するための標準バージョン管理テーブルです。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
table_name VARCHAR(32) PK NO - テーブル名(例: uacreg)。
table_version INT - NO 0 バージョン番号(例: 5)。
CREATE TABLE IF NOT EXISTS version (
    table_name VARCHAR(32) NOT NULL,
    table_version INT DEFAULT 0 NOT NULL,
    CONSTRAINT version_table_name_idx UNIQUE (table_name)
);
groups ポッド・グループ基本テーブル
ポッド・通話ルーム
複数人でのグループ通話・共有空間である「ポッド(Pod)」の基本情報を管理します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() ポッド一意 ID。
group_code VARCHAR(20) UNIQUE NO - 8桁のポッド番号(例: 800001)。入室時に指定。
host_peer_id VARCHAR(50) - NO '' ポッドのホスト端末通話番号。
name VARCHAR(100) - NO - ポッド名称。
CREATE TABLE IF NOT EXISTS groups (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    group_code VARCHAR(20) UNIQUE NOT NULL,
    host_user_id UUID NULL REFERENCES users(id) ON DELETE SET NULL,
    host_peer_id VARCHAR(50) NOT NULL DEFAULT '',
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS groups_group_code_idx ON groups(group_code);
group_members グループメンバーシップテーブル
ポッド・通話ルーム
ポッドに所属するメンバーのステータスおよびポッド内サブ内線番号を管理します。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() メンバーシップ一意 ID。
group_id UUID FK NO - 所属ポッド ID(groups.id を参照)。
peer_id VARCHAR(50) - NO - 参加メンバーの通話番号。
role VARCHAR(20) CHECK YES 'member' 権限ロール ('host', 'co_host', 'member')。
status VARCHAR(20) CHECK YES 'joined' 参加状態 ('invited', 'joined', 'declined', 'left')。
CREATE TABLE IF NOT EXISTS group_members (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    group_id UUID NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
    peer_id VARCHAR(50) NOT NULL,
    sub_extension VARCHAR(10) NOT NULL,
    role VARCHAR(20) DEFAULT 'member' CHECK (role IN ('host', 'co_host', 'member')),
    status VARCHAR(20) DEFAULT 'joined' CHECK (status IN ('invited', 'joined', 'declined', 'left')),
    joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(group_id, peer_id),
    UNIQUE(group_id, sub_extension)
);
membership_periods メンバー参加期間履歴テーブル
ポッド・通話ルーム
メンバーがポッドに参加していた期間を記録し、途中参加メンバーに対して参加前の過去ログを非共有にするプライバシー境界インデックスとして機能します。
CREATE TABLE IF NOT EXISTS membership_periods (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    group_id UUID NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
    peer_id VARCHAR(50) NOT NULL,
    joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    left_at TIMESTAMP WITH TIME ZONE NULL
);
group_timeline_events 統一タイムラインイベントテーブル
ポッド・通話ルーム
通話開始・終了、ボイスルーム起動、ファイル共有等のポッド内イベントを統一的に記録します。
CREATE TABLE IF NOT EXISTS group_timeline_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    group_id UUID NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
    event_type VARCHAR(30) NOT NULL CHECK (event_type IN (
        'call_start', 'call_end', 'file_shared', 'invite',
        'member_joined', 'member_left', 'voice_room_start',
        'voice_room_end', 'system_notice'
    )),
    sender_peer_id VARCHAR(50) NOT NULL,
    payload JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
voice_rooms ボイスルーム管理テーブル (SFU)
ポッド・通話ルーム
Cloudflare Calls SFU を用いた同時複数人通話ルームのセッション ID およびアクティブ状態を管理します。
CREATE TABLE IF NOT EXISTS voice_rooms (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    group_id UUID UNIQUE NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
    sfu_room_id VARCHAR(100) NOT NULL,
    is_active BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
voice_room_participants ボイスルーム参加者テーブル
ポッド・通話ルーム
ボイスルームに現在接続中の端末通話番号(`peer_id`)を追跡します。
CREATE TABLE IF NOT EXISTS voice_room_participants (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    voice_room_id UUID NOT NULL REFERENCES voice_rooms(id) ON DELETE CASCADE,
    peer_id VARCHAR(50) NOT NULL,
    joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(voice_room_id, peer_id)
);
call_logs 通話履歴・メタデータテーブル
通話履歴
WebRTC P2P 通話および SIP 外線/内線通話の発着信メタデータ・通話時間を記録します。音声データそのものはサーバー上に一切保存されず完全非保持(Zero Knowledge)です。
カラム名 データ型 キー/制約 NULL デフォルト値 説明
id UUID PK NO gen_random_uuid() 通話ログ一意 ID。
call_session_id VARCHAR(100) UNIQUE NO - シグナリングセッション UUID。
caller_peer_id VARCHAR(50) - NO - 発信元通話番号。
callee_peer_id VARCHAR(50) - NO - 着信先通話番号。
call_type VARCHAR(20) CHECK YES - 通話種別 ('webrtc_p2p', 'webrtc_pod', 'sip_inbound', 'sip_outbound')。
status VARCHAR(20) CHECK YES 'initiated' 通話状態 ('initiated', 'ringing', 'answered', 'rejected', 'missed', 'failed', 'ended')。
duration_seconds INT - YES 0 通話時間(秒)。
CREATE TABLE IF NOT EXISTS call_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    call_session_id VARCHAR(100) UNIQUE NOT NULL,
    caller_peer_id VARCHAR(50) NOT NULL,
    callee_peer_id VARCHAR(50) NOT NULL,
    call_type VARCHAR(20) CHECK (call_type IN ('webrtc_p2p', 'webrtc_pod', 'sip_inbound', 'sip_outbound')),
    status VARCHAR(20) DEFAULT 'initiated' CHECK (status IN ('initiated', 'ringing', 'answered', 'rejected', 'missed', 'failed', 'ended')),
    duration_seconds INT DEFAULT 0,
    started_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    answered_at TIMESTAMP WITH TIME ZONE NULL,
    ended_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
DDL をクリップボードにコピーしました!