@polardbx/pxbaas 0.1.0-beta.1 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +108 -0
  2. package/dist/commands/app/index.d.ts.map +1 -1
  3. package/dist/commands/app/index.js +219 -10
  4. package/dist/commands/app/index.js.map +1 -1
  5. package/dist/commands/db/index.d.ts +1 -0
  6. package/dist/commands/db/index.d.ts.map +1 -1
  7. package/dist/commands/db/index.js +49 -15
  8. package/dist/commands/db/index.js.map +1 -1
  9. package/dist/commands/login/index.d.ts.map +1 -1
  10. package/dist/commands/login/index.js +9 -34
  11. package/dist/commands/login/index.js.map +1 -1
  12. package/dist/commands/profile/index.d.ts.map +1 -1
  13. package/dist/commands/profile/index.js +3 -0
  14. package/dist/commands/profile/index.js.map +1 -1
  15. package/dist/credentials/index.d.ts +2 -0
  16. package/dist/credentials/index.d.ts.map +1 -1
  17. package/dist/credentials/index.js +2 -0
  18. package/dist/credentials/index.js.map +1 -1
  19. package/dist/db/apply-schema.d.ts +98 -11
  20. package/dist/db/apply-schema.d.ts.map +1 -1
  21. package/dist/db/apply-schema.js +247 -80
  22. package/dist/db/apply-schema.js.map +1 -1
  23. package/dist/db/sql-file.d.ts +55 -0
  24. package/dist/db/sql-file.d.ts.map +1 -1
  25. package/dist/db/sql-file.js +417 -0
  26. package/dist/db/sql-file.js.map +1 -1
  27. package/package.json +2 -2
  28. package/skills/pxbaas-app-spec/references/templates-examples.md +5 -2
  29. package/skills/pxbaas-cli/SKILL.md +7 -4
  30. package/skills/pxbaas-cli/references/app-deploy.md +29 -2
  31. package/skills/pxbaas-cli/references/auth-and-credentials.md +14 -2
  32. package/skills/pxbaas-cli/references/db-and-meta.md +12 -0
  33. package/skills/pxbaas-cli/references/multi-tenant-apps.md +35 -4
  34. package/templates/webapp/csr-bff/db/rls.json +13 -0
  35. package/templates/webapp/csr-bff/db/schema.sql +8 -28
  36. package/templates/webapp/manifest.json +6 -3
  37. package/templates/webapp/ssr-blog/db/rls.json +13 -0
  38. package/templates/webapp/ssr-blog/db/schema.sql +8 -28
  39. package/templates/webapp/ssr-shop/db/rls.json +16 -0
  40. package/templates/webapp/ssr-shop/db/schema.sql +12 -42
@@ -19,6 +19,18 @@ pxbaas db query "SELECT count(*) FROM users" --role anon --json
19
19
  { "rows": [ { "count(*)": 42 } ], "rowCount": 1, "fields": [ { "name": "count(*)", "dataType": "BIGINT" } ] }
20
20
  ```
21
21
  - **Gotchas:** `db query` can also run write/DDL SQL. If the statement contains INSERT/UPDATE/DELETE/DROP/ALTER/CREATE, treat it as destructive and confirm with the user first.
22
+ - **Multi-tenant:** with an MT profile (`--tenant-id`), `db query` routes through `/api/tenant-ddl/query` using the per-tenant service_role key and runs on the tenant's OWN database (`/pg/query` is platform-admin only and rejects per-tenant keys). `--role anon` is not applicable there — the tenant-ddl endpoint is service_role-only, so the query always runs as the tenant service_role. For batch DDL + RLS use `db push <file>` or `app db-init <name>` (see app-deploy.md); both are MT-aware.
23
+
24
+ ## db push — apply a SQL file (DDL + RLS + seed)
25
+
26
+ ```bash
27
+ pxbaas db push ./db/schema.sql --schema public --dry-run # preview the plan
28
+ pxbaas db push ./db/schema.sql --yes # apply (stop-on-first-error, no rollback)
29
+ ```
30
+
31
+ - **Flags:** `--schema <name>` (single-tenant `__SCHEMA__` target, default `public`) · `--rls <file>` (declarative `db/rls.json`) · `--role` (default service_role) · `--dry-run` · `--yes` · `--json`
32
+ - Mode-aware: single-tenant runs through `/pg/query`; multi-tenant through `/api/tenant-ddl` (de-qualifies `__SCHEMA__`, diverts RLS to the structured endpoints). Shows the full plan and requires authorization; never rolls back.
33
+ - `app db-init <name>` is the app-bundled convenience wrapper (reads `<appDir>/db/schema.sql` + `db/rls.json`).
22
34
 
23
35
  ## db tables — list tables
24
36
 
@@ -12,14 +12,20 @@ most common source of 401/403 confusion.
12
12
  | `/api/platform/*` (Studio: tenants CRUD) | **dashboard Basic Auth** (`DASHBOARD_USERNAME`/`DASHBOARD_PASSWORD`) | global service key → `401 Unauthorized` |
13
13
  | `/pg/query` (ad-hoc SQL) | **global service_role key**, sent as BOTH `Authorization: Bearer` and `apikey` | Basic Auth → 401 "apikey or Bearer required"; per-tenant key → `401` |
14
14
  | Data plane `/rest/v1`, `/auth/v1`; Management API `/v1/projects/default/*` (apps create/publish, functions deploy) | **per-tenant key** (Management API also needs `X-Tenant-Id`) | global key + `X-Tenant-Id` → `403 Global credentials cannot access tenant resources` |
15
+ | `/api/tenant-ddl/*` (tenant self-service DDL + RLS) | **per-tenant service_role key** — send only `Authorization: Bearer`; Kong verifies the JWT and injects the trusted `X-Tenant-Id` (never send it yourself) | global key → `403 GLOBAL_KEY_NOT_ALLOWED`; per-tenant anon → `403 ROLE_NOT_ALLOWED`; suspended tenant → `403 TENANT_NOT_ACTIVE` |
16
+
17
+ > RLS registry management is symmetric: `rls/tables` and `rls/policies` both support POST/GET/DELETE
18
+ > (DELETE on `rls/tables` does not require the table to still exist, so it cleans up after a DROP).
15
19
 
16
20
  - A per-tenant anon/service key carries a `tenant_id` claim, so **REST and Auth route from the
17
21
  key alone** — `X-Tenant-Id` is optional there. Issued user JWTs carry `aud=<tenant_id>`.
18
22
  - Using tenant X's key with `X-Tenant-Id: Y` → `403 Token tenant mismatch` (gateway guardrail).
19
- - CLI: `pxbaas login --endpoint <url> --tenant-id <id>` with the tenant's keys gives a working
20
- MT profile (`app capabilities`, `functions`, `rest` all fine), but **`pxbaas db query` returns
21
- 401 in MT** (the pg-meta path does not accept tenant keys). Run tenant SQL through
22
- `/pg/query` with the global key, using fully-qualified `tenant_<id>.<table>` names.
23
+ - CLI: `pxbaas login --endpoint <url> --tenant-id <id>` with the tenant's per-tenant keys gives a
24
+ working MT profile. As of the tenant self-service DDL capability, **`pxbaas db query`, `db push`,
25
+ and `app db-init` all work in MT** — they route through `/api/tenant-ddl` with the per-tenant
26
+ service_role key (DDL runs on the tenant's own db; the tenant MySQL account's `GRANT ALL ON
27
+ tenant_<id>.*` is the hard isolation boundary). The old "`db query` 401s in MT, use the global key
28
+ on `/pg/query`" guidance is obsolete; `/pg/query` remains platform-admin only.
23
29
 
24
30
  ## 2. Provisioning a tenant
25
31
 
@@ -63,6 +69,31 @@ curl -sS -o /dev/null -w '%{http_code}\n' -X POST "$ENDPOINT/rest/v1/posts" \
63
69
  -d '{"title":"x","author_id":"someone-else","status":"draft"}' # expect 403 PGRST302
64
70
  ```
65
71
 
72
+ ## 3b. Tenant self-service DDL / db-init as a normal tenant
73
+
74
+ A normal tenant (holding only its per-tenant service_role key — no platform admin credential) can
75
+ initialize and tear down its own app database through the CLI:
76
+
77
+ ```bash
78
+ pxbaas login --endpoint <url> --tenant-id <id> # paste the tenant's per-tenant anon + service keys
79
+ pxbaas app db-init <name> --dir ./apps --dry-run # preview the plan (DDL + seed + RLS)
80
+ pxbaas app db-init <name> --dir ./apps --yes # apply (stop-on-first-error, no rollback)
81
+ pxbaas db query "SELECT count(*) FROM posts" --json
82
+ pxbaas app destroy <name> --confirm --purge-db # optional: also drop the app's tables + RLS rows
83
+ ```
84
+
85
+ - `db-init`/`db push` read `db/schema.sql` (DDL + seed) and the declarative `db/rls.json` (RLS).
86
+ In MT the `__SCHEMA__` qualifier is dropped (the connection is already bound to the tenant db) and
87
+ cross-schema names are refused by GRANT.
88
+ - **RLS is registered through the structured `/api/tenant-ddl/rls/*` endpoints**, not raw central
89
+ INSERTs (the tenant account has no GRANT on the shared `supabase` db). `table_schema` is bound
90
+ server-side to the verified tenant db — the CLI never sends it, so a tenant cannot touch another's
91
+ policies. If `db/rls.json` is absent, RLS INSERTs embedded in `schema.sql` are parsed and routed
92
+ the same way.
93
+ - `--purge-db` DROPs the app's tables and DELETEs its RLS registry rows — both the policies and the
94
+ central `_mysqlrest_rls_tables` toggle (via `DELETE /api/tenant-ddl/rls/tables`). On instances that
95
+ predate that endpoint the toggle row is left (harmless; re-created on the next `db-init`).
96
+
66
97
  ## 4. Deploying a webapp into a tenant
67
98
 
68
99
  - Every Management API call needs `Authorization: Bearer <tenant service_role_key>` **and**
@@ -0,0 +1,13 @@
1
+ {
2
+ "$comment": "Declarative RLS for the CSR/BFF blog webapp. Applied by `pxbaas app db-init` (single-tenant AND multi-tenant). table_schema is NEVER declared here: single-tenant binds it to --schema (default public); multi-tenant binds it server-side to the tenant db.",
3
+ "tables": [
4
+ { "table": "posts", "rlsEnabled": true, "rlsForced": false }
5
+ ],
6
+ "policies": [
7
+ { "table": "posts", "name": "posts_select_anon", "role": "anon", "command": "SELECT", "using": "`status` = 'published'", "check": null, "permissive": true },
8
+ { "table": "posts", "name": "posts_select_auth", "role": "authenticated", "command": "SELECT", "using": "`status` = 'published' OR author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true },
9
+ { "table": "posts", "name": "posts_insert_auth", "role": "authenticated", "command": "INSERT", "using": null, "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
10
+ { "table": "posts", "name": "posts_update_auth", "role": "authenticated", "command": "UPDATE", "using": "author_id = {{request.jwt.claim.sub}}", "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
11
+ { "table": "posts", "name": "posts_delete_auth", "role": "authenticated", "command": "DELETE", "using": "author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true }
12
+ ]
13
+ }
@@ -6,11 +6,13 @@
6
6
  -- · 多租户实例:__SCHEMA__ = tenant_<tenant_id>
7
7
  -- sed 's/__SCHEMA__/public/g' db/schema.sql
8
8
  --
9
- -- 执行通道:单租户可 pxbaas db query;多租户走 POST {endpoint}/pg/query
10
- -- (全局 service_role key 作 Authorization+apikey),语句写全限定名。
9
+ -- 执行通道(推荐):`pxbaas app db-init <name>` —— 单/多租户通用:
10
+ -- 单租户 __SCHEMA__→public 经 /pg/query;多租户 __SCHEMA__ 去限定经 /api/tenant-ddl
11
+ -- 用 per-tenant service_role key 执行(GRANT 仅本租户库)。本文件只含 DDL + 种子;
12
+ -- RLS 声明在同目录 db/rls.json,由 db-init 一并应用。
11
13
  --
12
- -- ⚠️ RLS 元数据落点(写错静默失效=RLS 关闭):永远写中心库 supabase._mysqlrest_rls_*,
13
- -- 且 table_schema 填 __SCHEMA__;租户库内同名表无人读取,勿写入。
14
+ -- ⚠️ RLS 元数据落点(写错静默失效=RLS 关闭):永远写中心库 supabase._mysqlrest_rls_*;
15
+ -- db-init 自动绑定 table_schema(单租户=--schema,多租户=服务端强绑),勿手写库名。
14
16
  -- =============================================================================
15
17
 
16
18
  CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`posts` (
@@ -24,30 +26,8 @@ CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`posts` (
24
26
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
25
27
  );
26
28
 
27
- -- ---- RLS 注册 ----
28
- INSERT IGNORE INTO supabase._mysqlrest_rls_tables (table_schema, table_name, rls_enabled, rls_forced)
29
- VALUES ('__SCHEMA__','posts',1,0);
30
-
31
- -- ---- RLS 策略:anon 只读 published;authenticated 读 published 或本人;写改删限本人 ----
32
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
33
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
34
- VALUES ('__SCHEMA__','posts','posts_select_anon','anon','SELECT','`status` = ''published''',NULL,1);
35
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
36
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
37
- VALUES ('__SCHEMA__','posts','posts_select_auth','authenticated','SELECT',
38
- '`status` = ''published'' OR author_id = {{request.jwt.claim.sub}}',NULL,1);
39
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
40
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
41
- VALUES ('__SCHEMA__','posts','posts_insert_auth','authenticated','INSERT',NULL,
42
- 'author_id = {{request.jwt.claim.sub}}',1);
43
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
44
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
45
- VALUES ('__SCHEMA__','posts','posts_update_auth','authenticated','UPDATE',
46
- 'author_id = {{request.jwt.claim.sub}}','author_id = {{request.jwt.claim.sub}}',1);
47
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
48
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
49
- VALUES ('__SCHEMA__','posts','posts_delete_auth','authenticated','DELETE',
50
- 'author_id = {{request.jwt.claim.sub}}',NULL,1);
29
+ -- ---- RLS 注册与策略:声明在同目录 db/rls.json,由 `pxbaas app db-init` 应用 ----
30
+ -- anon 只读 published;authenticated 读 published 或本人;写/改/删限本人(author_id = jwt.sub)。
51
31
 
52
32
  -- ---- 种子文章(3 篇 published,幂等)----
53
33
  INSERT INTO `__SCHEMA__`.`posts` (title, content, author_id, author_name, status)
@@ -163,6 +163,7 @@
163
163
  "README.md",
164
164
  "backend/index.ts",
165
165
  "db/schema.sql",
166
+ "db/rls.json",
166
167
  "frontend/dist/index.html"
167
168
  ],
168
169
  "frontend": {
@@ -182,7 +183,7 @@
182
183
  },
183
184
  "ssrProbePath": "/posts",
184
185
  "nextSteps": [
185
- "Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>` (single-tenant). It reads db/schema.sql, substitutes __SCHEMA__=public, shows the full DDL + RLS + seed plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). The CLI never auto-runs DDL. Multi-tenant: use POST /pg/query with the global service_role key and tenant_<id>.<table> names.",
186
+ "Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>`. It reads db/schema.sql (DDL + seed) and db/rls.json (RLS registry), shows the full plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). Works in single-tenant (/pg/query) AND multi-tenant (/api/tenant-ddl with your per-tenant service_role key); the CLI never auto-runs DDL.",
186
187
  "Requires capabilities.sessionMode=proxy-injected-header.",
187
188
  "Data-plane base and anon key come from the platform-in SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed."
188
189
  ]
@@ -196,6 +197,7 @@
196
197
  "README.md",
197
198
  "backend/index.ts",
198
199
  "db/schema.sql",
200
+ "db/rls.json",
199
201
  "frontend/dist/admin.html",
200
202
  "frontend/dist/article.html",
201
203
  "frontend/dist/blog.js",
@@ -222,7 +224,7 @@
222
224
  "session": "proxy-injected"
223
225
  },
224
226
  "nextSteps": [
225
- "Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>` (single-tenant). It reads db/schema.sql, substitutes __SCHEMA__=public, shows the full DDL + RLS + seed plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). The CLI never auto-runs DDL. Multi-tenant: use POST /pg/query with the global service_role key and tenant_<id>.<table> names.",
227
+ "Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>`. It reads db/schema.sql (DDL + seed) and db/rls.json (RLS registry), shows the full plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). Works in single-tenant (/pg/query) AND multi-tenant (/api/tenant-ddl with your per-tenant service_role key); the CLI never auto-runs DDL.",
226
228
  "Requires capabilities.sessionMode=proxy-injected-header.",
227
229
  "The BFF forwards to the platform-injected SUPABASE_INTERNAL_URL with SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed."
228
230
  ]
@@ -236,6 +238,7 @@
236
238
  "README.md",
237
239
  "backend/index.ts",
238
240
  "db/schema.sql",
241
+ "db/rls.json",
239
242
  "frontend/dist/index.html"
240
243
  ],
241
244
  "frontend": {
@@ -255,7 +258,7 @@
255
258
  },
256
259
  "ssrProbePath": "/shop",
257
260
  "nextSteps": [
258
- "Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>` (single-tenant). It reads db/schema.sql, substitutes __SCHEMA__=public, shows the full DDL + RLS (products public-read; orders and order_items are owner-gated by buyer_email = jwt.email) + seed plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). The CLI never auto-runs DDL. Multi-tenant: use POST /pg/query with the global service_role key and tenant_<id>.<table> names.",
261
+ "Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>`. It reads db/schema.sql (DDL + seed) and db/rls.json (RLS: products public-read; orders and order_items owner-gated by buyer_email = jwt.email), shows the full plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). Works in single-tenant (/pg/query) AND multi-tenant (/api/tenant-ddl with your per-tenant service_role key); the CLI never auto-runs DDL.",
259
262
  "Create the admin account (admin@shop.demo) via GoTrue signup if the admin whitelist is needed.",
260
263
  "Data-plane base and anon key come from the platform-injected SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed. Requires the instance to report react/react-dom in capabilities.ssrLibs."
261
264
  ]
@@ -0,0 +1,13 @@
1
+ {
2
+ "$comment": "Declarative RLS for the SSR blog webapp. Applied by `pxbaas app db-init` (single-tenant AND multi-tenant). table_schema is NEVER declared here: single-tenant binds it to --schema (default public); multi-tenant binds it server-side to the tenant db.",
3
+ "tables": [
4
+ { "table": "posts_ssr", "rlsEnabled": true, "rlsForced": false }
5
+ ],
6
+ "policies": [
7
+ { "table": "posts_ssr", "name": "posts_ssr_select_anon", "role": "anon", "command": "SELECT", "using": "`status` = 'published'", "check": null, "permissive": true },
8
+ { "table": "posts_ssr", "name": "posts_ssr_select_auth", "role": "authenticated", "command": "SELECT", "using": "`status` = 'published' OR author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true },
9
+ { "table": "posts_ssr", "name": "posts_ssr_insert_auth", "role": "authenticated", "command": "INSERT", "using": null, "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
10
+ { "table": "posts_ssr", "name": "posts_ssr_update_auth", "role": "authenticated", "command": "UPDATE", "using": "author_id = {{request.jwt.claim.sub}}", "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
11
+ { "table": "posts_ssr", "name": "posts_ssr_delete_auth", "role": "authenticated", "command": "DELETE", "using": "author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true }
12
+ ]
13
+ }
@@ -6,11 +6,13 @@
6
6
  -- · 多租户实例:__SCHEMA__ = tenant_<tenant_id>
7
7
  -- sed 's/__SCHEMA__/public/g' db/schema.sql
8
8
  --
9
- -- 执行通道:单租户可 pxbaas db query;多租户走 POST {endpoint}/pg/query
10
- -- (全局 service_role key 作 Authorization+apikey),语句写全限定名。
9
+ -- 执行通道(推荐):`pxbaas app db-init <name>` —— 单/多租户通用:
10
+ -- 单租户 __SCHEMA__→public 经 /pg/query;多租户 __SCHEMA__ 去限定经 /api/tenant-ddl
11
+ -- 用 per-tenant service_role key 执行(GRANT 仅本租户库)。本文件只含 DDL + 种子;
12
+ -- RLS 声明在同目录 db/rls.json,由 db-init 一并应用。
11
13
  --
12
- -- ⚠️ RLS 元数据落点(写错静默失效=RLS 关闭):永远写中心库 supabase._mysqlrest_rls_*,
13
- -- 且 table_schema 填 __SCHEMA__;租户库内同名表无人读取,勿写入。
14
+ -- ⚠️ RLS 元数据落点(写错静默失效=RLS 关闭):永远写中心库 supabase._mysqlrest_rls_*;
15
+ -- db-init 自动绑定 table_schema(单租户=--schema,多租户=服务端强绑),勿手写库名。
14
16
  -- =============================================================================
15
17
 
16
18
  CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`posts_ssr` (
@@ -26,30 +28,8 @@ CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`posts_ssr` (
26
28
  KEY `idx_posts_ssr_status` (`status`, `created_at`)
27
29
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
28
30
 
29
- -- ---- RLS 注册 ----
30
- INSERT IGNORE INTO supabase._mysqlrest_rls_tables (table_schema, table_name, rls_enabled, rls_forced)
31
- VALUES ('__SCHEMA__','posts_ssr',1,0);
32
-
33
- -- ---- RLS 策略:anon 只读 published;authenticated 读 published 或本人;写改删限本人 ----
34
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
35
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
36
- VALUES ('__SCHEMA__','posts_ssr','posts_ssr_select_anon','anon','SELECT','`status` = ''published''',NULL,1);
37
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
38
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
39
- VALUES ('__SCHEMA__','posts_ssr','posts_ssr_select_auth','authenticated','SELECT',
40
- '`status` = ''published'' OR author_id = {{request.jwt.claim.sub}}',NULL,1);
41
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
42
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
43
- VALUES ('__SCHEMA__','posts_ssr','posts_ssr_insert_auth','authenticated','INSERT',NULL,
44
- 'author_id = {{request.jwt.claim.sub}}',1);
45
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
46
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
47
- VALUES ('__SCHEMA__','posts_ssr','posts_ssr_update_auth','authenticated','UPDATE',
48
- 'author_id = {{request.jwt.claim.sub}}','author_id = {{request.jwt.claim.sub}}',1);
49
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
50
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
51
- VALUES ('__SCHEMA__','posts_ssr','posts_ssr_delete_auth','authenticated','DELETE',
52
- 'author_id = {{request.jwt.claim.sub}}',NULL,1);
31
+ -- ---- RLS 注册与策略:声明在同目录 db/rls.json,由 `pxbaas app db-init` 应用 ----
32
+ -- anon 只读 published;authenticated 读 published 或本人;写/改/删限本人(author_id = jwt.sub)。
53
33
 
54
34
  -- ---- 种子文章(3 篇 published,幂等)----
55
35
  INSERT INTO `__SCHEMA__`.`posts_ssr` (title, content, author_id, author_name, status)
@@ -0,0 +1,16 @@
1
+ {
2
+ "$comment": "Declarative RLS for the shop webapp. Applied by `pxbaas app db-init` (single-tenant AND multi-tenant). table_schema is NEVER declared here: single-tenant binds it to --schema (default public); multi-tenant binds it server-side to the tenant db. using/check expressions may embed {{request.jwt.claim.*}} templates.",
3
+ "tables": [
4
+ { "table": "products", "rlsEnabled": true, "rlsForced": false },
5
+ { "table": "orders", "rlsEnabled": true, "rlsForced": false },
6
+ { "table": "order_items", "rlsEnabled": true, "rlsForced": false }
7
+ ],
8
+ "policies": [
9
+ { "table": "products", "name": "products_select_anon", "role": "anon", "command": "SELECT", "using": "1=1", "check": null, "permissive": true },
10
+ { "table": "products", "name": "products_select_auth", "role": "authenticated", "command": "SELECT", "using": "1=1", "check": null, "permissive": true },
11
+ { "table": "orders", "name": "orders_select_auth", "role": "authenticated", "command": "SELECT", "using": "`buyer_email` = {{request.jwt.claim.email}} OR {{request.jwt.claim.email}} = 'admin@shop.demo'", "check": null, "permissive": true },
12
+ { "table": "orders", "name": "orders_insert_auth", "role": "authenticated", "command": "INSERT", "using": null, "check": "`buyer_email` = {{request.jwt.claim.email}}", "permissive": true },
13
+ { "table": "order_items", "name": "order_items_select_auth", "role": "authenticated", "command": "SELECT", "using": "`buyer_email` = {{request.jwt.claim.email}} OR {{request.jwt.claim.email}} = 'admin@shop.demo'", "check": null, "permissive": true },
14
+ { "table": "order_items", "name": "order_items_insert_auth", "role": "authenticated", "command": "INSERT", "using": null, "check": "`buyer_email` = {{request.jwt.claim.email}}", "permissive": true }
15
+ ]
16
+ }
@@ -6,15 +6,16 @@
6
6
  -- · 多租户实例:__SCHEMA__ = tenant_<tenant_id>(每租户一套库)
7
7
  -- sed 's/__SCHEMA__/public/g' db/schema.sql
8
8
  --
9
- -- 执行通道:
10
- -- · 单租户:pxbaas db query "<单条 SQL>" --role service_role --profile <p>
11
- -- · 多租户:POST {endpoint}/pg/query(全局 service_role key 作 Authorization+apikey),
12
- -- 语句里必须写全限定名 tenant_<id>.<table>;pxbaas db query 在 MT 下会 401。
9
+ -- 执行通道(推荐):`pxbaas app db-init <name>` —— 单/多租户通用,自动:
10
+ -- · 单租户:__SCHEMA__ → public,经 /pg/query(全局 admin)执行;
11
+ -- · 多租户:__SCHEMA__ 去限定(连接已锁租户库),经 /api/tenant-ddl 用
12
+ -- per-tenant service_role key 执行;GRANT 仅本租户库,天然隔离。
13
+ -- 本文件只含 DDL + 种子;RLS 声明在同目录 db/rls.json,由 db-init 一并应用。
13
14
  --
14
15
  -- ⚠️ RLS 元数据的落点(写错静默失效,等价于关闭 RLS):
15
- -- 永远写中心库 supabase._mysqlrest_rls_tables / _mysqlrest_rls_policies,
16
- -- 且 table_schema 填 __SCHEMA__(单租户=public,多租户=tenant_<id>)。
17
- -- 租户库里由开通流程创建的同名 _mysqlrest_rls_* 表没有任何服务读取,勿写入。
16
+ -- 永远写中心库 supabase._mysqlrest_rls_tables / _mysqlrest_rls_policies。
17
+ -- db-init 会自动绑定 table_schema(单租户=--schema,多租户=服务端强绑租户库),
18
+ -- 无需也不要手写库名。租户库内同名 _mysqlrest_rls_* 表没有任何服务读取,勿写入。
18
19
  -- =============================================================================
19
20
 
20
21
  -- ---- 表结构 ----
@@ -54,41 +55,10 @@ CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`order_items` (
54
55
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
55
56
  );
56
57
 
57
- -- ---- RLS 注册(中心库 supabase)----
58
- INSERT IGNORE INTO supabase._mysqlrest_rls_tables (table_schema, table_name, rls_enabled, rls_forced)
59
- VALUES ('__SCHEMA__','products',1,0), ('__SCHEMA__','orders',1,0), ('__SCHEMA__','order_items',1,0);
60
-
61
- -- ---- RLS 策略 ----
62
- -- products:公开目录,anon / authenticated 均可读
63
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
64
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
65
- VALUES ('__SCHEMA__','products','products_select_anon','anon','SELECT','1=1',NULL,1);
66
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
67
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
68
- VALUES ('__SCHEMA__','products','products_select_auth','authenticated','SELECT','1=1',NULL,1);
69
-
70
- -- orders:仅本人(buyer_email = jwt.email)或管理员白名单可读;仅本人可写
71
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
72
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
73
- VALUES ('__SCHEMA__','orders','orders_select_auth','authenticated','SELECT',
74
- '`buyer_email` = {{request.jwt.claim.email}} OR {{request.jwt.claim.email}} = ''admin@shop.demo''',
75
- NULL, 1);
76
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
77
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
78
- VALUES ('__SCHEMA__','orders','orders_insert_auth','authenticated','INSERT',NULL,
79
- '`buyer_email` = {{request.jwt.claim.email}}', 1);
80
-
81
- -- order_items:仅本人(buyer_email = jwt.email)或管理员白名单可读;仅本人可写(owner 门禁落在本表,
82
- -- 不依赖前端按 order_id 过滤 —— 前端过滤不是安全边界,R2-05)。
83
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
84
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
85
- VALUES ('__SCHEMA__','order_items','order_items_select_auth','authenticated','SELECT',
86
- '`buyer_email` = {{request.jwt.claim.email}} OR {{request.jwt.claim.email}} = ''admin@shop.demo''',
87
- NULL, 1);
88
- INSERT IGNORE INTO supabase._mysqlrest_rls_policies
89
- (table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
90
- VALUES ('__SCHEMA__','order_items','order_items_insert_auth','authenticated','INSERT',NULL,
91
- '`buyer_email` = {{request.jwt.claim.email}}', 1);
58
+ -- ---- RLS 注册与策略:声明在同目录 db/rls.json,由 `pxbaas app db-init` 应用 ----
59
+ -- · products:公开目录,anon / authenticated 均可读;
60
+ -- · orders / order_items:仅本人(buyer_email = jwt.email)或管理员白名单可读,仅本人可写。
61
+ -- (不再内联 INSERT,避免与 rls.json 双源;如需手动注册见 db/rls.json。)
92
62
 
93
63
  -- ---- 种子商品(10 件,幂等)----
94
64
  INSERT INTO `__SCHEMA__`.`products` (name, description, price_cents, stock, emoji, category)