From 8fe844be4809c35fc2fe9c1798c6fdf646779010 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 15:36:01 +0800 Subject: [PATCH 01/29] refactor: remove uapiset from sor_get_uapi() Remove uapiset intermediate table from JOIN chain. auth_apiname is now read directly from upapp table. Before: JOIN uapi a, upapp b, uapiset c WHERE b.apisetid = c.id After: JOIN uapi a, upapp b (auth_apiname from upapp) --- uapi/apidata.py | 5 ++--- uapi/appapi.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/uapi/apidata.py b/uapi/apidata.py index 8687fa6..2566cdf 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -25,10 +25,9 @@ async def get_deerer(upappid, callerid): async def sor_get_uapi(sor, upappid, apiname): sql = """select a.*, -c.auth_apiname -from uapi a, upapp b, uapiset c +b.auth_apiname +from uapi a, upapp b where a.apisetid = b.apisetid - and b.apisetid = c.id and a.name = ${apiname}$ and b.id = ${upappid}$""" recs = await sor.sqlExe(sql, {'upappid': upappid, 'apiname': apiname}) diff --git a/uapi/appapi.py b/uapi/appapi.py index 6d205cf..c860b8c 100644 --- a/uapi/appapi.py +++ b/uapi/appapi.py @@ -90,10 +90,9 @@ async def get_userapikey(sor, upappid, callerid): async def sor_get_uapi(sor, upappid, apiname): sql = """select a.*, -c.auth_apiname -from uapi a, upapp b, uapiset c +b.auth_apiname +from uapi a, upapp b where a.apisetid = b.apisetid - and b.apisetid = c.id and a.name = ${apiname}$ and b.id = ${upappid}$""" recs = await sor.sqlExe(sql, {'upappid': upappid, 'apiname': apiname}) From 471362a9f13f44ce4719ea3219ceaa0f8cb2a952 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 15:36:37 +0800 Subject: [PATCH 02/29] chore: remove uapiset.json CRUD definition uapiset table is being removed. auth_apiname moved to upapp table. --- json/uapiset.json | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 json/uapiset.json diff --git a/json/uapiset.json b/json/uapiset.json deleted file mode 100644 index 0889ed8..0000000 --- a/json/uapiset.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "tblname":"uapiset", - "params":{ - "title":"API集", - "sortby":"name", - "browserfields":{ - "exclouded":["id" ], - "alters":{} - }, - "editexclouded":["id"], - "subtables":[ - { - "field":"apisetid", - "subtable":"uapi", - "title":"定义api" - } - ] - } -} From 98df132a7809d4a65fa55ef14bcdbf06dc13318f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 15:38:48 +0800 Subject: [PATCH 03/29] feat: add uapi subtable to upapp CRUD After removing uapiset, upapp directly manages its uapi records via apisetid. Added uapi as a subtable in upapp.json so users can view/edit APIs directly from the upapp detail page. --- json/upapp.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/json/upapp.json b/json/upapp.json index 8e8680e..ef7c92c 100644 --- a/json/upapp.json +++ b/json/upapp.json @@ -16,6 +16,11 @@ "field":"upappid", "subtable": "upappkey", "title": "APIKEY" + }, + { + "field":"apisetid", + "subtable": "uapi", + "title": "API" } ], "toolbar":{ From 3d11ff3dc14587c4c95f323f30571b6702e83e1f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 15:46:50 +0800 Subject: [PATCH 04/29] fix: upapp uapi subtable should link by upappid not apisetid After removing uapiset, each upapp owns its uapi records directly. Subtable field changed from apisetid to upappid. --- json/upapp.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json/upapp.json b/json/upapp.json index ef7c92c..e034730 100644 --- a/json/upapp.json +++ b/json/upapp.json @@ -18,7 +18,7 @@ "title": "APIKEY" }, { - "field":"apisetid", + "field":"upappid", "subtable": "uapi", "title": "API" } From f16aa302ebfdedfa91b643ffa763aae6cf4fe717 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 15:47:41 +0800 Subject: [PATCH 05/29] feat: add migration script for uapi apisetid -> upappid Script copies shared uapi records so each upapp gets its own copy, then updates the link from apisetid to upappid. --- scripts/migrate_uapi_upappid.py | 130 ++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 scripts/migrate_uapi_upappid.py diff --git a/scripts/migrate_uapi_upappid.py b/scripts/migrate_uapi_upappid.py new file mode 100644 index 0000000..ec32de9 --- /dev/null +++ b/scripts/migrate_uapi_upappid.py @@ -0,0 +1,130 @@ +""" +Migration: uapi table apisetid -> upappid + +Removes uapiset intermediate layer. Each upapp now owns its own uapi records. +For shared apisetid: copies uapi records so each upapp has its own copy. + +Usage (in Sage virtual env): + ./py3/bin/python3 ~/repos/uapi/scripts/migrate_uapi_upappid.py --output /tmp/migrate_uapi.sql + +Review the output SQL, then execute on your database. +""" +from appPublic.getConfig import getConfig +import asyncio, json, sys, argparse +from sqlor.dbpools import DBPools +from appPublic.uniqueID import getID + +config = getConfig('.') +db = DBPools(config.databases) +dbname = list(config.databases.keys())[0] + + +async def generate_migration_sql(): + """Generate SQL to migrate uapi.apisetid -> uapi.upappid.""" + lines = [ + "-- Migration: uapi table apisetid -> upappid", + "-- Removes uapiset intermediate layer.", + "-- Each upapp owns its own uapi records after migration.", + "", + "-- Step 1: Add upappid column to uapi", + "ALTER TABLE uapi ADD COLUMN upappid VARCHAR(21) DEFAULT NULL COMMENT '上位系统ID' AFTER id;", + "" + ] + + # Load data + async with db.sqlorContext(dbname) as sor: + # Get all upapps + upapps = await sor.sqlExe('select id, name, apisetid from upapp', {}) + # Get all uapis + uapis = await sor.sqlExe('select id, apisetid, name, httpmethod, path, headers, ioid, auth_apiname, response, params, data, chunk_match from uapi', {}) + + # Build mapping: apisetid -> [upapp1, upapp2, ...] + apiset_to_upapps = {} + for u in upapps: + aid = u.get('apisetid') + if aid: + apiset_to_upapps.setdefault(aid, []).append(u) + + # Build mapping: apisetid -> [uapi_records] + apiset_to_uapis = {} + for a in uapis: + aid = a.get('apisetid') + if aid: + apiset_to_uapis.setdefault(aid, []).append(a) + + inserts = [] + for apisetid, upapp_list in apiset_to_upapps.items(): + uapi_records = apiset_to_uapis.get(apisetid, []) + + if len(upapp_list) == 1: + # Single upapp owns this apisetid -> just update + upapp = upapp_list[0] + for uapi in uapi_records: + inserts.append( + f"UPDATE uapi SET upappid = '{upapp['id']}' WHERE id = '{uapi['id']}';" + ) + else: + # Multiple upapps share this apisetid -> pick first as owner, copy for rest + owner = upapp_list[0] + + # Update existing records to point to owner + for uapi in uapi_records: + inserts.append( + f"UPDATE uapi SET upappid = '{owner['id']}' WHERE id = '{uapi['id']}';" + ) + + # Copy for other upapps + for other_upapp in upapp_list[1:]: + for uapi in uapi_records: + new_id = getID() + old_id = uapi['id'] + fields = ['id', 'upappid', 'name', 'httpmethod', 'path', 'headers', 'ioid', 'response', 'params', 'data', 'chunk_match'] + vals = [f"'{new_id}'", f"'{other_upapp['id']}'"] + for f in fields[2:]: + v = uapi.get(f) + if v is None: + vals.append('NULL') + elif isinstance(v, str): + escaped = v.replace("'", "\\'") + vals.append(f"'{escaped}'") + else: + vals.append(str(v)) + col_str = ', '.join(fields) + val_str = ', '.join(vals) + inserts.append(f"INSERT INTO uapi ({col_str}) VALUES ({val_str});") + + if inserts: + lines.append("-- Step 2: Migrate data (updates + copies for shared apisetid)") + lines.extend(inserts) + lines.append("") + + lines.extend([ + "-- Step 3: Drop apisetid column (verify upappid has no NULLs first)", + "-- UPDATE uapi SET upappid = (SELECT ownerid FROM organization LIMIT 1) WHERE upappid IS NULL;", + "-- ALTER TABLE uapi DROP COLUMN apisetid;", + "", + "-- Step 4: Add index", + "CREATE INDEX idx_uapi_upappid ON uapi (upappid);", + "" + ]) + + return '\n'.join(lines) + + +async def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--output', '-o', default='-') + args = parser.parse_args() + + sql = await generate_migration_sql() + + if args.output == '-': + print(sql) + else: + with open(args.output, 'w', encoding='utf-8') as f: + f.write(sql) + print(f"Generated migration SQL -> {args.output}") + + +if __name__ == '__main__': + asyncio.run(main()) From af864460f0a87402dc59f6bfc496edd50e5e635c Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 16:01:04 +0800 Subject: [PATCH 06/29] fix: correct getConfig import path to appPublic.jsonConfig --- scripts/migrate_uapi_upappid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/migrate_uapi_upappid.py b/scripts/migrate_uapi_upappid.py index ec32de9..caad97c 100644 --- a/scripts/migrate_uapi_upappid.py +++ b/scripts/migrate_uapi_upappid.py @@ -9,7 +9,7 @@ Usage (in Sage virtual env): Review the output SQL, then execute on your database. """ -from appPublic.getConfig import getConfig +from appPublic.jsonConfig import getConfig import asyncio, json, sys, argparse from sqlor.dbpools import DBPools from appPublic.uniqueID import getID From f0950f3673f7c19c8ab4ec51fda140cbc7f44d0c Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 16:13:50 +0800 Subject: [PATCH 07/29] fix: remove apisetid references from uapi.json, add auth_apiname migration to script - uapi.json: remove apisetid from sortby/browserfields/editexclouded - migrate script: include upapp.auth_apiname creation and migration from uapiset --- README.md | 31 +++++++++++++++++++++++++++++++ json/uapi.json | 6 +++--- scripts/migrate_uapi_upappid.py | 11 +++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bcf3c4d..a61abff 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,37 @@ uapi --- +## 在 Sage 系统中的角色 + +uapi 是 Sage 平台的**配置化 API 网关层**,llmage(大模型管理模块)是其主要消费者。两者的协同关系: + +``` +llmage (模型管理) uapi (API 网关) + │ │ + │ llm 表 │ + │ upappid ──────────────→│ upapp 表 (baseurl, myappid, ownerid) + │ apiname ──────────────→│ uapi 表 (httpmethod, path, headers, ...) + │ │ uapiset 表 (auth_apiname) + │ │ upappkey 表 (apikey, secretkey) + │ │ + │ UpAppApi(request) │ + │ .stream_linify() ─────→│ StreamHttpClient → 外部 LLM API + │ .call() ──────────────→│ 同步/流式 HTTP 调用 + │ │ +``` + +**新增一个 LLM 的完整流程**: +1. 在 uapi 模块的 `uapiset` 中创建 API 集合(配置认证方式) +2. 在 `upapp` 中注册上位系统(baseurl、appkey 等) +3. 在 `uapi` 中定义具体的 API 端点(path、method、headers 模板、response 模板) +4. 在 `upappkey` 中分配 API 密钥给调用方 +5. 在 llmage 模块的 `llm` 表中注册模型,关联 `upappid` + `apiname` +6. 用户在 llmage 前端页面点击模型卡片 → 推理 → 通过 uapi 网关调用外部 API + +**优势**:新增模型无需修改 Python 代码,只需在数据库/CRUD 页面中配置 API 定义。 + +--- + ## 开发注意事项 1. **dbname 获取**:必须通过 `get_serverenv('get_module_dbname')('uapi')` 动态获取,禁止硬编码 diff --git a/json/uapi.json b/json/uapi.json index e28048f..f0db3dd 100644 --- a/json/uapi.json +++ b/json/uapi.json @@ -27,11 +27,11 @@ "title":"API", "description":"API定义", - "sortby":["apisetid", "name"], + "sortby":"name", "browserfields":{ - "exclouded":["id", "apisetid"], + "exclouded":["id"], "alters":{} }, - "editexclouded":["id", "apisetid"] + "editexclouded":["id"] } } diff --git a/scripts/migrate_uapi_upappid.py b/scripts/migrate_uapi_upappid.py index caad97c..089d902 100644 --- a/scripts/migrate_uapi_upappid.py +++ b/scripts/migrate_uapi_upappid.py @@ -24,10 +24,17 @@ async def generate_migration_sql(): lines = [ "-- Migration: uapi table apisetid -> upappid", "-- Removes uapiset intermediate layer.", - "-- Each upapp owns its own uapi records after migration.", "", - "-- Step 1: Add upappid column to uapi", + "-- Step 1: Schema changes", + "-- 1.1 Add upappid to uapi", "ALTER TABLE uapi ADD COLUMN upappid VARCHAR(21) DEFAULT NULL COMMENT '上位系统ID' AFTER id;", + "", + "-- 1.2 Add auth_apiname to upapp", + "ALTER TABLE upapp ADD COLUMN auth_apiname VARCHAR(200) DEFAULT NULL COMMENT '授权API名' AFTER apisetid;", + "", + "-- 1.3 Migrate auth_apiname from uapiset to upapp", + "UPDATE upapp u JOIN uapiset s ON u.apisetid = s.id", + "SET u.auth_apiname = s.auth_apiname WHERE s.auth_apiname IS NOT NULL;", "" ] From 29a397bbf41303cd9e7e68da1aa57f22c01e28ce Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 16:15:25 +0800 Subject: [PATCH 08/29] fix: remove duplicate auth_apiname migration from script (already done manually) --- scripts/migrate_uapi_upappid.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/scripts/migrate_uapi_upappid.py b/scripts/migrate_uapi_upappid.py index 089d902..fa20a30 100644 --- a/scripts/migrate_uapi_upappid.py +++ b/scripts/migrate_uapi_upappid.py @@ -25,16 +25,8 @@ async def generate_migration_sql(): "-- Migration: uapi table apisetid -> upappid", "-- Removes uapiset intermediate layer.", "", - "-- Step 1: Schema changes", - "-- 1.1 Add upappid to uapi", + "-- Step 1: Add upappid column to uapi", "ALTER TABLE uapi ADD COLUMN upappid VARCHAR(21) DEFAULT NULL COMMENT '上位系统ID' AFTER id;", - "", - "-- 1.2 Add auth_apiname to upapp", - "ALTER TABLE upapp ADD COLUMN auth_apiname VARCHAR(200) DEFAULT NULL COMMENT '授权API名' AFTER apisetid;", - "", - "-- 1.3 Migrate auth_apiname from uapiset to upapp", - "UPDATE upapp u JOIN uapiset s ON u.apisetid = s.id", - "SET u.auth_apiname = s.auth_apiname WHERE s.auth_apiname IS NOT NULL;", "" ] From f0bf47881bcf44c693811fe1ed8ff8dc55483012 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 16:31:25 +0800 Subject: [PATCH 09/29] fix: remove non-existent auth_apiname from uapi query, add error handling --- scripts/migrate_uapi_upappid.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/migrate_uapi_upappid.py b/scripts/migrate_uapi_upappid.py index fa20a30..94e0d86 100644 --- a/scripts/migrate_uapi_upappid.py +++ b/scripts/migrate_uapi_upappid.py @@ -31,11 +31,15 @@ async def generate_migration_sql(): ] # Load data - async with db.sqlorContext(dbname) as sor: - # Get all upapps - upapps = await sor.sqlExe('select id, name, apisetid from upapp', {}) - # Get all uapis - uapis = await sor.sqlExe('select id, apisetid, name, httpmethod, path, headers, ioid, auth_apiname, response, params, data, chunk_match from uapi', {}) + try: + async with db.sqlorContext(dbname) as sor: + # Get all upapps + upapps = await sor.sqlExe('select id, name, apisetid from upapp', {}) + # Get all uapis (removed non-existent auth_apiname field) + uapis = await sor.sqlExe('select id, apisetid, name, httpmethod, path, headers, ioid, response, params, data, chunk_match from uapi', {}) + except Exception as e: + print(f"Error querying database: {e}", file=sys.stderr) + sys.exit(1) # Build mapping: apisetid -> [upapp1, upapp2, ...] apiset_to_upapps = {} From 7bf871e985b92fe5ffb62e6fa4a8b3aa38b071d6 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 20 May 2026 16:38:08 +0800 Subject: [PATCH 10/29] fix: increase upappid length to VARCHAR(32) to avoid Data too long error --- scripts/migrate_uapi_upappid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/migrate_uapi_upappid.py b/scripts/migrate_uapi_upappid.py index 94e0d86..039588b 100644 --- a/scripts/migrate_uapi_upappid.py +++ b/scripts/migrate_uapi_upappid.py @@ -26,7 +26,7 @@ async def generate_migration_sql(): "-- Removes uapiset intermediate layer.", "", "-- Step 1: Add upappid column to uapi", - "ALTER TABLE uapi ADD COLUMN upappid VARCHAR(21) DEFAULT NULL COMMENT '上位系统ID' AFTER id;", + "ALTER TABLE uapi ADD COLUMN upappid VARCHAR(32) DEFAULT NULL COMMENT '上位系统ID' AFTER id;", "" ] From 8287c2c733cb745df10033123a13ce20aa7cab90 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 21 May 2026 12:46:36 +0800 Subject: [PATCH 11/29] feat: add json table definitions for all models (converted from xlsx) --- models/uapi.json | 159 +++++++++++++++++++++++++++++++++++++++++++ models/uapiio.json | 36 ++++++++++ models/uapiset.json | 39 +++++++++++ models/upapp.json | 82 ++++++++++++++++++++++ models/upappkey.json | 90 ++++++++++++++++++++++++ models/uptask.json | 74 ++++++++++++++++++++ 6 files changed, 480 insertions(+) create mode 100644 models/uapi.json create mode 100644 models/uapiio.json create mode 100644 models/uapiset.json create mode 100644 models/upapp.json create mode 100644 models/upappkey.json create mode 100644 models/uptask.json diff --git a/models/uapi.json b/models/uapi.json new file mode 100644 index 0000000..70c5747 --- /dev/null +++ b/models/uapi.json @@ -0,0 +1,159 @@ +{ + "summary": [ + { + "name": "uapi", + "title": "API接口", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "api名称", + "type": "str", + "length": 200 + }, + { + "name": "title", + "title": "API标题", + "type": "str", + "length": 100 + }, + { + "name": "apisetid", + "title": "API集id", + "type": "str", + "length": 32 + }, + { + "name": "description", + "title": "接口描述", + "type": "text" + }, + { + "name": "need_auth", + "title": "需要鉴权", + "type": "str", + "length": 1, + "default": "0" + }, + { + "name": "stream", + "title": "流式输出", + "type": "str", + "length": 20 + }, + { + "name": "path", + "title": "path", + "type": "str", + "length": 4000 + }, + { + "name": "httpmethod", + "title": "http方法", + "type": "str", + "length": 20, + "nullable": "yes", + "default": "GET" + }, + { + "name": "chunk_match", + "title": "流式匹配串", + "type": "str", + "length": 100 + }, + { + "name": "headers", + "title": "headers模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "params", + "title": "参数模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "data", + "title": "数据模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "response", + "title": "响应模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "ioid", + "title": "输入输出id", + "type": "str", + "length": 32, + "nullable": "yes" + }, + { + "name": "callbackurl", + "title": "回调url", + "type": "str", + "length": 1000, + "nullable": "yes" + } + ], + "indexes": [ + { + "name": "idx1", + "idxtype": "unique", + "idxfields": [ + "apisetid", + "name" + ] + } + ], + "codes": [ + { + "field": "httpmethod", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='httpmethod'" + }, + { + "field": "apisetid", + "table": "uapiset", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "need_auth", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='yesno'" + }, + { + "field": "stream", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='resp_mode'" + }, + { + "field": "ioid", + "table": "uapiio", + "valuefield": "id", + "textfield": "name" + } + ] +} \ No newline at end of file diff --git a/models/uapiio.json b/models/uapiio.json new file mode 100644 index 0000000..e60446c --- /dev/null +++ b/models/uapiio.json @@ -0,0 +1,36 @@ +{ + "summary": [ + { + "name": "uapiio", + "title": "输入输出", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "类型名", + "type": "str", + "length": 100 + }, + { + "name": "description", + "title": "类型说明", + "type": "text" + }, + { + "name": "input_fields", + "title": "输入字段", + "type": "text" + } + ] +} \ No newline at end of file diff --git a/models/uapiset.json b/models/uapiset.json new file mode 100644 index 0000000..0426ea7 --- /dev/null +++ b/models/uapiset.json @@ -0,0 +1,39 @@ +{ + "summary": [ + { + "name": "uapiset", + "title": "API集", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "API集名称", + "type": "str", + "length": 200 + }, + { + "name": "description", + "title": "描述", + "type": "text", + "default": "0" + }, + { + "name": "auth_apiname", + "title": "授权api名", + "type": "str", + "length": 200, + "nullable": "yes" + } + ] +} \ No newline at end of file diff --git a/models/upapp.json b/models/upapp.json new file mode 100644 index 0000000..42f432f --- /dev/null +++ b/models/upapp.json @@ -0,0 +1,82 @@ +{ + "summary": [ + { + "name": "upapp", + "title": "外部系统", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "上位应用名", + "type": "str", + "length": 200 + }, + { + "name": "description", + "title": "描述", + "type": "text", + "default": "0" + }, + { + "name": "ownerid", + "title": "所属机构", + "type": "str", + "length": 32, + "nullable": "yes" + }, + { + "name": "apisetid", + "title": "api集id", + "type": "str", + "length": 32 + }, + { + "name": "secretkey", + "title": "加密密钥", + "type": "str", + "length": 100 + }, + { + "name": "baseurl", + "title": "系统url", + "type": "str", + "length": 500 + }, + { + "name": "myappid", + "title": "我的appid", + "type": "str", + "length": 100 + }, + { + "name": "dynamic_func", + "title": "动态headers函数", + "type": "str", + "length": 255 + } + ], + "codes": [ + { + "field": "ownerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "apisetid", + "table": "uapiset", + "valuefield": "id", + "textfield": "name" + } + ] +} \ No newline at end of file diff --git a/models/upappkey.json b/models/upappkey.json new file mode 100644 index 0000000..58f1969 --- /dev/null +++ b/models/upappkey.json @@ -0,0 +1,90 @@ +{ + "summary": [ + { + "name": "upappkey", + "title": "上位系统密码表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "upappid", + "title": "应用id", + "type": "str", + "length": 32 + }, + { + "name": "ownerid", + "title": "属主id", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "apikey", + "title": "api密钥", + "type": "str", + "length": 4000, + "default": "0" + }, + { + "name": "apiuser", + "title": "api用户", + "type": "str", + "length": 100 + }, + { + "name": "apipasswd", + "title": "api密码", + "type": "str", + "length": 100 + }, + { + "name": "orgid", + "title": "属主机构id", + "type": "str", + "length": 32 + }, + { + "name": "is_first", + "title": "是否第一用户", + "type": "str", + "length": 1 + } + ], + "codes": [ + { + "field": "ownerid", + "table": "users", + "valuefield": "id", + "textfield": "username" + }, + { + "field": "upappid", + "table": "upapp", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "orgid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "is_first", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='yesno'" + } + ] +} \ No newline at end of file diff --git a/models/uptask.json b/models/uptask.json new file mode 100644 index 0000000..eebd3e8 --- /dev/null +++ b/models/uptask.json @@ -0,0 +1,74 @@ +{ + "summary": [ + { + "name": "uptask", + "title": "上游任务", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "userid", + "title": "用户id", + "type": "str", + "length": 32 + }, + { + "name": "executor_taskid", + "title": "执行方任务id", + "type": "str", + "length": 64 + }, + { + "name": "convert_func_name", + "title": "响应转换函数名", + "type": "str", + "length": 128 + }, + { + "name": "local_bizid", + "title": "本地业务id", + "type": "str", + "length": 32 + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 24 + }, + { + "name": "response_data", + "title": "相应数据", + "type": "text" + }, + { + "name": "start_timestamp", + "title": "开始时间", + "type": "time" + }, + { + "name": "end_timestamp", + "title": "结束时间", + "type": "time" + } + ], + "indexes": [ + { + "name": "idx1", + "idxtype": "unique", + "idxfields": [ + "executor_taskid" + ] + } + ] +} \ No newline at end of file From 0aa5a2a8620ab69ccb271bb9e5013b3f9702b7cb Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 21 May 2026 15:49:04 +0800 Subject: [PATCH 12/29] fix: remove uapiset references, use upappid for uapi-upapp direct join --- models/uapi.json | 20 +++++++------------- models/upapp.json | 13 +++++++------ uapi/apidata.py | 2 +- uapi/appapi.py | 4 ++-- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/models/uapi.json b/models/uapi.json index 70c5747..1e2ebd7 100644 --- a/models/uapi.json +++ b/models/uapi.json @@ -16,6 +16,12 @@ "type": "str", "length": 32 }, + { + "name": "upappid", + "title": "上位系统ID", + "type": "str", + "length": 32 + }, { "name": "name", "title": "api名称", @@ -28,12 +34,6 @@ "type": "str", "length": 100 }, - { - "name": "apisetid", - "title": "API集id", - "type": "str", - "length": 32 - }, { "name": "description", "title": "接口描述", @@ -116,7 +116,7 @@ "name": "idx1", "idxtype": "unique", "idxfields": [ - "apisetid", + "upappid", "name" ] } @@ -129,12 +129,6 @@ "textfield": "v", "cond": "parentid='httpmethod'" }, - { - "field": "apisetid", - "table": "uapiset", - "valuefield": "id", - "textfield": "name" - }, { "field": "need_auth", "table": "appcodes_kv", diff --git a/models/upapp.json b/models/upapp.json index 42f432f..cf87b9c 100644 --- a/models/upapp.json +++ b/models/upapp.json @@ -63,6 +63,13 @@ "title": "动态headers函数", "type": "str", "length": 255 + }, + { + "name": "auth_apiname", + "title": "认证API名", + "type": "str", + "length": 200, + "nullable": "yes" } ], "codes": [ @@ -71,12 +78,6 @@ "table": "organization", "valuefield": "id", "textfield": "orgname" - }, - { - "field": "apisetid", - "table": "uapiset", - "valuefield": "id", - "textfield": "name" } ] } \ No newline at end of file diff --git a/uapi/apidata.py b/uapi/apidata.py index 2566cdf..7fb7980 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -27,7 +27,7 @@ async def sor_get_uapi(sor, upappid, apiname): sql = """select a.*, b.auth_apiname from uapi a, upapp b -where a.apisetid = b.apisetid +where a.upappid = b.id and a.name = ${apiname}$ and b.id = ${upappid}$""" recs = await sor.sqlExe(sql, {'upappid': upappid, 'apiname': apiname}) diff --git a/uapi/appapi.py b/uapi/appapi.py index c860b8c..57d671d 100644 --- a/uapi/appapi.py +++ b/uapi/appapi.py @@ -20,7 +20,7 @@ async def get_callerid(orgid): async def sor_get_uapi_by_appname_apiname(sor, appname, apiname): sql = """select a.* from uapi a, upapp b -where a.apisetid = b.apisetid +where a.upappid = b.id and b.name = ${appname}$ and a.name = ${apiname}$""" recs = await sor.sqlExe(sql, {'apiname': apiname, 'appname': appname}) @@ -92,7 +92,7 @@ async def sor_get_uapi(sor, upappid, apiname): sql = """select a.*, b.auth_apiname from uapi a, upapp b -where a.apisetid = b.apisetid +where a.upappid = b.id and a.name = ${apiname}$ and b.id = ${upappid}$""" recs = await sor.sqlExe(sql, {'upappid': upappid, 'apiname': apiname}) From c7bbdb5bda54e1156df5d780b5458f004f6ca8ce Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 21 May 2026 15:50:19 +0800 Subject: [PATCH 13/29] chore: remove uapiset.json (table no longer exists) --- models/uapiset.json | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 models/uapiset.json diff --git a/models/uapiset.json b/models/uapiset.json deleted file mode 100644 index 0426ea7..0000000 --- a/models/uapiset.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "summary": [ - { - "name": "uapiset", - "title": "API集", - "primary": [ - "id" - ], - "catelog": "entity" - } - ], - "fields": [ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32 - }, - { - "name": "name", - "title": "API集名称", - "type": "str", - "length": 200 - }, - { - "name": "description", - "title": "描述", - "type": "text", - "default": "0" - }, - { - "name": "auth_apiname", - "title": "授权api名", - "type": "str", - "length": 200, - "nullable": "yes" - } - ] -} \ No newline at end of file From 369f6fea54dc2a0692926bd3fdfb3126bb73ce06 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 25 May 2026 17:15:56 +0800 Subject: [PATCH 14/29] feat: add token() auth function for Vidu API Token authentication - Add token(apikey) function in appapi.py returning 'Token {apikey}' - Import and register token() in init.py ServerEnv - Supports Vidu API's Token auth scheme (vs existing Bearer/Deerer) --- uapi/appapi.py | 3 +++ uapi/init.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/uapi/appapi.py b/uapi/appapi.py index 57d671d..a42085d 100644 --- a/uapi/appapi.py +++ b/uapi/appapi.py @@ -115,6 +115,9 @@ def deerer(myappid, apikey, secretkey): def bearer(apikey): return f'Bearer {apikey}' +def token(apikey): + return f'Token {apikey}' + async def sync_users(request, upappid, userid): db = DBPools() dbname = get_dbname() diff --git a/uapi/init.py b/uapi/init.py index 04978ff..ba9deeb 100644 --- a/uapi/init.py +++ b/uapi/init.py @@ -4,8 +4,9 @@ from .appapi import ( deerer, sor_get_uapi_by_appname_apiname, bearer, + token, get_callerid, - sor_get_callerid, + sor_get_callerid, get_deerer ) from .uptask import ( @@ -28,6 +29,7 @@ def load_uapi(): g.sor_get_callerid = sor_get_callerid g.sor_get_uapi_by_appname_apiname = sor_get_uapi_by_appname_apiname g.bearer = bearer + g.token = token g.check_uptask_status = check_uptask_status g.get_my_uptasks = get_my_uptasks g.uptask_feedback = uptask_feedback @@ -43,6 +45,10 @@ def load_uapi(): ## in your header template ## {{bearer(apikey)}} +# token usage (for APIs that use Token auth, e.g. Vidu) +## in your header template +## {{token(apikey)}} + # deerer usge ## in your header template ## {{deerer(myappid, apikey, secretkey)}} From b83828b073f51e42126f3cccaac1c9949ca43bf6 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 25 May 2026 17:33:23 +0800 Subject: [PATCH 15/29] Revert "feat: add token() auth function for Vidu API Token authentication" This reverts commit 369f6fea54dc2a0692926bd3fdfb3126bb73ce06. --- uapi/appapi.py | 3 --- uapi/init.py | 8 +------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/uapi/appapi.py b/uapi/appapi.py index a42085d..57d671d 100644 --- a/uapi/appapi.py +++ b/uapi/appapi.py @@ -115,9 +115,6 @@ def deerer(myappid, apikey, secretkey): def bearer(apikey): return f'Bearer {apikey}' -def token(apikey): - return f'Token {apikey}' - async def sync_users(request, upappid, userid): db = DBPools() dbname = get_dbname() diff --git a/uapi/init.py b/uapi/init.py index ba9deeb..04978ff 100644 --- a/uapi/init.py +++ b/uapi/init.py @@ -4,9 +4,8 @@ from .appapi import ( deerer, sor_get_uapi_by_appname_apiname, bearer, - token, get_callerid, - sor_get_callerid, + sor_get_callerid, get_deerer ) from .uptask import ( @@ -29,7 +28,6 @@ def load_uapi(): g.sor_get_callerid = sor_get_callerid g.sor_get_uapi_by_appname_apiname = sor_get_uapi_by_appname_apiname g.bearer = bearer - g.token = token g.check_uptask_status = check_uptask_status g.get_my_uptasks = get_my_uptasks g.uptask_feedback = uptask_feedback @@ -45,10 +43,6 @@ def load_uapi(): ## in your header template ## {{bearer(apikey)}} -# token usage (for APIs that use Token auth, e.g. Vidu) -## in your header template -## {{token(apikey)}} - # deerer usge ## in your header template ## {{deerer(myappid, apikey, secretkey)}} From 3e93f61594030dd26f3daa66d36d20c76cca385c Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 27 May 2026 13:23:33 +0800 Subject: [PATCH 16/29] refactor(models): convert to json format per database-table-definition-spec --- models/uapiset.json | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 models/uapiset.json diff --git a/models/uapiset.json b/models/uapiset.json new file mode 100644 index 0000000..0426ea7 --- /dev/null +++ b/models/uapiset.json @@ -0,0 +1,39 @@ +{ + "summary": [ + { + "name": "uapiset", + "title": "API集", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "API集名称", + "type": "str", + "length": 200 + }, + { + "name": "description", + "title": "描述", + "type": "text", + "default": "0" + }, + { + "name": "auth_apiname", + "title": "授权api名", + "type": "str", + "length": 200, + "nullable": "yes" + } + ] +} \ No newline at end of file From e0296882fe77000937505a89969c8b1914a723ba Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 27 May 2026 16:28:37 +0800 Subject: [PATCH 17/29] feat: add load_path.py for RBAC permission registration --- scripts/load_path.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 scripts/load_path.py diff --git a/scripts/load_path.py b/scripts/load_path.py new file mode 100644 index 0000000..8a758ce --- /dev/null +++ b/scripts/load_path.py @@ -0,0 +1,56 @@ +"""Generate RBAC permissions for uapi module paths. + +Run from Sage root with Sage venv: + cd ~/repos/sage && ./py3/bin/python ../uapi/scripts/load_path.py +""" +import os +import sys +import asyncio + +sage_root = os.environ.get('SAGE_ROOT') +if sage_root and sage_root not in sys.path: + sys.path.insert(0, sage_root) + +from sqlor.dbpools import DBPools +from appPublic.jsonConfig import getConfig +from appPublic.dictObject import DictObject +from appPublic.uniqueID import getID + + +paths = [ + ("/uapi", "logined"), + ("/uapi/jump_in.dspy", "logined"), + ("/uapi/minimax_callback.dspy", "any"), + ("/uapi/uptask_callback.dspy", "any"), + ("/uapi/viducallback", "any"), +] + + +async def main(): + config = getConfig('.') + DBPools(config.databases) + dbname = 'sage' + async with DBPools().sqlorContext(dbname) as sor: + cnt = 0 + for path, role in paths: + r = await sor.sqlExe( + 'select * from permission where permcode = ${permcode}$', + {'permcode': path} + ) + if len(r) == 0: + await sor.sqlExe( + '''insert into permission (id, permcode, permname, permtype) + values (${id}$, ${permcode}$, ${permname}$, ${permtype}$)''', + { + 'id': getID(), + 'permcode': path, + 'permname': path, + 'permtype': role, + } + ) + cnt += 1 + print(f'{cnt} path(s) inserted for uapi') + + +if __name__ == '__main__': + asyncio.run(main()) From cc5803d4a7f29df1ab3cac8b56527854c50fef3a Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 29 May 2026 12:07:55 +0800 Subject: [PATCH 18/29] refactor: optimize debug output - use debug_params, truncate SQL; add CRUD definitions --- wwwroot/uapi/add_uapi.dspy | 37 ++++ wwwroot/uapi/delete_uapi.dspy | 33 +++ wwwroot/uapi/get_uapi.dspy | 155 ++++++++++++++ wwwroot/uapi/index.ui | 291 ++++++++++++++++++++++++++ wwwroot/uapi/update_uapi.dspy | 36 ++++ wwwroot/uapiio/add_uapiio.dspy | 37 ++++ wwwroot/uapiio/delete_uapiio.dspy | 33 +++ wwwroot/uapiio/get_uapiio.dspy | 72 +++++++ wwwroot/uapiio/index.ui | 103 +++++++++ wwwroot/uapiio/update_uapiio.dspy | 36 ++++ wwwroot/upapp/add_upapp.dspy | 54 +++++ wwwroot/upapp/delete_upapp.dspy | 47 +++++ wwwroot/upapp/get_upapp.dspy | 129 ++++++++++++ wwwroot/upapp/index.ui | 258 +++++++++++++++++++++++ wwwroot/upapp/update_upapp.dspy | 73 +++++++ wwwroot/upappkey/add_upappkey.dspy | 71 +++++++ wwwroot/upappkey/delete_upappkey.dspy | 61 ++++++ wwwroot/upappkey/get_upappkey.dspy | 133 ++++++++++++ wwwroot/upappkey/index.ui | 189 +++++++++++++++++ wwwroot/upappkey/update_upappkey.dspy | 92 ++++++++ wwwroot/uptask_callback.dspy | 2 +- wwwroot/viducallback/index.dspy | 2 +- 22 files changed, 1942 insertions(+), 2 deletions(-) create mode 100644 wwwroot/uapi/add_uapi.dspy create mode 100644 wwwroot/uapi/delete_uapi.dspy create mode 100644 wwwroot/uapi/get_uapi.dspy create mode 100644 wwwroot/uapi/index.ui create mode 100644 wwwroot/uapi/update_uapi.dspy create mode 100644 wwwroot/uapiio/add_uapiio.dspy create mode 100644 wwwroot/uapiio/delete_uapiio.dspy create mode 100644 wwwroot/uapiio/get_uapiio.dspy create mode 100644 wwwroot/uapiio/index.ui create mode 100644 wwwroot/uapiio/update_uapiio.dspy create mode 100644 wwwroot/upapp/add_upapp.dspy create mode 100644 wwwroot/upapp/delete_upapp.dspy create mode 100644 wwwroot/upapp/get_upapp.dspy create mode 100644 wwwroot/upapp/index.ui create mode 100644 wwwroot/upapp/update_upapp.dspy create mode 100644 wwwroot/upappkey/add_upappkey.dspy create mode 100644 wwwroot/upappkey/delete_upappkey.dspy create mode 100644 wwwroot/upappkey/get_upappkey.dspy create mode 100644 wwwroot/upappkey/index.ui create mode 100644 wwwroot/upappkey/update_upappkey.dspy diff --git a/wwwroot/uapi/add_uapi.dspy b/wwwroot/uapi/add_uapi.dspy new file mode 100644 index 0000000..34e742d --- /dev/null +++ b/wwwroot/uapi/add_uapi.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('uapi', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/uapi/delete_uapi.dspy b/wwwroot/uapi/delete_uapi.dspy new file mode 100644 index 0000000..6cb7df1 --- /dev/null +++ b/wwwroot/uapi/delete_uapi.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('uapi', ns) + debug('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/uapi/get_uapi.dspy b/wwwroot/uapi/get_uapi.dspy new file mode 100644 index 0000000..ad31a14 --- /dev/null +++ b/wwwroot/uapi/get_uapi.dspy @@ -0,0 +1,155 @@ + +ns = params_kw.copy() + + +debug_params('get_uapi', ns) +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'name' + + + +sql = '''select a.*, b.httpmethod_text, c.need_auth_text, d.stream_text, e.ioid_text +from (select * from uapi where 1=1 [[filterstr]]) a left join (select k as httpmethod, + v as httpmethod_text from appcodes_kv where parentid='httpmethod') b on a.httpmethod = b.httpmethod left join (select k as need_auth, + v as need_auth_text from appcodes_kv where parentid='yesno') c on a.need_auth = c.need_auth left join (select k as stream, + v as stream_text from appcodes_kv where parentid='resp_mode') d on a.stream = d.stream left join (select id as ioid, + name as ioid_text from uapiio where 1 = 1) e on a.ioid = e.ioid''' + +filterjson = params_kw.get('data_filter') +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "upappid", + "title": "上位系统ID", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "api名称", + "type": "str", + "length": 200 + }, + { + "name": "title", + "title": "API标题", + "type": "str", + "length": 100 + }, + { + "name": "description", + "title": "接口描述", + "type": "text" + }, + { + "name": "need_auth", + "title": "需要鉴权", + "type": "str", + "length": 1, + "default": "0" + }, + { + "name": "stream", + "title": "流式输出", + "type": "str", + "length": 20 + }, + { + "name": "path", + "title": "path", + "type": "str", + "length": 4000 + }, + { + "name": "httpmethod", + "title": "http方法", + "type": "str", + "length": 20, + "nullable": "yes", + "default": "GET" + }, + { + "name": "chunk_match", + "title": "流式匹配串", + "type": "str", + "length": 100 + }, + { + "name": "headers", + "title": "headers模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "params", + "title": "参数模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "data", + "title": "数据模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "response", + "title": "响应模版", + "type": "text", + "nullable": "yes" + }, + { + "name": "ioid", + "title": "输入输出id", + "type": "str", + "length": 32, + "nullable": "yes" + }, + { + "name": "callbackurl", + "title": "回调url", + "type": "str", + "length": 1000, + "nullable": "yes" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'sql({len(sql)}ch): {sql[:200]}') +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/uapi/index.ui b/wwwroot/uapi/index.ui new file mode 100644 index 0000000..7860011 --- /dev/null +++ b/wwwroot/uapi/index.ui @@ -0,0 +1,291 @@ + +{ + "id":"uapi_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"API", + + + + "description":"API定义", + + + "toolbar":{ + "tools": [ + { + "selected_row": true, + "name": "test", + "icon": "{{entire_url('/imgs/test.svg')}}", + "label": "api测试" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_uapi.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_uapi.dspy')}}", + + + "update_data_url":"{{entire_url('update_uapi.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_uapi.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": {} +}, + + + "editexclouded":[ + "id" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "upappid", + "title": "上位系统ID", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "上位系统ID" + }, + { + "name": "name", + "title": "api名称", + "type": "str", + "length": 200, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "api名称" + }, + { + "name": "title", + "title": "API标题", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "API标题" + }, + { + "name": "description", + "title": "接口描述", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "接口描述" + }, + { + "name": "need_auth", + "title": "需要鉴权", + "type": "str", + "length": 1, + "default": "0", + "label": "需要鉴权", + "uitype": "code", + "valueField": "need_auth", + "textField": "need_auth_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "need_auth", + "textField": "need_auth_text", + "cond": "parentid='yesno'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "stream", + "title": "流式输出", + "type": "str", + "length": 20, + "label": "流式输出", + "uitype": "code", + "valueField": "stream", + "textField": "stream_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "stream", + "textField": "stream_text", + "cond": "parentid='resp_mode'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "path", + "title": "path", + "type": "str", + "length": 4000, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "path" + }, + { + "name": "httpmethod", + "title": "http方法", + "type": "str", + "length": 20, + "nullable": "yes", + "default": "GET", + "label": "http方法", + "uitype": "code", + "valueField": "httpmethod", + "textField": "httpmethod_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "httpmethod", + "textField": "httpmethod_text", + "cond": "parentid='httpmethod'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "chunk_match", + "title": "流式匹配串", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "流式匹配串" + }, + { + "name": "headers", + "title": "headers模版", + "type": "text", + "nullable": "yes", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "headers模版" + }, + { + "name": "params", + "title": "参数模版", + "type": "text", + "nullable": "yes", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "参数模版" + }, + { + "name": "data", + "title": "数据模版", + "type": "text", + "nullable": "yes", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "数据模版" + }, + { + "name": "response", + "title": "响应模版", + "type": "text", + "nullable": "yes", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "响应模版" + }, + { + "name": "ioid", + "title": "输入输出id", + "type": "str", + "length": 32, + "nullable": "yes", + "label": "输入输出id", + "uitype": "code", + "valueField": "ioid", + "textField": "ioid_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "uapiio", + "tblvalue": "id", + "tbltext": "name", + "valueField": "ioid", + "textField": "ioid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "callbackurl", + "title": "回调url", + "type": "str", + "length": 1000, + "nullable": "yes", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "回调url" + } +] + }, + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "test", + "actiontype": "urlwidget", + "target": "PopupWindow", + "options": { + "methid": "POST", + "params": {}, + "url": "{{entire_url('/uapi/uapi_test.ui')}}" + } + } +] + +} \ No newline at end of file diff --git a/wwwroot/uapi/update_uapi.dspy b/wwwroot/uapi/update_uapi.dspy new file mode 100644 index 0000000..ccfbec3 --- /dev/null +++ b/wwwroot/uapi/update_uapi.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('uapi', ns) + debug('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/uapiio/add_uapiio.dspy b/wwwroot/uapiio/add_uapiio.dspy new file mode 100644 index 0000000..8a40cd3 --- /dev/null +++ b/wwwroot/uapiio/add_uapiio.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('uapiio', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/uapiio/delete_uapiio.dspy b/wwwroot/uapiio/delete_uapiio.dspy new file mode 100644 index 0000000..112d14b --- /dev/null +++ b/wwwroot/uapiio/delete_uapiio.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('uapiio', ns) + debug('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/uapiio/get_uapiio.dspy b/wwwroot/uapiio/get_uapiio.dspy new file mode 100644 index 0000000..99908ed --- /dev/null +++ b/wwwroot/uapiio/get_uapiio.dspy @@ -0,0 +1,72 @@ + +ns = params_kw.copy() + + +debug_params('get_uapiio', ns) +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'name' + + + +sql = '''select * from uapiio where 1=1 [[filterstr]]''' + +filterjson = params_kw.get('data_filter') +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "类型名", + "type": "str", + "length": 100 + }, + { + "name": "description", + "title": "类型说明", + "type": "text" + }, + { + "name": "input_fields", + "title": "输入字段", + "type": "text" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'sql({len(sql)}ch): {sql[:200]}') +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/uapiio/index.ui b/wwwroot/uapiio/index.ui new file mode 100644 index 0000000..f299e93 --- /dev/null +++ b/wwwroot/uapiio/index.ui @@ -0,0 +1,103 @@ + +{ + "id":"uapiio_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"API输入输出", + + + + "description":"API的输入输出定义", + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_uapiio.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_uapiio.dspy')}}", + + + "update_data_url":"{{entire_url('update_uapiio.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_uapiio.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": {} +}, + + + "editexclouded":[ + "id" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "name", + "title": "类型名", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "类型名" + }, + { + "name": "description", + "title": "类型说明", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "类型说明" + }, + { + "name": "input_fields", + "title": "输入字段", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "输入字段" + } +] + }, + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +} \ No newline at end of file diff --git a/wwwroot/uapiio/update_uapiio.dspy b/wwwroot/uapiio/update_uapiio.dspy new file mode 100644 index 0000000..6d17571 --- /dev/null +++ b/wwwroot/uapiio/update_uapiio.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('uapiio', ns) + debug('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/upapp/add_upapp.dspy b/wwwroot/upapp/add_upapp.dspy new file mode 100644 index 0000000..63a8593 --- /dev/null +++ b/wwwroot/upapp/add_upapp.dspy @@ -0,0 +1,54 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if params_kw.get('secretkey'): + ns['secretkey'] = password_encode(params_kw.get('secretkey')) + + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('upapp', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/upapp/delete_upapp.dspy b/wwwroot/upapp/delete_upapp.dspy new file mode 100644 index 0000000..3e25a6a --- /dev/null +++ b/wwwroot/upapp/delete_upapp.dspy @@ -0,0 +1,47 @@ + +ns = { + 'id':params_kw['id'], +} + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('upapp', ns) + debug('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/upapp/get_upapp.dspy b/wwwroot/upapp/get_upapp.dspy new file mode 100644 index 0000000..f2ca27a --- /dev/null +++ b/wwwroot/upapp/get_upapp.dspy @@ -0,0 +1,129 @@ + +ns = params_kw.copy() + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid +ns['userorgid'] = userorgid + +debug_params('get_upapp', ns) +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'name' + + + +sql = '''select a.*, b.ownerid_text +from (select * from upapp where 1=1 [[filterstr]]) a left join (select id as ownerid, + orgname as ownerid_text from organization where 1 = 1) b on a.ownerid = b.ownerid''' + +filterjson = params_kw.get('data_filter') +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "上位应用名", + "type": "str", + "length": 200 + }, + { + "name": "description", + "title": "描述", + "type": "text", + "default": "0" + }, + { + "name": "ownerid", + "title": "所属机构", + "type": "str", + "length": 32, + "nullable": "yes" + }, + { + "name": "apisetid", + "title": "api集id", + "type": "str", + "length": 32 + }, + { + "name": "secretkey", + "title": "加密密钥", + "type": "str", + "length": 100 + }, + { + "name": "baseurl", + "title": "系统url", + "type": "str", + "length": 500 + }, + { + "name": "myappid", + "title": "我的appid", + "type": "str", + "length": 100 + }, + { + "name": "dynamic_func", + "title": "动态headers函数", + "type": "str", + "length": 255 + }, + { + "name": "auth_apiname", + "title": "认证API名", + "type": "str", + "length": 200, + "nullable": "yes" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'sql({len(sql)}ch): {sql[:200]}') +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/upapp/index.ui b/wwwroot/upapp/index.ui new file mode 100644 index 0000000..cd85b4d --- /dev/null +++ b/wwwroot/upapp/index.ui @@ -0,0 +1,258 @@ + +{ + "id":"upapp_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"上位系统", + + + + "description":"上位系统", + + + "toolbar":{ + "tools": [ + { + "name": "jumpin", + "label": "跳转到", + "selected_data": true + }, + { + "selected_row": true, + "name": "upappkey", + "icon": "{{entire_url('/imgs/upappkey.svg')}}", + "label": "APIKEY" + }, + { + "selected_row": true, + "name": "uapi", + "icon": "{{entire_url('/imgs/uapi.svg')}}", + "label": "API" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_upapp.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_upapp.dspy')}}", + + + "update_data_url":"{{entire_url('update_upapp.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_upapp.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "ownerid" + ], + "alters": {} +}, + + + "editexclouded":[ + "ownerid" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "name", + "title": "上位应用名", + "type": "str", + "length": 200, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "上位应用名" + }, + { + "name": "description", + "title": "描述", + "type": "text", + "default": "0", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "描述" + }, + { + "name": "ownerid", + "title": "所属机构", + "type": "str", + "length": 32, + "nullable": "yes", + "label": "所属机构", + "uitype": "code", + "valueField": "ownerid", + "textField": "ownerid_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "ownerid", + "textField": "ownerid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "apisetid", + "title": "api集id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "api集id" + }, + { + "name": "secretkey", + "title": "加密密钥", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "password", + "datatype": "str", + "label": "加密密钥" + }, + { + "name": "baseurl", + "title": "系统url", + "type": "str", + "length": 500, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "系统url" + }, + { + "name": "myappid", + "title": "我的appid", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "我的appid" + }, + { + "name": "dynamic_func", + "title": "动态headers函数", + "type": "str", + "length": 255, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "动态headers函数" + }, + { + "name": "auth_apiname", + "title": "认证API名", + "type": "str", + "length": 200, + "nullable": "yes", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "认证API名" + } +] + }, + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "jumpin", + "actiontype": "urlwidget", + "target": "self", + "options": { + "url": "{{entire_url('/uapi/jump_in.dspy')}}" + } + }, + { + "wid": "self", + "event": "upappkey", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "APIKEY", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=upappkey", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "upappid", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../upappkey')}}" + } + }, + { + "wid": "self", + "event": "uapi", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "API", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=uapi", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "upappid", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../uapi')}}" + } + } +] + +} \ No newline at end of file diff --git a/wwwroot/upapp/update_upapp.dspy b/wwwroot/upapp/update_upapp.dspy new file mode 100644 index 0000000..0a95885 --- /dev/null +++ b/wwwroot/upapp/update_upapp.dspy @@ -0,0 +1,73 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid + + +if params_kw.get('secretkey'): + ns['secretkey'] = password_encode(params_kw.get('secretkey')) + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + + ns1 = { + + "ownerid": userorgid, + + + "id": params_kw.id + } + recs = await sor.R('upapp', ns1) + if len(recs) < 1: + return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"Record no exist or with wrong ownership" + } + } + + r = await sor.U('upapp', ns) + debug('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/upappkey/add_upappkey.dspy b/wwwroot/upappkey/add_upappkey.dspy new file mode 100644 index 0000000..65b9a75 --- /dev/null +++ b/wwwroot/upappkey/add_upappkey.dspy @@ -0,0 +1,71 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if params_kw.get('apikey'): + ns['apikey'] = password_encode(params_kw.get('apikey')) + +if params_kw.get('apipasswd'): + ns['apipasswd'] = password_encode(params_kw.get('apipasswd')) + + +userid = await get_user() +if not userid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userid + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['orgid'] = userorgid + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('upappkey', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/upappkey/delete_upappkey.dspy b/wwwroot/upappkey/delete_upappkey.dspy new file mode 100644 index 0000000..9277b1f --- /dev/null +++ b/wwwroot/upappkey/delete_upappkey.dspy @@ -0,0 +1,61 @@ + +ns = { + 'id':params_kw['id'], +} + +userid = await get_user() +if not userid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userid + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['orgid'] = userorgid + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('upappkey', ns) + debug('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/upappkey/get_upappkey.dspy b/wwwroot/upappkey/get_upappkey.dspy new file mode 100644 index 0000000..603eb10 --- /dev/null +++ b/wwwroot/upappkey/get_upappkey.dspy @@ -0,0 +1,133 @@ + +ns = params_kw.copy() + +userid = await get_user() +if not userid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userid +ns['userid'] = userid + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['orgid'] = userorgid +ns['userorgid'] = userorgid + +debug_params('get_upappkey', ns) +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + + +sql = '''select a.*, b.ownerid_text, c.upappid_text, d.orgid_text, e.is_first_text +from (select * from upappkey where 1=1 [[filterstr]]) a left join (select id as ownerid, + username as ownerid_text from users where 1 = 1) b on a.ownerid = b.ownerid left join (select id as upappid, + name as upappid_text from upapp where 1 = 1) c on a.upappid = c.upappid left join (select id as orgid, + orgname as orgid_text from organization where 1 = 1) d on a.orgid = d.orgid left join (select k as is_first, + v as is_first_text from appcodes_kv where parentid='yesno') e on a.is_first = e.is_first''' + +filterjson = params_kw.get('data_filter') +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "upappid", + "title": "应用id", + "type": "str", + "length": 32 + }, + { + "name": "ownerid", + "title": "属主id", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "apikey", + "title": "api密钥", + "type": "str", + "length": 4000, + "default": "0" + }, + { + "name": "apiuser", + "title": "api用户", + "type": "str", + "length": 100 + }, + { + "name": "apipasswd", + "title": "api密码", + "type": "str", + "length": 100 + }, + { + "name": "orgid", + "title": "属主机构id", + "type": "str", + "length": 32 + }, + { + "name": "is_first", + "title": "是否第一用户", + "type": "str", + "length": 1 + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'sql({len(sql)}ch): {sql[:200]}') +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/upappkey/index.ui b/wwwroot/upappkey/index.ui new file mode 100644 index 0000000..ba1364e --- /dev/null +++ b/wwwroot/upappkey/index.ui @@ -0,0 +1,189 @@ + +{ + "id":"upappkey_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"上位系统密码", + + + + "description":"上位系统密码", + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_upappkey.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_upappkey.dspy')}}", + + + "update_data_url":"{{entire_url('update_upappkey.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_upappkey.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "ownerid", + "orgid" + ], + "alters": {} +}, + + + "editexclouded":[ + "id", + "upappid", + "ownerid", + "orgid" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "upappid", + "title": "应用id", + "type": "str", + "length": 32, + "label": "应用id", + "uitype": "code", + "valueField": "upappid", + "textField": "upappid_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "upapp", + "tblvalue": "id", + "tbltext": "name", + "valueField": "upappid", + "textField": "upappid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "ownerid", + "title": "属主id", + "type": "str", + "length": 32, + "default": "0", + "label": "属主id", + "uitype": "code", + "valueField": "ownerid", + "textField": "ownerid_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "users", + "tblvalue": "id", + "tbltext": "username", + "valueField": "ownerid", + "textField": "ownerid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "apikey", + "title": "api密钥", + "type": "str", + "length": 4000, + "default": "0", + "cwidth": 18, + "uitype": "password", + "datatype": "str", + "label": "api密钥" + }, + { + "name": "apiuser", + "title": "api用户", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "api用户" + }, + { + "name": "apipasswd", + "title": "api密码", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "password", + "datatype": "str", + "label": "api密码" + }, + { + "name": "orgid", + "title": "属主机构id", + "type": "str", + "length": 32, + "label": "属主机构id", + "uitype": "code", + "valueField": "orgid", + "textField": "orgid_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "orgid", + "textField": "orgid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "is_first", + "title": "是否第一用户", + "type": "str", + "length": 1, + "label": "是否第一用户", + "uitype": "code", + "valueField": "is_first", + "textField": "is_first_text", + "params": { + "dbname": "{{get_module_dbname('uapi')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "is_first", + "textField": "is_first_text", + "cond": "parentid='yesno'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + } +] + }, + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +} \ No newline at end of file diff --git a/wwwroot/upappkey/update_upappkey.dspy b/wwwroot/upappkey/update_upappkey.dspy new file mode 100644 index 0000000..460ab81 --- /dev/null +++ b/wwwroot/upappkey/update_upappkey.dspy @@ -0,0 +1,92 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + +userid = await get_user() +if not userid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userid + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['orgid'] = userorgid + + +if params_kw.get('apikey'): + ns['apikey'] = password_encode(params_kw.get('apikey')) + +if params_kw.get('apipasswd'): + ns['apipasswd'] = password_encode(params_kw.get('apipasswd')) + + +db = DBPools() +dbname = get_module_dbname('uapi') +async with db.sqlorContext(dbname) as sor: + + ns1 = { + + "orgid": userorgid, + + + "ownerid": userid, + + "id": params_kw.id + } + recs = await sor.R('upappkey', ns1) + if len(recs) < 1: + return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"Record no exist or with wrong ownership" + } + } + + r = await sor.U('upappkey', ns) + debug('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/uptask_callback.dspy b/wwwroot/uptask_callback.dspy index 219a9ad..e06b2e6 100644 --- a/wwwroot/uptask_callback.dspy +++ b/wwwroot/uptask_callback.dspy @@ -1,4 +1,4 @@ -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) if parmas_kw.task_id is None: e = Exception(f'need a task_id') raise e diff --git a/wwwroot/viducallback/index.dspy b/wwwroot/viducallback/index.dspy index 62b85c3..6ab62cd 100644 --- a/wwwroot/viducallback/index.dspy +++ b/wwwroot/viducallback/index.dspy @@ -1,4 +1,4 @@ -debug(f'{params_kw=} +debug_params('params_kw', params_kw) taskid = params_kw.id async with get_sor_context(request._run_ns, 'uapi') as sor: llmusage = await get_llmlage_by_taskid(taskid) From 4892a4e46044977006dfaf786b6b91001fece230 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 29 May 2026 13:18:22 +0800 Subject: [PATCH 19/29] chore: remove CRUD definition dirs from tracking, add gitignore --- .gitignore | 5 + wwwroot/uapi/add_uapi.dspy | 37 ---- wwwroot/uapi/delete_uapi.dspy | 33 --- wwwroot/uapi/get_uapi.dspy | 155 -------------- wwwroot/uapi/index.ui | 291 -------------------------- wwwroot/uapi/update_uapi.dspy | 36 ---- wwwroot/uapiio/add_uapiio.dspy | 37 ---- wwwroot/uapiio/delete_uapiio.dspy | 33 --- wwwroot/uapiio/get_uapiio.dspy | 72 ------- wwwroot/uapiio/index.ui | 103 --------- wwwroot/uapiio/update_uapiio.dspy | 36 ---- wwwroot/upapp/add_upapp.dspy | 54 ----- wwwroot/upapp/delete_upapp.dspy | 47 ----- wwwroot/upapp/get_upapp.dspy | 129 ------------ wwwroot/upapp/index.ui | 258 ----------------------- wwwroot/upapp/update_upapp.dspy | 73 ------- wwwroot/upappkey/add_upappkey.dspy | 71 ------- wwwroot/upappkey/delete_upappkey.dspy | 61 ------ wwwroot/upappkey/get_upappkey.dspy | 133 ------------ wwwroot/upappkey/index.ui | 189 ----------------- wwwroot/upappkey/update_upappkey.dspy | 92 -------- 21 files changed, 5 insertions(+), 1940 deletions(-) create mode 100644 .gitignore delete mode 100644 wwwroot/uapi/add_uapi.dspy delete mode 100644 wwwroot/uapi/delete_uapi.dspy delete mode 100644 wwwroot/uapi/get_uapi.dspy delete mode 100644 wwwroot/uapi/index.ui delete mode 100644 wwwroot/uapi/update_uapi.dspy delete mode 100644 wwwroot/uapiio/add_uapiio.dspy delete mode 100644 wwwroot/uapiio/delete_uapiio.dspy delete mode 100644 wwwroot/uapiio/get_uapiio.dspy delete mode 100644 wwwroot/uapiio/index.ui delete mode 100644 wwwroot/uapiio/update_uapiio.dspy delete mode 100644 wwwroot/upapp/add_upapp.dspy delete mode 100644 wwwroot/upapp/delete_upapp.dspy delete mode 100644 wwwroot/upapp/get_upapp.dspy delete mode 100644 wwwroot/upapp/index.ui delete mode 100644 wwwroot/upapp/update_upapp.dspy delete mode 100644 wwwroot/upappkey/add_upappkey.dspy delete mode 100644 wwwroot/upappkey/delete_upappkey.dspy delete mode 100644 wwwroot/upappkey/get_upappkey.dspy delete mode 100644 wwwroot/upappkey/index.ui delete mode 100644 wwwroot/upappkey/update_upappkey.dspy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..223d309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# CRUD definition directories (auto-generated by Sage platform) +wwwroot/uapi/ +wwwroot/uapiio/ +wwwroot/upapp/ +wwwroot/upappkey/ diff --git a/wwwroot/uapi/add_uapi.dspy b/wwwroot/uapi/add_uapi.dspy deleted file mode 100644 index 34e742d..0000000 --- a/wwwroot/uapi/add_uapi.dspy +++ /dev/null @@ -1,37 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None -id = params_kw.id -if not id or len(id) > 32: - id = uuid() -ns['id'] = id - - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.C('uapi', ns.copy()) - return { - "widgettype":"Message", - "options":{ - "cwidth":16, - "cheight":9, - "title":"Add Success", - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Add Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/uapi/delete_uapi.dspy b/wwwroot/uapi/delete_uapi.dspy deleted file mode 100644 index 6cb7df1..0000000 --- a/wwwroot/uapi/delete_uapi.dspy +++ /dev/null @@ -1,33 +0,0 @@ - -ns = { - 'id':params_kw['id'], -} - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.D('uapi', ns) - debug('delete success'); - return { - "widgettype":"Message", - "options":{ - "title":"Delete Success", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"ok" - } - } - -debug('Delete failed'); -return { - "widgettype":"Error", - "options":{ - "title":"Delete Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/uapi/get_uapi.dspy b/wwwroot/uapi/get_uapi.dspy deleted file mode 100644 index ad31a14..0000000 --- a/wwwroot/uapi/get_uapi.dspy +++ /dev/null @@ -1,155 +0,0 @@ - -ns = params_kw.copy() - - -debug_params('get_uapi', ns) -if not ns.get('page'): - ns['page'] = 1 -if not ns.get('sort'): - - - ns['sort'] = 'name' - - - -sql = '''select a.*, b.httpmethod_text, c.need_auth_text, d.stream_text, e.ioid_text -from (select * from uapi where 1=1 [[filterstr]]) a left join (select k as httpmethod, - v as httpmethod_text from appcodes_kv where parentid='httpmethod') b on a.httpmethod = b.httpmethod left join (select k as need_auth, - v as need_auth_text from appcodes_kv where parentid='yesno') c on a.need_auth = c.need_auth left join (select k as stream, - v as stream_text from appcodes_kv where parentid='resp_mode') d on a.stream = d.stream left join (select id as ioid, - name as ioid_text from uapiio where 1 = 1) e on a.ioid = e.ioid''' - -filterjson = params_kw.get('data_filter') -fields_str=r'''[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32 - }, - { - "name": "upappid", - "title": "上位系统ID", - "type": "str", - "length": 32 - }, - { - "name": "name", - "title": "api名称", - "type": "str", - "length": 200 - }, - { - "name": "title", - "title": "API标题", - "type": "str", - "length": 100 - }, - { - "name": "description", - "title": "接口描述", - "type": "text" - }, - { - "name": "need_auth", - "title": "需要鉴权", - "type": "str", - "length": 1, - "default": "0" - }, - { - "name": "stream", - "title": "流式输出", - "type": "str", - "length": 20 - }, - { - "name": "path", - "title": "path", - "type": "str", - "length": 4000 - }, - { - "name": "httpmethod", - "title": "http方法", - "type": "str", - "length": 20, - "nullable": "yes", - "default": "GET" - }, - { - "name": "chunk_match", - "title": "流式匹配串", - "type": "str", - "length": 100 - }, - { - "name": "headers", - "title": "headers模版", - "type": "text", - "nullable": "yes" - }, - { - "name": "params", - "title": "参数模版", - "type": "text", - "nullable": "yes" - }, - { - "name": "data", - "title": "数据模版", - "type": "text", - "nullable": "yes" - }, - { - "name": "response", - "title": "响应模版", - "type": "text", - "nullable": "yes" - }, - { - "name": "ioid", - "title": "输入输出id", - "type": "str", - "length": 32, - "nullable": "yes" - }, - { - "name": "callbackurl", - "title": "回调url", - "type": "str", - "length": 1000, - "nullable": "yes" - } -]''' -ori_fields = json.loads(fields_str) -if not filterjson: - fields = [ f['name'] for f in ori_fields ] - filterjson = default_filterjson(fields, ns) -filterdic = ns.copy() -filterdic['filterstr'] = '' -filterdic['userorgid'] = '${userorgid}$' -filterdic['userid'] = '${userid}$' -if filterjson: - dbf = DBFilter(filterjson) - conds = dbf.gen(ns) - if conds: - ns.update(dbf.consts) - conds = f' and {conds}' - filterdic['filterstr'] = conds -ac = ArgsConvert('[[', ']]') -vars = ac.findAllVariables(sql) -NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } -filterdic.update(NameSpace) -sql = ac.convert(sql, filterdic) - -debug(f'sql({len(sql)}ch): {sql[:200]}') -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.sqlPaging(sql, ns) - return r -return { - "total":0, - "rows":[] -} \ No newline at end of file diff --git a/wwwroot/uapi/index.ui b/wwwroot/uapi/index.ui deleted file mode 100644 index 7860011..0000000 --- a/wwwroot/uapi/index.ui +++ /dev/null @@ -1,291 +0,0 @@ - -{ - "id":"uapi_tbl", - "widgettype":"Tabular", - "options":{ - "width":"100%", - "height":"100%", - - - "title":"API", - - - - "description":"API定义", - - - "toolbar":{ - "tools": [ - { - "selected_row": true, - "name": "test", - "icon": "{{entire_url('/imgs/test.svg')}}", - "label": "api测试" - } - ] -}, - - "css":"card", - - - "editable":{ - - "new_data_url":"{{entire_url('add_uapi.dspy')}}", - - - "delete_data_url":"{{entire_url('delete_uapi.dspy')}}", - - - "update_data_url":"{{entire_url('update_uapi.dspy')}}" - - }, - - - "data_url":"{{entire_url('./get_uapi.dspy')}}", - - "data_method":"GET", - "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, - "row_options":{ - - - - "browserfields": { - "exclouded": [ - "id" - ], - "alters": {} -}, - - - "editexclouded":[ - "id" -], - - "fields":[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "id" - }, - { - "name": "upappid", - "title": "上位系统ID", - "type": "str", - "length": 32, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "上位系统ID" - }, - { - "name": "name", - "title": "api名称", - "type": "str", - "length": 200, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "api名称" - }, - { - "name": "title", - "title": "API标题", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "API标题" - }, - { - "name": "description", - "title": "接口描述", - "type": "text", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "接口描述" - }, - { - "name": "need_auth", - "title": "需要鉴权", - "type": "str", - "length": 1, - "default": "0", - "label": "需要鉴权", - "uitype": "code", - "valueField": "need_auth", - "textField": "need_auth_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "appcodes_kv", - "tblvalue": "k", - "tbltext": "v", - "valueField": "need_auth", - "textField": "need_auth_text", - "cond": "parentid='yesno'" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "stream", - "title": "流式输出", - "type": "str", - "length": 20, - "label": "流式输出", - "uitype": "code", - "valueField": "stream", - "textField": "stream_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "appcodes_kv", - "tblvalue": "k", - "tbltext": "v", - "valueField": "stream", - "textField": "stream_text", - "cond": "parentid='resp_mode'" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "path", - "title": "path", - "type": "str", - "length": 4000, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "path" - }, - { - "name": "httpmethod", - "title": "http方法", - "type": "str", - "length": 20, - "nullable": "yes", - "default": "GET", - "label": "http方法", - "uitype": "code", - "valueField": "httpmethod", - "textField": "httpmethod_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "appcodes_kv", - "tblvalue": "k", - "tbltext": "v", - "valueField": "httpmethod", - "textField": "httpmethod_text", - "cond": "parentid='httpmethod'" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "chunk_match", - "title": "流式匹配串", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "流式匹配串" - }, - { - "name": "headers", - "title": "headers模版", - "type": "text", - "nullable": "yes", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "headers模版" - }, - { - "name": "params", - "title": "参数模版", - "type": "text", - "nullable": "yes", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "参数模版" - }, - { - "name": "data", - "title": "数据模版", - "type": "text", - "nullable": "yes", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "数据模版" - }, - { - "name": "response", - "title": "响应模版", - "type": "text", - "nullable": "yes", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "响应模版" - }, - { - "name": "ioid", - "title": "输入输出id", - "type": "str", - "length": 32, - "nullable": "yes", - "label": "输入输出id", - "uitype": "code", - "valueField": "ioid", - "textField": "ioid_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "uapiio", - "tblvalue": "id", - "tbltext": "name", - "valueField": "ioid", - "textField": "ioid_text" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "callbackurl", - "title": "回调url", - "type": "str", - "length": 1000, - "nullable": "yes", - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "回调url" - } -] - }, - - - - "page_rows":160, - "cache_limit":5 - } - - ,"binds":[ - { - "wid": "self", - "event": "test", - "actiontype": "urlwidget", - "target": "PopupWindow", - "options": { - "methid": "POST", - "params": {}, - "url": "{{entire_url('/uapi/uapi_test.ui')}}" - } - } -] - -} \ No newline at end of file diff --git a/wwwroot/uapi/update_uapi.dspy b/wwwroot/uapi/update_uapi.dspy deleted file mode 100644 index ccfbec3..0000000 --- a/wwwroot/uapi/update_uapi.dspy +++ /dev/null @@ -1,36 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None - - - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - - r = await sor.U('uapi', ns) - debug('update success'); - return { - "widgettype":"Message", - "options":{ - "title":"Update Success", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Update Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/uapiio/add_uapiio.dspy b/wwwroot/uapiio/add_uapiio.dspy deleted file mode 100644 index 8a40cd3..0000000 --- a/wwwroot/uapiio/add_uapiio.dspy +++ /dev/null @@ -1,37 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None -id = params_kw.id -if not id or len(id) > 32: - id = uuid() -ns['id'] = id - - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.C('uapiio', ns.copy()) - return { - "widgettype":"Message", - "options":{ - "cwidth":16, - "cheight":9, - "title":"Add Success", - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Add Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/uapiio/delete_uapiio.dspy b/wwwroot/uapiio/delete_uapiio.dspy deleted file mode 100644 index 112d14b..0000000 --- a/wwwroot/uapiio/delete_uapiio.dspy +++ /dev/null @@ -1,33 +0,0 @@ - -ns = { - 'id':params_kw['id'], -} - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.D('uapiio', ns) - debug('delete success'); - return { - "widgettype":"Message", - "options":{ - "title":"Delete Success", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"ok" - } - } - -debug('Delete failed'); -return { - "widgettype":"Error", - "options":{ - "title":"Delete Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/uapiio/get_uapiio.dspy b/wwwroot/uapiio/get_uapiio.dspy deleted file mode 100644 index 99908ed..0000000 --- a/wwwroot/uapiio/get_uapiio.dspy +++ /dev/null @@ -1,72 +0,0 @@ - -ns = params_kw.copy() - - -debug_params('get_uapiio', ns) -if not ns.get('page'): - ns['page'] = 1 -if not ns.get('sort'): - - - ns['sort'] = 'name' - - - -sql = '''select * from uapiio where 1=1 [[filterstr]]''' - -filterjson = params_kw.get('data_filter') -fields_str=r'''[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32 - }, - { - "name": "name", - "title": "类型名", - "type": "str", - "length": 100 - }, - { - "name": "description", - "title": "类型说明", - "type": "text" - }, - { - "name": "input_fields", - "title": "输入字段", - "type": "text" - } -]''' -ori_fields = json.loads(fields_str) -if not filterjson: - fields = [ f['name'] for f in ori_fields ] - filterjson = default_filterjson(fields, ns) -filterdic = ns.copy() -filterdic['filterstr'] = '' -filterdic['userorgid'] = '${userorgid}$' -filterdic['userid'] = '${userid}$' -if filterjson: - dbf = DBFilter(filterjson) - conds = dbf.gen(ns) - if conds: - ns.update(dbf.consts) - conds = f' and {conds}' - filterdic['filterstr'] = conds -ac = ArgsConvert('[[', ']]') -vars = ac.findAllVariables(sql) -NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } -filterdic.update(NameSpace) -sql = ac.convert(sql, filterdic) - -debug(f'sql({len(sql)}ch): {sql[:200]}') -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.sqlPaging(sql, ns) - return r -return { - "total":0, - "rows":[] -} \ No newline at end of file diff --git a/wwwroot/uapiio/index.ui b/wwwroot/uapiio/index.ui deleted file mode 100644 index f299e93..0000000 --- a/wwwroot/uapiio/index.ui +++ /dev/null @@ -1,103 +0,0 @@ - -{ - "id":"uapiio_tbl", - "widgettype":"Tabular", - "options":{ - "width":"100%", - "height":"100%", - - - "title":"API输入输出", - - - - "description":"API的输入输出定义", - - - "css":"card", - - - "editable":{ - - "new_data_url":"{{entire_url('add_uapiio.dspy')}}", - - - "delete_data_url":"{{entire_url('delete_uapiio.dspy')}}", - - - "update_data_url":"{{entire_url('update_uapiio.dspy')}}" - - }, - - - "data_url":"{{entire_url('./get_uapiio.dspy')}}", - - "data_method":"GET", - "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, - "row_options":{ - - - - "browserfields": { - "exclouded": [ - "id" - ], - "alters": {} -}, - - - "editexclouded":[ - "id" -], - - "fields":[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "id" - }, - { - "name": "name", - "title": "类型名", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "类型名" - }, - { - "name": "description", - "title": "类型说明", - "type": "text", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "类型说明" - }, - { - "name": "input_fields", - "title": "输入字段", - "type": "text", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "输入字段" - } -] - }, - - - - "page_rows":160, - "cache_limit":5 - } - - ,"binds":[] - -} \ No newline at end of file diff --git a/wwwroot/uapiio/update_uapiio.dspy b/wwwroot/uapiio/update_uapiio.dspy deleted file mode 100644 index 6d17571..0000000 --- a/wwwroot/uapiio/update_uapiio.dspy +++ /dev/null @@ -1,36 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None - - - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - - r = await sor.U('uapiio', ns) - debug('update success'); - return { - "widgettype":"Message", - "options":{ - "title":"Update Success", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Update Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/upapp/add_upapp.dspy b/wwwroot/upapp/add_upapp.dspy deleted file mode 100644 index 63a8593..0000000 --- a/wwwroot/upapp/add_upapp.dspy +++ /dev/null @@ -1,54 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None -id = params_kw.id -if not id or len(id) > 32: - id = uuid() -ns['id'] = id - -if params_kw.get('secretkey'): - ns['secretkey'] = password_encode(params_kw.get('secretkey')) - - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userorgid - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.C('upapp', ns.copy()) - return { - "widgettype":"Message", - "options":{ - "cwidth":16, - "cheight":9, - "title":"Add Success", - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Add Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/upapp/delete_upapp.dspy b/wwwroot/upapp/delete_upapp.dspy deleted file mode 100644 index 3e25a6a..0000000 --- a/wwwroot/upapp/delete_upapp.dspy +++ /dev/null @@ -1,47 +0,0 @@ - -ns = { - 'id':params_kw['id'], -} - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userorgid - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.D('upapp', ns) - debug('delete success'); - return { - "widgettype":"Message", - "options":{ - "title":"Delete Success", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"ok" - } - } - -debug('Delete failed'); -return { - "widgettype":"Error", - "options":{ - "title":"Delete Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/upapp/get_upapp.dspy b/wwwroot/upapp/get_upapp.dspy deleted file mode 100644 index f2ca27a..0000000 --- a/wwwroot/upapp/get_upapp.dspy +++ /dev/null @@ -1,129 +0,0 @@ - -ns = params_kw.copy() - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userorgid -ns['userorgid'] = userorgid - -debug_params('get_upapp', ns) -if not ns.get('page'): - ns['page'] = 1 -if not ns.get('sort'): - - - ns['sort'] = 'name' - - - -sql = '''select a.*, b.ownerid_text -from (select * from upapp where 1=1 [[filterstr]]) a left join (select id as ownerid, - orgname as ownerid_text from organization where 1 = 1) b on a.ownerid = b.ownerid''' - -filterjson = params_kw.get('data_filter') -fields_str=r'''[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32 - }, - { - "name": "name", - "title": "上位应用名", - "type": "str", - "length": 200 - }, - { - "name": "description", - "title": "描述", - "type": "text", - "default": "0" - }, - { - "name": "ownerid", - "title": "所属机构", - "type": "str", - "length": 32, - "nullable": "yes" - }, - { - "name": "apisetid", - "title": "api集id", - "type": "str", - "length": 32 - }, - { - "name": "secretkey", - "title": "加密密钥", - "type": "str", - "length": 100 - }, - { - "name": "baseurl", - "title": "系统url", - "type": "str", - "length": 500 - }, - { - "name": "myappid", - "title": "我的appid", - "type": "str", - "length": 100 - }, - { - "name": "dynamic_func", - "title": "动态headers函数", - "type": "str", - "length": 255 - }, - { - "name": "auth_apiname", - "title": "认证API名", - "type": "str", - "length": 200, - "nullable": "yes" - } -]''' -ori_fields = json.loads(fields_str) -if not filterjson: - fields = [ f['name'] for f in ori_fields ] - filterjson = default_filterjson(fields, ns) -filterdic = ns.copy() -filterdic['filterstr'] = '' -filterdic['userorgid'] = '${userorgid}$' -filterdic['userid'] = '${userid}$' -if filterjson: - dbf = DBFilter(filterjson) - conds = dbf.gen(ns) - if conds: - ns.update(dbf.consts) - conds = f' and {conds}' - filterdic['filterstr'] = conds -ac = ArgsConvert('[[', ']]') -vars = ac.findAllVariables(sql) -NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } -filterdic.update(NameSpace) -sql = ac.convert(sql, filterdic) - -debug(f'sql({len(sql)}ch): {sql[:200]}') -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.sqlPaging(sql, ns) - return r -return { - "total":0, - "rows":[] -} \ No newline at end of file diff --git a/wwwroot/upapp/index.ui b/wwwroot/upapp/index.ui deleted file mode 100644 index cd85b4d..0000000 --- a/wwwroot/upapp/index.ui +++ /dev/null @@ -1,258 +0,0 @@ - -{ - "id":"upapp_tbl", - "widgettype":"Tabular", - "options":{ - "width":"100%", - "height":"100%", - - - "title":"上位系统", - - - - "description":"上位系统", - - - "toolbar":{ - "tools": [ - { - "name": "jumpin", - "label": "跳转到", - "selected_data": true - }, - { - "selected_row": true, - "name": "upappkey", - "icon": "{{entire_url('/imgs/upappkey.svg')}}", - "label": "APIKEY" - }, - { - "selected_row": true, - "name": "uapi", - "icon": "{{entire_url('/imgs/uapi.svg')}}", - "label": "API" - } - ] -}, - - "css":"card", - - - "editable":{ - - "new_data_url":"{{entire_url('add_upapp.dspy')}}", - - - "delete_data_url":"{{entire_url('delete_upapp.dspy')}}", - - - "update_data_url":"{{entire_url('update_upapp.dspy')}}" - - }, - - - "data_url":"{{entire_url('./get_upapp.dspy')}}", - - "data_method":"GET", - "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, - "row_options":{ - - - - "browserfields": { - "exclouded": [ - "ownerid" - ], - "alters": {} -}, - - - "editexclouded":[ - "ownerid" -], - - "fields":[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "id" - }, - { - "name": "name", - "title": "上位应用名", - "type": "str", - "length": 200, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "上位应用名" - }, - { - "name": "description", - "title": "描述", - "type": "text", - "default": "0", - "length": 0, - "uitype": "text", - "datatype": "text", - "label": "描述" - }, - { - "name": "ownerid", - "title": "所属机构", - "type": "str", - "length": 32, - "nullable": "yes", - "label": "所属机构", - "uitype": "code", - "valueField": "ownerid", - "textField": "ownerid_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "organization", - "tblvalue": "id", - "tbltext": "orgname", - "valueField": "ownerid", - "textField": "ownerid_text" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "apisetid", - "title": "api集id", - "type": "str", - "length": 32, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "api集id" - }, - { - "name": "secretkey", - "title": "加密密钥", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "password", - "datatype": "str", - "label": "加密密钥" - }, - { - "name": "baseurl", - "title": "系统url", - "type": "str", - "length": 500, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "系统url" - }, - { - "name": "myappid", - "title": "我的appid", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "我的appid" - }, - { - "name": "dynamic_func", - "title": "动态headers函数", - "type": "str", - "length": 255, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "动态headers函数" - }, - { - "name": "auth_apiname", - "title": "认证API名", - "type": "str", - "length": 200, - "nullable": "yes", - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "认证API名" - } -] - }, - - - - "page_rows":160, - "cache_limit":5 - } - - ,"binds":[ - { - "wid": "self", - "event": "jumpin", - "actiontype": "urlwidget", - "target": "self", - "options": { - "url": "{{entire_url('/uapi/jump_in.dspy')}}" - } - }, - { - "wid": "self", - "event": "upappkey", - "actiontype": "urlwidget", - "target": "PopupWindow", - "popup_options": { - "title": "APIKEY", - "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=upappkey", - "resizable": true, - "height": "70%", - "width": "70%" - }, - "params_mapping": { - "mapping": { - "id": "upappid", - "referer_widget": "referer_widget" - }, - "need_other": false - }, - "options": { - "method": "POST", - "params": {}, - "url": "{{entire_url('../upappkey')}}" - } - }, - { - "wid": "self", - "event": "uapi", - "actiontype": "urlwidget", - "target": "PopupWindow", - "popup_options": { - "title": "API", - "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=uapi", - "resizable": true, - "height": "70%", - "width": "70%" - }, - "params_mapping": { - "mapping": { - "id": "upappid", - "referer_widget": "referer_widget" - }, - "need_other": false - }, - "options": { - "method": "POST", - "params": {}, - "url": "{{entire_url('../uapi')}}" - } - } -] - -} \ No newline at end of file diff --git a/wwwroot/upapp/update_upapp.dspy b/wwwroot/upapp/update_upapp.dspy deleted file mode 100644 index 0a95885..0000000 --- a/wwwroot/upapp/update_upapp.dspy +++ /dev/null @@ -1,73 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userorgid - - -if params_kw.get('secretkey'): - ns['secretkey'] = password_encode(params_kw.get('secretkey')) - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - - ns1 = { - - "ownerid": userorgid, - - - "id": params_kw.id - } - recs = await sor.R('upapp', ns1) - if len(recs) < 1: - return { - "widgettype":"Error", - "options":{ - "title":"Update Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"Record no exist or with wrong ownership" - } - } - - r = await sor.U('upapp', ns) - debug('update success'); - return { - "widgettype":"Message", - "options":{ - "title":"Update Success", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Update Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/upappkey/add_upappkey.dspy b/wwwroot/upappkey/add_upappkey.dspy deleted file mode 100644 index 65b9a75..0000000 --- a/wwwroot/upappkey/add_upappkey.dspy +++ /dev/null @@ -1,71 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None -id = params_kw.id -if not id or len(id) > 32: - id = uuid() -ns['id'] = id - -if params_kw.get('apikey'): - ns['apikey'] = password_encode(params_kw.get('apikey')) - -if params_kw.get('apipasswd'): - ns['apipasswd'] = password_encode(params_kw.get('apipasswd')) - - -userid = await get_user() -if not userid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userid - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['orgid'] = userorgid - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.C('upappkey', ns.copy()) - return { - "widgettype":"Message", - "options":{ - "cwidth":16, - "cheight":9, - "title":"Add Success", - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Add Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/upappkey/delete_upappkey.dspy b/wwwroot/upappkey/delete_upappkey.dspy deleted file mode 100644 index 9277b1f..0000000 --- a/wwwroot/upappkey/delete_upappkey.dspy +++ /dev/null @@ -1,61 +0,0 @@ - -ns = { - 'id':params_kw['id'], -} - -userid = await get_user() -if not userid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userid - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['orgid'] = userorgid - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.D('upappkey', ns) - debug('delete success'); - return { - "widgettype":"Message", - "options":{ - "title":"Delete Success", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"ok" - } - } - -debug('Delete failed'); -return { - "widgettype":"Error", - "options":{ - "title":"Delete Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"failed" - } -} \ No newline at end of file diff --git a/wwwroot/upappkey/get_upappkey.dspy b/wwwroot/upappkey/get_upappkey.dspy deleted file mode 100644 index 603eb10..0000000 --- a/wwwroot/upappkey/get_upappkey.dspy +++ /dev/null @@ -1,133 +0,0 @@ - -ns = params_kw.copy() - -userid = await get_user() -if not userid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userid -ns['userid'] = userid - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['orgid'] = userorgid -ns['userorgid'] = userorgid - -debug_params('get_upappkey', ns) -if not ns.get('page'): - ns['page'] = 1 -if not ns.get('sort'): - - ns['sort'] = 'id' - - -sql = '''select a.*, b.ownerid_text, c.upappid_text, d.orgid_text, e.is_first_text -from (select * from upappkey where 1=1 [[filterstr]]) a left join (select id as ownerid, - username as ownerid_text from users where 1 = 1) b on a.ownerid = b.ownerid left join (select id as upappid, - name as upappid_text from upapp where 1 = 1) c on a.upappid = c.upappid left join (select id as orgid, - orgname as orgid_text from organization where 1 = 1) d on a.orgid = d.orgid left join (select k as is_first, - v as is_first_text from appcodes_kv where parentid='yesno') e on a.is_first = e.is_first''' - -filterjson = params_kw.get('data_filter') -fields_str=r'''[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32 - }, - { - "name": "upappid", - "title": "应用id", - "type": "str", - "length": 32 - }, - { - "name": "ownerid", - "title": "属主id", - "type": "str", - "length": 32, - "default": "0" - }, - { - "name": "apikey", - "title": "api密钥", - "type": "str", - "length": 4000, - "default": "0" - }, - { - "name": "apiuser", - "title": "api用户", - "type": "str", - "length": 100 - }, - { - "name": "apipasswd", - "title": "api密码", - "type": "str", - "length": 100 - }, - { - "name": "orgid", - "title": "属主机构id", - "type": "str", - "length": 32 - }, - { - "name": "is_first", - "title": "是否第一用户", - "type": "str", - "length": 1 - } -]''' -ori_fields = json.loads(fields_str) -if not filterjson: - fields = [ f['name'] for f in ori_fields ] - filterjson = default_filterjson(fields, ns) -filterdic = ns.copy() -filterdic['filterstr'] = '' -filterdic['userorgid'] = '${userorgid}$' -filterdic['userid'] = '${userid}$' -if filterjson: - dbf = DBFilter(filterjson) - conds = dbf.gen(ns) - if conds: - ns.update(dbf.consts) - conds = f' and {conds}' - filterdic['filterstr'] = conds -ac = ArgsConvert('[[', ']]') -vars = ac.findAllVariables(sql) -NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } -filterdic.update(NameSpace) -sql = ac.convert(sql, filterdic) - -debug(f'sql({len(sql)}ch): {sql[:200]}') -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - r = await sor.sqlPaging(sql, ns) - return r -return { - "total":0, - "rows":[] -} \ No newline at end of file diff --git a/wwwroot/upappkey/index.ui b/wwwroot/upappkey/index.ui deleted file mode 100644 index ba1364e..0000000 --- a/wwwroot/upappkey/index.ui +++ /dev/null @@ -1,189 +0,0 @@ - -{ - "id":"upappkey_tbl", - "widgettype":"Tabular", - "options":{ - "width":"100%", - "height":"100%", - - - "title":"上位系统密码", - - - - "description":"上位系统密码", - - - "css":"card", - - - "editable":{ - - "new_data_url":"{{entire_url('add_upappkey.dspy')}}", - - - "delete_data_url":"{{entire_url('delete_upappkey.dspy')}}", - - - "update_data_url":"{{entire_url('update_upappkey.dspy')}}" - - }, - - - "data_url":"{{entire_url('./get_upappkey.dspy')}}", - - "data_method":"GET", - "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, - "row_options":{ - - - - "browserfields": { - "exclouded": [ - "id", - "ownerid", - "orgid" - ], - "alters": {} -}, - - - "editexclouded":[ - "id", - "upappid", - "ownerid", - "orgid" -], - - "fields":[ - { - "name": "id", - "title": "id", - "type": "str", - "length": 32, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "id" - }, - { - "name": "upappid", - "title": "应用id", - "type": "str", - "length": 32, - "label": "应用id", - "uitype": "code", - "valueField": "upappid", - "textField": "upappid_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "upapp", - "tblvalue": "id", - "tbltext": "name", - "valueField": "upappid", - "textField": "upappid_text" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "ownerid", - "title": "属主id", - "type": "str", - "length": 32, - "default": "0", - "label": "属主id", - "uitype": "code", - "valueField": "ownerid", - "textField": "ownerid_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "users", - "tblvalue": "id", - "tbltext": "username", - "valueField": "ownerid", - "textField": "ownerid_text" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "apikey", - "title": "api密钥", - "type": "str", - "length": 4000, - "default": "0", - "cwidth": 18, - "uitype": "password", - "datatype": "str", - "label": "api密钥" - }, - { - "name": "apiuser", - "title": "api用户", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "str", - "datatype": "str", - "label": "api用户" - }, - { - "name": "apipasswd", - "title": "api密码", - "type": "str", - "length": 100, - "cwidth": 18, - "uitype": "password", - "datatype": "str", - "label": "api密码" - }, - { - "name": "orgid", - "title": "属主机构id", - "type": "str", - "length": 32, - "label": "属主机构id", - "uitype": "code", - "valueField": "orgid", - "textField": "orgid_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "organization", - "tblvalue": "id", - "tbltext": "orgname", - "valueField": "orgid", - "textField": "orgid_text" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - }, - { - "name": "is_first", - "title": "是否第一用户", - "type": "str", - "length": 1, - "label": "是否第一用户", - "uitype": "code", - "valueField": "is_first", - "textField": "is_first_text", - "params": { - "dbname": "{{get_module_dbname('uapi')}}", - "table": "appcodes_kv", - "tblvalue": "k", - "tbltext": "v", - "valueField": "is_first", - "textField": "is_first_text", - "cond": "parentid='yesno'" - }, - "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" - } -] - }, - - - - "page_rows":160, - "cache_limit":5 - } - - ,"binds":[] - -} \ No newline at end of file diff --git a/wwwroot/upappkey/update_upappkey.dspy b/wwwroot/upappkey/update_upappkey.dspy deleted file mode 100644 index 460ab81..0000000 --- a/wwwroot/upappkey/update_upappkey.dspy +++ /dev/null @@ -1,92 +0,0 @@ - -ns = params_kw.copy() -for k,v in ns.items(): - if v == 'NaN' or v == 'null': - ns[k] = None - -userid = await get_user() -if not userid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['ownerid'] = userid - - -userorgid = await get_userorgid() -if not userorgid: - return { - "widgettype":"Error", - "options":{ - "title":"Authorization Error", - "timeout":3, - "cwidth":16, - "cheight":9, - "message":"Please login" - } - } -ns['orgid'] = userorgid - - -if params_kw.get('apikey'): - ns['apikey'] = password_encode(params_kw.get('apikey')) - -if params_kw.get('apipasswd'): - ns['apipasswd'] = password_encode(params_kw.get('apipasswd')) - - -db = DBPools() -dbname = get_module_dbname('uapi') -async with db.sqlorContext(dbname) as sor: - - ns1 = { - - "orgid": userorgid, - - - "ownerid": userid, - - "id": params_kw.id - } - recs = await sor.R('upappkey', ns1) - if len(recs) < 1: - return { - "widgettype":"Error", - "options":{ - "title":"Update Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"Record no exist or with wrong ownership" - } - } - - r = await sor.U('upappkey', ns) - debug('update success'); - return { - "widgettype":"Message", - "options":{ - "title":"Update Success", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"ok" - } - } - -return { - "widgettype":"Error", - "options":{ - "title":"Update Error", - "cwidth":16, - "cheight":9, - "timeout":3, - "message":"failed" - } -} \ No newline at end of file From 77b4b14525d7b8d5ebb36870f6a7f657a47b7f27 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 29 May 2026 17:59:26 +0800 Subject: [PATCH 20/29] feat: respect module_cache config for UAPI cache --- uapi/apidata.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/uapi/apidata.py b/uapi/apidata.py index 7fb7980..3a5177a 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -8,10 +8,23 @@ from appPublic.streamhttpclient import StreamHttpClient, liner from appPublic.dictObject import DictObject from appPublic.log import debug, exception, error from appPublic.aes import aes_encode_b64 +from appPublic.jsonConfig import getConfig from ahserver.globalEnv import password_decode from ahserver.serverenv import get_serverenv, ServerEnv from random import randint + +def _cache_enabled(): + """Check if cache is enabled for uapi module in config.json""" + try: + config = getConfig() + module_cache = config.module_cache + if module_cache is None: + return True + return getattr(module_cache, 'uapi', True) + except Exception: + return True + async def get_deerer(upappid, callerid): db = DBPools() dbname = get_dbname() @@ -69,9 +82,10 @@ class UAPIData: async def get_apiusers(self, appid, orgid=None): key = appid - d = self.org_users.get(key) - if d: - return d + if _cache_enabled(): + d = self.org_users.get(key) + if d: + return d env = ServerEnv() async with get_sor_context(env, 'uapi') as sor: sql = """select @@ -122,9 +136,10 @@ where b.orgid = c.ownerid async def get_api(self, appid, apiname): key = f'{appid}.{apiname}' - api = self.apidata.get(key) - if api: - return api + if _cache_enabled(): + api = self.apidata.get(key) + if api: + return api env = ServerEnv() async with get_sor_context(env, 'uapi') as sor: d = await sor_get_uapi(sor, appid, apiname) From d67cc0b0da80063628b74a4cba908c5e2e50801a Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sat, 30 May 2026 10:55:31 +0800 Subject: [PATCH 21/29] fix: uapi cache key mismatch and write without cache gate --- uapi/apidata.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/uapi/apidata.py b/uapi/apidata.py index 3a5177a..f536aa2 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -118,7 +118,8 @@ where b.orgid = c.ownerid e = Exception(f'{appid=} {orgid=} get none user') exception(f'{e}') raise e - self.apidata[key] = d + if _cache_enabled(): + self.org_users[key] = d return d e = Exception(f'{appid=} {orgid=} get none user') exception(f'{e}') @@ -147,7 +148,8 @@ where b.orgid = c.ownerid e = Exception(f'{appid=}, {apiname=} get none api') exception(f'{e}') raise e - self.apidata[key] = d + if _cache_enabled(): + self.apidata[key] = d return d e = Exception(f'{appid=}, {apiname=} get none api') exception(f'{e}') From 4360f345760ae3203016c382b5f72b7911ad6e88 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sat, 30 May 2026 11:01:05 +0800 Subject: [PATCH 22/29] fix: get_apiusers cache key missing orgid causing cross-org data leak --- uapi/apidata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uapi/apidata.py b/uapi/apidata.py index f536aa2..6bfa8fe 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -81,7 +81,7 @@ class UAPIData: return None async def get_apiusers(self, appid, orgid=None): - key = appid + key = f'{appid}.{orgid}' if orgid else str(appid) if _cache_enabled(): d = self.org_users.get(key) if d: From e7350700075365a9f23f9f95fe1fae94bf28c48e Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 1 Jun 2026 18:10:31 +0800 Subject: [PATCH 23/29] refactor: bind hot_reload event via EventDispatcher, add on_hot_reload to UAPIData --- uapi/__pycache__/apidata.cpython-310.pyc | Bin 0 -> 5134 bytes uapi/__pycache__/init.cpython-310.pyc | Bin 0 -> 970 bytes uapi/apidata.py | 8 +++++++- uapi/init.py | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 uapi/__pycache__/apidata.cpython-310.pyc create mode 100644 uapi/__pycache__/init.cpython-310.pyc diff --git a/uapi/__pycache__/apidata.cpython-310.pyc b/uapi/__pycache__/apidata.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..417b1ebb7e3bc52df32bd180da24fa5558eca9bb GIT binary patch literal 5134 zcma)ATW=f372cV>az$M%OR^<9$;NTeFqP@1Fp|`b)5MmWxM*27Y8r$=K`c3IiSlw+ zo?ZIFE&-)V`_>}pV`Bq0(3kv)zNEimUi#3!0E^4|GFZE6|PxO|X%e|A$ zle*83S9+_>RZY*uYrRv=Q<|>Er+e$obxqI58@)5lGrhCTv%0+yKht}*`7G!~vGj@2 zd`_GY%XkLjq*%f8d9fk>ZCHvQ;9d0wsRSUy?66`|61IQl3dwwH;E*zveoV6KX30voxEl$TSV>tj;dhI zXg}|!iLxUpF$Yr%VU)F^q?3xM)qeF7dMa4{TAJMI?x3-_A7|T`7ewtmRnac`<2=lPM|3EF2lS9UOgD3EMwv38% zhdIncSzuMv{403|-5rwF_~(MCpnQRnJw){}(~{#kgZ#{bKjww`gk3Ujz?a@N{`|_& z%FV(mjDj7SW#7=wt-{{3Q5+Qa&>T92lf6Gy>ky9T`t6YSQXi`S|Cn8izLK!J9?lT z5y6(Ea(fTLeMn9@BSs~QIx@;JrsgWQzh82_P8i3L#5gY6c)47{gvTg;yQ`ScIx4G; z=}z<-XVK11qv8i7{uQ>2zr{FP`>$_r{>O6UcfstKsK!4!44JQ1!h0Y-hQUJk#$%%} z3s!K!AMz)BByKBbg>?uMzRl%}ITu!8e#V7eFw{=LQM-@aLk)fqwk6DU2)*Vt2KTcl zhPMV`{pC%+T@U;DZVO@{@%gu8+AFO(+zhlBg0_DT))!&;NG!pXpk40@zXA_LFTIDZ z^S>F*F+5*#XpHV3y*4~wMlqI81c3eSKLOX2~SmHYvn=PQ?2NpZ+ zRM}Z5$Yb*R=$cWCgAR%@8dvHdv~QRYIARS1ZagyK4^R*k!iTotg#nIP*x*?VhMa|A3?EC@^3pRlGS9O0tJMvo^d=y5>#Vh)rms$w2I zPng)=qVnE<`{s=;Alv^x(i+z40@_<;%{0vkT0UnD7z&vgVWTGC*_o=Oa!12Kh6rN8 z4Q?G1t@2u}Zqm(Ltsl{8NblslJ{z1E@25T<(U<{Hk`5Iu>~kOxa*W6934jX}pHOB= z>5$C_uU@~4U>fYglYlcir0k}7OGa@TiXb9pJ-8M_30VLrDuYl~Zy>6WPGBFzlRpG4 z-#`Vcx3=-pqT`WO}kaAHr` z2$bLxTgI=S8Cr$42Y&!Gy##3DP>r>Rc(F(QHj0DdqIiNmbpFEr$T$SGZQPk7_0`M< zT!MsHdV|{zv#2j)BRCXn#vu|SUozUo(#yM#cAtsAYY{6b~z*K9}};94&0xhWQ6?uU?NUYqW_8}`OEf-Wpb3U4IDwbC3 z=W~VsnL#0|@PEtnnGPt&*hB8|1}x|s@`OJf%@e-O3N^o>SY56_(jk(1V-y$t4kFiP zfXB!(G(E}~G(ECMO-~XB-8yPQ=m^JC1Ys7GHdyw`9R$ZwA3zD~VJyrQFcpk~Fy$cB zq(DqY9JI#~xigB$?Ye#yL3Xm$zz^^%1M_J2U>^%u~qG{l(JdOUqWFXU*;a$Kz`t{HMA`q6FA%a``S5b4OVA7dJH== z{!9rg9Z6$n8w6qJ9{ji9g8gBf)WMSr6TSfd75uiL6Fa1S@DWRUimg4xM(&N=LVJo0 zPeEX%@uv-B9}ZGQXWiJdfuk<2gIX|OQZ6t!MQ}qYZE1-w)h>_J<86-hQw@*>_hk|^ zpdy{00f|XQ@*-6e(aV$6{4uJUdrS$EKv0>TB$w|K#k$92US{Rfef%Dy%8Sau3IseB zF{L%fpmO@ojA#iaCs6rMG$7g|FbR0XB~Y$H+^oFDXn0)J@Mvmy#KmO>9xn|YK&C^= z8DCleh9%&=odY_*rV=j)Opa9IK_#@}4L~Q>)ZpcQ19*Kj176=^g__flKgC%!e8~n; z7f=o6ryd47#1)C|dOBN>H;J117NxmUjn*nh^hjB>`~@myViNftHFb_nzNOWAT&HxS zrFi=(24pmpA1uuH((**HluyBT{=HVwzO!`PcjOQ7Izl*dF$(LgmhxMz(e=5ds;$jg zbJ7M&%h*0B;o+nX;_tR!MuNH;4bc=Wug{{STuL BxpDvi literal 0 HcmV?d00001 diff --git a/uapi/__pycache__/init.cpython-310.pyc b/uapi/__pycache__/init.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..131f67d4a972a953c8394969a25753e533844c0a GIT binary patch literal 970 zcmZva&u`N(6vyo}&95eD*G*_3AtZ#5dRhN~A;bh6xJ*dXF1|!=ZPs*g6WMO1p>Z02 z2psvBeC4z=H!kp=yU`tR;*X#07yG?$URx9yfi`7Nm%lRPzb{b&DHGTh%DItktWd?DwT)w;9XTvo<% zyEd*w7j<>FYTWQ{`;(Mn#^;x=-1%!O1%#iRcPAaZwYMSgcii$h=62sOE6|?2g>F6r zEV&_Hh^5!0qBZ~qws%8d3~e9Sw-GS1F)+3XFtI5xwHYw81K_~sz}yzV!j{0&4zJ<= z-30phnk+}(_5_@~4432Y0Vq$-e4q4A-AHca3WTUl$1RAUUAAa#rDfA{G1=Ex5*p*x zL&ON+Qm?&$%rw@M?jsUJiii*~Vt~jI1)>DFc)<;~R_i^Sg|m7kT+yzo2e(p3FMbex z2oAqrP8Y3`QwU5+!|SP*U2CQoz!vqYwnyESOEF$w6bJB`c@BsuK8fen-Z2Xv(eRIc z0&b_112^UiXYL{>FSu)~fX{Q6-7aHP!A09*_;1o72J97dF<4$Yl+p*_ f6McZkcv|vGwPM}K*H|wGHO$EidYI->PV?X|&TH>V literal 0 HcmV?d00001 diff --git a/uapi/apidata.py b/uapi/apidata.py index 6bfa8fe..5923464 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -64,7 +64,13 @@ class UAPIData: self.apidata = {} self.apikeys = {} self.org_users = {} - + + def on_hot_reload(self, data=None): + """Event handler for hot_reload event. Clears all caches.""" + self.apidata.clear() + self.apikeys.clear() + self.org_users.clear() + async def get_userapikey(self, appid, callerid): users = await self.get_apiusers(appid) for u in users: diff --git a/uapi/init.py b/uapi/init.py index 04978ff..8e49e78 100644 --- a/uapi/init.py +++ b/uapi/init.py @@ -32,6 +32,9 @@ def load_uapi(): g.get_my_uptasks = get_my_uptasks g.uptask_feedback = uptask_feedback g.uptask_started = uptask_started + # Bind hot_reload event — instance method, WeakMethod safe (stored on g) + if hasattr(g, 'event_dispatcher'): + g.event_dispatcher.bind('hot_reload', g.uapi_data.on_hot_reload) # USAGE in dspy From 7c9767628530e5fd2000a098bd6e9f264d3c3ff7 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 1 Jun 2026 18:10:39 +0800 Subject: [PATCH 24/29] chore: remove __pycache__, add to gitignore --- .gitignore | 1 + uapi/__pycache__/apidata.cpython-310.pyc | Bin 5134 -> 0 bytes uapi/__pycache__/init.cpython-310.pyc | Bin 970 -> 0 bytes 3 files changed, 1 insertion(+) delete mode 100644 uapi/__pycache__/apidata.cpython-310.pyc delete mode 100644 uapi/__pycache__/init.cpython-310.pyc diff --git a/.gitignore b/.gitignore index 223d309..f68195f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ wwwroot/uapi/ wwwroot/uapiio/ wwwroot/upapp/ wwwroot/upappkey/ +__pycache__/ diff --git a/uapi/__pycache__/apidata.cpython-310.pyc b/uapi/__pycache__/apidata.cpython-310.pyc deleted file mode 100644 index 417b1ebb7e3bc52df32bd180da24fa5558eca9bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5134 zcma)ATW=f372cV>az$M%OR^<9$;NTeFqP@1Fp|`b)5MmWxM*27Y8r$=K`c3IiSlw+ zo?ZIFE&-)V`_>}pV`Bq0(3kv)zNEimUi#3!0E^4|GFZE6|PxO|X%e|A$ zle*83S9+_>RZY*uYrRv=Q<|>Er+e$obxqI58@)5lGrhCTv%0+yKht}*`7G!~vGj@2 zd`_GY%XkLjq*%f8d9fk>ZCHvQ;9d0wsRSUy?66`|61IQl3dwwH;E*zveoV6KX30voxEl$TSV>tj;dhI zXg}|!iLxUpF$Yr%VU)F^q?3xM)qeF7dMa4{TAJMI?x3-_A7|T`7ewtmRnac`<2=lPM|3EF2lS9UOgD3EMwv38% zhdIncSzuMv{403|-5rwF_~(MCpnQRnJw){}(~{#kgZ#{bKjww`gk3Ujz?a@N{`|_& z%FV(mjDj7SW#7=wt-{{3Q5+Qa&>T92lf6Gy>ky9T`t6YSQXi`S|Cn8izLK!J9?lT z5y6(Ea(fTLeMn9@BSs~QIx@;JrsgWQzh82_P8i3L#5gY6c)47{gvTg;yQ`ScIx4G; z=}z<-XVK11qv8i7{uQ>2zr{FP`>$_r{>O6UcfstKsK!4!44JQ1!h0Y-hQUJk#$%%} z3s!K!AMz)BByKBbg>?uMzRl%}ITu!8e#V7eFw{=LQM-@aLk)fqwk6DU2)*Vt2KTcl zhPMV`{pC%+T@U;DZVO@{@%gu8+AFO(+zhlBg0_DT))!&;NG!pXpk40@zXA_LFTIDZ z^S>F*F+5*#XpHV3y*4~wMlqI81c3eSKLOX2~SmHYvn=PQ?2NpZ+ zRM}Z5$Yb*R=$cWCgAR%@8dvHdv~QRYIARS1ZagyK4^R*k!iTotg#nIP*x*?VhMa|A3?EC@^3pRlGS9O0tJMvo^d=y5>#Vh)rms$w2I zPng)=qVnE<`{s=;Alv^x(i+z40@_<;%{0vkT0UnD7z&vgVWTGC*_o=Oa!12Kh6rN8 z4Q?G1t@2u}Zqm(Ltsl{8NblslJ{z1E@25T<(U<{Hk`5Iu>~kOxa*W6934jX}pHOB= z>5$C_uU@~4U>fYglYlcir0k}7OGa@TiXb9pJ-8M_30VLrDuYl~Zy>6WPGBFzlRpG4 z-#`Vcx3=-pqT`WO}kaAHr` z2$bLxTgI=S8Cr$42Y&!Gy##3DP>r>Rc(F(QHj0DdqIiNmbpFEr$T$SGZQPk7_0`M< zT!MsHdV|{zv#2j)BRCXn#vu|SUozUo(#yM#cAtsAYY{6b~z*K9}};94&0xhWQ6?uU?NUYqW_8}`OEf-Wpb3U4IDwbC3 z=W~VsnL#0|@PEtnnGPt&*hB8|1}x|s@`OJf%@e-O3N^o>SY56_(jk(1V-y$t4kFiP zfXB!(G(E}~G(ECMO-~XB-8yPQ=m^JC1Ys7GHdyw`9R$ZwA3zD~VJyrQFcpk~Fy$cB zq(DqY9JI#~xigB$?Ye#yL3Xm$zz^^%1M_J2U>^%u~qG{l(JdOUqWFXU*;a$Kz`t{HMA`q6FA%a``S5b4OVA7dJH== z{!9rg9Z6$n8w6qJ9{ji9g8gBf)WMSr6TSfd75uiL6Fa1S@DWRUimg4xM(&N=LVJo0 zPeEX%@uv-B9}ZGQXWiJdfuk<2gIX|OQZ6t!MQ}qYZE1-w)h>_J<86-hQw@*>_hk|^ zpdy{00f|XQ@*-6e(aV$6{4uJUdrS$EKv0>TB$w|K#k$92US{Rfef%Dy%8Sau3IseB zF{L%fpmO@ojA#iaCs6rMG$7g|FbR0XB~Y$H+^oFDXn0)J@Mvmy#KmO>9xn|YK&C^= z8DCleh9%&=odY_*rV=j)Opa9IK_#@}4L~Q>)ZpcQ19*Kj176=^g__flKgC%!e8~n; z7f=o6ryd47#1)C|dOBN>H;J117NxmUjn*nh^hjB>`~@myViNftHFb_nzNOWAT&HxS zrFi=(24pmpA1uuH((**HluyBT{=HVwzO!`PcjOQ7Izl*dF$(LgmhxMz(e=5ds;$jg zbJ7M&%h*0B;o+nX;_tR!MuNH;4bc=Wug{{STuL BxpDvi diff --git a/uapi/__pycache__/init.cpython-310.pyc b/uapi/__pycache__/init.cpython-310.pyc deleted file mode 100644 index 131f67d4a972a953c8394969a25753e533844c0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 970 zcmZva&u`N(6vyo}&95eD*G*_3AtZ#5dRhN~A;bh6xJ*dXF1|!=ZPs*g6WMO1p>Z02 z2psvBeC4z=H!kp=yU`tR;*X#07yG?$URx9yfi`7Nm%lRPzb{b&DHGTh%DItktWd?DwT)w;9XTvo<% zyEd*w7j<>FYTWQ{`;(Mn#^;x=-1%!O1%#iRcPAaZwYMSgcii$h=62sOE6|?2g>F6r zEV&_Hh^5!0qBZ~qws%8d3~e9Sw-GS1F)+3XFtI5xwHYw81K_~sz}yzV!j{0&4zJ<= z-30phnk+}(_5_@~4432Y0Vq$-e4q4A-AHca3WTUl$1RAUUAAa#rDfA{G1=Ex5*p*x zL&ON+Qm?&$%rw@M?jsUJiii*~Vt~jI1)>DFc)<;~R_i^Sg|m7kT+yzo2e(p3FMbex z2oAqrP8Y3`QwU5+!|SP*U2CQoz!vqYwnyESOEF$w6bJB`c@BsuK8fen-Z2Xv(eRIc z0&b_112^UiXYL{>FSu)~fX{Q6-7aHP!A09*_;1o72J97dF<4$Yl+p*_ f6McZkcv|vGwPM}K*H|wGHO$EidYI->PV?X|&TH>V From 87d2cd409dca9b7871556e491b3f1b1febf2020d Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 1 Jun 2026 22:53:05 +0800 Subject: [PATCH 25/29] debug: add hot_reload handler logging --- uapi/apidata.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uapi/apidata.py b/uapi/apidata.py index 5923464..d412416 100644 --- a/uapi/apidata.py +++ b/uapi/apidata.py @@ -67,6 +67,8 @@ class UAPIData: def on_hot_reload(self, data=None): """Event handler for hot_reload event. Clears all caches.""" + from appPublic.log import debug + debug(f'[uapi] on_hot_reload called, clearing caches (data={data})') self.apidata.clear() self.apikeys.clear() self.org_users.clear() From 5e5a2afbba3b99b8285bb0cf736de540b2f49574 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 18 Jun 2026 11:38:38 +0800 Subject: [PATCH 26/29] =?UTF-8?q?uapi:=20uapi=E8=A1=A8editexclouded?= =?UTF-8?q?=E5=8A=A0upappid;=20uapiio=E5=A2=9E=E5=8A=A0data=5Ffilter?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- json/uapi.json | 2 +- json/uapiio.json | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/json/uapi.json b/json/uapi.json index f0db3dd..106708b 100644 --- a/json/uapi.json +++ b/json/uapi.json @@ -32,6 +32,6 @@ "exclouded":["id"], "alters":{} }, - "editexclouded":["id"] + "editexclouded":["id","upappid"] } } diff --git a/json/uapiio.json b/json/uapiio.json index 07d5129..6d7a6b5 100644 --- a/json/uapiio.json +++ b/json/uapiio.json @@ -4,6 +4,12 @@ "title":"API输入输出", "description":"API的输入输出定义", "sortby": "name", + "data_filter":{ + "fields":[ + {"name":"name","title":"类型名","uitype":"str"}, + {"name":"description","title":"类型说明","uitype":"str"} + ] + }, "browserfields":{ "exclouded":["id"], "alters":{} From 2d0a836549ace9b8e5975e4980b698b309d4a285 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 18 Jun 2026 11:42:22 +0800 Subject: [PATCH 27/29] =?UTF-8?q?uapi:=20upapp=E5=88=A0=E9=99=A4=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E5=88=B0tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- json/upapp.json | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/json/upapp.json b/json/upapp.json index e034730..e234cd1 100644 --- a/json/upapp.json +++ b/json/upapp.json @@ -22,26 +22,6 @@ "subtable": "uapi", "title": "API" } - ], - "toolbar":{ - "tools":[ - { - "name":"jumpin", - "label":"跳转到", - "selected_data": true - } - ] - }, - "binds":[ - { - "wid":"self", - "event":"jumpin", - "actiontype":"urlwidget", - "target":"self", - "options":{ - "url":"{{entire_url('/uapi/jump_in.dspy')}}" - } - } - ] + ] } } From 40291ee9b1bb179253410718d8a23652052414bb Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 18 Jun 2026 11:46:26 +0800 Subject: [PATCH 28/29] =?UTF-8?q?uapi:=20uapiio=20data=5Ffilter=E5=AD=97?= =?UTF-8?q?=E6=AE=B5key=E7=94=B1name=E6=94=B9=E4=B8=BAfield=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DAssertionError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- json/uapiio.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/json/uapiio.json b/json/uapiio.json index 6d7a6b5..314348c 100644 --- a/json/uapiio.json +++ b/json/uapiio.json @@ -6,8 +6,8 @@ "sortby": "name", "data_filter":{ "fields":[ - {"name":"name","title":"类型名","uitype":"str"}, - {"name":"description","title":"类型说明","uitype":"str"} + {"field":"name","title":"类型名","uitype":"str"}, + {"field":"description","title":"类型说明","uitype":"str"} ] }, "browserfields":{ From a4c0d3cbb05344fb662854926cccf38fb7faad50 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 19 Jun 2026 15:01:43 +0800 Subject: [PATCH 29/29] feat: add i18n translations (zh/en/jp/ko) for all modules --- i18n/en/msg.txt | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ i18n/jp/msg.txt | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ i18n/ko/msg.txt | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ i18n/zh/msg.txt | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 i18n/en/msg.txt create mode 100644 i18n/jp/msg.txt create mode 100644 i18n/ko/msg.txt create mode 100644 i18n/zh/msg.txt diff --git a/i18n/en/msg.txt b/i18n/en/msg.txt new file mode 100644 index 0000000..eb38b58 --- /dev/null +++ b/i18n/en/msg.txt @@ -0,0 +1,74 @@ +API: API +APIKEY: APIKEY +API接口: API Interface +API标题: API Title +API输入输出: API Input/Output +API集: API Collection +API集名称: API Collection Name +Add Error: Add Error +Add Success: Add Success +Authorization Error: Authorization Error +Cancel: Cancel +Conform: Confirm +Delete Error: Delete Error +Delete Success: Delete Success +Discard: Discard +Please login: Please login +Record no exist or with wrong ownership: Record no exist or with wrong ownership +Reset: Reset +Submit: Submit +Update Error: Update Error +Update Success: Update Success +api名称: API Name +api密码: API Password +api密钥: API Secret +api测试: API Test +api用户: API User +api集id: API Collection ID +failed: failed +headers模版: Headers Template +http方法: HTTP Method +id: id +ok: ok +path: path +上位应用名: Parent Application Name +上位系统: Parent System +上位系统ID: Parent System ID +上位系统密码: Parent System Password +上位系统密码表: Parent System Password Table +上游任务: Upstream Task +加密密钥: Encryption Key +动态headers函数: Dynamic Headers Function +参数模版: Parameter Template +响应模版: Response Template +响应转换函数名: Response Transform Function Name +回调url: Callback URL +外部系统: External System +属主id: Owner ID +属主机构id: Owner Organization ID +应用id: Application ID +开始时间: Start Time +我的appid: My App ID +所属机构: Organization +执行方任务id: Executor Task ID +授权api名: Authorized API Name +接口描述: Interface Description +描述: Description +数据模版: Data Template +是否第一用户: Is First User +本地业务id: Local Business ID +流式匹配串: Stream Match String +流式输出: Stream Output +状态: Status +用户id: User ID +相应数据: Response Data +类型名: Type Name +类型说明: Type Description +系统url: System URL +结束时间: End Time +认证API名: Auth API Name +跳转到: Jump to +输入字段: Input Field +输入输出: Input/Output +输入输出id: Input/Output ID +需要鉴权: Requires Auth diff --git a/i18n/jp/msg.txt b/i18n/jp/msg.txt new file mode 100644 index 0000000..c21db94 --- /dev/null +++ b/i18n/jp/msg.txt @@ -0,0 +1,74 @@ +API: API +APIKEY: APIKEY +API接口: APIインターフェース +API标题: APIタイトル +API输入输出: API入出力 +API集: APIコレクション +API集名称: APIコレクション名 +Add Error: 追加エラー +Add Success: 追加成功 +Authorization Error: 認証エラー +Cancel: キャンセル +Conform: 確認 +Delete Error: 削除エラー +Delete Success: 削除成功 +Discard: 破棄 +Please login: ログインしてください +Record no exist or with wrong ownership: レコードが存在しないか、所有権が不正です +Reset: リセット +Submit: 送信 +Update Error: 更新エラー +Update Success: 更新成功 +api名称: API名称 +api密码: APIパスワード +api密钥: APIシークレット +api测试: APIテスト +api用户: APIユーザー +api集id: APIコレクションID +failed: 失敗 +headers模版: ヘッダーテンプレート +http方法: HTTPメソッド +id: id +ok: ok +path: path +上位应用名: 上位アプリケーション名 +上位系统: 上位システム +上位系统ID: 上位システムID +上位系统密码: 上位システムパスワード +上位系统密码表: 上位システムパスワード表 +上游任务: 上流タスク +加密密钥: 暗号化キー +动态headers函数: 動的ヘッダー関数 +参数模版: パラメーターテンプレート +响应模版: レスポンステンプレート +响应转换函数名: レスポンス変換関数名 +回调url: コールバックURL +外部系统: 外部システム +属主id: 所有者ID +属主机构id: 所有者組織ID +应用id: アプリケーションID +开始时间: 開始時間 +我的appid: マイAppID +所属机构: 所属組織 +执行方任务id: 実行タスクID +授权api名: 認可API名 +接口描述: インターフェース説明 +描述: 説明 +数据模版: データテンプレート +是否第一用户: 最初のユーザーか +本地业务id: ローカル業務ID +流式匹配串: ストリームマッチ文字列 +流式输出: ストリーム出力 +状态: ステータス +用户id: ユーザーID +相应数据: 対応データ +类型名: 型名 +类型说明: 型説明 +系统url: システムURL +结束时间: 終了時間 +认证API名: 認証API名 +跳转到: ジャンプ先 +输入字段: 入力フィールド +输入输出: 入出力 +输入输出id: 入出力ID +需要鉴权: 認証が必要 diff --git a/i18n/ko/msg.txt b/i18n/ko/msg.txt new file mode 100644 index 0000000..3c6913d --- /dev/null +++ b/i18n/ko/msg.txt @@ -0,0 +1,74 @@ +API: API +APIKEY: APIKEY +API接口: API 인터페이스 +API标题: API 제목 +API输入输出: API 입출력 +API集: API 컬렉션 +API集名称: API 컬렉션 이름 +Add Error: 추가 오류 +Add Success: 추가 성공 +Authorization Error: 인증 오류 +Cancel: 취소 +Conform: 확인 +Delete Error: 삭제 오류 +Delete Success: 삭제 성공 +Discard: 폐기 +Please login: 로그인해주세요 +Record no exist or with wrong ownership: 레코드가 존재하지 않거나 소유권이 잘못되었습니다 +Reset: 초기화 +Submit: 제출 +Update Error: 업데이트 오류 +Update Success: 업데이트 성공 +api名称: API 이름 +api密码: API 비밀번호 +api密钥: API 비밀키 +api测试: API 테스트 +api用户: API 사용자 +api集id: API 컬렉션 ID +failed: 실패 +headers模版: 헤더 템플릿 +http方法: HTTP 메서드 +id: id +ok: ok +path: path +上位应用名: 상위 애플리케이션 이름 +上位系统: 상위 시스템 +上位系统ID: 상위 시스템 ID +上位系统密码: 상위 시스템 비밀번호 +上位系统密码表: 상위 시스템 비밀번호 테이블 +上游任务: 업스트림 작업 +加密密钥: 암호화 키 +动态headers函数: 동적 헤더 함수 +参数模版: 파라미터 템플릿 +响应模版: 응답 템플릿 +响应转换函数名: 응답 변환 함수 이름 +回调url: 콜백 URL +外部系统: 외부 시스템 +属主id: 소유자 ID +属主机构id: 소유자 조직 ID +应用id: 애플리케이션 ID +开始时间: 시작 시간 +我的appid: 내 App ID +所属机构: 소속 조직 +执行方任务id: 실행자 작업 ID +授权api名: 인증된 API 이름 +接口描述: 인터페이스 설명 +描述: 설명 +数据模版: 데이터 템플릿 +是否第一用户: 첫 번째 사용자 여부 +本地业务id: 로컬 비즈니스 ID +流式匹配串: 스트림 매칭 문자열 +流式输出: 스트림 출력 +状态: 상태 +用户id: 사용자 ID +相应数据: 응답 데이터 +类型名: 타입 이름 +类型说明: 타입 설명 +系统url: 시스템 URL +结束时间: 종료 시간 +认证API名: 인증 API 이름 +跳转到: 이동 +输入字段: 입력 필드 +输入输出: 입출력 +输入输出id: 입출력 ID +需要鉴权: 인증 필요 diff --git a/i18n/zh/msg.txt b/i18n/zh/msg.txt new file mode 100644 index 0000000..c93ae7c --- /dev/null +++ b/i18n/zh/msg.txt @@ -0,0 +1,74 @@ +API: API +APIKEY: APIKEY +API接口: API接口 +API标题: API标题 +API输入输出: API输入输出 +API集: API集 +API集名称: API集名称 +Add Error: Add Error +Add Success: Add Success +Authorization Error: Authorization Error +Cancel: Cancel +Conform: Conform +Delete Error: Delete Error +Delete Success: Delete Success +Discard: Discard +Please login: Please login +Record no exist or with wrong ownership: Record no exist or with wrong ownership +Reset: Reset +Submit: Submit +Update Error: Update Error +Update Success: Update Success +api名称: api名称 +api密码: api密码 +api密钥: api密钥 +api测试: api测试 +api用户: api用户 +api集id: api集id +failed: failed +headers模版: headers模版 +http方法: http方法 +id: id +ok: ok +path: path +上位应用名: 上位应用名 +上位系统: 上位系统 +上位系统ID: 上位系统ID +上位系统密码: 上位系统密码 +上位系统密码表: 上位系统密码表 +上游任务: 上游任务 +加密密钥: 加密密钥 +动态headers函数: 动态headers函数 +参数模版: 参数模版 +响应模版: 响应模版 +响应转换函数名: 响应转换函数名 +回调url: 回调url +外部系统: 外部系统 +属主id: 属主id +属主机构id: 属主机构id +应用id: 应用id +开始时间: 开始时间 +我的appid: 我的appid +所属机构: 所属机构 +执行方任务id: 执行方任务id +授权api名: 授权api名 +接口描述: 接口描述 +描述: 描述 +数据模版: 数据模版 +是否第一用户: 是否第一用户 +本地业务id: 本地业务id +流式匹配串: 流式匹配串 +流式输出: 流式输出 +状态: 状态 +用户id: 用户id +相应数据: 相应数据 +类型名: 类型名 +类型说明: 类型说明 +系统url: 系统url +结束时间: 结束时间 +认证API名: 认证API名 +跳转到: 跳转到 +输入字段: 输入字段 +输入输出: 输入输出 +输入输出id: 输入输出id +需要鉴权: 需要鉴权