@wecode-team/cms-supabase-api 0.1.29

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.
package/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # @wecode-team/cms-supabase-api
2
+
3
+ 一个基于 Hono 框架的动态 CMS API 包,使用 Supabase 作为后端数据库,支持动态表管理、数据关联、用户认证等功能。
4
+
5
+ ## 📖 设计理念
6
+
7
+ ### 核心思想
8
+
9
+ 本包的核心理念是 **"JSON Schema 驱动的动态 CMS"**:
10
+
11
+ 1. **模型即配置**:通过 JSON Schema 定义数据模型,系统自动创建对应的数据库表
12
+ 2. **动态表管理**:无需手写 SQL,通过 API 动态创建、修改、删除数据表
13
+ 3. **关系支持**:支持 `belongsTo`、`hasMany`、`belongsToMany` 三种关联关系
14
+ 4. **多租户隔离**:通过表名前缀(Session ID)实现数据隔离
15
+
16
+ ### 架构设计
17
+
18
+ ```
19
+ ┌─────────────────────────────────────────────────────────────┐
20
+ │ Hono Application │
21
+ ├─────────────────────────────────────────────────────────────┤
22
+ │ Route Helpers │
23
+ │ ├── createCmsRoutes() 一键创建所有路由 │
24
+ │ ├── createModelRoute() 模型管理路由 │
25
+ │ ├── createDynamicDataRoute() 动态数据路由 │
26
+ │ └── createDynamicAuthRoute() 动态认证路由 │
27
+ ├─────────────────────────────────────────────────────────────┤
28
+ │ Handlers (处理器层) │
29
+ │ ├── models.ts 模型 CRUD │
30
+ │ ├── data.ts 数据 CRUD + 关联查询 │
31
+ │ └── auth.ts 登录/认证/用户管理 │
32
+ ├─────────────────────────────────────────────────────────────┤
33
+ │ Services (服务层) │
34
+ │ ├── DynamicTableService 动态表操作 │
35
+ │ ├── CmsModelService 模型配置管理 │
36
+ │ └── AuthService 认证服务 │
37
+ ├─────────────────────────────────────────────────────────────┤
38
+ │ Supabase (数据层) │
39
+ │ ├── PostgreSQL 关系型数据库 │
40
+ │ ├── RPC Functions 存储过程 │
41
+ │ └── Row Level Security 行级安全 │
42
+ └─────────────────────────────────────────────────────────────┘
43
+ ```
44
+
45
+ ## 🚀 安装
46
+
47
+ ```bash
48
+ npm install @wecode-team/cms-supabase-api
49
+ # 或
50
+ pnpm add @wecode-team/cms-supabase-api
51
+ # 或
52
+ yarn add @wecode-team/cms-supabase-api
53
+ ```
54
+
55
+ ## 📋 依赖
56
+
57
+ ### Peer Dependencies(必须安装)
58
+
59
+ ```json
60
+ {
61
+ "hono": "^4.0.0",
62
+ "@supabase/supabase-js": "^2.0.0"
63
+ }
64
+ ```
65
+
66
+ ## 🔧 快速开始
67
+
68
+ ### 第一步:初始化 Supabase
69
+
70
+ 在 Supabase SQL 编辑器中执行 `supabase-setup.sql` 文件中的 SQL 脚本:
71
+
72
+ ```sql
73
+ -- 执行完整的 supabase-setup.sql 文件
74
+ -- 这将创建必要的函数和表
75
+ ```
76
+
77
+ ## 🔐 Auth(登录/注册/权限)方案
78
+
79
+ 本项目的 CMS 鉴权支持:
80
+
81
+ - 首次进入需要注册管理员
82
+ - 每个 `session_id` 仅一个管理员
83
+ - `session_id` 维度权限隔离
84
+ - Supabase Auth 邮箱重写(避免跨 session 冲突)
85
+ - 不依赖 `execute_sql`(需要的 registry 表由你手动创建)
86
+
87
+ 详见:`AUTH_SCHEME.md`
88
+
89
+ ### 第二步:创建 Hono 应用
90
+
91
+ ```typescript
92
+ import { Hono } from 'hono'
93
+ import { cors } from 'hono/cors'
94
+ import {
95
+ initializeSupabase,
96
+ initializeCmsSystem,
97
+ createCmsRoutes
98
+ } from '@wecode-team/cms-supabase-api'
99
+
100
+ const app = new Hono()
101
+
102
+ // CORS 配置
103
+ app.use('/*', cors())
104
+
105
+ // 初始化 Supabase
106
+ initializeSupabase({
107
+ url: process.env.SUPABASE_URL!,
108
+ key: process.env.SUPABASE_ANON_KEY!
109
+ })
110
+
111
+ // 初始化 CMS 系统表
112
+ await initializeCmsSystem()
113
+
114
+ // 一键创建所有 CMS 路由
115
+ createCmsRoutes(app)
116
+
117
+ export default app
118
+ ```
119
+
120
+ ### 第三步:定义数据模型
121
+
122
+ 通过 API 创建模型:
123
+
124
+ ```typescript
125
+ // POST /models
126
+ {
127
+ "name": "文章模型",
128
+ "table_name": "posts",
129
+ "json_schema": {
130
+ "fields": [
131
+ {
132
+ "name": "title",
133
+ "type": "string",
134
+ "comment": "文章标题",
135
+ "required": true,
136
+ "maxLength": 255
137
+ },
138
+ {
139
+ "name": "content",
140
+ "type": "text",
141
+ "comment": "文章内容",
142
+ "required": true
143
+ },
144
+ {
145
+ "name": "author",
146
+ "type": "relation",
147
+ "comment": "作者",
148
+ "required": true,
149
+ "relation": {
150
+ "type": "belongsTo",
151
+ "target": "users",
152
+ "foreignKey": "author_id",
153
+ "displayField": "name"
154
+ }
155
+ },
156
+ {
157
+ "name": "status",
158
+ "type": "string",
159
+ "comment": "状态",
160
+ "defaultValue": "draft"
161
+ }
162
+ ]
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## 📚 API 路由
168
+
169
+ ### 模型管理
170
+
171
+ | 方法 | 路径 | 描述 |
172
+ |------|------|------|
173
+ | GET | `/models` | 获取所有模型 |
174
+ | POST | `/models` | 创建新模型(同时创建数据表) |
175
+ | PUT | `/models` | 更新模型 |
176
+ | DELETE | `/models?id={id}` | 删除模型(同时删除数据表) |
177
+
178
+ ### 数据管理
179
+
180
+ | 方法 | 路径 | 描述 |
181
+ |------|------|------|
182
+ | GET | `/data/:tableName` | 获取表数据(支持分页、搜索) |
183
+ | GET | `/data/:tableName/with-relations` | 获取带关联数据的表数据 |
184
+ | POST | `/data/:tableName` | 创建新数据 |
185
+ | PUT | `/data/:tableName` | 更新数据 |
186
+ | DELETE | `/data/:tableName?id={id}` | 删除数据 |
187
+
188
+ ### 关联数据
189
+
190
+ | 方法 | 路径 | 描述 |
191
+ |------|------|------|
192
+ | GET | `/relation/:tableName/options` | 获取关联表选项(用于下拉选择) |
193
+
194
+ ### 认证
195
+
196
+ | 方法 | 路径 | 描述 |
197
+ |------|------|------|
198
+ | GET | `/auth/signup/status/:tableName` | 查询当前 session 是否允许首次注册管理员 |
199
+ | POST | `/auth/signup/:tableName` | 首次注册管理员(服务端代理 Supabase signUp) |
200
+ | POST | `/auth/:tableName/login` | 用户登录 |
201
+ | GET | `/auth/:tableName/current` | 获取当前用户信息 |
202
+ | POST | `/auth/:tableName/verify` | 验证 Token |
203
+ | GET | `/auth/user/:tableName` | 获取当前用户信息(兼容历史路径) |
204
+ | POST | `/auth/verify/:tableName` | 验证 Token(兼容历史路径) |
205
+
206
+ > 提示:后台管理接口(`/models`、`/data/*` 等)建议携带请求头 `X-Session-Id`,用于按 `session_id` 维度校验管理员权限。
207
+
208
+ ## 🏗️ 字段类型
209
+
210
+ | 类型 | PostgreSQL 类型 | 说明 |
211
+ |------|-----------------|------|
212
+ | `string` | `text` | 短文本 |
213
+ | `text` | `text` | 长文本 |
214
+ | `integer` | `int4` | 整数 |
215
+ | `float` | `float8` | 浮点数 |
216
+ | `boolean` | `bool` | 布尔值 |
217
+ | `date` | `date` | 日期 |
218
+ | `datetime` | `timestamptz` | 日期时间 |
219
+ | `json` | `jsonb` | JSON 对象 |
220
+ | `email` | `text` | 邮箱(前端验证) |
221
+ | `relation` | `uuid/int4` | 关联字段(外键) |
222
+
223
+ ## 🔗 关系配置
224
+
225
+ ### belongsTo(多对一)
226
+
227
+ ```typescript
228
+ {
229
+ "name": "author",
230
+ "type": "relation",
231
+ "relation": {
232
+ "type": "belongsTo",
233
+ "target": "users", // 关联的目标表
234
+ "foreignKey": "author_id", // 当前表的外键字段
235
+ "displayField": "name" // 下拉显示的字段
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### hasMany(一对多)
241
+
242
+ ```typescript
243
+ {
244
+ "name": "posts",
245
+ "type": "relation",
246
+ "relation": {
247
+ "type": "hasMany",
248
+ "target": "posts",
249
+ "foreignKey": "author_id" // 目标表的外键字段
250
+ }
251
+ }
252
+ ```
253
+
254
+ ## 🔐 多租户支持(Session ID)
255
+
256
+ 通过表名前缀实现数据隔离:
257
+
258
+ ```typescript
259
+ // 前端设置 Session ID
260
+ setSessionId("tenant123")
261
+
262
+ // API 调用时自动添加前缀
263
+ // GET /data/posts → 实际查询 tenant123_posts 表
264
+ ```
265
+
266
+ ## 🛠️ 高级用法
267
+
268
+ ### 自定义路由
269
+
270
+ ```typescript
271
+ import { Hono } from 'hono'
272
+ import {
273
+ createModelRoute,
274
+ createDynamicDataRoute,
275
+ createAuthRoute
276
+ } from '@wecode-team/cms-supabase-api'
277
+
278
+ const app = new Hono()
279
+
280
+ // 只创建模型路由
281
+ createModelRoute(app)
282
+
283
+ // 只创建数据路由
284
+ createDynamicDataRoute(app)
285
+
286
+ // 创建固定表名的认证路由
287
+ createAuthRoute(app, 'cms_users')
288
+ ```
289
+
290
+ ### 使用认证中间件
291
+
292
+ ```typescript
293
+ import { requireAuth, getModels } from '@wecode-team/cms-supabase-api'
294
+
295
+ // 保护路由
296
+ app.get('/protected/models', requireAuth(getModels))
297
+ ```
298
+
299
+ ### 直接使用服务层
300
+
301
+ ```typescript
302
+ import {
303
+ getDynamicTableService,
304
+ getCmsModelService
305
+ } from '@wecode-team/cms-supabase-api'
306
+
307
+ const tableService = getDynamicTableService()
308
+ const modelService = getCmsModelService()
309
+
310
+ // 创建表
311
+ await tableService.createTable('my_table', {
312
+ fields: [
313
+ { name: 'title', type: 'string', required: true }
314
+ ]
315
+ })
316
+
317
+ // 插入数据
318
+ await tableService.insertData('my_table', { title: 'Hello' })
319
+ ```
320
+
321
+ ## 🔧 环境变量
322
+
323
+ | 变量 | 说明 | 默认值 |
324
+ |------|------|--------|
325
+ | `SUPABASE_URL` | Supabase 项目 URL | - |
326
+ | `SUPABASE_ANON_KEY` | Supabase 匿名密钥 | - |
327
+ | `JWT_SECRET` | JWT 签名密钥 | `'your-secret-key-change-in-production'` |
328
+
329
+ ## 🐛 常见问题
330
+
331
+ ### 1. 外键约束错误
332
+
333
+ 如果遇到 `violates foreign key constraint` 错误,是因为数据库中的外键约束引用了错误的表。解决方法:
334
+
335
+ ```sql
336
+ -- 删除错误的外键约束
337
+ ALTER TABLE "your_table" DROP CONSTRAINT IF EXISTS "constraint_name";
338
+ ```
339
+
340
+ ### 2. CHECK 约束错误
341
+
342
+ 如果遇到 `violates check constraint` 错误:
343
+
344
+ ```sql
345
+ -- 删除 CHECK 约束
346
+ ALTER TABLE "your_table" DROP CONSTRAINT IF EXISTS "table_status_check";
347
+ ```
348
+
349
+ ### 3. 函数不存在错误
350
+
351
+ 如果遇到 `Could not find the function` 错误,需要执行初始化 SQL:
352
+
353
+ ```sql
354
+ -- 执行 supabase-setup.sql 中的所有语句
355
+ ```
356
+
357
+ ## 📄 许可证
358
+
359
+ MIT
@@ -0,0 +1,267 @@
1
+ # 使用示例
2
+
3
+ ## 基础示例
4
+
5
+ ### Cloudflare Workers
6
+
7
+ ```typescript
8
+ // worker.ts
9
+ import { Hono } from 'hono'
10
+ import {
11
+ initializeSupabase,
12
+ createCmsRoutes,
13
+ testConnection
14
+ } from '@wecode-team/cms-supabase-api'
15
+
16
+ const app = new Hono()
17
+
18
+ // 初始化 Supabase
19
+ initializeSupabase({
20
+ url: 'https://your-project.supabase.co',
21
+ key: 'your-anon-key'
22
+ })
23
+
24
+ // 测试连接
25
+ await testConnection()
26
+
27
+ // 创建所有 CMS 路由
28
+ createCmsRoutes(app)
29
+
30
+ // 添加自定义路由
31
+ app.get('/', (c) => {
32
+ return c.json({ message: 'CMS API is running' })
33
+ })
34
+
35
+ export default {
36
+ fetch: app.fetch,
37
+ }
38
+ ```
39
+
40
+ ### Node.js (使用 @hono/node-server)
41
+
42
+ ```typescript
43
+ // server.ts
44
+ import { serve } from '@hono/node-server'
45
+ import { Hono } from 'hono'
46
+ import {
47
+ initializeSupabase,
48
+ createCmsRoutes
49
+ } from '@wecode-team/cms-supabase-api'
50
+
51
+ const app = new Hono()
52
+
53
+ // 初始化
54
+ initializeSupabase({
55
+ url: process.env.SUPABASE_URL!,
56
+ key: process.env.SUPABASE_ANON_KEY!
57
+ })
58
+
59
+ // 添加路由
60
+ createCmsRoutes(app)
61
+
62
+ // 启动服务器
63
+ const port = 3000
64
+ console.log(`Server is running on port ${port}`)
65
+
66
+ serve({
67
+ fetch: app.fetch,
68
+ port,
69
+ })
70
+ ```
71
+
72
+ ### Bun
73
+
74
+ ```typescript
75
+ // server.ts
76
+ import { Hono } from 'hono'
77
+ import {
78
+ initializeSupabase,
79
+ createCmsRoutes
80
+ } from '@wecode-team/cms-supabase-api'
81
+
82
+ const app = new Hono()
83
+
84
+ initializeSupabase({
85
+ url: Bun.env.SUPABASE_URL!,
86
+ key: Bun.env.SUPABASE_ANON_KEY!
87
+ })
88
+
89
+ createCmsRoutes(app)
90
+
91
+ export default {
92
+ port: 3000,
93
+ fetch: app.fetch,
94
+ }
95
+ ```
96
+
97
+ ## 高级示例
98
+
99
+ ### 自定义路由和中间件
100
+
101
+ ```typescript
102
+ import { Hono } from 'hono'
103
+ import { cors } from 'hono/cors'
104
+ import {
105
+ initializeSupabase,
106
+ createModelRoute,
107
+ createDynamicDataRoute,
108
+ requireAuth,
109
+ getModels
110
+ } from '@wecode-team/cms-supabase-api'
111
+
112
+ const app = new Hono()
113
+
114
+ // 添加 CORS
115
+ app.use('*', cors())
116
+
117
+ // 初始化
118
+ initializeSupabase({
119
+ url: process.env.SUPABASE_URL!,
120
+ key: process.env.SUPABASE_ANON_KEY!
121
+ })
122
+
123
+ // 公开的模型路由
124
+ createModelRoute(app)
125
+
126
+ // 需要认证的数据路由
127
+ app.use('/api/data/*', requireAuth(async (c) => {
128
+ // 继续处理请求
129
+ return c.next()
130
+ }))
131
+
132
+ createDynamicDataRoute(app)
133
+
134
+ // 自定义路由
135
+ app.get('/health', (c) => {
136
+ return c.json({ status: 'ok' })
137
+ })
138
+
139
+ export default app
140
+ ```
141
+
142
+ ### 使用固定表名的认证
143
+
144
+ ```typescript
145
+ import { Hono } from 'hono'
146
+ import {
147
+ initializeSupabase,
148
+ createAuthRoute
149
+ } from '@wecode-team/cms-supabase-api'
150
+
151
+ const app = new Hono()
152
+
153
+ initializeSupabase({
154
+ url: process.env.SUPABASE_URL!,
155
+ key: process.env.SUPABASE_ANON_KEY!
156
+ })
157
+
158
+ // 使用固定的用户表名
159
+ createAuthRoute(app, 'cms_users')
160
+
161
+ export default app
162
+ ```
163
+
164
+ ### 直接使用 Handlers
165
+
166
+ ```typescript
167
+ import { Hono } from 'hono'
168
+ import {
169
+ initializeSupabase,
170
+ getTableData,
171
+ createTableData,
172
+ login
173
+ } from '@wecode-team/cms-supabase-api'
174
+
175
+ const app = new Hono()
176
+
177
+ initializeSupabase({
178
+ url: process.env.SUPABASE_URL!,
179
+ key: process.env.SUPABASE_ANON_KEY!
180
+ })
181
+
182
+ // 自定义路由处理
183
+ app.get('/custom/:tableName', async (c) => {
184
+ const tableName = c.req.param('tableName')
185
+ return getTableData(c, tableName)
186
+ })
187
+
188
+ app.post('/custom/:tableName', async (c) => {
189
+ const tableName = c.req.param('tableName')
190
+ return createTableData(c, tableName)
191
+ })
192
+
193
+ app.post('/login/:tableName', async (c) => {
194
+ const tableName = c.req.param('tableName')
195
+ return login(c, tableName)
196
+ })
197
+
198
+ export default app
199
+ ```
200
+
201
+ ## API 调用示例
202
+
203
+ ### 创建模型
204
+
205
+ ```bash
206
+ curl -X POST http://localhost:3000/models \
207
+ -H "Content-Type: application/json" \
208
+ -d '{
209
+ "name": "文章",
210
+ "table_name": "articles",
211
+ "json_schema": {
212
+ "fields": [
213
+ {
214
+ "name": "title",
215
+ "type": "string",
216
+ "required": true
217
+ },
218
+ {
219
+ "name": "content",
220
+ "type": "text",
221
+ "required": true
222
+ }
223
+ ]
224
+ }
225
+ }'
226
+ ```
227
+
228
+ ### 获取数据
229
+
230
+ ```bash
231
+ # 获取第一页数据
232
+ curl http://localhost:3000/data/articles?page=1&limit=10
233
+
234
+ # 搜索数据
235
+ curl http://localhost:3000/data/articles?search=test&page=1&limit=10
236
+ ```
237
+
238
+ ### 创建数据
239
+
240
+ ```bash
241
+ curl -X POST http://localhost:3000/data/articles \
242
+ -H "Content-Type: application/json" \
243
+ -d '{
244
+ "title": "我的第一篇文章",
245
+ "content": "这是文章内容"
246
+ }'
247
+ ```
248
+
249
+ ### 用户登录
250
+
251
+ ```bash
252
+ curl -X POST http://localhost:3000/auth/cms_users/login \
253
+ -H "Content-Type: application/json" \
254
+ -d '{
255
+ "username": "admin",
256
+ "password": "password123"
257
+ }'
258
+ ```
259
+
260
+ ### 使用认证 token
261
+
262
+ ```bash
263
+ curl http://localhost:3000/auth/cms_users/current \
264
+ -H "Authorization: Bearer YOUR_JWT_TOKEN"
265
+ ```
266
+
267
+
@@ -0,0 +1,36 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { SupabaseConfig } from "../types";
3
+ /**
4
+ * 初始化Supabase客户端
5
+ */
6
+ export declare function initializeSupabase(config: SupabaseConfig): SupabaseClient;
7
+ /**
8
+ * 获取当前Supabase客户端实例
9
+ */
10
+ export declare function getSupabase(): SupabaseClient;
11
+ /**
12
+ * 测试Supabase连接
13
+ */
14
+ export declare function testConnection(): Promise<boolean>;
15
+ /**
16
+ * 自动执行 supabase-setup.sql 的内容
17
+ * 通过直接创建表和函数来避免手动执行 SQL 文件
18
+ */
19
+ export declare function executeSupabaseSetup(): Promise<boolean>;
20
+ /**
21
+ * 初始化CMS系统表
22
+ */
23
+ export declare function initializeCmsSystem(): Promise<boolean>;
24
+ /**
25
+ * 关闭Supabase连接(实际上Supabase客户端不需要显式关闭)
26
+ */
27
+ export declare function closeSupabase(): Promise<void>;
28
+ /**
29
+ * 获取完整的 Supabase 设置 SQL 脚本
30
+ * 用户可以复制此脚本到 Supabase SQL 编辑器中执行
31
+ */
32
+ export declare function getSupabaseSetupSQL(): string;
33
+ export declare const initializeDatabase: typeof initializeSupabase;
34
+ export declare const getDatabase: typeof getSupabase;
35
+ export declare const syncDatabase: typeof initializeCmsSystem;
36
+ export declare const closeDatabase: typeof closeSupabase;