データモデル Data model
管理者ポータルが触る主要エンティティ・フィールド・リレーションをドメイン別に整理しました。
全 schema (public / admin / marketing / analytics / extensions) の俯瞰は
共通データモデルを、TS 型は parky/web/portal/admin/src/lib/api.ts を参照してください。
スキーマと TS 型がずれた場合はマイグレーション側を正とし、TS 型をフォローする運用です。
Core admin-portal entities, fields, and relationships grouped by domain.
For the cross-product schema overview (public / admin / marketing / analytics / extensions) see the
shared data model; TS interfaces live in parky/web/portal/admin/src/lib/api.ts.
When schema and TS types diverge, migrations win — TS follows.
admins / roles / role_permissions / admin_tasks / admin_notifications / admin_activity_logs / blocked_email_hashes / app_config(+history) / notification_failures は admin schema 配下で service_role only。アクセスは Cloudflare Workers BFF(/v1/admin/*)経由のみ(Supabase Edge Functions は使用しない)。public 側のテーブルとはアクセス権限が異なる。
admins / roles / role_permissions / admin_tasks / admin_notifications / admin_activity_logs / blocked_email_hashes / app_config(+history) / notification_failures live in the admin schema and are service_role only. Reach them only through the Cloudflare Workers BFF (/v1/admin/*) — Supabase Edge Functions are not used.
ドメイン俯瞰図 Domain map
flowchart LR
subgraph Parking["🅿️ Parking domain"]
PL[parking_lots]
PLI[parking_lot_images]
PLT[parking_lot_tags]
PLO[parking_lot_owners]
PLH[parking_lot_hours]
PLDO[parking_lot_date_overrides]
%% parking_lot_attributes (028) と parking_lot_payment_methods (057) は tags に統合済み
PR[parking_reviews]
PS[parking_sessions]
end
subgraph Users["👤 Users"]
AU[app_users]
USP[user_saved_parkings]
UAL[user_activity_logs]
US[user_subscriptions]
end
subgraph Owner["🏢 Owners"]
OW[owners]
OA[owner_applications]
OC[owner_credits]
CT[credit_transactions]
B[boosts]
end
subgraph Game["🏆 Gamification"]
BD[badge_definitions]
UBP[user_badge_progress]
UE[user_exp]
LD[level_definitions]
AER[activity_exp_rules]
end
subgraph Theme["🎨 Customization"]
CTh[customization_themes]
CTI[customization_theme_items]
CTP[customization_theme_parts]
TG[theme_gifts]
UT[user_themes]
end
subgraph Ops["🛠 Operations"]
ST[support_tickets]
ER[error_reports]
AT[admin_tasks]
UN[user_notifications]
AN[admin_notifications]
end
subgraph Content["📝 Content"]
ART[articles]
ADS[ads]
TAG[tags]
end
subgraph Rev["💰 Revenue"]
SP[subscription_plans]
RT[revenue_transactions]
RMS[(revenue_monthly_summary
view)]
end
subgraph Sys["⚙️ System"]
AD[admins]
RL[roles]
RP[role_permissions]
AS[assets]
CD[codes]
end
PL --> PLI
PL --> PLT
PL --> PLH
PL --> PLDO
PL --> PLO
PL --> PR
PL --> PS
PLO --> OW
AU --> PS
AU --> PR
AU --> USP
AU --> UAL
AU --> US
AU --> UE
AU --> UBP
OW --> OA
OW --> OC
OW --> CT
OW --> B
B --> PL
BD --> UBP
LD --> UE
CTh --> CTI
CTI --> CTP
CTh --> TG
TG --> UT
AT -.-> ST
AT -.-> ER
AT -.-> OA
AT -.-> PL
SP --> US
RT --> SP
AD --> RL
RL --> RP
主要テーブル定義 Core table definitions
parking_lots core
駐車場の中核テーブル。座標 SoT は PostGIS の location (geography 4326)、lat/lng はトリガー派生。shape_type で point/line/area を切替、code (citext) は自然キー。周辺検索は nearby_parking_lots() RPC。
The core lot table. Coordinate SoT is PostGIS location (geography 4326); lat/lng are trigger-derived. shape_type switches point/line/area, code (citext) is the natural key. Nearby search via nearby_parking_lots().
| Field | Type | Notes |
|---|---|---|
id | uuid | PK |
code | citext NOT NULL | 自然キー (URL / 連携 ID)Natural key |
name | text NOT NULL | — |
address | text | — |
location | geography(Point, 4326) | 座標 SoTCoordinate SoT |
lat / lng | double precision | 表示用 (派生)Display-only (derived) |
area | geometry | shape_type='area' 用ポリゴンPolygon for shape_type='area' |
shape_type | text NOT NULL (default 'point') | point / line / area |
parent_id / variant_label | uuid / text | バリアント分割用 (子駐車場の親参照)Parent / variant label for sub-lots |
total_spaces | int | — |
operating_hours | text | 表示用文字列。詳細は parking_lot_hoursDisplay string; detail in parking_lot_hours |
structure / entry_method / entry_difficulty | text | codes 値で保持Stored as code values |
max_height_m / max_width_m / max_length_m / max_weight_t | numeric | 車両制限Vehicle limits |
min_clearance_cm / max_tire_width_mm | int | 最低地上高 / タイヤ幅制限Min clearance / max tire width |
max_parking_duration_min | int | 連続駐車可能時間 (分)Max continuous parking (minutes) |
receipt_available | boolean | 領収書発行可否Receipt available |
operator_code / place_id | text | 運営会社コード / Google Places IDOperator code / Google Places ID |
status | text NOT NULL (default 'active') | codes.parking_lot_status: pending / active / on_hold / withdrawn |
source | text (default 'manual') | データ取得元Data origin |
raw_text | text | 取込元の生データ (デバッグ用)Raw ingest text (debug) |
rules | jsonb (default '[]') | 料金ルールの旧 jsonb 列。2026-04-24 階層化以降は parking_lot_pricing_rules + parking_lot_pricing_groups が正本。互換のため残置Legacy jsonb of pricing rules. Authoritative source is now parking_lot_pricing_rules + parking_lot_pricing_groups; this column is kept for backwards compatibility only. |
created_at / updated_at / deleted_at | timestamptz | — |
parking_lot_pricing_groups / parking_spots core 2026-04-24 new
2026-04-24 に lot → pricing_group → spot の 3 階層を導入。parking_lot_pricing_rules は pricing_group_id NOT NULL、parking_sessions は parking_spot_id NOT NULL になり、新規セッション作成時には spot を必ず確定させる必要がある。
2026-04-24 introduced the lot → pricing_group → spot hierarchy. parking_lot_pricing_rules.pricing_group_id is NOT NULL and parking_sessions.parking_spot_id is NOT NULL — every new session must pin to a specific spot.
| Field | Type | Notes |
|---|---|---|
parking_lot_pricing_groups.id | uuid | PK |
parking_lot_pricing_groups.parking_lot_id | uuid NOT NULL | FK |
parking_lot_pricing_groups.code | citext NOT NULL | lot 内一意Unique within a lot |
parking_lot_pricing_groups.name / is_default / display_order | text / boolean / int | — |
| parking_spots | ||
id | uuid | PK |
parking_lot_id / pricing_group_id | uuid NOT NULL | FK / FK |
code | citext NOT NULL | 区画コード (例: A-12)Spot code (e.g. A-12) |
vehicle_type_max / is_ev_charger / ev_connector_type / accessibility | text / boolean / text / text | — |
width_mm / length_mm / height_mm | int | 区画寸法Spot dimensions |
floorplan_x / floorplan_y / location | numeric / numeric / geography(Point) | 場内座標 + GPSFloor-plan XY + GPS |
is_reservable / is_active / meta | boolean / boolean / jsonb | — |
created_at / updated_at / deleted_at | timestamptz | — |
app_users core
auth_user_id で auth.users と 1:1 紐付け。退会は status='withdrawn' + PII 匿名化で運用 (deleted_at 列なし)。
Linked 1:1 to auth.users via auth_user_id. Withdrawals use status='withdrawn' + PII anonymization (no deleted_at).
| Field | Type | Notes |
|---|---|---|
id | uuid | PK |
auth_user_id | uuid | FK → auth.users (RLS 主軸) |
display_name | text NOT NULL | — |
handle | text | @ハンドル (UNIQUE)Public handle (UNIQUE) |
email | text NOT NULL | — |
vehicle_type | text (default 'sedan') | codes.vehicle_type: sedan / kei / large / bike / minivan / high_roof |
premium | boolean (default false) | — |
status | text NOT NULL (default 'active') | codes.user_status: active / withdrawn / suspended / blocked |
profile_visibility | text NOT NULL (default 'friends') | public / friends / private |
device_fingerprints | text[] | 既知デバイス指紋 (referral 不正検知)Known device fingerprints (referral abuse detection) |
signup_ip | inet | — |
referral_code_used | text | — |
last_active_at / created_at | timestamptz | — |
通知設定 / 同意履歴 / OS パーミッション / 検索プリセット / ストリーク等は user_notification_prefs / user_consents / user_device_permissions / user_search_presets / user_streaks に分離。
Notification prefs, consents, OS permissions, search presets and streaks live in their own tables (user_notification_prefs, user_consents, user_device_permissions, user_search_presets, user_streaks).
parking_sessions
ユーザーの駐車履歴。parking_spot_id NOT NULL で必ず特定 spot に紐付く。金額は total_amount_minor (bigint, minor unit)。geo_verified / geo_distance_m で位置検証、client_request_id で冪等性。updated_at / deleted_at は無く、cancel は status='cancelled' + cancelled_reason。
Per-user parking history. parking_spot_id is NOT NULL — sessions always pin to a specific spot. Amount is bigint in minor units. geo_verified / geo_distance_m validate location; client_request_id enforces idempotency. No updated_at / deleted_at — cancellation uses status='cancelled' + cancelled_reason.
| Field | Type | Notes |
|---|---|---|
id | uuid | PK |
user_id / parking_lot_id / parking_spot_id | uuid NOT NULL × 3 | FK / FK / FK (parking_spot_id は 2026-04-24 必須化) |
started_at / ended_at | timestamptz | started_at NOT NULL、ended_at NULL=駐車中 |
planned_end_at | timestamptz | 予定終了時刻 (session_notifications trigger)Planned end (drives session notifications) |
status | text NOT NULL (default 'parking') | codes.session_status: parking / completed / cancelled |
cancelled_reason | text | — |
vehicle_type | text | codes.vehicle_type |
total_amount_minor | bigint | 最終確定額 (minor unit)Final billed amount (minor units) |
user_entered_fee_minor / fee_mismatch_flag | int / boolean | ユーザー手入力額 + 計算額との突合User-entered fee + calc-vs-entered mismatch flag |
start_lat / start_lng / end_lat / end_lng | double precision | — |
geo_verified / geo_distance_m | boolean / numeric | 入庫時位置検証 (gamification cap で参照)Start-location verification (gamification gate) |
client_request_id | uuid | 冪等性キーIdempotency key |
memo / bad_reason | text | — |
created_at | timestamptz | — |
parking_lot_hours core
駐車場の時間窓。1 行 = 1 種別の時間窓 (business / entry / exit / after_hours_exit)。
曜日ロジックは day_of_week / day_type、UNIQUE は部分 UNIQUE INDEX
uq_parking_lot_hours_window_key(window_type 含む)で担保。
Time windows for a parking lot. Each row represents a single window kind
(business / entry / exit / after_hours_exit).
Day-of-week logic uses day_of_week / day_type; uniqueness is enforced via the partial UNIQUE index
uq_parking_lot_hours_window_key (which includes window_type).
| Field | Type | Notes |
|---|---|---|
id, parking_lot_id | uuid | PK, FK |
window_type | text NOT NULL | business / entry / exit / after_hours_exit (codes.parking_hours_window_type) |
day_type | text | codes.day_type: weekday / saturday / sunday / holiday / holiday_eve / all (NULL 可) |
day_of_week | smallint (0-6) | 0=Sun 〜 6=Sat |
is_24h, is_closed | boolean | — |
open_time, close_time | time / text | 24:00 表記可 |
effective_from, effective_to | date | 期間限定ルール用 |
parking_lot_date_overrides core 2026-04-24 new
特定日の営業時間・料金オーバーライド。花火大会や元旦特別営業など、通常ルールを 1 日だけ上書きする例外テーブル。
1 駐車場 × 1 日で 1 行 (UNIQUE parking_lot_id + override_date)。hours jsonb で business / entry / exit / after_hours_exit の任意キーを上書き、pricing jsonb 配列で料金を置換 (NULL=通常通り / []=当日無料)。
Per-day hour / pricing overrides for a parking lot (fireworks, New-Year specials, …). One row per lot × date; hours jsonb overrides any subset of business / entry / exit / after_hours_exit, pricing jsonb array replaces rules (NULL = normal, [] = free that day).
| Field | Type | Notes |
|---|---|---|
id | uuid | PK |
parking_lot_id | uuid | FK (ON DELETE CASCADE) |
override_date | date | 対象日 (JST) |
label | text NOT NULL | 例外の名称 (例: 隅田川花火大会) |
hours | jsonb | キー: business / entry / exit / after_hours_exit。省略キーは通常ルールへフォールバック |
pricing | jsonb (array) | NULL=通常 / []=無料 / 配列=置換 |
note | text | 社内メモ |
| RLS | — | SELECT 公開 / 書込は admin または当該 lot の owner (current_owner_id()) |
helper: get_parking_lot_date_override(lot_id, date)。
優先順位 (時刻判定 RPC が参照): 日付オーバーライド → 祝日行 → 曜日行 → day_type → all フォールバック。詳細は 2026-04-24 ADR。
jp_holidays master 2026-04-24 new
日本の祝日マスター。date PK / kind∈(national / substitute / national_holiday_bridge)。2026・2027 年の 34 件を seed 済 (振替休日含む)。
helper is_jp_holiday(date) → boolean で参照。parking_lot_hours.day_type='holiday' の判定に使われる。
Japanese holiday master with 34 rows seeded for 2026 / 2027. Used by the window-active RPC to resolve day_type='holiday' rows.
owners, owner_applications, owner_credits, credit_transactions
owners— 個人/法人の 2 種類、ステータス管理。individual / business, with status.owner_applications— 申請ごとに 1 行。書類は JSONB で保存。one row per application, documents stored as JSONB.owner_credits— オーナー 1 件につき 1 行の残高スナップショット。one row per owner with the current balance.credit_transactions— 4 種の取引履歴 (admin_charge / purchase / consumption / refund)。four transaction types (admin_charge / purchase / consumption / refund).
badge_definitions, user_badge_progress, user_exp, level_definitions, activity_exp_rules gamification
| Table | Purpose |
|---|---|
badge_definitions | バッジ定義 (名前、アイコン、条件、閾値)badge definitions (name, icon, conditions, threshold) |
user_badge_progress | ユーザー × バッジの進捗カウントprogress count per user × badge |
user_exp | 総 EXP と現在レベルtotal EXP and current level |
level_definitions | レベル毎の必要 EXPrequired EXP per level |
activity_exp_rules | アクティビティ型 → 付与 EXPactivity type → EXP granted |
user_activity_logs | 全ての行動イベント。metadata (JSONB) にイベント詳細all activity events; detail in metadata (JSONB) |
user_activity_log_targets | ログの参照先 (parking / review / user 等) のマルチ参照multi-entity references for each log |
customization_themes, ..._items, ..._parts, theme_gifts, user_themes theming
customization_themes— テーマの容器。theme container.customization_theme_items— テーマ ↔ パーツの M2M。M2M between theme and parts.customization_theme_parts— pin / icon / color / loading の個別パーツ。アセット参照。individual pin / icon / color / loading parts, referencing assets.theme_gifts— 管理者 → ユーザーへの配布記録。records of gifts from admin to user.user_themes— ユーザーの所有テーマと取得経路 (purchase/gift)。user-owned themes with acquisition type.
support_tickets, error_reports, admin_tasks ops
admin_tasks が 4 種類のソース (support / misinformation_report / owner_application / parking_new_registration)
を task_kind + ref_id で指す設計。ポリモーフィックなリレーション。
admin_tasks polymorphically references four source tables via task_kind + ref_id,
which is what enables the unified task inbox.
revenue_transactions & revenue_monthly_summary revenue
revenue_transactions— 全ての売上行。channel(subscription / parking / boost) とtransaction_typeを持つ。every revenue row, with achannelandtransaction_type.revenue_monthly_summary— 月別集計ビュー。Sales ページの「月別」タブで使用。a monthly rollup view used by the Sales page's monthly tab.
admins, roles, role_permissions system
管理者は Supabase Auth ユーザー (auth.users) と admin.admins の 2 層で管理。結合キーは admin.admins.user_id(旧名 auth_user_id ではなく user_id)。email は uq_admins_email で UNIQUE、status は active / inactive 等。notification_prefs JSONB に通知購読フラグ (new_owner / new_parking / sales_daily / sales_monthly / system_alert) を持つ。ロール定義は admin.roles、付与権限キーは admin.role_permissions (role_id + permission_key UNIQUE)。
Admins are tracked in both Supabase Auth (auth.users) and admin.admins. The join column is admin.admins.user_id (not auth_user_id). email is UNIQUE (uq_admins_email); status uses values such as active / inactive. notification_prefs JSONB stores per-admin subscription flags (new_owner / new_parking / sales_daily / sales_monthly / system_alert). Role definitions live in admin.roles; granted permission keys in admin.role_permissions (role_id + permission_key UNIQUE).
| Table | Field | Notes |
|---|---|---|
admin.admins | id | uuid PK |
user_id | uuid → auth.users.id(NULL 可:Auth 紐付け前の招待状態を許容) | |
name / email | text NOT NULL(email は UNIQUE) | |
role_id | uuid NOT NULL → admin.roles.id | |
status | text default 'active' | |
notification_prefs | jsonb — 通知購読フラグ | |
admin.roles | id | uuid PK |
name | text UNIQUE | |
color / is_system | UI 表示色 / システム作成ロール保護 | |
admin.role_permissions | role_id | uuid → admin.roles.id |
permission_key | text — 例: parking.edit((role_id, permission_key) UNIQUE) |
admin_tasks, admin_notifications, admin_activity_logs system
admin.admin_tasks—task_kind∈misinformation_report/support/owner_application/parking_new_registration/parking_fee_mismatch。(task_kind, ref_id)UNIQUE。urgency∈ urgent / high / medium / low。task_kindvalues:misinformation_report/support/owner_application/parking_new_registration/parking_fee_mismatch. UNIQUE on(task_kind, ref_id);urgency∈ urgent / high / medium / low.admin.admin_notifications—categorydefault'system'、readbool、deleted_atによるソフトデリート。Realtime 購読でヘッダーバッジを更新。categorydefaults to'system',readbool, soft-deletable viadeleted_at. Subscribed by the header badge over Realtime.admin.admin_activity_logs— 監査ログ(POST のみ非冪等)。fetchAdminActivityLogsPage/ BFF/v1/admin/activity-logsで参照。Audit log (POST is non-idempotent). Read viafetchAdminActivityLogsPage/ BFF/v1/admin/activity-logs.
codes master
カテゴリ付きラベル辞書 (user_status / vehicle_type / ...)。起動時に一括ロードされ、UI のドロップダウンとラベル表示に使われます。
A category-keyed label dictionary (user_status / vehicle_type / ...) that's loaded once at boot and powers every dropdown and label in the UI.
TypeScript 型との対応 TypeScript type mapping
各テーブルは src/lib/api.ts に対応する TS インターフェースを持ちます。
代表的なものを以下に示しますが、新しい機能を追加するときは必ずこのファイルを一次情報として確認してください。
Every table has a matching TS interface in src/lib/api.ts.
Use that file as the source of truth when adding new features.
| Table | Interface |
|---|---|
parking_lots | ParkingLot (+ ParkingRule, RuleTier) |
app_users | AppUser |
parking_sessions | ParkingSession |
parking_reviews | ParkingReview |
owners | Owner |
owner_applications | OwnerApplication |
owner_credits | OwnerCredit |
credit_transactions | CreditTransaction |
boosts | Boost |
badge_definitions | BadgeDefinition (+ BadgeCondition) |
user_badge_progress | UserBadgeProgress |
user_exp | UserEXP |
level_definitions | LevelDefinition |
activity_exp_rules | ActivityExpRule |
user_activity_logs | UserActivityLog |
customization_themes | CustomizationTheme |
customization_theme_parts | CustomizationThemePart |
articles / ads | Article / Ad |
user_notifications / admin_notifications | UserNotification / AdminNotification |
admin_tasks | AdminTask |
admins / roles | Admin / Role |
assets | Asset |
subscription_plans | SubscriptionPlan |
tags | Tag |
parky/infra/supabase/migrations/) と TS 型の両方で進化します。齟齬があった場合はマイグレーション側を正とし、TS 型をフォローしてください。
Schema lives in both the migrations under parky/infra/supabase/migrations/ and in TypeScript. If they disagree, migrations win — update the TS types to follow.