SB-Ticket データベーススキーマ設計書
データベース: Supabase PostgreSQL
バージョン: 001_initial_schema + 002_rls_policies + 003_functions
最終更新: 2026-03-17
目次
- ER図
- ENUM型定義
- テーブル一覧
- テーブル詳細
- ビュー
- インデックス一覧
- RLSポリシー
- ストアド関数
- トリガー
ER図
erDiagram
players ||--o{ event_players : "出場"
players ||--o{ tickets : "販売"
players ||--o{ fans : "ファン管理"
players ||--o{ sponsors : "スポンサー"
players ||--o{ kickbacks : "キックバック"
players ||--o{ player_rewards : "還元"
players ||--o{ notifications : "通知受信"
players ||--o{ page_views : "PV"
admins ||--o{ events : "作成"
admins ||--o{ lps : "公開承認"
admins ||--o{ player_rewards : "確定承認"
events ||--o{ event_players : "出場選手"
events ||--o{ tickets : "チケット"
events ||--o{ lps : "LP(1:1)"
events ||--o{ notifications : "関連通知"
events ||--o{ player_rewards : "還元"
events ||--o{ page_views : "PV"
events ||--o{ fans : "最終購入イベント"
fans ||--o{ tickets : "購入"
sponsors ||--o{ kickbacks : "キックバック"
players {
UUID id PK
VARCHAR login_id UK
VARCHAR password_hash
VARCHAR name
VARCHAR name_kana
VARCHAR gym
VARCHAR weight_class
TEXT profile_text
TEXT profile_image_url
TEXT phone_encrypted
TEXT email_encrypted
JSONB sns_links
VARCHAR slug UK
user_role role
BOOLEAN is_locked
INTEGER failed_attempts
TIMESTAMPTZ locked_at
VARCHAR last_password_hash
BOOLEAN must_change_password
TIMESTAMPTZ password_changed_at
BOOLEAN phone_verified
JSONB push_subscription
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
TIMESTAMPTZ deleted_at
}
admins {
UUID id PK
VARCHAR login_id UK
VARCHAR password_hash
VARCHAR name
TEXT phone_encrypted
user_role role
BOOLEAN is_locked
INTEGER failed_attempts
TIMESTAMPTZ locked_at
VARCHAR last_password_hash
BOOLEAN must_change_password
BOOLEAN phone_verified
JSONB push_subscription
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
TIMESTAMPTZ deleted_at
}
events {
UUID id PK
VARCHAR name
DATE event_date
VARCHAR venue
TIME door_open_time
TIME start_time
DATE ticket_sale_date
DATE ticket_deadline_date
JSONB seat_types
TEXT_ARRAY sales_channels
TEXT pia_base_url
TEXT description
TEXT_ARRAY promo_images
VARCHAR status
UUID created_by FK
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
TIMESTAMPTZ deleted_at
}
event_players {
UUID id PK
UUID event_id FK
UUID player_id FK
INTEGER quota
VARCHAR ref_code UK
TEXT pia_url
TEXT eplus_url
TEXT_ARRAY promo_images
DECIMAL reward_rate
TIMESTAMPTZ created_at
}
fans {
UUID id PK
UUID player_id FK
VARCHAR name
TEXT phone_encrypted
TEXT email_encrypted
TEXT memo
UUID last_purchase_event_id FK
TIMESTAMPTZ last_purchase_at
INTEGER total_purchases
BOOLEAN is_dormant
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
TIMESTAMPTZ deleted_at
}
tickets {
UUID id PK
UUID event_id FK
UUID player_id FK
UUID fan_id FK
VARCHAR seat_type
INTEGER quantity
INTEGER unit_price
INTEGER total_amount
ticket_route route
VARCHAR buyer_name
payment_method payment_method
BOOLEAN is_paid
TIMESTAMPTZ paid_at
VARCHAR memo
JSONB webhook_payload
TIMESTAMPTZ purchased_at
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
}
lps {
UUID id PK
UUID event_id FK_UK
lp_status status
TEXT html_content
TEXT hero_image_url
TEXT ogp_image_url
VARCHAR meta_title
TEXT meta_description
VARCHAR association_ref_code UK
TEXT association_pia_url
JSONB editor_data
VARCHAR generated_by_model
TIMESTAMPTZ published_at
UUID published_by FK
TIMESTAMPTZ unpublished_at
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
}
sponsors {
UUID id PK
UUID player_id FK
VARCHAR company_name
VARCHAR industry
VARCHAR contact_name
TEXT contact_phone_encrypted
TEXT contact_email_encrypted
TEXT logo_url
TEXT website_url
INTEGER contract_amount
DATE contract_start_date
DATE contract_end_date
concept_status concept_sales_status
TEXT concept_memo
BOOLEAN is_public_to_association
BOOLEAN is_approved_for_display
TEXT memo
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
TIMESTAMPTZ deleted_at
}
kickbacks {
UUID id PK
UUID sponsor_id FK
UUID player_id FK
INTEGER contract_amount
DECIMAL kickback_rate
INTEGER kickback_amount "GENERATED"
DATE payment_due_date
BOOLEAN is_paid
DATE paid_date
TEXT transfer_info_encrypted
TEXT memo
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
}
player_rewards {
UUID id PK
UUID event_id FK
UUID player_id FK
INTEGER lp_sales_amount
DECIMAL reward_rate
INTEGER reward_amount "GENERATED"
VARCHAR status
DATE payment_due_date
DATE paid_date
TIMESTAMPTZ confirmed_at
UUID confirmed_by FK
TEXT memo
TIMESTAMPTZ created_at
TIMESTAMPTZ updated_at
}
notifications {
UUID id PK
user_role recipient_type
UUID recipient_id
notification_type type
notification_method method
VARCHAR title
TEXT body
UUID related_event_id FK
UUID related_player_id FK
BOOLEAN is_read
BOOLEAN is_sent
TIMESTAMPTZ sent_at
VARCHAR sms_sid
TEXT error_message
TIMESTAMPTZ created_at
}
audit_logs {
UUID id PK
VARCHAR actor_type
UUID actor_id
VARCHAR action
VARCHAR resource_type
UUID resource_id
JSONB changes
INET ip_address
TEXT user_agent
TIMESTAMPTZ created_at
}
page_views {
UUID id PK
VARCHAR page_type
UUID player_id FK
UUID event_id FK
DATE viewed_at
INTEGER count
TIMESTAMPTZ created_at
}
sms_verification_codes {
UUID id PK
VARCHAR user_type
UUID user_id
VARCHAR code_hash
INTEGER attempts
TIMESTAMPTZ expires_at
BOOLEAN verified
TIMESTAMPTZ created_at
}
ENUM型定義
user_role(ユーザーロール)
| 値 | 説明 |
|---|
admin | 協会管理者 |
player | 選手 |
staff | 協会スタッフ |
concept | concept管理者(スポンサー営業) |
ticket_route(チケット販売経路)
| 値 | 説明 |
|---|
player_pia | 選手QR/URL経由(ぴあ) |
player_eplus | 選手URL経由(イープラス) |
association_lp | 協会LP経由(ぴあ) |
player_cash | 選手手売り(現金) |
player_transfer | 選手手売り(振込) |
day_ticket | 当日券 |
lp_status(LP公開ステータス)
| 値 | 説明 |
|---|
draft | 下書き |
reviewing | レビュー中 |
published | 公開中 |
unpublished | 非公開 |
concept_status(スポンサー営業ステータス)
| 値 | 説明 |
|---|
untouched | 未接触 |
approached | アプローチ済 |
negotiating | 交渉中 |
closed | 成約 |
passed | 見送り |
notification_type(通知種別)
| 値 | 説明 |
|---|
purchase | チケット購入通知 |
deadline | 締切通知 |
lp_published | LP公開通知 |
reward_confirmed | 還元額確定通知 |
reward_paid | 還元支払完了通知 |
notification_method(通知方法)
| 値 | 説明 |
|---|
sms | SMS |
push | Web Push |
both | SMS + Web Push |
payment_method(支払方法)
| 値 | 説明 |
|---|
cash | 現金 |
transfer | 振込 |
other | その他 |
テーブル一覧
| # | テーブル名 | 説明 | 論理削除 | RLS |
|---|
| 1 | players | 選手 | deleted_at | 有効 |
| 2 | admins | 管理者(admin/staff/concept) | deleted_at | 有効 |
| 3 | events | 試合・イベント | deleted_at | 有効 |
| 4 | event_players | 試合 x 選手 中間テーブル | - | 有効 |
| 5 | fans | ファン(購入者DB) | deleted_at | 有効 |
| 6 | tickets | チケット販売実績 | - | 有効 |
| 7 | lps | 試合LP(ランディングページ) | - | 有効 |
| 8 | sponsors | スポンサー企業 | deleted_at | 有効 |
| 9 | kickbacks | キックバック(concept紹介料) | - | 有効 |
| 10 | player_rewards | 選手還元(LP経由売上の還元管理) | - | 有効 |
| 11 | notifications | 通知 | - | 有効 |
| 12 | audit_logs | 操作ログ | - | 有効 |
| 13 | page_views | ページ閲覧数(ランキング用) | - | 有効 |
| 14 | sms_verification_codes | SMS認証コード(一時テーブル) | - | 有効 |
テーブル詳細
1. players(選手)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
login_id | VARCHAR(50) | UK, NOT NULL | - | ログインID |
password_hash | VARCHAR(255) | NOT NULL | - | bcryptハッシュ |
name | VARCHAR(100) | NOT NULL | - | 表示名 |
name_kana | VARCHAR(100) | - | - | カナ名 |
gym | VARCHAR(100) | - | - | ジム・所属 |
weight_class | VARCHAR(50) | - | - | 階級 |
profile_text | TEXT | - | - | プロフィール文 |
profile_image_url | TEXT | - | - | プロフィール画像URL |
phone_encrypted | TEXT | - | - | 電話番号(AES-256暗号化) |
email_encrypted | TEXT | - | - | メールアドレス(AES-256暗号化) |
sns_links | JSONB | - | '{}' | SNSリンク(JSON) |
slug | VARCHAR(100) | UK, NOT NULL | - | 公開ページURL用スラッグ |
role | user_role | - | 'player' | ロール |
is_locked | BOOLEAN | - | FALSE | アカウントロック状態 |
failed_attempts | INTEGER | - | 0 | 連続失敗回数 |
locked_at | TIMESTAMPTZ | - | - | ロック日時 |
last_password_hash | VARCHAR(255) | - | - | 前回パスワードハッシュ(再使用防止) |
must_change_password | BOOLEAN | - | TRUE | 初回ログイン時パスワード変更要否 |
password_changed_at | TIMESTAMPTZ | - | - | パスワード変更日時 |
phone_verified | BOOLEAN | - | FALSE | SMS認証済フラグ |
push_subscription | JSONB | - | - | Web Push購読情報 |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
deleted_at | TIMESTAMPTZ | - | - | 論理削除日時 |
セキュリティ: phone_encrypted, email_encrypted はAES-256で暗号化して保存。5回連続ログイン失敗でアカウントロック。
2. admins(管理者)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
login_id | VARCHAR(50) | UK, NOT NULL | - | ログインID |
password_hash | VARCHAR(255) | NOT NULL | - | bcryptハッシュ |
name | VARCHAR(100) | NOT NULL | - | 名前 |
phone_encrypted | TEXT | - | - | 電話番号(AES-256暗号化) |
role | user_role | NOT NULL | - | ロール(admin/staff/concept) |
is_locked | BOOLEAN | - | FALSE | ロック状態 |
failed_attempts | INTEGER | - | 0 | 連続失敗回数 |
locked_at | TIMESTAMPTZ | - | - | ロック日時 |
last_password_hash | VARCHAR(255) | - | - | 前回パスワードハッシュ |
must_change_password | BOOLEAN | - | TRUE | 初回パスワード変更要否 |
phone_verified | BOOLEAN | - | FALSE | SMS認証済フラグ |
push_subscription | JSONB | - | - | Web Push購読情報 |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
deleted_at | TIMESTAMPTZ | - | - | 論理削除日時 |
CHECK制約: role IN ('admin', 'staff', 'concept')
3. events(試合・イベント)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
name | VARCHAR(100) | NOT NULL | - | 試合名 |
event_date | DATE | NOT NULL | - | 開催日 |
venue | VARCHAR(100) | NOT NULL | - | 会場 |
door_open_time | TIME | - | - | 開場時刻 |
start_time | TIME | NOT NULL | - | 開始時刻 |
ticket_sale_date | DATE | NOT NULL | - | チケット発売日 |
ticket_deadline_date | DATE | NOT NULL | - | チケット販売締切日 |
seat_types | JSONB | NOT NULL | - | 席種情報 [{name, price}] |
sales_channels | TEXT[] | NOT NULL | - | 販売チャネル ['pia', 'eplus', 'cash'] |
pia_base_url | TEXT | - | - | ぴあベースURL |
description | TEXT | - | - | 試合紹介テキスト |
promo_images | TEXT[] | - | '{}' | 宣材画像URL配列 |
status | VARCHAR(20) | - | 'upcoming' | ステータス |
created_by | UUID | FK→admins | - | 作成者(管理者) |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
deleted_at | TIMESTAMPTZ | - | - | 論理削除日時 |
ステータス値: upcoming, ongoing, completed, cancelled
4. event_players(試合 x 選手 中間テーブル)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
event_id | UUID | FK→events, NOT NULL | - | 試合ID |
player_id | UUID | FK→players, NOT NULL | - | 選手ID |
quota | INTEGER | - | 0 | ノルマ枚数 |
ref_code | VARCHAR(100) | UK | - | リファレンスコード sb_player007_event001 |
pia_url | TEXT | - | - | ぴあ参照付き完全URL |
eplus_url | TEXT | - | - | イープラスURL |
promo_images | TEXT[] | - | - | 宣材写真URL(複数) |
reward_rate | DECIMAL(5,2) | - | 0 | LP経由売上の還元率(%) |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
UNIQUE制約: (event_id, player_id) -- 同一試合に同一選手を二重登録防止
外部キー動作: ON DELETE CASCADE(試合/選手削除時に自動削除)
5. fans(ファン・購入者DB)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
player_id | UUID | FK→players, NOT NULL | - | 紐づく選手 |
name | VARCHAR(100) | - | - | ファン名 |
phone_encrypted | TEXT | - | - | 電話番号(AES-256暗号化) |
email_encrypted | TEXT | - | - | メール(AES-256暗号化) |
memo | TEXT | - | - | メモ |
last_purchase_event_id | UUID | FK→events | - | 最終購入イベント |
last_purchase_at | TIMESTAMPTZ | - | - | 最終購入日時 |
total_purchases | INTEGER | - | 0 | 累計購入枚数 |
is_dormant | BOOLEAN | - | FALSE | 掘り起こし対象フラグ |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
deleted_at | TIMESTAMPTZ | - | - | 論理削除日時 |
ドメインロジック: 直近2試合で購入のないファンは is_dormant = TRUE に更新(掘り起こし対象)
6. tickets(チケット販売実績)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
event_id | UUID | FK→events, NOT NULL | - | 試合ID |
player_id | UUID | FK→players | - | 販売選手(NULL=協会枠/当日券) |
fan_id | UUID | FK→fans | - | 購入ファン |
seat_type | VARCHAR(50) | NOT NULL | - | 席種 |
quantity | INTEGER | NOT NULL | - | 枚数 |
unit_price | INTEGER | NOT NULL | - | 単価(円) |
total_amount | INTEGER | NOT NULL | - | 合計金額 |
route | ticket_route | NOT NULL | - | 販売経路 |
buyer_name | VARCHAR(100) | - | - | 購入者名(手入力) |
payment_method | payment_method | - | - | 支払方法 |
is_paid | BOOLEAN | - | FALSE | 支払済フラグ |
paid_at | TIMESTAMPTZ | - | - | 支払日時 |
memo | VARCHAR(200) | - | - | メモ |
webhook_payload | JSONB | - | - | ぴあWebhook生データ |
purchased_at | TIMESTAMPTZ | - | NOW() | 購入日時 |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
CHECK制約: quantity > 0
7. lps(試合LP - ランディングページ)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
event_id | UUID | FK→events, UK, NOT NULL | - | 試合ID(1:1) |
status | lp_status | - | 'draft' | 公開ステータス |
html_content | TEXT | - | - | 生成HTML |
hero_image_url | TEXT | - | - | ヒーロー画像URL |
ogp_image_url | TEXT | - | - | OGP画像URL(1200x630) |
meta_title | VARCHAR(200) | - | - | メタタイトル |
meta_description | TEXT | - | - | メタディスクリプション |
association_ref_code | VARCHAR(100) | UK | - | 協会枠リファレンスコード |
association_pia_url | TEXT | - | - | 協会枠ぴあURL |
editor_data | JSONB | - | - | ビジュアルエディタ保存データ |
generated_by_model | VARCHAR(100) | - | - | 生成AIモデル名 |
published_at | TIMESTAMPTZ | - | - | 公開日時 |
published_by | UUID | FK→admins | - | 公開承認者 |
unpublished_at | TIMESTAMPTZ | - | - | 非公開日時 |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
UNIQUE制約: event_id -- イベントとLP は 1:1 関係
8. sponsors(スポンサー企業)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
player_id | UUID | FK→players, NOT NULL | - | 紐づく選手 |
company_name | VARCHAR(200) | NOT NULL | - | 企業名 |
industry | VARCHAR(100) | - | - | 業種 |
contact_name | VARCHAR(100) | NOT NULL | - | 担当者名 |
contact_phone_encrypted | TEXT | NOT NULL | - | 連絡先電話番号(AES-256暗号化) |
contact_email_encrypted | TEXT | - | - | 連絡先メール(AES-256暗号化) |
logo_url | TEXT | - | - | ロゴURL |
website_url | TEXT | - | - | WebサイトURL |
contract_amount | INTEGER | - | - | 契約金額(円) |
contract_start_date | DATE | - | - | 契約開始日 |
contract_end_date | DATE | - | - | 契約終了日 |
concept_sales_status | concept_status | - | 'untouched' | concept営業ステータス |
concept_memo | TEXT | - | - | concept営業メモ |
is_public_to_association | BOOLEAN | - | FALSE | 協会への公開許可 |
is_approved_for_display | BOOLEAN | - | FALSE | 公開ページ掲載承認 |
memo | TEXT | - | - | メモ |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
deleted_at | TIMESTAMPTZ | - | - | 論理削除日時 |
9. kickbacks(キックバック)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
sponsor_id | UUID | FK→sponsors, NOT NULL | - | スポンサーID |
player_id | UUID | FK→players | - | 選手ID |
contract_amount | INTEGER | NOT NULL | - | concept AGI受注金額(円) |
kickback_rate | DECIMAL(5,2) | NOT NULL | - | キックバック割合(%) |
kickback_amount | INTEGER | GENERATED STORED | - | キックバック金額(自動計算) |
payment_due_date | DATE | - | - | 支払期日 |
is_paid | BOOLEAN | - | FALSE | 支払済フラグ |
paid_date | DATE | - | - | 支払日 |
transfer_info_encrypted | TEXT | - | - | 振込先情報(AES-256暗号化) |
memo | TEXT | - | - | メモ |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
GENERATED列: kickback_amount = FLOOR(contract_amount * kickback_rate / 100)
10. player_rewards(選手還元)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
event_id | UUID | FK→events, NOT NULL | - | 試合ID |
player_id | UUID | FK→players, NOT NULL | - | 選手ID |
lp_sales_amount | INTEGER | - | 0 | LP経由売上金額 |
reward_rate | DECIMAL(5,2) | NOT NULL | - | 還元率(%) |
reward_amount | INTEGER | GENERATED STORED | - | 還元金額(自動計算) |
status | VARCHAR(20) | - | 'pending' | ステータス |
payment_due_date | DATE | - | - | 支払期日 |
paid_date | DATE | - | - | 支払日 |
confirmed_at | TIMESTAMPTZ | - | - | 確定日時 |
confirmed_by | UUID | FK→admins | - | 確定承認者 |
memo | TEXT | - | - | メモ |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
updated_at | TIMESTAMPTZ | - | NOW() | 更新日時 |
GENERATED列: reward_amount = FLOOR(lp_sales_amount * reward_rate / 100)
UNIQUE制約: (event_id, player_id) -- 試合ごとに選手1レコード
ステータス値: pending, confirmed, paid
11. notifications(通知)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
recipient_type | user_role | NOT NULL | - | 受信者タイプ |
recipient_id | UUID | NOT NULL | - | 受信者ID |
type | notification_type | NOT NULL | - | 通知種別 |
method | notification_method | NOT NULL | - | 通知方法 |
title | VARCHAR(200) | NOT NULL | - | タイトル |
body | TEXT | NOT NULL | - | 本文 |
related_event_id | UUID | FK→events | - | 関連イベント |
related_player_id | UUID | FK→players | - | 関連選手 |
is_read | BOOLEAN | - | FALSE | 既読フラグ |
is_sent | BOOLEAN | - | FALSE | 送信済フラグ |
sent_at | TIMESTAMPTZ | - | - | 送信日時 |
sms_sid | VARCHAR(100) | - | - | Twilio SMS SID |
error_message | TEXT | - | - | エラーメッセージ |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
12. audit_logs(操作ログ)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
actor_type | VARCHAR(10) | NOT NULL | - | 操作者タイプ (player/admin/system) |
actor_id | UUID | - | - | 操作者ID |
action | VARCHAR(50) | NOT NULL | - | アクション名 |
resource_type | VARCHAR(50) | NOT NULL | - | 対象テーブル名 |
resource_id | UUID | - | - | 対象リソースID |
changes | JSONB | - | - | 変更内容 {before, after} |
ip_address | INET | - | - | クライアントIPアドレス |
user_agent | TEXT | - | - | User-Agent |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
13. page_views(ページ閲覧数)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
page_type | VARCHAR(20) | NOT NULL | - | ページ種別 (player_page/lp) |
player_id | UUID | FK→players | - | 選手ID |
event_id | UUID | FK→events | - | イベントID |
viewed_at | DATE | NOT NULL | - | 閲覧日 |
count | INTEGER | - | 1 | 日次集計カウント |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
UNIQUE制約: (page_type, player_id, event_id, viewed_at) -- 日次集計の重複防止
14. sms_verification_codes(SMS認証コード)
| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|
id | UUID | PK | gen_random_uuid() | 主キー |
user_type | VARCHAR(10) | NOT NULL | - | ユーザー種別 (player/admin) |
user_id | UUID | NOT NULL | - | ユーザーID |
code_hash | VARCHAR(255) | NOT NULL | - | 6桁コードのハッシュ |
attempts | INTEGER | - | 0 | 試行回数 |
expires_at | TIMESTAMPTZ | NOT NULL | - | 有効期限(5分後) |
verified | BOOLEAN | - | FALSE | 認証済フラグ |
created_at | TIMESTAMPTZ | - | NOW() | 作成日時 |
アクセス制限: RLSポリシーなし(service_roleキーのみアクセス可)
ビュー
purchases(購入ビュー - ファンCRM用)
CREATE VIEW purchases AS
SELECT
t.id AS purchase_id,
t.fan_id,
t.player_id,
t.event_id,
t.seat_type,
t.quantity,
t.unit_price,
t.total_amount,
t.route,
t.is_paid,
t.purchased_at
FROM tickets t
WHERE t.fan_id IS NOT NULL;
fan_id が紐づくチケットのみを抽出し、ファンCRM画面での購入履歴表示に使用。
インデックス一覧
| テーブル | インデックス名 | カラム | 用途 |
|---|
players | idx_players_login_id | login_id | ログイン検索 |
players | idx_players_slug | slug | 公開ページURL解決 |
players | idx_players_created_at | created_at | 時系列ソート |
admins | idx_admins_login_id | login_id | ログイン検索 |
admins | idx_admins_role | role | ロール別検索 |
events | idx_events_date | event_date | 日付検索 |
events | idx_events_status | status | ステータス検索 |
events | idx_events_created_at | created_at | 時系列ソート |
event_players | idx_ep_event | event_id | 試合別検索 |
event_players | idx_ep_player | player_id | 選手別検索 |
event_players | idx_ep_ref_code | ref_code | リファレンスコード検索 |
fans | idx_fans_player | player_id | 選手別ファン検索 |
fans | idx_fans_dormant | is_dormant, player_id | 掘り起こし対象検索 |
fans | idx_fans_created_at | created_at | 時系列ソート |
tickets | idx_tickets_event | event_id | 試合別チケット検索 |
tickets | idx_tickets_player | player_id | 選手別チケット検索 |
tickets | idx_tickets_fan | fan_id | ファン別購入検索 |
tickets | idx_tickets_route | route | 販売経路別検索 |
tickets | idx_tickets_purchased_at | purchased_at | 購入日時ソート |
lps | idx_lps_event | event_id | 試合別LP検索 |
lps | idx_lps_status | status | ステータス検索 |
sponsors | idx_sponsors_player | player_id | 選手別スポンサー検索 |
sponsors | idx_sponsors_concept_status | concept_sales_status | 営業ステータス検索 |
kickbacks | idx_kickbacks_sponsor | sponsor_id | スポンサー別検索 |
kickbacks | idx_kickbacks_paid | is_paid | 支払状態検索 |
player_rewards | idx_rewards_event | event_id | 試合別還元検索 |
player_rewards | idx_rewards_player | player_id | 選手別還元検索 |
player_rewards | idx_rewards_status | status | ステータス検索 |
notifications | idx_notifications_recipient | recipient_type, recipient_id | 受信者別検索 |
notifications | idx_notifications_read | is_read | 未読検索 |
notifications | idx_notifications_created_at | created_at | 時系列ソート |
audit_logs | idx_audit_actor | actor_type, actor_id | 操作者別検索 |
audit_logs | idx_audit_resource | resource_type, resource_id | リソース別検索 |
audit_logs | idx_audit_action | action | アクション別検索 |
audit_logs | idx_audit_created | created_at | 時系列ソート |
page_views | idx_pv_date | viewed_at | 日付検索 |
page_views | idx_pv_player | player_id | 選手別PV検索 |
page_views | idx_pv_event | event_id | イベント別PV検索 |
sms_verification_codes | idx_sms_user | user_type, user_id | ユーザー別検索 |
sms_verification_codes | idx_sms_expires | expires_at | 有効期限検索 |
RLSポリシー
全14テーブルでRow Level Securityが有効化されている。
ヘルパー関数
| 関数 | 戻り値 | 説明 |
|---|
auth.get_user_role() | user_role | JWTのapp_metadata.roleからロールを取得 |
auth.get_user_id() | UUID | JWTのapp_metadata.user_idからIDを取得 |
auth.is_admin() | BOOLEAN | admin/staffロールか判定 |
auth.is_concept() | BOOLEAN | conceptロールか判定 |
ポリシー一覧
players
| ポリシー名 | 操作 | 条件 |
|---|
players_select_own | SELECT | role='player' AND id=自分 |
players_select_admin | SELECT | admin/staff |
players_select_concept | SELECT | concept |
players_update_own | UPDATE | role='player' AND id=自分 |
players_update_admin | UPDATE | admin |
players_insert_admin | INSERT | admin |
admins
| ポリシー名 | 操作 | 条件 |
|---|
admins_select_admin | SELECT | admin |
admins_select_own | SELECT | id=自分 |
admins_insert_admin | INSERT | admin |
admins_update_admin | UPDATE | admin |
admins_update_own | UPDATE | id=自分 |
events
| ポリシー名 | 操作 | 条件 |
|---|
events_select_authenticated | SELECT | 全認証ユーザー |
events_insert_admin | INSERT | admin |
events_update_admin | UPDATE | admin |
events_delete_admin | DELETE | admin |
event_players
| ポリシー名 | 操作 | 条件 |
|---|
ep_select_own | SELECT | role='player' AND player_id=自分 |
ep_select_admin | SELECT | admin/staff |
ep_insert_admin | INSERT | admin |
ep_update_admin | UPDATE | admin |
ep_delete_admin | DELETE | admin |
tickets
| ポリシー名 | 操作 | 条件 |
|---|
tickets_select_own | SELECT | role='player' AND player_id=自分 |
tickets_select_admin | SELECT | admin/staff |
tickets_insert_player | INSERT | role='player' AND player_id=自分 |
tickets_insert_admin | INSERT | admin/staff |
tickets_update_admin | UPDATE | admin/staff |
fans
| ポリシー名 | 操作 | 条件 |
|---|
fans_select_own | SELECT | role='player' AND player_id=自分 |
fans_insert_own | INSERT | role='player' AND player_id=自分 |
fans_update_own | UPDATE | role='player' AND player_id=自分 |
fans_delete_own | DELETE | role='player' AND player_id=自分 |
fans_select_admin | SELECT | admin/staff |
lps
| ポリシー名 | 操作 | 条件 |
|---|
lps_select_published | SELECT | status='published'(匿名含む) |
lps_select_authenticated | SELECT | 全認証ユーザー |
lps_insert_admin | INSERT | admin |
lps_update_admin | UPDATE | admin |
lps_delete_admin | DELETE | admin |
sponsors
| ポリシー名 | 操作 | 条件 |
|---|
sponsors_select_own | SELECT | role='player' AND player_id=自分 |
sponsors_select_admin | SELECT | admin/staff |
sponsors_select_concept | SELECT | concept |
sponsors_insert_own | INSERT | role='player' AND player_id=自分 |
sponsors_update_own | UPDATE | role='player' AND player_id=自分 |
sponsors_update_concept | UPDATE | concept |
sponsors_update_admin | UPDATE | admin |
kickbacks
| ポリシー名 | 操作 | 条件 |
|---|
kickbacks_select_admin | SELECT | admin/concept |
kickbacks_insert_admin | INSERT | admin/concept |
kickbacks_update_admin | UPDATE | admin/concept |
kickbacks_delete_admin | DELETE | admin |
選手はアクセス不可。
player_rewards
| ポリシー名 | 操作 | 条件 |
|---|
rewards_select_own | SELECT | role='player' AND player_id=自分 |
rewards_select_admin | SELECT | admin/staff |
rewards_insert_admin | INSERT | admin/staff |
rewards_update_admin | UPDATE | admin/staff |
notifications
| ポリシー名 | 操作 | 条件 |
|---|
notifications_select_own | SELECT | recipient_id=自分 |
notifications_update_own | UPDATE | recipient_id=自分 |
notifications_insert_admin | INSERT | admin/concept |
audit_logs
| ポリシー名 | 操作 | 条件 |
|---|
audit_select_admin | SELECT | admin/concept |
audit_insert_authenticated | INSERT | 全認証ユーザー |
page_views
| ポリシー名 | 操作 | 条件 |
|---|
pv_insert_anon | INSERT | 誰でも(匿名含む) |
pv_select_admin | SELECT | admin/staff |
pv_select_own | SELECT | role='player' AND player_id=自分 |
sms_verification_codes
RLSポリシーなし。service_roleキー経由のサーバーサイド処理のみアクセス可能。
ストアド関数
1. calculate_achievement_rate
選手のチケット販売達成率を計算する。
| 項目 | 内容 |
|---|
| 引数 | p_event_id UUID, p_player_id UUID |
| 戻り値 | JSONB ({event_id, player_id, quota, sold, achievement_rate, remaining}) |
| 言語 | plpgsql |
| 揮発性 | STABLE |
| ロジック | event_playersからノルマ取得、ticketsから販売枚数集計、達成率 = sold / quota * 100 |
2. get_dig_out_fans
直近2試合で購入のないファンを取得する(掘り起こし対象)。
| 項目 | 内容 |
|---|
| 引数 | p_player_id UUID |
| 戻り値 | TABLE (fan_id, fan_name, last_purchase_at, last_event_name, total_purchases, memo) |
| 言語 | plpgsql |
| 揮発性 | STABLE |
| ロジック | 直近2試合のIDを取得し、そのいずれでも購入がないファン、または一度も購入していないファンを返す |
3. get_dashboard_stats
管理者ダッシュボード用KPIを取得する。
| 項目 | 内容 |
|---|
| 引数 | p_event_id UUID |
| 戻り値 | JSONB |
| 言語 | plpgsql |
| 揮発性 | STABLE |
| ロジック | 総チケット数、総売上、販売経路別集計、席種別集計、選手別集計(達成率込み)、LP状態を返す |
戻り値構造:
{
"event": { "id", "name", "event_date", "venue", "status" },
"summary": { "total_tickets", "total_revenue", "lp_status" },
"by_route": [{ "route", "quantity", "revenue" }],
"by_seat_type": [{ "seat_type", "quantity", "revenue" }],
"by_player": [{ "player_id", "player_name", "quota", "sold", "revenue", "achievement_rate" }]
}
4. update_dig_out_flags
掘り起こし対象フラグの一括更新。直近2試合で購入のないファンに is_dormant = TRUE を設定する。
| 項目 | 内容 |
|---|
| 引数 | なし |
| 戻り値 | JSONB ({flagged_as_dormant, cleared_dormant, executed_at}) |
| 言語 | plpgsql |
| 揮発性 | VOLATILE |
| ロジック | 全選手のファンについて、直近2試合の購入有無でフラグを更新/解除 |
5. calculate_rewards
試合ごとのLP経由売上から選手還元を一括計算する。
| 項目 | 内容 |
|---|
| 引数 | p_event_id UUID |
| 戻り値 | JSONB ({event_id, players_calculated, details, calculated_at}) |
| 言語 | plpgsql |
| 揮発性 | VOLATILE |
| ロジック | event_playersの各選手について、LP経由(association_lpルート)の売上を集計し、還元率を掛けてplayer_rewardsにUPSERT |
トリガー
update_updated_at_column
BEFORE UPDATEトリガーにより、更新時にupdated_atカラムを自動的に現在日時に設定する。
対象テーブル: players, admins, events, tickets, fans, lps, sponsors, kickbacks, player_rewards
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
暗号化方針
以下のカラムはAES-256で暗号化して保存される。アプリケーション層で暗号化/復号を行う。
| テーブル | カラム | 対象データ |
|---|
players | phone_encrypted | 電話番号 |
players | email_encrypted | メールアドレス |
fans | phone_encrypted | 電話番号 |
fans | email_encrypted | メールアドレス |
sponsors | contact_phone_encrypted | 担当者電話番号 |
sponsors | contact_email_encrypted | 担当者メールアドレス |
kickbacks | transfer_info_encrypted | 振込先情報 |
論理削除
以下のテーブルはdeleted_atカラムによる論理削除を採用。物理削除は行わない。
players
admins
events
fans
sponsors