Skip to content

Latest commit

 

History

History
382 lines (291 loc) · 25.2 KB

DATABASE_DESIGN.md

File metadata and controls

382 lines (291 loc) · 25.2 KB

データベース設計(Database Design)

本書をスキーマ仕様のリポジトリ上の正とする(実装は supabase/migrations 等と整合させる)。docs/TRD.md §4 から参照する。

DBMS: PostgreSQL(Supabase 内蔵)。物理名は snake_case・複数形を原則とする(下記 ER 図の大文字エンティティ名は概念的対応のみ)。

スキーマ変更のルール

Supabase ダッシュボード(Table Editor / SQL Editor 等)でのスキーマ変更は原則禁止。 テーブル・列・インデックス・RLS・ポリシーはすべて supabase/migrations/ に SQL として追加し、PR でレビューする。例外・緊急時の扱いは docs/Rule.md「データベーススキーマ(Supabase)」に従う。


関連

種別 参照
機能要件 FEATURE_REQUIREMENTS.md
技術全体 TRD.md
外部メモ(任意) DB 設計(Notion)

ER 図

erDiagram
    PROFILES {
        uuid id PK "auth.users の id と一致"
        varchar email "メールアドレス (NOT NULL)"
        varchar display_currency "表示希望通貨 (例: JPY)"
        boolean onboarding_completed "初回オンボーディング完了フラグ"
        timestamp created_at "default: now()"
        timestamp updated_at
    }

    CATEGORIES {
        int id PK "自動採番"
        varchar name "ジャンル名 (NOT NULL)"
        varchar color_code "テーマカラー (NOT NULL)"
        varchar icon_name "UI アイコン名"
    }

    CYCLES {
        int id PK "自動採番"
        varchar name "monthly, yearly, weekly 等 (NOT NULL)"
    }

    SERVICES {
        uuid id PK
        varchar name "サービス名 (NOT NULL)"
        int category_id FK "categories 参照"
        uuid user_id FK "profiles 参照。NULL=プリセットマスタ"
        varchar logo_key "同梱ロゴ識別子 assets/services/{logo_key}.jpeg"
        varchar logo_uri "ロゴ画像 URL(外部URL用・任意)"
        varchar icon_name "画像がない場合の UI アイコン名"
    }

    PLANS {
        uuid id PK
        uuid service_id FK "services 参照"
        varchar name "プラン名 (NOT NULL)"
        numeric price "価格 (NOT NULL)"
        varchar currency "決済通貨"
        int cycle_id FK "cycles 参照 → 支払周期"
    }

    SUBSCRIPTIONS {
        uuid id PK
        uuid user_id FK "profiles 参照"
        uuid plan_id FK "plans 参照"
        date start_date "契約の最初の請求日 (NOT NULL)"
        date next_billing_date "次回更新日 (NOT NULL)"
        numeric custom_price "ユーザー編集料金 numeric(14,4) NULL可 / NULL時は plans.price"
        varchar status "active / paused / cancelled"
        timestamp cancelled_at "解約日時(cancelled 時必須)"
        text memo "解約リンクなど"
        timestamp created_at "default: now()"
        timestamp updated_at
    }

    NOTIFICATION_SETTINGS {
        uuid id PK
        uuid subscription_id FK "subscriptions 参照"
        int notify_days_before "何日前に通知するか"
    }

    USAGE_LOGS {
        uuid id PK
        uuid user_id FK "profiles 参照"
        uuid subscription_id FK "subscriptions 参照"
        date used_date "利用日 (NOT NULL)"
        timestamp created_at "default: now()"
    }

    PROFILES ||--o{ SUBSCRIPTIONS : "契約"
    PROFILES ||--o{ USAGE_LOGS : "記録"
    CATEGORIES ||--o{ SERVICES : "分類"
    CYCLES ||--o{ PLANS : "周期"
    SERVICES ||--o{ PLANS : "複数プラン"
    PLANS ||--o{ SUBSCRIPTIONS : "契約のベース"
    SUBSCRIPTIONS ||--o{ NOTIFICATION_SETTINGS : "通知タイミング複数"
    SUBSCRIPTIONS ||--o{ USAGE_LOGS : "利用履歴"
Loading

補足: 旧メモにあった notify_timing は notification_settings へ分離。プリセットサービスのロゴは services.logo_key で assets/services/ の同梱画像と紐づける(logo_uri は外部 URL 用)。ユーザー固有アイコンがマスタに要る場合は別途検討。

プリセットマスタ: categories / cycles / services / plans の初期データは supabase/migrations/ で管理する(seed.sql はローカル開発用ユーザー・サンプル契約のみ)。出典は Notion「主要サブスク一覧」を正とし(services 53 件 / plans 204 件 ※無料プラン6件は 20260609120000 で削除)、内容は docs/preset-services-template.csv に出力(各値は要レビュー)。cycle は monthly / yearly / weekly のみで、3ヶ月・期間保証などはプラン名に期間を併記し近い cycle へ丸める。ロゴは 53 件中 49 件が同梱画像(logo_key)にマッチ、未同梱の 4 件(LINE MUSIC / AppleCare+ / dマガジン / コミックシーモア)は logo_key=NULL、外部 URL があるものは logo_uri にフォールバック。生成は scripts/generate-preset-seed.js。


命名・共通ルール

項目 方針
主キー uuid(gen_random_uuid() 等)または int + GENERATED BY DEFAULT AS IDENTITY(マスタ)
日時 timestamptz(created_at / updated_at)
金額 numeric(通貨とセットで解釈)
論理削除 現状なし(削除は物理削除 or アーカイブは要件次第)

新規 / 変更テーブル一覧

物理名 概要 備考
profiles ユーザー設定(表示通貨など) 旧 Users 想定。id = auth.users.id
categories ジャンルマスタ
cycles 支払周期マスタ(月額/年額/週額等) 新規
services サービスマスタ 旧 master_services 想定。改修
plans サービス別プラン(価格・通貨・周期) 新規。1 サービス複数プラン
subscriptions ユーザーの契約台帳 改修。plan_id 参照でスリム化
notification_settings サブスクごとの通知タイミング(複数可) 新規(notify_timing の切り出し)
usage_logs 利用実績(使用チェック)
line_links ユーザーと LINE userId の紐付け 新規(20260606130000)
line_link_codes LINE 連携用ワンタイムコード(短命) 新規(20260606130000)

マイグレーションファイル名は追加時に本表へ追記する。


テーブル定義

型は PostgreSQL。NULL 欄は NO = NOT NULL。

profiles(旧 Users)

カラム名 型 NULL デフォルト 説明
id uuid NO — PK。auth.users.id と一致
email varchar(255) NO — ログインアドレス
display_currency varchar(8) NO 'JPY' F-13: 表示ベース通貨
onboarding_completed boolean NO false F-01: 初回オンボーディング(サブスク一括登録)を完了したか。false の間は初回ログイン時にオンボーディング画面を表示する
created_at timestamptz NO now()
updated_at timestamptz YES — 最終更新

インデックス

インデックス名 カラム 種別 目的
profiles_pkey id PRIMARY —

自動作成: auth.users への INSERT を契機に public.handle_new_user() トリガが profiles 行(id / email)を作成する。メール認証・Google OAuth いずれの登録でも適用される(マイグレーション: 20260605160000_create_profile_on_signup.sql)。


categories

カラム名 型 NULL デフォルト 説明
id int NO identity PK
name varchar(64) NO — 例: 動画・音楽・AI
color_code varchar(16) NO — グラフ用 Hex 等
icon_name varchar(64) YES — UI アイコン名

インデックス

インデックス名 カラム 種別 目的
categories_pkey id PRIMARY —

ジャンル対応: id 1〜17 は src/domain/genre.ts の GENRE_IDS 順(ai, video, music, … other)と対応。name は日本語ラベル(画面表示用)。


cycles

カラム名 型 NULL デフォルト 説明
id int NO identity PK
name varchar(32) NO — 例: monthly, yearly, weekly

インデックス

インデックス名 カラム 種別 目的
cycles_pkey id PRIMARY —
cycles_name_key name UNIQUE コードとして一意想定(要否は実装で確定)

services(旧 Master_Services)

カラム名 型 NULL デフォルト 説明
id uuid NO gen_random_uuid() PK
name varchar(255) NO — 例: Netflix, Spotify
category_id int NO — FK → categories.id
user_id uuid YES — FK → profiles.id。NULL=プリセット、NOT NULL=ユーザーカスタム(F-02)
logo_key varchar(100) YES — 同梱ロゴ assets/services/{logo_key}.jpeg の識別子
logo_uri varchar(2048) YES — 外部ロゴ URL(任意)
icon_name varchar(128) YES — 画像がない場合の UI アイコン名

インデックス

インデックス名 カラム 種別 目的
services_pkey id PRIMARY —
services_logo_key_key logo_key UNIQUE プリセット slug の一意(NULL 除く)
(任意) category_id INDEX ジャンルフィルタ

plans(新規)

カラム名 型 NULL デフォルト 説明
id uuid NO gen_random_uuid() PK
service_id uuid NO — FK → services.id
name varchar(255) NO — 例: Premium, 学割
price numeric(14,4) NO — 決済金額
currency varchar(8) NO 'JPY' このプランの決済通貨
cycle_id int NO — FK → cycles.id
default_trial_days integer YES — 052: プリセット登録時のデフォルトお試し日数(NULL 可)

インデックス

インデックス名 カラム 種別 目的
plans_pkey id PRIMARY —
(任意) service_id INDEX サービス単位のプラン一覧

subscriptions

カラム名 型 NULL デフォルト 説明
id uuid NO gen_random_uuid() PK
user_id uuid NO — FK → profiles.id(RLS 対象)
plan_id uuid NO — FK → plans.id
start_date date NO — 契約の最初の請求日(不変)。F-09 利用頻度計算など
next_billing_date date NO — F-10 通知の基準日。請求ごとに更新
custom_price numeric(14,4) YES — F-01: ユーザー編集料金。NULL 時は plans.price を使用
status varchar(50) NO 'active' F-03: active / paused / cancelled のいずれか(CHECK 制約で限定)
cancelled_at timestamptz YES — F-04/044: 解約日時。cancelled のとき必須・それ以外は NULL(CHECK 制約で整合)
trial_ends_on date YES — 052: お試し終了日。NULL のとき通常課金。終了後は自動クリア
memo text YES — 解約 URL・メモ
created_at timestamptz NO now()
updated_at timestamptz YES —

インデックス

インデックス名 カラム 種別 目的
subscriptions_pkey id PRIMARY —
(任意) user_id INDEX ユーザーの契約一覧
(任意) next_billing_date INDEX 通知バッチ

notification_settings(新規)

カラム名 型 NULL デフォルト 説明
id uuid NO gen_random_uuid() PK
subscription_id uuid NO — FK → subscriptions.id
notify_days_before int NO — 更新の何日前に通知するか(F-12)

インデックス

インデックス名 カラム 種別 目的
notification_settings_pkey id PRIMARY —
(任意) subscription_id INDEX サブスク単位の設定取得

usage_logs

カラム名 型 NULL デフォルト 説明
id uuid NO gen_random_uuid() PK
user_id uuid NO — FK → profiles.id
subscription_id uuid NO — FK → subscriptions.id
used_date date NO — 利用日(F-08)。同一サブスク × 日で一意推奨
created_at timestamptz NO now() 記録時刻

インデックス

インデックス名 カラム 種別 目的
usage_logs_pkey id PRIMARY —
usage_logs_subscription_id_used_date_key subscription_id, used_date UNIQUE 同日二重記録防止

line_links(新規)

LINE 公式アカウントのトークから利用実績を記録するための、ユーザーと LINE userId の 1:1 紐付け。書き込み(連携確定)は Edge Function が service_role で行う。

カラム名 型 NULL デフォルト 説明
user_id uuid NO — PK。FK → profiles.id
line_user_id text NO — LINE のユーザーID(UNIQUE)
linked_at timestamptz NO now() 連携した時刻

RLS: 自分の行のみ select / delete(auth.uid() = user_id)。insert/update は service_role のみ。

line_link_codes(新規)

連携用のワンタイムコード。アプリが RPC create_line_link_code() で発行し、ユーザーが LINE に送ると Edge Function が照合して line_links を作成する。

カラム名 型 NULL デフォルト 説明
code text NO — PK。6 桁の数字コード
user_id uuid NO — FK → profiles.id
expires_at timestamptz NO — 有効期限(発行から 10 分)
created_at timestamptz NO now() 発行時刻

RLS: ポリシーなし(一般ユーザーは直接アクセス不可)。発行は SECURITY DEFINER の RPC、照合は service_role。


マイグレーション運用

  • スキーマの変更はマイグレーションファイルのみ(UI 上のエディタで本番・Staging を直接いじらない。理由・例外は docs/Rule.md)
  • ローカル: Supabase CLI(docs/TRD.md §1 参照)
  • スキーマ変更後、本書の「テーブル一覧」のマイグレーション列を更新する

RLS(Row Level Security)

  • profiles: 原則として「自分の行のみ select / update」(auth.uid() = id)
  • subscriptions: select / insert に加え、update(一時停止・再開・解約)と delete(F-04 物理削除)も auth.uid() = user_id に限定して許可(20260617000000)
  • subscriptions / usage_logs / notification_settings: user_id または親 subscription 経由でユーザーに紐づく行のみ
  • usage_logs: select に加え、insert / delete も auth.uid() = user_id に限定して許可(F-08 利用チェック/取り消し)
  • マスタ(categories, cycles, services, plans): anon / authenticated に SELECT 可(プリセット参照用)。services は user_id IS NULL のプリセットのみ全員が読める。カスタム(user_id = auth.uid())は本人のみ SELECT(F-02)

next_billing_date の自動繰り上げ(DB 関数・cron)

  • calc_next_billing_date(start_date, cycle, today): 契約開始日とサイクルから「今日以降の最初の請求日」を算出する純粋関数
  • refresh_due_billing_dates(): status = active かつ next_billing_date < 今日(JST) の契約を一括更新。戻り値は更新件数
  • pg_cron ジョブ refresh-due-billing-dates: 毎日 JST 0:10(UTC 15:10)に refresh_due_billing_dates() を実行
  • 開始日を起点に周期を加算するため、毎月の請求日(例: 15 日)を維持し、月末 clamp によるドリフトを防ぐ

変更履歴

日付 変更内容
2026-06-26 subscription_price_history 追加(F-03 料金変更の「今月から」対応、20260626180000)
2026-06-26 DAZN「W杯(月額更新)」プラン追加、Gemini の Google AI Plus 料金を ¥725 に更新(20260626120000)
2026-06-25 subscriptions.trial_ends_on(date・NULL 可)、plans.default_trial_days(integer・NULL 可)追加。お試し期間(052、20260625120000)
2026-06-18 services.user_id 追加、create_custom_subscription RPC、カスタムサービス用 RLS(F-02 / 038、20260618100000)
2026-06-17 subscriptions.cancelled_at(timestamptz・NULL 可・status との整合 CHECK 制約)追加、update / delete の RLS ポリシー追加(044 一時停止・解約・削除、20260617000000)
2026-06-16 profiles.onboarding_completed(boolean・NOT NULL・default false)追加、profiles の UPDATE RLS(自分の行のみ)追加、既存契約ありユーザーを完了扱いに backfill(初回オンボーディングのサブスク一括登録、F-01、20260616120000)
2026-06-12 subscriptions.custom_price(numeric(14,4)・NULL 可・非負 CHECK 制約)追加と INSERT RLS ポリシー追加(F-01 プリセット登録、20260612170000)
2026-06-09 重複サービスを参照する test@test.com の契約(55555555-…)を正プランへ付け替え、参照の消えた誤plan(44444444-…)・誤service(33333333-…)を削除して重複を解消(20260609130000)
2026-06-09 プリセット整理: 重複サービス(手動投入の 33333333-… Netflix/Amazon Prime Video/DAZN)と配下プランを削除、無料プラン(price=0、金額不定は除外)を削除、Figma/Gemini を同梱ロゴ(logo_key)方式へ統一(20260609120000)
2026-06-06 Dropbox のプリセット価格を税込に統一(20260606160000)
2026-06-06 AppleCare+「iPhone(月払い)」プランを追加(20260606150000)
2026-06-06 既存ユーザーの profiles backfill と create_line_link_code の profile 自動作成(20260606140000)
2026-06-06 line_links / line_link_codes と連携コード発行 RPC を追加(LINE 連携)
2026-06-06 プリセットマスタ投入: services.logo_key 追加、categories 17 ジャンル、RLS(マスタ SELECT 可)、Notion 由来の services 53 件 / plans 210 件を投入、migration 20260606120000 / 20260606120100
2026-06-05 subscriptions.status に CHECK 制約を追加(active / paused / cancelled に限定)
2026-06-05 マスタテーブルの select RLS ポリシー追加。next_billing_date 自動繰り上げ関数・pg_cron を追加
2026-06-04 usage_logs に INSERT / DELETE の RLS ポリシーを追加(F-08 利用チェック)
2026-06-02 subscriptions.start_date(契約の最初の請求日)を追加
2026-05-15 「スキーマ変更のルール」: Supabase UI での変更は原則禁止、マイグレーション運用を明記
2026-05-15 初版: ER・テーブル定義ドラフトを反映