総テーブル数
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
);