From 5170787d43fe2a22d6cb85b4526770b1f583d445 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 30 Mar 2026 00:42:48 +0800 Subject: [PATCH] docs: add database schema snapshot and validation index links --- docs/ARCHITECTURE_AND_CONFIG.md | 2 + docs/DB_SCHEMA_SNAPSHOT.md | 215 ++++++++++++++++++++++++++++++++ docs/index.md | 1 + 3 files changed, 218 insertions(+) create mode 100644 docs/DB_SCHEMA_SNAPSHOT.md diff --git a/docs/ARCHITECTURE_AND_CONFIG.md b/docs/ARCHITECTURE_AND_CONFIG.md index ac1ae3e..56b05a9 100644 --- a/docs/ARCHITECTURE_AND_CONFIG.md +++ b/docs/ARCHITECTURE_AND_CONFIG.md @@ -25,6 +25,8 @@ - 後端任務進度與驗收條件 - `docs/API_CLIENTS_SQL.sql` - `api_clients` 白名單表與初始資料 SQL +- `docs/DB_SCHEMA_SNAPSHOT.md` + - 目前資料庫 schema 快照(欄位/索引/約束) ## 目前狀態(2026-03-29) - 後端骨架:已建立(FastAPI + SQLAlchemy) diff --git a/docs/DB_SCHEMA_SNAPSHOT.md b/docs/DB_SCHEMA_SNAPSHOT.md new file mode 100644 index 0000000..5a84318 --- /dev/null +++ b/docs/DB_SCHEMA_SNAPSHOT.md @@ -0,0 +1,215 @@ +# DB Schema Snapshot + +- Date: 2026-03-30 +- Database: `member.ose.tw` +- Schema: `public` + +## Tables + +- `api_clients` +- `auth_sync_state` +- `companies` +- `modules` +- `permissions` +- `sites` +- `user_scope_permissions` +- `users` + +## `api_clients` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `client_key`: `text` (not null: `true`) +- `name`: `text` (not null: `true`) +- `status`: `client_status` (not null: `true`) +- `api_key_hash`: `text` (not null: `true`) +- `allowed_origins`: `jsonb` (not null: `true`) +- `allowed_ips`: `jsonb` (not null: `true`) +- `allowed_paths`: `jsonb` (not null: `true`) +- `rate_limit_per_min`: `integer` (not null: `false`) +- `expires_at`: `timestamp with time zone` (not null: `false`) +- `last_used_at`: `timestamp with time zone` (not null: `false`) +- `created_at`: `timestamp with time zone` (not null: `true`) +- `updated_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `api_clients_client_key_key` (`u`): UNIQUE (client_key) +- `api_clients_pkey` (`p`): PRIMARY KEY (id) + +### Indexes + +- `api_clients_client_key_key`: `CREATE UNIQUE INDEX api_clients_client_key_key ON public.api_clients USING btree (client_key)` +- `api_clients_pkey`: `CREATE UNIQUE INDEX api_clients_pkey ON public.api_clients USING btree (id)` +- `idx_api_clients_expires_at`: `CREATE INDEX idx_api_clients_expires_at ON public.api_clients USING btree (expires_at)` +- `idx_api_clients_status`: `CREATE INDEX idx_api_clients_status ON public.api_clients USING btree (status)` + +## `auth_sync_state` + +### Columns + +- `user_id`: `uuid` (not null: `true`) +- `last_synced_at`: `timestamp with time zone` (not null: `false`) +- `source_version`: `text` (not null: `false`) +- `last_error`: `text` (not null: `false`) +- `updated_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `auth_sync_state_pkey` (`p`): PRIMARY KEY (user_id) +- `auth_sync_state_user_id_fkey` (`f`): FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + +### Indexes + +- `auth_sync_state_pkey`: `CREATE UNIQUE INDEX auth_sync_state_pkey ON public.auth_sync_state USING btree (user_id)` + +## `companies` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `company_key`: `text` (not null: `true`) +- `name`: `text` (not null: `true`) +- `status`: `record_status` (not null: `true`) +- `created_at`: `timestamp with time zone` (not null: `true`) +- `updated_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `companies_company_key_key` (`u`): UNIQUE (company_key) +- `companies_pkey` (`p`): PRIMARY KEY (id) + +### Indexes + +- `companies_company_key_key`: `CREATE UNIQUE INDEX companies_company_key_key ON public.companies USING btree (company_key)` +- `companies_pkey`: `CREATE UNIQUE INDEX companies_pkey ON public.companies USING btree (id)` + +## `modules` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `module_key`: `text` (not null: `true`) +- `name`: `text` (not null: `true`) +- `status`: `record_status` (not null: `true`) +- `created_at`: `timestamp with time zone` (not null: `true`) +- `updated_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `modules_module_key_key` (`u`): UNIQUE (module_key) +- `modules_pkey` (`p`): PRIMARY KEY (id) + +### Indexes + +- `modules_module_key_key`: `CREATE UNIQUE INDEX modules_module_key_key ON public.modules USING btree (module_key)` +- `modules_pkey`: `CREATE UNIQUE INDEX modules_pkey ON public.modules USING btree (id)` + +## `permissions` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `user_id`: `uuid` (not null: `true`) +- `scope_type`: `character varying(32)` (not null: `true`) +- `scope_id`: `character varying(128)` (not null: `true`) +- `module`: `character varying(128)` (not null: `true`) +- `action`: `character varying(32)` (not null: `true`) +- `created_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `permissions_pkey` (`p`): PRIMARY KEY (id) +- `permissions_user_id_fkey` (`f`): FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +- `uq_permissions_user_scope_module_action` (`u`): UNIQUE (user_id, scope_type, scope_id, module, action) + +### Indexes + +- `idx_permissions_user_id`: `CREATE INDEX idx_permissions_user_id ON public.permissions USING btree (user_id)` +- `permissions_pkey`: `CREATE UNIQUE INDEX permissions_pkey ON public.permissions USING btree (id)` +- `uq_permissions_user_scope_module_action`: `CREATE UNIQUE INDEX uq_permissions_user_scope_module_action ON public.permissions USING btree (user_id, scope_type, scope_id, module, action)` + +## `sites` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `site_key`: `text` (not null: `true`) +- `company_id`: `uuid` (not null: `true`) +- `name`: `text` (not null: `true`) +- `status`: `record_status` (not null: `true`) +- `created_at`: `timestamp with time zone` (not null: `true`) +- `updated_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `sites_company_id_fkey` (`f`): FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE +- `sites_pkey` (`p`): PRIMARY KEY (id) +- `sites_site_key_key` (`u`): UNIQUE (site_key) + +### Indexes + +- `idx_sites_company_id`: `CREATE INDEX idx_sites_company_id ON public.sites USING btree (company_id)` +- `sites_pkey`: `CREATE UNIQUE INDEX sites_pkey ON public.sites USING btree (id)` +- `sites_site_key_key`: `CREATE UNIQUE INDEX sites_site_key_key ON public.sites USING btree (site_key)` + +## `user_scope_permissions` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `user_id`: `uuid` (not null: `true`) +- `module_id`: `uuid` (not null: `true`) +- `action`: `permission_action` (not null: `true`) +- `scope_type`: `scope_type` (not null: `true`) +- `company_id`: `uuid` (not null: `false`) +- `site_id`: `uuid` (not null: `false`) +- `created_at`: `timestamp with time zone` (not null: `true`) +- `updated_at`: `timestamp with time zone` (not null: `true`) + +### Constraints + +- `user_scope_permissions_check` (`c`): CHECK ((((scope_type = 'company'::scope_type) AND (company_id IS NOT NULL) AND (site_id IS NULL)) OR ((scope_type = 'site'::scope_type) AND (site_id IS NOT NULL) AND (company_id IS NULL)))) +- `user_scope_permissions_company_id_fkey` (`f`): FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE +- `user_scope_permissions_module_id_fkey` (`f`): FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE +- `user_scope_permissions_pkey` (`p`): PRIMARY KEY (id) +- `user_scope_permissions_site_id_fkey` (`f`): FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE +- `user_scope_permissions_user_id_fkey` (`f`): FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + +### Indexes + +- `idx_usp_company_id`: `CREATE INDEX idx_usp_company_id ON public.user_scope_permissions USING btree (company_id)` +- `idx_usp_module_id`: `CREATE INDEX idx_usp_module_id ON public.user_scope_permissions USING btree (module_id)` +- `idx_usp_site_id`: `CREATE INDEX idx_usp_site_id ON public.user_scope_permissions USING btree (site_id)` +- `idx_usp_user_id`: `CREATE INDEX idx_usp_user_id ON public.user_scope_permissions USING btree (user_id)` +- `uq_usp_company`: `CREATE UNIQUE INDEX uq_usp_company ON public.user_scope_permissions USING btree (user_id, module_id, action, scope_type, company_id) WHERE (scope_type = 'company'::scope_type)` +- `uq_usp_site`: `CREATE UNIQUE INDEX uq_usp_site ON public.user_scope_permissions USING btree (user_id, module_id, action, scope_type, site_id) WHERE (scope_type = 'site'::scope_type)` +- `user_scope_permissions_pkey`: `CREATE UNIQUE INDEX user_scope_permissions_pkey ON public.user_scope_permissions USING btree (id)` + +## `users` + +### Columns + +- `id`: `uuid` (not null: `true`) +- `authentik_sub`: `text` (not null: `true`) +- `email`: `text` (not null: `false`) +- `display_name`: `text` (not null: `false`) +- `status`: `record_status` (not null: `true`) +- `created_at`: `timestamp with time zone` (not null: `true`) +- `updated_at`: `timestamp with time zone` (not null: `true`) +- `authentik_user_id`: `integer` (not null: `false`) +- `is_active`: `boolean` (not null: `true`) + +### Constraints + +- `users_authentik_sub_key` (`u`): UNIQUE (authentik_sub) +- `users_email_key` (`u`): UNIQUE (email) +- `users_pkey` (`p`): PRIMARY KEY (id) + +### Indexes + +- `idx_users_authentik_sub`: `CREATE INDEX idx_users_authentik_sub ON public.users USING btree (authentik_sub)` +- `users_authentik_sub_key`: `CREATE UNIQUE INDEX users_authentik_sub_key ON public.users USING btree (authentik_sub)` +- `users_email_key`: `CREATE UNIQUE INDEX users_email_key ON public.users USING btree (email)` +- `users_pkey`: `CREATE UNIQUE INDEX users_pkey ON public.users USING btree (id)` diff --git a/docs/index.md b/docs/index.md index e6e7937..1ed249a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,6 +16,7 @@ ## SQL 與配置 - `docs/API_CLIENTS_SQL.sql` +- `docs/DB_SCHEMA_SNAPSHOT.md` ## 給前端 AI 的一句話交接 請先完成 `/me`、`/me/permissions/snapshot`、`/admin/permissions/grant|revoke` 三組 API 對接,並依 `FRONTEND_IMPLEMENTATION_CHECKLIST.md` 逐項完成。