feat: CRUD definitions, build script, DDL generation
- 3 CRUD JSON files: customer_balance, accounting_records, sync_state - Build script with model validation, CRUD validation, DDL generation - DDL: db/schema.sql (72 lines, 7 tables) - Scripts: validate_models.py, validate_crud.py, generate_ddl.py
This commit is contained in:
parent
5936a2f328
commit
acb9674375
21
build.sh
21
build.sh
@ -1,3 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# SageAPI build script
|
||||||
|
set -e
|
||||||
|
|
||||||
xls2ui -m ../models -o ../wwwroot sageapi *.json
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
echo "=== SageAPI Build ==="
|
||||||
|
|
||||||
|
# 1. Validate JSON models
|
||||||
|
echo "[1/3] Validating model definitions..."
|
||||||
|
python3 scripts/validate_models.py
|
||||||
|
|
||||||
|
# 2. Validate CRUD JSON
|
||||||
|
echo "[2/3] Validating CRUD definitions..."
|
||||||
|
python3 scripts/validate_crud.py
|
||||||
|
|
||||||
|
# 3. Generate DDL
|
||||||
|
echo "[3/3] Generating DDL..."
|
||||||
|
python3 scripts/generate_ddl.py
|
||||||
|
|
||||||
|
echo "=== Build Complete ==="
|
||||||
|
|||||||
152
db/schema.sql
Normal file
152
db/schema.sql
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
-- SageAPI DDL (auto-generated)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `accounting_records` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键',
|
||||||
|
`customer_id` VARCHAR(32) NOT NULL DEFAULT COMMENT '客户ID',
|
||||||
|
`llmid` VARCHAR(32) NULL DEFAULT COMMENT '模型ID',
|
||||||
|
`model_name` VARCHAR(128) NULL DEFAULT COMMENT '模型名称',
|
||||||
|
`pricing_id` VARCHAR(32) NULL DEFAULT COMMENT '定价ID',
|
||||||
|
`input_tokens` BIGINT NULL COMMENT '输入token数',
|
||||||
|
`output_tokens` BIGINT NULL COMMENT '输出token数',
|
||||||
|
`total_tokens` BIGINT NULL COMMENT '总token数',
|
||||||
|
`quantity` DECIMAL(15,4) NULL COMMENT '用量(图片数/分钟数等)',
|
||||||
|
`amount` DECIMAL(15,6) NOT NULL DEFAULT 0.0 COMMENT '金额',
|
||||||
|
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币单位',
|
||||||
|
`request_id` VARCHAR(64) NULL DEFAULT COMMENT '请求ID(幂等键)',
|
||||||
|
`transno` VARCHAR(64) NULL DEFAULT COMMENT '事务号',
|
||||||
|
`status` VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/accounted/failed',
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `accounting_records` ADD INDEX `idx_customer_id` (`customer_id`);
|
||||||
|
|
||||||
|
ALTER TABLE `accounting_records` ADD INDEX `idx_llmid` (`llmid`);
|
||||||
|
|
||||||
|
ALTER TABLE `accounting_records` ADD UNIQUE `idx_request_id` (`request_id`);
|
||||||
|
|
||||||
|
ALTER TABLE `accounting_records` ADD INDEX `idx_status` (`status`);
|
||||||
|
|
||||||
|
ALTER TABLE `accounting_records` ADD INDEX `idx_created_at` (`created_at`);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `customer_balance` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键,即 customer_id',
|
||||||
|
`balance` DECIMAL(15,4) NOT NULL DEFAULT 0.0 COMMENT '当前余额',
|
||||||
|
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币单位',
|
||||||
|
`credit_limit` DECIMAL(15,4) NULL COMMENT '信用额度',
|
||||||
|
`last_recharge` DATETIME NULL COMMENT '最后充值时间',
|
||||||
|
`last_consumption` DATETIME NULL COMMENT '最后消费时间',
|
||||||
|
`status` VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态: active/suspended/arrears',
|
||||||
|
`sync_version` VARCHAR(32) NULL DEFAULT COMMENT '同步版本号',
|
||||||
|
`cached_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缓存更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `customer_balance` ADD INDEX `idx_status` (`status`);
|
||||||
|
|
||||||
|
ALTER TABLE `customer_balance` ADD INDEX `idx_balance` (`balance`);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `llmage_cache` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键',
|
||||||
|
`llmid` VARCHAR(32) NOT NULL DEFAULT COMMENT '关联模型ID',
|
||||||
|
`model_name` VARCHAR(128) NULL DEFAULT COMMENT '模型名称',
|
||||||
|
`upappid` VARCHAR(32) NOT NULL DEFAULT COMMENT '上游应用ID',
|
||||||
|
`apiname` VARCHAR(128) NOT NULL DEFAULT COMMENT 'API名称',
|
||||||
|
`api_url` VARCHAR(512) NULL DEFAULT COMMENT 'API端点URL',
|
||||||
|
`api_params` TEXT NULL COMMENT 'API参数配置JSON',
|
||||||
|
`model_params` TEXT NULL COMMENT '模型参数配置JSON(max_tokens, temperature等)',
|
||||||
|
`status` VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态: active/inactive',
|
||||||
|
`sync_version` VARCHAR(32) NULL DEFAULT COMMENT '同步版本号',
|
||||||
|
`cached_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缓存写入时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `llmage_cache` ADD INDEX `idx_llmid` (`llmid`);
|
||||||
|
|
||||||
|
ALTER TABLE `llmage_cache` ADD INDEX `idx_upappid` (`upappid`);
|
||||||
|
|
||||||
|
ALTER TABLE `llmage_cache` ADD INDEX `idx_apiname` (`apiname`);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `pricing_cache` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键,对应 pricing_program id (ppid)',
|
||||||
|
`llmid` VARCHAR(32) NOT NULL DEFAULT COMMENT '关联模型ID',
|
||||||
|
`model_name` VARCHAR(128) NULL DEFAULT COMMENT '模型名称',
|
||||||
|
`pricing_type` VARCHAR(32) NOT NULL DEFAULT COMMENT '计费类型: token/image/video/audio',
|
||||||
|
`input_price` DECIMAL(10,6) NULL COMMENT '输入单价(每千token)',
|
||||||
|
`output_price` DECIMAL(10,6) NULL COMMENT '输出单价(每千token)',
|
||||||
|
`unit_price` DECIMAL(10,6) NULL COMMENT '统一单价(按次/按图/按分钟等)',
|
||||||
|
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币单位',
|
||||||
|
`status` VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态: active/inactive/deprecated',
|
||||||
|
`effective_from` DATETIME NULL COMMENT '生效时间',
|
||||||
|
`effective_to` DATETIME NULL COMMENT '失效时间',
|
||||||
|
`sync_version` VARCHAR(32) NULL DEFAULT COMMENT '同步版本号',
|
||||||
|
`cached_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缓存写入时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `pricing_cache` ADD INDEX `idx_llmid` (`llmid`);
|
||||||
|
|
||||||
|
ALTER TABLE `pricing_cache` ADD INDEX `idx_pricing_type` (`pricing_type`);
|
||||||
|
|
||||||
|
ALTER TABLE `pricing_cache` ADD INDEX `idx_status` (`status`);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `sync_state` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键',
|
||||||
|
`entity_type` VARCHAR(32) NOT NULL DEFAULT COMMENT '实体类型: users/pricing/llmage/uapi',
|
||||||
|
`entity_id` VARCHAR(64) NULL DEFAULT COMMENT '实体标识(全量同步时为空)',
|
||||||
|
`last_sync_time` DATETIME NULL COMMENT '最后同步时间',
|
||||||
|
`sync_version` VARCHAR(32) NULL DEFAULT COMMENT 'Sage返回的版本标识',
|
||||||
|
`sync_status` VARCHAR(16) NOT NULL DEFAULT 'success' COMMENT '同步状态: success/pending/failed',
|
||||||
|
`error_msg` TEXT NULL COMMENT '失败原因',
|
||||||
|
`retry_count` INT NOT NULL DEFAULT 0 COMMENT '重试次数',
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `sync_state` ADD INDEX `idx_entity_type` (`entity_type`);
|
||||||
|
|
||||||
|
ALTER TABLE `sync_state` ADD UNIQUE `idx_entity_type_id` (`entity_type`,`entity_id`);
|
||||||
|
|
||||||
|
ALTER TABLE `sync_state` ADD INDEX `idx_sync_status` (`sync_status`);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `uapi_cache` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键',
|
||||||
|
`upappid` VARCHAR(32) NOT NULL DEFAULT COMMENT '上游应用ID',
|
||||||
|
`apiname` VARCHAR(128) NOT NULL DEFAULT COMMENT 'API名称',
|
||||||
|
`method` VARCHAR(16) NULL DEFAULT 'POST' COMMENT 'HTTP方法',
|
||||||
|
`endpoint` VARCHAR(512) NULL DEFAULT COMMENT 'API端点',
|
||||||
|
`auth_type` VARCHAR(32) NULL DEFAULT 'bearer' COMMENT '认证类型',
|
||||||
|
`rate_limit` INT NULL COMMENT '速率限制(次/分钟)',
|
||||||
|
`description` TEXT NULL COMMENT 'API描述',
|
||||||
|
`status` VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态',
|
||||||
|
`sync_version` VARCHAR(32) NULL DEFAULT COMMENT '同步版本号',
|
||||||
|
`cached_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缓存写入时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `uapi_cache` ADD UNIQUE `idx_upappid_apiname` (`upappid`,`apiname`);
|
||||||
|
|
||||||
|
ALTER TABLE `uapi_cache` ADD INDEX `idx_status` (`status`);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `users_cache` (
|
||||||
|
`id` VARCHAR(32) NOT NULL DEFAULT COMMENT '主键,对应 users 表 id',
|
||||||
|
`username` VARCHAR(128) NOT NULL DEFAULT COMMENT '用户名',
|
||||||
|
`orgid` VARCHAR(32) NULL DEFAULT COMMENT '组织ID',
|
||||||
|
`orgname` VARCHAR(255) NULL DEFAULT COMMENT '组织名称',
|
||||||
|
`email` VARCHAR(128) NULL DEFAULT COMMENT '邮箱',
|
||||||
|
`phone` VARCHAR(32) NULL DEFAULT COMMENT '手机号',
|
||||||
|
`status` VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态: active/inactive/suspended',
|
||||||
|
`created_at` DATETIME NULL COMMENT '创建时间',
|
||||||
|
`updated_at` DATETIME NULL COMMENT '更新时间',
|
||||||
|
`sync_version` VARCHAR(32) NULL DEFAULT COMMENT '同步版本号',
|
||||||
|
`cached_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缓存写入时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE `users_cache` ADD INDEX `idx_username` (`username`);
|
||||||
|
|
||||||
|
ALTER TABLE `users_cache` ADD INDEX `idx_orgid` (`orgid`);
|
||||||
|
|
||||||
|
ALTER TABLE `users_cache` ADD INDEX `idx_sync_version` (`sync_version`);
|
||||||
41
json/accounting_records.json
Normal file
41
json/accounting_records.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"accounting_records": {
|
||||||
|
"params": {
|
||||||
|
"title": "记账记录管理",
|
||||||
|
"dbname": "sageapi",
|
||||||
|
"page_size": 20
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{"name": "id", "title": "记录ID", "type": "string", "readonly": true, "width": 120},
|
||||||
|
{"name": "customer_id", "title": "客户ID", "type": "string", "width": 120},
|
||||||
|
{"name": "llmid", "title": "模型ID", "type": "string", "width": 120},
|
||||||
|
{"name": "model_name", "title": "模型名称", "type": "string", "width": 140},
|
||||||
|
{"name": "pricing_id", "title": "定价ID", "type": "string", "width": 100},
|
||||||
|
{"name": "input_tokens", "title": "输入Token", "type": "int", "width": 90, "align": "right"},
|
||||||
|
{"name": "output_tokens", "title": "输出Token", "type": "int", "width": 90, "align": "right"},
|
||||||
|
{"name": "total_tokens", "title": "总Token", "type": "int", "width": 90, "align": "right"},
|
||||||
|
{"name": "quantity", "title": "用量", "type": "float", "width": 80, "align": "right"},
|
||||||
|
{"name": "amount", "title": "金额", "type": "float", "width": 90, "align": "right"},
|
||||||
|
{"name": "currency", "title": "货币", "type": "string", "width": 60},
|
||||||
|
{"name": "request_id", "title": "请求ID", "type": "string", "width": 120},
|
||||||
|
{"name": "transno", "title": "事务号", "type": "string", "width": 120},
|
||||||
|
{"name": "status", "title": "状态", "type": "select", "width": 80, "options": [{"value": "pending", "label": "待处理"}, {"value": "accounted", "label": "已记账"}, {"value": "failed", "label": "失败"}]},
|
||||||
|
{"name": "created_at", "title": "创建时间", "type": "datetime", "width": 160, "readonly": true},
|
||||||
|
{"name": "updated_at", "title": "更新时间", "type": "datetime", "width": 160, "readonly": true}
|
||||||
|
],
|
||||||
|
"list": {
|
||||||
|
"columns": ["id", "customer_id", "model_name", "amount", "total_tokens", "status", "created_at"],
|
||||||
|
"filters": [
|
||||||
|
{"field": "customer_id", "type": "text", "label": "客户ID"},
|
||||||
|
{"field": "status", "type": "select", "label": "状态", "options": [{"value": "", "label": "全部"}, {"value": "pending", "label": "待处理"}, {"value": "accounted", "label": "已记账"}, {"value": "failed", "label": "失败"}]},
|
||||||
|
{"field": "date_from", "type": "date", "label": "起始日期"},
|
||||||
|
{"field": "date_to", "type": "date", "label": "截止日期"}
|
||||||
|
],
|
||||||
|
"sort": {"field": "created_at", "order": "desc"}
|
||||||
|
},
|
||||||
|
"view": {"fields": ["id", "customer_id", "llmid", "model_name", "pricing_id", "input_tokens", "output_tokens", "total_tokens", "quantity", "amount", "currency", "request_id", "transno", "status", "created_at", "updated_at"]},
|
||||||
|
"create": {"enabled": false},
|
||||||
|
"update": {"enabled": false},
|
||||||
|
"delete": {"enabled": false}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
json/customer_balance.json
Normal file
33
json/customer_balance.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"customer_balance": {
|
||||||
|
"params": {
|
||||||
|
"title": "客户余额管理",
|
||||||
|
"dbname": "sageapi",
|
||||||
|
"page_size": 20
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{"name": "id", "title": "客户ID", "type": "string", "readonly": true, "width": 120},
|
||||||
|
{"name": "balance", "title": "余额", "type": "float", "width": 100, "align": "right"},
|
||||||
|
{"name": "currency", "title": "货币", "type": "string", "width": 60},
|
||||||
|
{"name": "credit_limit", "title": "信用额度", "type": "float", "width": 100, "align": "right"},
|
||||||
|
{"name": "last_recharge", "title": "最后充值", "type": "datetime", "width": 160},
|
||||||
|
{"name": "last_consumption", "title": "最后消费", "type": "datetime", "width": 160},
|
||||||
|
{"name": "status", "title": "状态", "type": "select", "width": 80, "options": [{"value": "active", "label": "正常"}, {"value": "suspended", "label": "暂停"}, {"value": "arrears", "label": "欠费"}]},
|
||||||
|
{"name": "cached_at", "title": "缓存时间", "type": "datetime", "width": 160, "readonly": true}
|
||||||
|
],
|
||||||
|
"list": {
|
||||||
|
"columns": ["id", "balance", "currency", "status", "last_recharge", "cached_at"],
|
||||||
|
"filters": [
|
||||||
|
{"field": "id", "type": "text", "label": "客户ID"},
|
||||||
|
{"field": "status", "type": "select", "label": "状态", "options": [{"value": "", "label": "全部"}, {"value": "active", "label": "正常"}, {"value": "suspended", "label": "暂停"}, {"value": "arrears", "label": "欠费"}]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"view": {"fields": ["id", "balance", "currency", "credit_limit", "last_recharge", "last_consumption", "status", "cached_at"]},
|
||||||
|
"create": {"enabled": false},
|
||||||
|
"update": {
|
||||||
|
"enabled": true,
|
||||||
|
"fields": ["balance", "credit_limit", "status"]
|
||||||
|
},
|
||||||
|
"delete": {"enabled": false}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
json/sync_state.json
Normal file
36
json/sync_state.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"sync_state": {
|
||||||
|
"params": {
|
||||||
|
"title": "同步状态管理",
|
||||||
|
"dbname": "sageapi",
|
||||||
|
"page_size": 20
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{"name": "id", "title": "ID", "type": "string", "readonly": true, "width": 100},
|
||||||
|
{"name": "entity_type", "title": "实体类型", "type": "select", "width": 100, "options": [{"value": "users", "label": "用户"}, {"value": "pricing", "label": "定价"}, {"value": "uapi", "label": "UAPI"}, {"value": "llmage", "label": "LLMage"}]},
|
||||||
|
{"name": "entity_id", "title": "实体ID", "type": "string", "width": 120},
|
||||||
|
{"name": "last_sync_time", "title": "最后同步时间", "type": "datetime", "width": 160},
|
||||||
|
{"name": "sync_version", "title": "同步版本", "type": "string", "width": 100},
|
||||||
|
{"name": "sync_status", "title": "同步状态", "type": "select", "width": 80, "options": [{"value": "success", "label": "成功"}, {"value": "pending", "label": "待同步"}, {"value": "failed", "label": "失败"}]},
|
||||||
|
{"name": "error_msg", "title": "错误信息", "type": "text", "width": 200},
|
||||||
|
{"name": "retry_count", "title": "重试次数", "type": "int", "width": 70, "align": "right"},
|
||||||
|
{"name": "created_at", "title": "创建时间", "type": "datetime", "width": 160, "readonly": true},
|
||||||
|
{"name": "updated_at", "title": "更新时间", "type": "datetime", "width": 160, "readonly": true}
|
||||||
|
],
|
||||||
|
"list": {
|
||||||
|
"columns": ["entity_type", "entity_id", "sync_status", "last_sync_time", "retry_count", "updated_at"],
|
||||||
|
"filters": [
|
||||||
|
{"field": "entity_type", "type": "select", "label": "实体类型", "options": [{"value": "", "label": "全部"}, {"value": "users", "label": "用户"}, {"value": "pricing", "label": "定价"}, {"value": "uapi", "label": "UAPI"}, {"value": "llmage", "label": "LLMage"}]},
|
||||||
|
{"field": "sync_status", "type": "select", "label": "状态", "options": [{"value": "", "label": "全部"}, {"value": "success", "label": "成功"}, {"value": "pending", "label": "待同步"}, {"value": "failed", "label": "失败"}]}
|
||||||
|
],
|
||||||
|
"sort": {"field": "updated_at", "order": "desc"}
|
||||||
|
},
|
||||||
|
"view": {"fields": ["id", "entity_type", "entity_id", "last_sync_time", "sync_version", "sync_status", "error_msg", "retry_count", "created_at", "updated_at"]},
|
||||||
|
"create": {"enabled": false},
|
||||||
|
"update": {
|
||||||
|
"enabled": true,
|
||||||
|
"fields": ["sync_status", "retry_count"]
|
||||||
|
},
|
||||||
|
"delete": {"enabled": false}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
scripts/generate_ddl.py
Executable file
55
scripts/generate_ddl.py
Executable file
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate DDL from SageAPI model JSON definitions."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
def generate_ddl(models_dir='models', output_path='db/schema.sql'):
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
ddl_lines = ['-- SageAPI DDL (auto-generated)', '']
|
||||||
|
|
||||||
|
for f in sorted(os.listdir(models_dir)):
|
||||||
|
if not f.endswith('.json'):
|
||||||
|
continue
|
||||||
|
with open(os.path.join(models_dir, f)) as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
|
||||||
|
summary = data['summary'][0]
|
||||||
|
tblname = summary['name']
|
||||||
|
primary = summary['primary']
|
||||||
|
btick = '`'
|
||||||
|
|
||||||
|
ddl_lines.append(f'CREATE TABLE IF NOT EXISTS {btick}{tblname}{btick} (')
|
||||||
|
|
||||||
|
col_defs = []
|
||||||
|
for field in data['fields']:
|
||||||
|
nullable = 'NULL' if field.get('nullable', True) else 'NOT NULL'
|
||||||
|
default = ''
|
||||||
|
if field.get('default') is not None:
|
||||||
|
if isinstance(field['default'], str) and field['default'] not in ('CURRENT_TIMESTAMP', 'NULL', ''):
|
||||||
|
default = f"DEFAULT '{field['default']}'"
|
||||||
|
else:
|
||||||
|
default = f"DEFAULT {field['default']}"
|
||||||
|
comment = f"COMMENT '{field.get('comment', '')}'"
|
||||||
|
fname = f'{btick}{field["name"]}{btick}'
|
||||||
|
col_defs.append(f' {fname} {field["type"]} {nullable} {default} {comment}'.strip())
|
||||||
|
|
||||||
|
pk = f'{btick}{primary}{btick}'
|
||||||
|
col_defs.append(f' PRIMARY KEY ({pk})')
|
||||||
|
ddl_lines.append(',\n'.join(col_defs))
|
||||||
|
ddl_lines.append(') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;')
|
||||||
|
ddl_lines.append('')
|
||||||
|
|
||||||
|
tbl = f'{btick}{tblname}{btick}'
|
||||||
|
for idx in data.get('idxfields', []):
|
||||||
|
unique = 'UNIQUE' if idx.get('unique') else 'INDEX'
|
||||||
|
cols = ','.join(f'{btick}{c}{btick}' for c in idx['fields'])
|
||||||
|
idx_name = f'{btick}{idx["name"]}{btick}'
|
||||||
|
ddl_lines.append(f'ALTER TABLE {tbl} ADD {unique} {idx_name} ({cols});')
|
||||||
|
ddl_lines.append('')
|
||||||
|
|
||||||
|
with open(output_path, 'w') as fh:
|
||||||
|
fh.write('\n'.join(ddl_lines))
|
||||||
|
print(f' Generated: {output_path} ({len(ddl_lines)} lines)')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
generate_ddl()
|
||||||
32
scripts/validate_crud.py
Executable file
32
scripts/validate_crud.py
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Validate SageAPI CRUD JSON definitions."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def validate_crud(json_dir='json'):
|
||||||
|
errors = 0
|
||||||
|
for f in sorted(os.listdir(json_dir)):
|
||||||
|
if not f.endswith('.json'):
|
||||||
|
continue
|
||||||
|
filepath = os.path.join(json_dir, f)
|
||||||
|
with open(filepath) as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
|
||||||
|
for tblname, config in data.items():
|
||||||
|
if 'params' not in config:
|
||||||
|
print(f'ERROR: {f} root key "{tblname}" missing "params"')
|
||||||
|
errors += 1
|
||||||
|
if 'fields' not in config:
|
||||||
|
print(f'ERROR: {f} root key "{tblname}" missing "fields"')
|
||||||
|
errors += 1
|
||||||
|
if not errors:
|
||||||
|
print(f' OK: {f} ({tblname})')
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print(f'\n{errors} error(s) found')
|
||||||
|
sys.exit(1)
|
||||||
|
print('All CRUD definitions valid')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
validate_crud()
|
||||||
41
scripts/validate_models.py
Executable file
41
scripts/validate_models.py
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Validate SageAPI model JSON definitions."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def validate_models(models_dir='models'):
|
||||||
|
errors = 0
|
||||||
|
for f in sorted(os.listdir(models_dir)):
|
||||||
|
if not f.endswith('.json'):
|
||||||
|
continue
|
||||||
|
filepath = os.path.join(models_dir, f)
|
||||||
|
with open(filepath) as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
|
||||||
|
for key in ['summary', 'fields', 'idxfields']:
|
||||||
|
if key not in data:
|
||||||
|
print(f'ERROR: {f} missing "{key}"')
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
if not data.get('summary') or not isinstance(data['summary'], list):
|
||||||
|
print(f'ERROR: {f} summary must be a non-empty list')
|
||||||
|
errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
summary = data['summary'][0]
|
||||||
|
if 'primary' not in summary:
|
||||||
|
print(f'ERROR: {f} summary must have "primary" field')
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
table_name = summary.get('name', '?')
|
||||||
|
primary = summary.get('primary', '?')
|
||||||
|
print(f' OK: {f} ({table_name}, primary={primary})')
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print(f'\n{errors} error(s) found')
|
||||||
|
sys.exit(1)
|
||||||
|
print('All model definitions valid')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
validate_models()
|
||||||
Loading…
x
Reference in New Issue
Block a user