把 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>
54 lines
2.6 KiB
SQL
54 lines
2.6 KiB
SQL
-- 0001_create_users_models.up.sql
|
||
--
|
||
-- DB 接入塊 0/塊 1:建立模型庫持久化的最小集合(users + models)。
|
||
-- 對齊 docs/autoflow/04-architecture/database.md §0.3、§4、§5.1。
|
||
--
|
||
-- 為何第一份 migration 含 users:models.owner_user_id 是 REFERENCES users(id) 的 FK,
|
||
-- 必須先有 users 表。雛形 users 為 stub(固定 demo-user),但 schema 與 FK 約束第一份就要到位。
|
||
--
|
||
-- 環境事實(已驗證):PostgreSQL 14.23,已裝 extension 只有 plpgsql。
|
||
-- - gen_random_uuid():PG14 內建(pgcrypto 已併入核心),可直接用,不需 CREATE EXTENSION。
|
||
-- - CITEXT:未裝。users.email 改用 TEXT,大小寫不敏感唯一改用 functional unique index
|
||
-- ON lower(email)(塊 1 真正寫 user 落盤前,users 僅為 stub,影響極小)。
|
||
|
||
-- users(Phase 1 stub;雛形固定 demo-user)
|
||
CREATE TABLE users (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
email TEXT NOT NULL,
|
||
name TEXT,
|
||
password_hash TEXT,
|
||
org_id UUID,
|
||
roles TEXT[] NOT NULL DEFAULT '{}',
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
deleted_at TIMESTAMPTZ
|
||
);
|
||
-- email 大小寫不敏感唯一(取代 CITEXT,因 PG14.23 未裝 citext extension)。
|
||
CREATE UNIQUE INDEX uq_users_email_lower ON users (lower(email));
|
||
|
||
-- models(塊 1 主體;使用者最關心,重啟後模型庫資料還在)
|
||
CREATE TABLE models (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
owner_user_id UUID NOT NULL REFERENCES users(id),
|
||
name TEXT NOT NULL,
|
||
description TEXT,
|
||
storage_key TEXT NOT NULL,
|
||
file_size BIGINT NOT NULL,
|
||
file_checksum TEXT,
|
||
faa_object_key TEXT, -- ADR-017 (a) B1,nullable(上傳類留 NULL)
|
||
target_chip TEXT,
|
||
input_shape INT[],
|
||
classes TEXT[],
|
||
framework TEXT,
|
||
source TEXT NOT NULL,
|
||
source_job_id UUID,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
uploaded_at TIMESTAMPTZ,
|
||
deleted_at TIMESTAMPTZ
|
||
);
|
||
-- owner-scoped index(List by owner / chip / source;只索引未刪除紀錄)。
|
||
CREATE INDEX idx_models_owner_active ON models (owner_user_id) WHERE deleted_at IS NULL;
|
||
CREATE INDEX idx_models_owner_chip_active ON models (owner_user_id, target_chip) WHERE deleted_at IS NULL;
|
||
CREATE INDEX idx_models_owner_source_active ON models (owner_user_id, source) WHERE deleted_at IS NULL;
|