データモデル Data model
モバイルアプリは管理者ポータル・オーナーポータルと同じ Supabase PostgreSQL データベースを共有します。 そのため、データモデルは 管理者ポータルのデータモデル と完全に同一です。 本ページでは、モバイルアプリから直接読み書きする主要エンティティを中心に整理します。
The mobile app shares the same Supabase PostgreSQL database as the admin and owner portals, so the data model is identical to the admin portal data model. This page focuses on the core entities the mobile app reads from and writes to directly.
parky/infra/supabase/migrations/000_init_schema.sql。
コードマスター(ステータス・種別等の列挙値)は codes テーブルで管理され、
CLAUDE.md のコードマスター方針に従い、列挙値は英語小文字のコード値で保持します。
The canonical schema lives in parky/infra/supabase/migrations/000_init_schema.sql.
Enumerated values (status, kind, etc.) are managed in the codes master table,
and per the CLAUDE.md code-master policy they are stored as lowercase English code values.
7.1 ドメイン俯瞰図 7.1 Domain map
flowchart LR
subgraph Parking["🅿️ 駐車場ドメイン"]
PL[parking_lots]
PLI[parking_lot_images]
PLT[parking_lot_tags]
PLA[parking_lot_attributes]
PLPM[parking_lot_payment_methods]
PLPR[parking_lot_pricing_rules]
PLH[parking_lot_hours]
PR[parking_reviews]
PS[parking_sessions]
end
subgraph Users["👤 ユーザー"]
AU[app_users]
USP[user_saved_parkings]
UAL[user_activity_logs]
US[user_subscriptions]
UPT[user_push_tokens]
UN[user_notifications]
UE[user_exp]
UB[user_badges]
UBP[user_badge_progress]
UT[user_themes]
UAT[user_active_themes]
end
subgraph Game["🏆 ゲーミフィケーション"]
BD[badge_definitions]
LD[level_definitions]
AER[activity_exp_rules]
end
subgraph Content["📝 コンテンツ"]
ART[articles]
ADS[ads]
TAG[tags]
ER[error_reports]
ST[support_tickets]
end
subgraph Sys["⚙️ マスター"]
CD[codes]
SP[subscription_plans]
end
PL --> PLI
PL --> PLT
PL --> PLA
PL --> PLPM
PL --> PLPR
PL --> PLH
PL --> PR
PL --> PS
AU --> PS
AU --> PR
AU --> USP
AU --> UAL
AU --> US
AU --> UPT
AU --> UN
AU --> UE
AU --> UB
AU --> UBP
AU --> UT
AU --> UAT
BD --> UBP
BD --> UB
LD --> UE
US --> SP
7.2 主要テーブル定義(モバイル視点) 7.2 Core table definitions (mobile-centric view)
parking_lots core
駐車場の中核テーブル。GPS 座標は PostGIS の geography 型で保持され、nearby_parking_lots() RPC で周辺検索します。
The core parking-lot table. GPS is stored in a PostGIS geography column and queried via the nearby_parking_lots() RPC.
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
name | text | — |
address | text | 表示用住所Display address |
lat / lng | numeric | 表示用。実体は locationDisplay-only; source is location |
location | geography(point) | PostGIS |
total_spaces | int | 収容台数Capacity |
operating_hours | text | parking_lot_hours で詳細管理Detail lives in parking_lot_hours |
structure | code (text) | flat / multistory / underground 等flat / multistory / underground, etc. |
entry_method | code (text) | gate / flap / barrier_free 等gate / flap / barrier_free, etc. |
max_height_m | numeric | 車両制限Vehicle limits |
max_width_m | numeric | 〃ditto |
max_length_m | numeric | 〃ditto |
max_weight_t | numeric | 〃ditto |
min_clearance_cm | int | 最低地上高Minimum clearance |
status | code (text) | new / active / hidden / closed |
source | code (text) | データ取得元Data origin |
created_at / updated_at | timestamptz | — |
deleted_at | timestamptz | ソフトデリートSoft delete |
parking_lot_pricing_rules core
時間帯・曜日別の料金ルール。複数行の組合せでキャップ付きの料金表を表現します。
Time-of-day / day-of-week pricing rules. Multiple rows combine to express a capped pricing table.
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
parking_lot_id | uuid | FK |
day_of_week | int[] | 適用曜日(0=Sun 〜 6=Sat)Applicable weekdays (0=Sun ... 6=Sat) |
start_time / end_time | time | 時間帯Time window |
unit_minutes | int | 課金単位(例:30分)Billing unit (e.g. 30 min) |
unit_price | int | 単位あたり円Yen per unit |
cap_minutes | int | 最大料金の対象時間(例:720=12h)Window for the cap price (e.g. 720 = 12 h) |
cap_price | int | 最大料金(円)Cap price (yen) |
priority | int | 競合時の優先度Priority when rules overlap |
parking_sessions core
モバイルアプリの中心エンティティ。入出庫時刻・請求額・ステータスを保持します。
The central entity of the mobile app. Holds entry/exit timestamps, billed amount, and status.
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
user_id | uuid | FK → app_users |
parking_lot_id | uuid | FK → parking_lots |
started_at | timestamptz | 入庫時刻Entry time |
ended_at | timestamptz | 出庫時刻(NULL=駐車中)Exit time (NULL = still parked) |
status | code (text) | pending/parking/ended/cancelled |
total_amount | int | 円(最終確定額)Yen (final billed amount) |
vehicle_type | code (text) | sedan / suv / kei / truck 等sedan / suv / kei / truck, etc. |
memo | text | メモFree-form memo |
personal_rating | code (text) | good / bad / null(個人評価)good / bad / null (personal rating) |
start_lat/start_lng | numeric | GPS記録GPS snapshot |
created_at/updated_at/deleted_at | timestamptz | — |
parking_reviews
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
user_id | uuid | 投稿ユーザーPosting user |
parking_lot_id | uuid | 対象駐車場Target parking lot |
session_id | uuid | 紐付くセッション(任意)Linked session (optional) |
rating | int | 1–5 |
comment | text | — |
image_urls | text[] | Cloudflare R2 URL(最大4枚)Cloudflare R2 URLs (up to 4) |
status | code (text) | draft/pending/approved/rejected |
created_at/deleted_at | timestamptz | — |
app_users core
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK (Supabase Auth の user.id と一致)PK (matches the Supabase Auth user.id) |
display_name | text | — |
email | — | |
avatar_url | text | Cloudflare R2 |
vehicle_type | code (text) | コードマスター vehicle_typeCode master vehicle_type |
premium | boolean | プレミアム契約中かのスナップショットSnapshot of whether a premium subscription is active |
status | code (text) | user_status: active/withdrawn/suspended |
notification_prefs | jsonb | 通知種別ごとの ON/OFFPer-notification-type ON/OFF flags |
locale | text | ja / en |
created_at/deleted_at | timestamptz | — |
user_push_tokens
FCM デバイストークン。起動時に upsert し、Push 送信時に参照します。
FCM device tokens. Upserted on app boot and read when sending push notifications.
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
user_id | uuid | FK |
device_id | text | デバイス一意IDUnique device ID |
fcm_token | text | FCM v1 トークンFCM v1 token |
device_type | code (text) | ios / android(device_type マスター)ios / android (via the device_type master) |
app_version | text | — |
last_seen_at | timestamptz | — |
| UNIQUE (user_id, device_id) | ||
user_notifications
アプリ内通知の受信箱。Push と同じレコードを ここに INSERT し、Realtime で端末に配信されます。
The in-app notification inbox. The same record that backs a push is INSERTed here and streamed to devices via Realtime.
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
user_id | uuid | FK |
notif_type | code (text) | system/promo/fee_alert/session/review, etc. |
notif_category | code (text) | カテゴリグループ(UIタブ分類)Category group (UI tab bucket) |
title | text | — |
body | text | — |
deep_link | text | タップで遷移する画面(parky://)Target screen opened on tap (parky://) |
payload | jsonb | 任意の付帯情報Arbitrary attached data |
sent_at | timestamptz | 送信時刻Sent-at time |
read_at | timestamptz | 既読時刻(NULL=未読)Read-at time (NULL = unread) |
delivered_at | timestamptz | 端末到達Delivered to device |
status | code (text) | notif_status: queued/sent/delivered/failed |
user_saved_parkings
| カラムColumn | 型Type |
|---|---|
user_id, parking_lot_id | uuid (PK 複合)uuid (composite PK) |
created_at | timestamptz |
user_activity_logs
全ての行動イベント。metadata(JSONB)にイベント固有の詳細を保持し、バッジ条件エンジンはこのメタデータをドット記法パスで評価します。
Every activity event. Event-specific detail lives in metadata (JSONB), and the badge condition engine evaluates that metadata via dot-notation paths.
| カラムColumn | 型Type | 備考Notes |
|---|---|---|
id | uuid | PK |
user_id | uuid | FK |
activity_type | text | session_start / session_end / review_post / search, etc. |
metadata | jsonb | イベント詳細Event detail |
occurred_at | timestamptz | — |
user_exp / level_definitions / activity_exp_rules / badge_definitions / user_badges / user_badge_progress gamification
管理者ポータルと同じゲーミフィケーション基盤を共有。モバイルは書き込みしません(DB トリガ・Cloudflare Workers 経由で自動更新)。
Shares the same gamification stack as the admin portal. The mobile app never writes to it directly — updates are driven by DB triggers and Cloudflare Workers.
user_subscriptions / subscription_plans revenue
subscription_plans:Free / Plus 等のマスター: master of plans such as Free / Plususer_subscriptions:契約行。IAPレシート検証後に Cloudflare Workers が INSERT/UPDATE。status: trial/active/grace/cancelled/expired: subscription rows. An Cloudflare Workers INSERT/UPDATEs after IAP receipt verification.status: trial/active/grace/cancelled/expired
articles / ads
メディア記事と広告のマスター。モバイルは read-only。
Master tables for media articles and ads. The mobile app is read-only.
support_tickets / error_reports ops
support_tickets:ユーザーが送信、管理者が対応。ステータス:open/in_progress/waiting_user/resolved/closed: submitted by users and handled by admins. Status: open/in_progress/waiting_user/resolved/closederror_reports:駐車場情報の誤り報告。ステータス:submitted/triaging/resolved/rejected: reports of incorrect parking-lot information. Status: submitted/triaging/resolved/rejected
codes master
カテゴリ付きラベル辞書。起動時に一括フェッチし、クライアントのドロップダウン・ラベル表示に使用。既存カテゴリは user_status, vehicle_type, session_status, notif_type, notif_status, notif_target, device_type 等。
A category-keyed label dictionary. Fetched once on app boot and used to power every dropdown and label on the client. Existing categories include user_status, vehicle_type, session_status, notif_type, notif_status, notif_target, device_type, and more.
7.3 モバイル固有の読み書きマトリクス 7.3 Mobile read/write matrix
| テーブルTable | モバイル読取Mobile read | モバイル書込Mobile write | RLS 要点RLS key points |
|---|---|---|---|
parking_lots (+サブ)(and sub-tables) | ✅ 全件✅ All | ❌ | status != 'hidden' かつ未削除status != 'hidden' and not soft-deleted |
parking_sessions | ✅ 自分のみ✅ Self only | ✅ 自分のみ✅ Self only | user_id = auth.uid() |
parking_reviews | ✅ approved + 自分の draft✅ approved + own drafts | ✅ 自分のレビュー✅ Own reviews | 同上Same as above |
app_users | ✅ 自分のみ✅ Self only | ✅ 自分のプロフィール✅ Own profile | 同上Same as above |
user_saved_parkings | ✅ 自分のみ✅ Self only | ✅ 自分のみ✅ Self only | 同上Same as above |
user_notifications | ✅ 自分のみ✅ Self only | ✅ read_at 更新のみ✅ read_at updates only | 同上Same as above |
user_push_tokens | ✅ 自分のみ✅ Self only | ✅ 自分のみ (upsert)✅ Self only (upsert) | 同上Same as above |
user_activity_logs | ❌ | ✅ INSERT のみ✅ INSERT only | 自分ユーザーIDのみ許可Only the caller's user ID is allowed |
user_exp/user_badges/user_badge_progress | ✅ 自分のみ✅ Self only | ❌(DBトリガで自動)❌ (auto via DB triggers) | 同上Same as above |
user_subscriptions | ✅ 自分のみ✅ Self only | ❌(Edge 経由)❌ (via Cloudflare Workers) | 同上Same as above |
support_tickets | ✅ 自分のみ✅ Self only | ✅ INSERT + 返信✅ INSERT + replies | 同上Same as above |
error_reports | ✅ 自分のみ✅ Self only | ✅ INSERT のみ✅ INSERT only | 同上Same as above |
articles / ads | ✅ 公開中✅ Published | ❌ | published_at <= now() |
codes | ✅ 全件✅ All | ❌ | — |
7.4 データライフサイクル 7.4 Data lifecycle
| データData | 作成Creation | 更新Update | 削除Deletion |
|---|---|---|---|
| ユーザーUser | サインアップ時On signup | プロフィール編集Profile edits | 退会 → withdrawn + 個人情報匿名化Withdrawal → withdrawn + PII anonymized |
| 駐車セッションParking session | 駐車開始時On parking start | 駐車中・駐車終了時During and at end of parking | ソフト削除(履歴から非表示)Soft delete (hidden from history) |
| レビューReview | 投稿時On post | 編集(一定期間内)Edit (within a grace window) | ソフト削除Soft delete |
| 通知Notification | 送信時On send | 既読化Mark as read | 90日経過で自動アーカイブ(Cron)Auto-archived after 90 days (cron) |
| 活動ログActivity log | 行動時On activity | — | 2年経過で集計化Aggregated after 2 years |
| Push トークンPush token | 起動時On app boot | 起動時 last_seen_at 更新last_seen_at refreshed on boot | 30日アクセスなしで削除Deleted after 30 days of inactivity |
7.5 データ更新ポリシー 7.5 Update policy
- 楽観的更新Optimistic updates:レビュー・メモ・保存等はクライアントで即時反映、失敗時にロールバック+トースト通知: reviews, memos, saves, etc. are reflected on the client immediately, rolled back with a toast on failure
- 悲観的確定Pessimistic commit:駐車終了の料金確定は必ずサーバーRPCを待つ: final pricing at end-of-parking always waits on the server RPC
- 冪等性Idempotency:全てのRPCは
client_request_idを受け取り、重複リクエストを排除: every RPC accepts aclient_request_idand de-duplicates repeat requests - ソフトデリートSoft delete:ユーザー直接操作の削除は
deleted_atカラム利用(CLAUDE.md準拠): user-initiated deletions use thedeleted_atcolumn (per CLAUDE.md)