feat: 规范化PocketBase数据库文档与原生API访问
- 将数据库文档拆分为按collection命名的标准文件,统一格式 - 补充tbl_company、tbl_system_dict等表的原生访问规则 - 新增users_tag、document_create等字段 - 优化用户资料更新接口,支持非必填字段 - 添加公司原生API测试脚本 - 归档本次变更至OpenSpec
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
### 1. tbl_system_dict (系统词典)
|
||||
**类型:** Base Collection
|
||||
**读写权限:** 所有人可读;仅 `ManagePlatform`/管理员可写
|
||||
|
||||
| 字段名 | 类型 | 备注 |
|
||||
| :--- | :--- | :--- |
|
||||
@@ -30,23 +31,30 @@
|
||||
|
||||
| 字段名 | 类型 | 备注 |
|
||||
| :--- | :--- | :--- |
|
||||
| company_id | text | 自定义公司id |
|
||||
| company_id | text | 公司业务id,由数据库自动生成,默认形如 `WX-COMPANY-时间串` |
|
||||
| company_name | text | 公司名称 |
|
||||
| company_type | text | 公司类型 |
|
||||
| company_entity | text | 公司法人 |
|
||||
| company_usci | text | 统一社会信用代码 |
|
||||
| company_nationality | text | 国家 |
|
||||
| company_nationality_code | text | 国家编码 |
|
||||
| company_province | text | 省份 |
|
||||
| company_province_code | text | 省份编码 |
|
||||
| company_city | text | 城市 |
|
||||
| company_city_code | text | 城市编码 |
|
||||
| company_district | text | 区/县 |
|
||||
| company_district_code | text | 区/县编码 |
|
||||
| company_postalcode | text | 邮编 |
|
||||
| company_add | text | 地址 |
|
||||
| company_status | text | 公司状态 |
|
||||
| company_level | text | 公司等级 |
|
||||
| company_owner_openid | text | 公司所有者 openid |
|
||||
| company_remark | text | 备注 |
|
||||
|
||||
**索引规划 (Indexes):**
|
||||
* `CREATE UNIQUE INDEX` 针对 `company_id` (确保业务主键唯一)
|
||||
* `CREATE INDEX` 针对 `company_usci` (加速信用代码检索)
|
||||
* `CREATE INDEX` 针对 `company_owner_openid` (加速按公司所有者 openid 查询)
|
||||
|
||||
---
|
||||
|
||||
@@ -78,6 +86,7 @@
|
||||
| users_wx_openid | text | 微信号 |
|
||||
| users_level | text | 用户等级 |
|
||||
| users_type | text | 用户类型 |
|
||||
| users_tag | text | 用户标签 |
|
||||
| users_status | text | 用户状态 |
|
||||
| company_id | text | 公司id (存储 tbl_company.company_id) |
|
||||
| users_parent_id | text | 用户父级id (存储 tbl_users.users_id) |
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"init:newpb": "node pocketbase.newpb.js",
|
||||
"init:documents": "node pocketbase.documents.js",
|
||||
"init:dictionary": "node pocketbase.dictionary.js",
|
||||
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.js"
|
||||
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.js",
|
||||
"test:company-native-api": "node test-tbl-company-native-api.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
@@ -23,6 +23,11 @@ const pb = new PocketBase(PB_URL);
|
||||
const collectionData = {
|
||||
name: 'tbl_system_dict',
|
||||
type: 'base',
|
||||
listRule: '',
|
||||
viewRule: '',
|
||||
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||
fields: [
|
||||
{ name: 'system_dict_id', type: 'text', required: true },
|
||||
{ name: 'dict_name', type: 'text', required: true },
|
||||
@@ -68,6 +73,11 @@ function buildCollectionPayload(target, existingCollection) {
|
||||
return {
|
||||
name: target.name,
|
||||
type: target.type,
|
||||
listRule: target.listRule,
|
||||
viewRule: target.viewRule,
|
||||
createRule: target.createRule,
|
||||
updateRule: target.updateRule,
|
||||
deleteRule: target.deleteRule,
|
||||
fields: target.fields.map((field) => normalizeFieldPayload(field, null)),
|
||||
indexes: target.indexes,
|
||||
};
|
||||
@@ -91,6 +101,11 @@ function buildCollectionPayload(target, existingCollection) {
|
||||
return {
|
||||
name: target.name,
|
||||
type: target.type,
|
||||
listRule: target.listRule,
|
||||
viewRule: target.viewRule,
|
||||
createRule: target.createRule,
|
||||
updateRule: target.updateRule,
|
||||
deleteRule: target.deleteRule,
|
||||
fields: fields,
|
||||
indexes: target.indexes,
|
||||
};
|
||||
|
||||
@@ -47,8 +47,11 @@ const collections = [
|
||||
{
|
||||
name: 'tbl_document',
|
||||
type: 'base',
|
||||
listRule: '',
|
||||
viewRule: '',
|
||||
fields: [
|
||||
{ name: 'document_id', type: 'text', required: true },
|
||||
{ name: 'document_create', type: 'autodate', onCreate: true, onUpdate: false },
|
||||
{ name: 'document_effect_date', type: 'date' },
|
||||
{ name: 'document_expiry_date', type: 'date' },
|
||||
{ name: 'document_type', type: 'text', required: true },
|
||||
@@ -77,6 +80,7 @@ const collections = [
|
||||
],
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX idx_tbl_document_document_id ON tbl_document (document_id)',
|
||||
'CREATE INDEX idx_tbl_document_document_create ON tbl_document (document_create)',
|
||||
'CREATE INDEX idx_tbl_document_document_owner ON tbl_document (document_owner)',
|
||||
'CREATE INDEX idx_tbl_document_document_type ON tbl_document (document_type)',
|
||||
'CREATE INDEX idx_tbl_document_document_status ON tbl_document (document_status)',
|
||||
@@ -130,6 +134,11 @@ function normalizeFieldPayload(field, existingField) {
|
||||
payload.mimeTypes = Array.isArray(field.mimeTypes) && field.mimeTypes.length ? field.mimeTypes : null;
|
||||
}
|
||||
|
||||
if (field.type === 'autodate') {
|
||||
payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
|
||||
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ const collections = [
|
||||
{
|
||||
name: 'tbl_system_dict',
|
||||
type: 'base',
|
||||
listRule: '',
|
||||
viewRule: '',
|
||||
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||
fields: [
|
||||
{ name: 'system_dict_id', type: 'text', required: true },
|
||||
{ name: 'dict_name', type: 'text', required: true },
|
||||
@@ -33,23 +38,30 @@ const collections = [
|
||||
name: 'tbl_company',
|
||||
type: 'base',
|
||||
fields: [
|
||||
{ name: 'company_id', type: 'text', required: true },
|
||||
{ name: 'company_id', type: 'text', required: true, autogeneratePattern: 'WX-COMPANY-[0-9]{13}' },
|
||||
{ name: 'company_name', type: 'text' },
|
||||
{ name: 'company_type', type: 'text' },
|
||||
{ name: 'company_entity', type: 'text' },
|
||||
{ name: 'company_usci', type: 'text' },
|
||||
{ name: 'company_nationality', type: 'text' },
|
||||
{ name: 'company_nationality_code', type: 'text' },
|
||||
{ name: 'company_province', type: 'text' },
|
||||
{ name: 'company_province_code', type: 'text' },
|
||||
{ name: 'company_city', type: 'text' },
|
||||
{ name: 'company_city_code', type: 'text' },
|
||||
{ name: 'company_district', type: 'text' },
|
||||
{ name: 'company_district_code', type: 'text' },
|
||||
{ name: 'company_postalcode', type: 'text' },
|
||||
{ name: 'company_add', type: 'text' },
|
||||
{ name: 'company_status', type: 'text' },
|
||||
{ name: 'company_level', type: 'text' },
|
||||
{ name: 'company_owner_openid', type: 'text' },
|
||||
{ name: 'company_remark', type: 'text' }
|
||||
],
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)',
|
||||
'CREATE INDEX idx_company_usci ON tbl_company (company_usci)'
|
||||
'CREATE INDEX idx_company_usci ON tbl_company (company_usci)',
|
||||
'CREATE INDEX idx_company_owner_openid ON tbl_company (company_owner_openid)'
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -122,6 +134,11 @@ async function createOrUpdateCollection(collectionData) {
|
||||
const payload = {
|
||||
name: collectionData.name,
|
||||
type: collectionData.type,
|
||||
listRule: collectionData.listRule,
|
||||
viewRule: collectionData.viewRule,
|
||||
createRule: collectionData.createRule,
|
||||
updateRule: collectionData.updateRule,
|
||||
deleteRule: collectionData.deleteRule,
|
||||
fields: collectionData.fields,
|
||||
indexes: collectionData.indexes
|
||||
};
|
||||
|
||||
@@ -52,6 +52,7 @@ const collections = [
|
||||
{ name: 'users_phone', type: 'text' },
|
||||
{ name: 'users_level', type: 'text' },
|
||||
{ name: 'users_type', type: 'text' },
|
||||
{ name: 'users_tag', type: 'text' },
|
||||
{ name: 'users_status', type: 'text' },
|
||||
{ name: 'company_id', type: 'text' },
|
||||
{ name: 'users_parent_id', type: 'text' },
|
||||
|
||||
125
script/test-tbl-company-native-api.js
Normal file
125
script/test-tbl-company-native-api.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import fs from 'node:fs/promises'
|
||||
|
||||
const runtimePath = new URL('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js', import.meta.url)
|
||||
const runtimeText = await fs.readFile(runtimePath, 'utf8')
|
||||
|
||||
function readRuntimeValue(name) {
|
||||
const match = runtimeText.match(new RegExp(`${name}:\\s*'([^']*)'`))
|
||||
if (!match) {
|
||||
throw new Error(`未在 runtime.js 中找到 ${name}`)
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
const baseUrl = readRuntimeValue('APP_BASE_URL')
|
||||
const adminToken = readRuntimeValue('POCKETBASE_AUTH_TOKEN')
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function requestJson(path, options = {}) {
|
||||
const res = await fetch(`${baseUrl}${path}`, options)
|
||||
const text = await res.text()
|
||||
let json = null
|
||||
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null
|
||||
} catch {
|
||||
throw new Error(`接口 ${path} 返回了非 JSON 响应:${text}`)
|
||||
}
|
||||
|
||||
return { res, json }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const knownCompanyId = 'WX-COMPANY-10001'
|
||||
const initialOwnerOpenid = 'wx-owner-create'
|
||||
const updatedOwnerOpenid = 'wx-owner-updated'
|
||||
|
||||
const createResult = await requestJson('/pb/api/collections/tbl_company/records', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
company_name: '原生接口测试公司',
|
||||
company_nationality: '中国',
|
||||
company_nationality_code: 'CN',
|
||||
company_province: '上海',
|
||||
company_province_code: '310000',
|
||||
company_city: '上海',
|
||||
company_city_code: '310100',
|
||||
company_district: '浦东新区',
|
||||
company_district_code: '310115',
|
||||
company_status: '有效',
|
||||
company_owner_openid: initialOwnerOpenid,
|
||||
}),
|
||||
})
|
||||
|
||||
assert(createResult.res.ok, `公开创建失败:${JSON.stringify(createResult.json)}`)
|
||||
assert(typeof createResult.json?.company_id === 'string' && createResult.json.company_id.startsWith('WX-COMPANY-'), '公开创建未自动生成 company_id')
|
||||
assert(createResult.json?.company_owner_openid === initialOwnerOpenid, '公开创建返回的 company_owner_openid 不匹配')
|
||||
assert(createResult.json?.company_city_code === '310100', '公开创建返回的 company_city_code 不匹配')
|
||||
|
||||
const byCompanyIdResult = await requestJson(
|
||||
`/pb/api/collections/tbl_company/records?filter=${encodeURIComponent(`company_id="${knownCompanyId}"`)}&perPage=1&page=1`
|
||||
)
|
||||
|
||||
assert(byCompanyIdResult.res.ok, `按 company_id 查询失败:${JSON.stringify(byCompanyIdResult.json)}`)
|
||||
assert(byCompanyIdResult.json?.totalItems === 1, `按 company_id 查询结果数量不为 1:${JSON.stringify(byCompanyIdResult.json)}`)
|
||||
assert(byCompanyIdResult.json?.items?.[0]?.company_id === knownCompanyId, '按 company_id 查询返回了错误的记录')
|
||||
|
||||
const listResult = await requestJson('/pb/api/collections/tbl_company/records?perPage=5&page=1')
|
||||
assert(listResult.res.ok, `公开列表查询失败:${JSON.stringify(listResult.json)}`)
|
||||
assert((listResult.json?.totalItems || 0) >= 1, '公开列表查询未返回任何数据')
|
||||
|
||||
const createdCompanyId = createResult.json.company_id
|
||||
const createdLookupResult = await requestJson(
|
||||
`/pb/api/collections/tbl_company/records?filter=${encodeURIComponent(`company_id="${createdCompanyId}"`)}&perPage=1&page=1`
|
||||
)
|
||||
assert(createdLookupResult.res.ok, `按 company_id 查询测试记录失败:${JSON.stringify(createdLookupResult.json)}`)
|
||||
assert(createdLookupResult.json?.totalItems === 1, `按 company_id 查询测试记录数量不为 1:${JSON.stringify(createdLookupResult.json)}`)
|
||||
|
||||
const createdRecordId = createdLookupResult.json.items[0].id
|
||||
const updateResult = await requestJson(`/pb/api/collections/tbl_company/records/${createdRecordId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${adminToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
company_name: '原生接口测试公司-已更新',
|
||||
company_owner_openid: updatedOwnerOpenid,
|
||||
company_district: '徐汇区',
|
||||
company_district_code: '310104',
|
||||
}),
|
||||
})
|
||||
assert(updateResult.res.ok, `按 company_id 定位后更新失败:${JSON.stringify(updateResult.json)}`)
|
||||
assert(updateResult.json?.company_name === '原生接口测试公司-已更新', '更新后的 company_name 不正确')
|
||||
assert(updateResult.json?.company_owner_openid === updatedOwnerOpenid, '更新后的 company_owner_openid 不正确')
|
||||
assert(updateResult.json?.company_district_code === '310104', '更新后的 company_district_code 不正确')
|
||||
|
||||
const cleanupResult = await requestJson(`/pb/api/collections/tbl_company/records/${createResult.json.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
assert(cleanupResult.res.ok, `测试清理失败:${JSON.stringify(cleanupResult.json)}`)
|
||||
|
||||
console.log(JSON.stringify({
|
||||
createdCompanyId: createdCompanyId,
|
||||
queriedCompanyId: byCompanyIdResult.json.items[0].company_id,
|
||||
publicListTotalItems: listResult.json.totalItems,
|
||||
updatedRecordId: createdRecordId,
|
||||
updatedOwnerOpenid: updateResult.json.company_owner_openid,
|
||||
updatedDistrictCode: updateResult.json.company_district_code,
|
||||
cleanupDeletedId: createResult.json.id,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
await main()
|
||||
Reference in New Issue
Block a user