把 visionA-backend 6 個 in-memory store 接到資料庫持久化,範圍=完整 (PG 全接 + session 接 Redis + 交易韌性)。interface / handler 不動, 只加 DB 實作 + 換 wiring,config 未設 DB 時保留 in-memory fallback。 - 塊 0 基礎建設:pgx/v5 連線池 + DatabaseConfig/RedisConfig + golang-migrate runner(embed)+ cmd/migrate + testcontainers 測試基礎建設 - 塊 1 model → Postgres:array 映射、upsert 保留 CreatedAt、faa_object_key、 三維 filter(owner/chip/source)、soft-delete partial index - 塊 2 device → Postgres:partial unique(已刪 serial 可重註冊)、雙狀態欄位 - 塊 3 token → Postgres:pairing_tokens + session_tokens 分表、token_hash 當 PK - 塊 4 userSession → Redis:idle + absolute 雙 TTL 取代 cleanup goroutine (tunnel session 維持 in-memory,yamux handle 不可序列化) - 塊 5 交易/韌性:WithTx helper + 刪 device cascade 撤銷 token(同 tx 原子) + /healthz ping PG/Redis(fail-fast 503)+ pgx error 統一映射(不洩漏 raw error) 降級策略(fail-fast):PG 掉 → 持久資料 API 回 503;Redis 掉 → session 失敗 不自動 fallback in-memory(避免多機 session 不同步)。 DB:PostgreSQL 14.23(gen_random_uuid 內建、無 citext → email 用 lower() unique index)。每塊經 Reviewer 審查 + 真 PG/Redis testcontainers 全量 dbtest 綠燈, in-memory fallback 未受影響。 docs: 同步更新 database.md(schema/config/migration 清單)+ api-spec.md (409/503 錯誤碼、/healthz 新行為、device unpair cascade)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
2.4 KiB
SQL
45 lines
2.4 KiB
SQL
-- 0002_create_devices.up.sql
|
||
--
|
||
-- DB 接入塊 2:建立 devices 表(裝置綁定身分長期保存)。
|
||
-- 對齊 docs/autoflow/04-architecture/database.md §2.2、§4、§5.1。
|
||
--
|
||
-- 接續 0001(users + models);owner_user_id 是 REFERENCES users(id) 的 FK,users 表已於 0001 建立。
|
||
--
|
||
-- 環境事實(已驗證,與 0001 相同):PostgreSQL 14.23,gen_random_uuid() PG14 內建可直接用。
|
||
|
||
-- devices(雙狀態模型:remote_status[tunnel-level] + status[USB-level],見 §2.2 Minor-3)
|
||
CREATE TABLE devices (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
owner_user_id UUID NOT NULL REFERENCES users(id),
|
||
name TEXT NOT NULL,
|
||
device_type TEXT,
|
||
serial_number TEXT,
|
||
|
||
-- tunnel-level 狀態(雲端 remote-proxy 觀察 tunnel 連線)
|
||
remote_status TEXT NOT NULL DEFAULT 'offline', -- online | offline | reconnecting | error
|
||
last_seen_at TIMESTAMPTZ, -- 最後一次 tunnel heartbeat 時間
|
||
last_connected_at TIMESTAMPTZ, -- tunnel 最近一次建立時間
|
||
|
||
-- USB-level 狀態(local agent 上報)
|
||
status TEXT NOT NULL DEFAULT 'unknown', -- online | offline | unknown
|
||
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
paired_at TIMESTAMPTZ, -- 配對完成時間(nullable)
|
||
deleted_at TIMESTAMPTZ
|
||
);
|
||
|
||
-- partial unique index(取代 table-level UNIQUE(owner_user_id, serial_number))。
|
||
-- 決策:已 soft-delete 的 device serial「能」重新註冊(見 §4 / §0.4 決策 2)。
|
||
-- 語意:唯一性只對「未刪除」(deleted_at IS NULL)紀錄成立;
|
||
-- 刪掉一筆後,同 (owner, serial) 可再 INSERT 一筆新的(新 id),不違反 unique。
|
||
-- 這讓使用者把裝置刪了之後仍能用同一個實體 serial 重新註冊。
|
||
CREATE UNIQUE INDEX uq_devices_owner_serial_active
|
||
ON devices (owner_user_id, serial_number)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
-- owner-scoped index(List by owner;只索引未刪除紀錄)。
|
||
CREATE INDEX idx_devices_owner_active ON devices (owner_user_id) WHERE deleted_at IS NULL;
|
||
-- remote_status filter(前端優先顯示雲端連線狀態;只索引未刪除紀錄)。
|
||
CREATE INDEX idx_devices_remote_status ON devices (remote_status) WHERE deleted_at IS NULL;
|